Skip to content

Instantly share code, notes, and snippets.

@GeoffCapper
Forked from andytill/DragResizer.java
Last active June 23, 2022 19:04
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save GeoffCapper/54fb16ac548f2c13b99f273b79293517 to your computer and use it in GitHub Desktop.
Save GeoffCapper/54fb16ac548f2c13b99f273b79293517 to your computer and use it in GitHub Desktop.
DragResizer can be used to add mouse listeners to a Region and make it resizable by the user by clicking and dragging the border in the same way as a window. Height and width resizing from all cardinal points is possible, along with the option of dragging the region, and constraining it within the bounds of the parent.
import javafx.event.EventHandler;
import javafx.scene.Cursor;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Region;
/**
* {@link DragResizer} can be used to add mouse listeners to a {@link Region}
* and make it moveable and resizable by the user by clicking and dragging the
* border in the same way as a window, or clicking within the window to move it
* around.
* <p>
* Height and width resizing is implemented, from the sides and corners.
* Dragging of the region is also optionally available, and the movement and
* resizing can be constrained within the bounds of the parent.
* <p>
* Usage:
* <pre>DragResizer.makeResizable(myAnchorPane);</pre> makes the region
* resizable, but not moveable, and not constrained within the parent.
* <p>
* Usage:
* <pre>DragResizer.makeResizable(myAnchorPane, true, true);</pre> makes the
* region resizable and moveable, but only within the bounds of the parent.
* <p>
* Builds on the modifications of Cannibalsticky to the original version by
* AndyTill.
* <p>
*
* @author Geoff Capper
*/
public class DragResizer {
/**
* Enum containing the zones that we can drag around.
*/
enum Zone {
NONE, N, NE, E, SE, S, SW, W, NW, C
}
/**
* The margin around the control that a user can click in to start resizing
* the region.
*/
private static final int RESIZE_MARGIN = 5;
/**
* How small can we go?
*/
private static final int MIN_SIZE = 10;
private final Region region;
private double y;
private double x;
private boolean initMinHeight;
private boolean initMinWidth;
private Zone zone;
private boolean dragging;
/**
* Whether the sizing and movement of the region is constrained within the
* bounds of the parent.
*/
private final boolean constrainToParent;
/**
* Whether dragging of the region is allowed.
*/
private final boolean allowMove;
private DragResizer(Region aRegion, boolean allowMove, boolean constrainToParent) {
region = aRegion;
this.constrainToParent = constrainToParent;
this.allowMove = allowMove;
}
/**
* Makes the region resizable, but not moveable or constrained in any way.
*
* @param region
*/
public static void makeResizable(Region region) {
DragResizer.makeResizable(region, false, false);
}
/**
* Makes the region resizable, and optionally moveable, and constrained
* within the bounds of the parent.
*
* @param region
* @param allowMove Allow a click in the centre of the region to start
* dragging it around.
* @param constrainToParent Prevent movement and/or resizing outside the
* parent.
*/
public static void makeResizable(Region region, boolean allowMove, boolean constrainToParent) {
final DragResizer resizer = new DragResizer(region, allowMove, constrainToParent);
region.setOnMousePressed(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
resizer.mousePressed(event);
}
});
region.setOnMouseDragged(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
resizer.mouseDragged(event);
}
});
region.setOnMouseMoved(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
resizer.mouseOver(event);
}
});
region.setOnMouseReleased(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
resizer.mouseReleased(event);
}
});
}
protected void mouseReleased(MouseEvent event) {
dragging = false;
region.setCursor(Cursor.DEFAULT);
}
protected void mouseOver(MouseEvent event) {
if (isInDraggableZone(event) || dragging) {
switch (zone) {
case N: {
region.setCursor(Cursor.N_RESIZE);
break;
}
case NE: {
region.setCursor(Cursor.NE_RESIZE);
break;
}
case E: {
region.setCursor(Cursor.E_RESIZE);
break;
}
case SE: {
region.setCursor(Cursor.SE_RESIZE);
break;
}
case S: {
region.setCursor(Cursor.S_RESIZE);
break;
}
case SW: {
region.setCursor(Cursor.SW_RESIZE);
break;
}
case W: {
region.setCursor(Cursor.W_RESIZE);
break;
}
case NW: {
region.setCursor(Cursor.NW_RESIZE);
break;
}
case C: {
region.setCursor(Cursor.MOVE);
break;
}
}
} else {
region.setCursor(Cursor.DEFAULT);
}
}
protected boolean isInDraggableZone(MouseEvent event) {
zone = Zone.NONE;
if ((event.getY() < RESIZE_MARGIN) && (event.getX() < RESIZE_MARGIN)) {
zone = Zone.NW;
} else if ((event.getY() < RESIZE_MARGIN) && (event.getX() > (region.getWidth() - RESIZE_MARGIN))) {
zone = Zone.NE;
} else if ((event.getY() > (region.getHeight() - RESIZE_MARGIN)) && (event.getX() > (region.getWidth() - RESIZE_MARGIN))) {
zone = Zone.SE;
} else if ((event.getY() > (region.getHeight() - RESIZE_MARGIN)) && (event.getX() < RESIZE_MARGIN)) {
zone = Zone.SW;
} else if (event.getY() < RESIZE_MARGIN) {
zone = Zone.N;
} else if (event.getX() < RESIZE_MARGIN) {
zone = Zone.W;
} else if (event.getY() > (region.getHeight() - RESIZE_MARGIN)) {
zone = Zone.S;
} else if (event.getX() > (region.getWidth() - RESIZE_MARGIN)) {
zone = Zone.E;
} else if (allowMove) {
zone = Zone.C;
}
return !Zone.NONE.equals(zone);
}
protected void mouseDragged(MouseEvent event) {
if (!dragging) {
return;
}
double deltaY = event.getSceneY() - y;
double deltaX = event.getSceneX() - x;
double originY = region.getLayoutY();
double originX = region.getLayoutX();
double newHeight = region.getMinHeight();
double newWidth = region.getMinWidth();
switch (zone) {
case N: {
originY += deltaY;
newHeight -= deltaY;
break;
}
case NE: {
originY += deltaY;
newHeight -= deltaY;
newWidth += deltaX;
break;
}
case E: {
newWidth += deltaX;
break;
}
case SE: {
newHeight += deltaY;
newWidth += deltaX;
break;
}
case S: {
newHeight += deltaY;
break;
}
case SW: {
originX += deltaX;
newHeight += deltaY;
newWidth -= deltaX;
break;
}
case W: {
originX += deltaX;
newWidth -= deltaX;
break;
}
case NW: {
originY += deltaY;
originX += deltaX;
newWidth -= deltaX;
newHeight -= deltaY;
break;
}
case C: {
originY += deltaY;
originX += deltaX;
break;
}
}
if (constrainToParent) {
if (originX < 0) {
if (!Zone.C.equals(zone)) {
newWidth -= Math.abs(originX);
}
originX = 0;
}
if (originY < 0) {
if (!Zone.C.equals(zone)) {
newHeight -= Math.abs(originY);
}
originY = 0;
}
if (Zone.C.equals(zone)) {
if ((newHeight + originY) > region.getParent().getBoundsInLocal().getHeight()) {
originY = region.getParent().getBoundsInLocal().getHeight() - newHeight;
}
if ((newWidth + originX) > region.getParent().getBoundsInLocal().getWidth()) {
originX = region.getParent().getBoundsInLocal().getWidth() - newWidth;
}
} else {
if ((newHeight + originY) > region.getParent().getBoundsInLocal().getHeight()) {
newHeight = region.getParent().getBoundsInLocal().getHeight() - originY;
}
if ((newWidth + originX) > region.getParent().getBoundsInLocal().getWidth()) {
newWidth = region.getParent().getBoundsInLocal().getWidth() - originX;
}
}
}
if (newWidth < MIN_SIZE) {
newWidth = MIN_SIZE;
}
if (newHeight < MIN_SIZE) {
newHeight = MIN_SIZE;
}
if (!Zone.C.equals(zone)) {
// need to set Pref Height/Width otherwise they act as minima.
region.setMinHeight(newHeight);
region.setPrefHeight(newHeight);
region.setMinWidth(newWidth);
region.setPrefWidth(newWidth);
}
region.relocate(originX, originY);
y = event.getSceneY();
x = event.getSceneX();
}
protected void mousePressed(MouseEvent event) {
// ignore clicks outside of the draggable margin
if (!isInDraggableZone(event)) {
return;
}
dragging = true;
// make sure that the minimum height is set to the current height once,
// setting a min height that is smaller than the current height will
// have no effect
if (!initMinHeight) {
region.setMinHeight(region.getHeight());
initMinHeight = true;
}
y = event.getSceneY();
if (!initMinWidth) {
region.setMinWidth(region.getWidth());
initMinWidth = true;
}
x = event.getSceneX();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment