├── .gitignore ├── README.md └── src └── sample ├── DragResizeMod.java └── Main.java /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | *.jar 5 | *.war 6 | *.ear 7 | 8 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 9 | hs_err_pid* 10 | 11 | # files for the dex VM 12 | *.dex 13 | 14 | # Java class files 15 | *.class 16 | 17 | # generated files 18 | bin/ 19 | gen/ 20 | 21 | # Local configuration file (sdk path, etc) 22 | local.properties 23 | 24 | # Eclipse project files 25 | .classpath 26 | .project 27 | 28 | # Proguard folder generated by Eclipse 29 | proguard/ 30 | 31 | # Intellij project files 32 | *.iml 33 | *.ipr 34 | *.iws 35 | .idea/ 36 | 37 | #Gradle 38 | .gradletasknamecache 39 | .gradle/ 40 | build/ 41 | bin/ 42 | 43 | # DB and Cache dirs 44 | cache/ 45 | derby/ 46 | client_secrets.json 47 | .oauth-credentials/ 48 | derby.log -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JavaFX-Resizable-Draggable-Node 2 | Demo Video: https://www.youtube.com/watch?v=peUcqBTWw34 3 | -------------------------------------------------------------------------------- /src/sample/DragResizeMod.java: -------------------------------------------------------------------------------- 1 | package sample; 2 | 3 | import javafx.event.EventHandler; 4 | import javafx.scene.Cursor; 5 | import javafx.scene.Node; 6 | import javafx.scene.canvas.Canvas; 7 | import javafx.scene.input.MouseEvent; 8 | import javafx.scene.shape.Rectangle; 9 | 10 | /** 11 | * ************* How to use ************************ 12 | * 13 | * Rectangle rectangle = new Rectangle(50, 50); 14 | * rectangle.setFill(Color.BLACK); 15 | * DragResizeMod.makeResizable(rectangle, null); 16 | * 17 | * Pane root = new Pane(); 18 | * root.getChildren().add(rectangle); 19 | * 20 | * primaryStage.setScene(new Scene(root, 300, 275)); 21 | * primaryStage.show(); 22 | * 23 | * ************* OnDragResizeEventListener ********** 24 | * 25 | * You need to override OnDragResizeEventListener and 26 | * 1) preform out of main field bounds check 27 | * 2) make changes to the node 28 | * (this class will not change anything in node coordinates) 29 | * 30 | * There is defaultListener and it works only with Canvas nad Rectangle 31 | */ 32 | 33 | public class DragResizeMod { 34 | public interface OnDragResizeEventListener { 35 | void onDrag(Node node, double x, double y, double h, double w); 36 | 37 | void onResize(Node node, double x, double y, double h, double w); 38 | } 39 | 40 | private static final OnDragResizeEventListener defaultListener = new OnDragResizeEventListener() { 41 | @Override 42 | public void onDrag(Node node, double x, double y, double h, double w) { 43 | /* 44 | // TODO find generic way to get parent width and height of any node 45 | // can perform out of bounds check here if you know your parent size 46 | if (x > width - w ) x = width - w; 47 | if (y > height - h) y = height - h; 48 | if (x < 0) x = 0; 49 | if (y < 0) y = 0; 50 | */ 51 | setNodeSize(node, x, y, h, w); 52 | } 53 | 54 | @Override 55 | public void onResize(Node node, double x, double y, double h, double w) { 56 | /* 57 | // TODO find generic way to get parent width and height of any node 58 | // can perform out of bounds check here if you know your parent size 59 | if (w > width - x) w = width - x; 60 | if (h > height - y) h = height - y; 61 | if (x < 0) x = 0; 62 | if (y < 0) y = 0; 63 | */ 64 | setNodeSize(node, x, y, h, w); 65 | } 66 | 67 | private void setNodeSize(Node node, double x, double y, double h, double w) { 68 | node.setLayoutX(x); 69 | node.setLayoutY(y); 70 | // TODO find generic way to set width and height of any node 71 | // here we cant set height and width to node directly. 72 | // or i just cant find how to do it, 73 | // so you have to wright resize code anyway for your Nodes... 74 | //something like this 75 | if (node instanceof Canvas) { 76 | ((Canvas) node).setWidth(w); 77 | ((Canvas) node).setHeight(h); 78 | } else if (node instanceof Rectangle) { 79 | ((Rectangle) node).setWidth(w); 80 | ((Rectangle) node).setHeight(h); 81 | } 82 | } 83 | }; 84 | 85 | public static enum S { 86 | DEFAULT, 87 | DRAG, 88 | NW_RESIZE, 89 | SW_RESIZE, 90 | NE_RESIZE, 91 | SE_RESIZE, 92 | E_RESIZE, 93 | W_RESIZE, 94 | N_RESIZE, 95 | S_RESIZE; 96 | } 97 | 98 | 99 | private double clickX, clickY, nodeX, nodeY, nodeH, nodeW; 100 | 101 | private S state = S.DEFAULT; 102 | 103 | private Node node; 104 | private OnDragResizeEventListener listener = defaultListener; 105 | 106 | private static final int MARGIN = 8; 107 | private static final double MIN_W = 30; 108 | private static final double MIN_H = 20; 109 | 110 | private DragResizeMod(Node node, OnDragResizeEventListener listener) { 111 | this.node = node; 112 | if (listener != null) 113 | this.listener = listener; 114 | } 115 | 116 | public static void makeResizable(Node node) { 117 | makeResizable(node, null); 118 | } 119 | 120 | public static void makeResizable(Node node, OnDragResizeEventListener listener) { 121 | final DragResizeMod resizer = new DragResizeMod(node, listener); 122 | 123 | node.setOnMousePressed(new EventHandler() { 124 | @Override 125 | public void handle(MouseEvent event) { 126 | resizer.mousePressed(event); 127 | } 128 | }); 129 | node.setOnMouseDragged(new EventHandler() { 130 | @Override 131 | public void handle(MouseEvent event) { 132 | resizer.mouseDragged(event); 133 | } 134 | }); 135 | node.setOnMouseMoved(new EventHandler() { 136 | @Override 137 | public void handle(MouseEvent event) { 138 | resizer.mouseOver(event); 139 | } 140 | }); 141 | node.setOnMouseReleased(new EventHandler() { 142 | @Override 143 | public void handle(MouseEvent event) { 144 | resizer.mouseReleased(event); 145 | } 146 | }); 147 | } 148 | 149 | protected void mouseReleased(MouseEvent event) { 150 | node.setCursor(Cursor.DEFAULT); 151 | state = S.DEFAULT; 152 | } 153 | 154 | protected void mouseOver(MouseEvent event) { 155 | S state = currentMouseState(event); 156 | Cursor cursor = getCursorForState(state); 157 | node.setCursor(cursor); 158 | } 159 | 160 | private S currentMouseState(MouseEvent event) { 161 | S state = S.DEFAULT; 162 | boolean left = isLeftResizeZone(event); 163 | boolean right = isRightResizeZone(event); 164 | boolean top = isTopResizeZone(event); 165 | boolean bottom = isBottomResizeZone(event); 166 | 167 | if (left && top) state = S.NW_RESIZE; 168 | else if (left && bottom) state = S.SW_RESIZE; 169 | else if (right && top) state = S.NE_RESIZE; 170 | else if (right && bottom) state = S.SE_RESIZE; 171 | else if (right) state = S.E_RESIZE; 172 | else if (left) state = S.W_RESIZE; 173 | else if (top) state = S.N_RESIZE; 174 | else if (bottom) state = S.S_RESIZE; 175 | else if (isInDragZone(event)) state = S.DRAG; 176 | 177 | return state; 178 | } 179 | 180 | private static Cursor getCursorForState(S state) { 181 | switch (state) { 182 | case NW_RESIZE: 183 | return Cursor.NW_RESIZE; 184 | case SW_RESIZE: 185 | return Cursor.SW_RESIZE; 186 | case NE_RESIZE: 187 | return Cursor.NE_RESIZE; 188 | case SE_RESIZE: 189 | return Cursor.SE_RESIZE; 190 | case E_RESIZE: 191 | return Cursor.E_RESIZE; 192 | case W_RESIZE: 193 | return Cursor.W_RESIZE; 194 | case N_RESIZE: 195 | return Cursor.N_RESIZE; 196 | case S_RESIZE: 197 | return Cursor.S_RESIZE; 198 | default: 199 | return Cursor.DEFAULT; 200 | } 201 | } 202 | 203 | 204 | protected void mouseDragged(MouseEvent event) { 205 | 206 | if (listener != null) { 207 | double mouseX = parentX(event.getX()); 208 | double mouseY = parentY(event.getY()); 209 | if (state == S.DRAG) { 210 | listener.onDrag(node, mouseX - clickX, mouseY - clickY, nodeH, nodeW); 211 | } else if (state != S.DEFAULT) { 212 | //resizing 213 | double newX = nodeX; 214 | double newY = nodeY; 215 | double newH = nodeH; 216 | double newW = nodeW; 217 | 218 | // Right Resize 219 | if (state == S.E_RESIZE || state == S.NE_RESIZE || state == S.SE_RESIZE) { 220 | newW = mouseX - nodeX; 221 | } 222 | // Left Resize 223 | if (state == S.W_RESIZE || state == S.NW_RESIZE || state == S.SW_RESIZE) { 224 | newX = mouseX; 225 | newW = nodeW + nodeX - newX; 226 | } 227 | 228 | // Bottom Resize 229 | if (state == S.S_RESIZE || state == S.SE_RESIZE || state == S.SW_RESIZE) { 230 | newH = mouseY - nodeY; 231 | } 232 | // Top Resize 233 | if (state == S.N_RESIZE || state == S.NW_RESIZE || state == S.NE_RESIZE) { 234 | newY = mouseY; 235 | newH = nodeH + nodeY - newY; 236 | } 237 | 238 | //min valid rect Size Check 239 | if (newW < MIN_W) { 240 | if (state == S.W_RESIZE || state == S.NW_RESIZE || state == S.SW_RESIZE) 241 | newX = newX - MIN_W + newW; 242 | newW = MIN_W; 243 | } 244 | 245 | if (newH < MIN_H) { 246 | if (state == S.N_RESIZE || state == S.NW_RESIZE || state == S.NE_RESIZE) 247 | newY = newY + newH - MIN_H; 248 | newH = MIN_H; 249 | } 250 | 251 | listener.onResize(node, newX, newY, newH, newW); 252 | } 253 | } 254 | } 255 | 256 | protected void mousePressed(MouseEvent event) { 257 | 258 | if (isInResizeZone(event)) { 259 | setNewInitialEventCoordinates(event); 260 | state = currentMouseState(event); 261 | } else if (isInDragZone(event)) { 262 | setNewInitialEventCoordinates(event); 263 | state = S.DRAG; 264 | } else { 265 | state = S.DEFAULT; 266 | } 267 | } 268 | 269 | private void setNewInitialEventCoordinates(MouseEvent event) { 270 | nodeX = nodeX(); 271 | nodeY = nodeY(); 272 | nodeH = nodeH(); 273 | nodeW = nodeW(); 274 | clickX = event.getX(); 275 | clickY = event.getY(); 276 | } 277 | 278 | private boolean isInResizeZone(MouseEvent event) { 279 | return isLeftResizeZone(event) || isRightResizeZone(event) 280 | || isBottomResizeZone(event) || isTopResizeZone(event); 281 | } 282 | 283 | private boolean isInDragZone(MouseEvent event) { 284 | double xPos = parentX(event.getX()); 285 | double yPos = parentY(event.getY()); 286 | double nodeX = nodeX() + MARGIN; 287 | double nodeY = nodeY() + MARGIN; 288 | double nodeX0 = nodeX() + nodeW() - MARGIN; 289 | double nodeY0 = nodeY() + nodeH() - MARGIN; 290 | 291 | return (xPos > nodeX && xPos < nodeX0) && (yPos > nodeY && yPos < nodeY0); 292 | } 293 | 294 | private boolean isLeftResizeZone(MouseEvent event) { 295 | return intersect(0, event.getX()); 296 | } 297 | 298 | private boolean isRightResizeZone(MouseEvent event) { 299 | return intersect(nodeW(), event.getX()); 300 | } 301 | 302 | private boolean isTopResizeZone(MouseEvent event) { 303 | return intersect(0, event.getY()); 304 | } 305 | 306 | private boolean isBottomResizeZone(MouseEvent event) { 307 | return intersect(nodeH(), event.getY()); 308 | } 309 | 310 | private boolean intersect(double side, double point) { 311 | return side + MARGIN > point && side - MARGIN < point; 312 | } 313 | 314 | private double parentX(double localX) { 315 | return nodeX() + localX; 316 | } 317 | 318 | private double parentY(double localY) { 319 | return nodeY() + localY; 320 | } 321 | 322 | private double nodeX() { 323 | return node.getBoundsInParent().getMinX(); 324 | } 325 | 326 | private double nodeY() { 327 | return node.getBoundsInParent().getMinY(); 328 | } 329 | 330 | private double nodeW() { 331 | return node.getBoundsInParent().getWidth(); 332 | } 333 | 334 | private double nodeH() { 335 | return node.getBoundsInParent().getHeight(); 336 | } 337 | } -------------------------------------------------------------------------------- /src/sample/Main.java: -------------------------------------------------------------------------------- 1 | package sample; 2 | 3 | import javafx.application.Application; 4 | import javafx.scene.Scene; 5 | import javafx.scene.layout.Pane; 6 | import javafx.scene.paint.Color; 7 | import javafx.scene.shape.Rectangle; 8 | import javafx.stage.Stage; 9 | 10 | 11 | 12 | public class Main extends Application { 13 | 14 | @Override 15 | public void start(Stage primaryStage) throws Exception{ 16 | primaryStage.setTitle("Drag Resize Demo"); 17 | Pane root = new Pane(); 18 | 19 | Rectangle rectangle = new Rectangle(50, 50); 20 | rectangle.setFill(Color.BLACK); 21 | DragResizeMod.makeResizable(rectangle, null); 22 | 23 | Rectangle rectangle2 = new Rectangle(50, 50); 24 | rectangle.setFill(Color.RED); 25 | DragResizeMod.makeResizable(rectangle2); 26 | 27 | root.getChildren().addAll(rectangle, rectangle2); 28 | primaryStage.setScene(new Scene(root, 300, 275)); 29 | primaryStage.show(); 30 | } 31 | 32 | 33 | public static void main(String[] args) { 34 | launch(args); 35 | } 36 | } 37 | --------------------------------------------------------------------------------