I am working on making a screen recorder in JavaFX and one utility that is mandatory in the screen recorder is to let the user define how much area to record.
I m
Here is a revision @Alexander.Berg 's post. This correctly handles the minWidth and maxWidth properties of the Scene. His version does not do this: it completely ignores maxWidth and butchers minWidth. There is also a piece of code at the bottom that allows dragging the window.
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
public class ResizeHelper {
public static void addResizeListener(Stage stage) {
ResizeListener resizeListener = new ResizeListener(stage);
stage.getScene().addEventHandler(MouseEvent.MOUSE_MOVED, resizeListener);
stage.getScene().addEventHandler(MouseEvent.MOUSE_PRESSED, resizeListener);
stage.getScene().addEventHandler(MouseEvent.MOUSE_DRAGGED, resizeListener);
stage.getScene().addEventHandler(MouseEvent.MOUSE_EXITED, resizeListener);
stage.getScene().addEventHandler(MouseEvent.MOUSE_EXITED_TARGET, resizeListener);
ObservableList<Node> children = stage.getScene().getRoot().getChildrenUnmodifiable();
for (Node child : children) {
addListenerDeeply(child, resizeListener);
}
}
private static void addListenerDeeply(Node node, EventHandler<MouseEvent> listener) {
node.addEventHandler(MouseEvent.MOUSE_MOVED, listener);
node.addEventHandler(MouseEvent.MOUSE_PRESSED, listener);
node.addEventHandler(MouseEvent.MOUSE_DRAGGED, listener);
node.addEventHandler(MouseEvent.MOUSE_EXITED, listener);
node.addEventHandler(MouseEvent.MOUSE_EXITED_TARGET, listener);
if (node instanceof Parent) {
Parent parent = (Parent) node;
ObservableList<Node> children = parent.getChildrenUnmodifiable();
for (Node child : children) {
addListenerDeeply(child, listener);
}
}
}
private static class ResizeListener implements EventHandler<MouseEvent> {
private Stage stage;
private Cursor cursorEvent = Cursor.DEFAULT;
private int border = 4;
private double startX = 0;
private double startY = 0;
private double startScreenX = 0;
private double startScreenY = 0;
public ResizeListener(Stage stage) {
this.stage = stage;
}
@Override
public void handle(MouseEvent mouseEvent) {
EventType<? extends MouseEvent> mouseEventType = mouseEvent.getEventType();
Scene scene = stage.getScene();
double mouseEventX = mouseEvent.getSceneX();
double mouseEventY = mouseEvent.getSceneY();
double sceneWidth = scene.getWidth();
double sceneHeight = scene.getHeight();
if (MouseEvent.MOUSE_MOVED.equals(mouseEventType)) {
if (mouseEventX < border && mouseEventY < border) {
cursorEvent = Cursor.NW_RESIZE;
} else if (mouseEventX < border && mouseEventY > sceneHeight - border) {
cursorEvent = Cursor.SW_RESIZE;
} else if (mouseEventX > sceneWidth - border && mouseEventY < border) {
cursorEvent = Cursor.NE_RESIZE;
} else if (mouseEventX > sceneWidth - border && mouseEventY > sceneHeight - border) {
cursorEvent = Cursor.SE_RESIZE;
} else if (mouseEventX < border) {
cursorEvent = Cursor.W_RESIZE;
} else if (mouseEventX > sceneWidth - border) {
cursorEvent = Cursor.E_RESIZE;
} else if (mouseEventY < border) {
cursorEvent = Cursor.N_RESIZE;
} else if (mouseEventY > sceneHeight - border) {
cursorEvent = Cursor.S_RESIZE;
} else {
cursorEvent = Cursor.DEFAULT;
}
scene.setCursor(cursorEvent);
} else if (MouseEvent.MOUSE_EXITED.equals(mouseEventType) || MouseEvent.MOUSE_EXITED_TARGET.equals(mouseEventType)) {
scene.setCursor(Cursor.DEFAULT);
} else if (MouseEvent.MOUSE_PRESSED.equals(mouseEventType)) {
startX = stage.getWidth() - mouseEventX;
startY = stage.getHeight() - mouseEventY;
} else if (MouseEvent.MOUSE_DRAGGED.equals(mouseEventType)) {
if (!Cursor.DEFAULT.equals(cursorEvent)) {
if (!Cursor.W_RESIZE.equals(cursorEvent) && !Cursor.E_RESIZE.equals(cursorEvent)) {
double minHeight = stage.getMinHeight() > (border * 2) ? stage.getMinHeight() : (border * 2);
double maxHeight = stage.getMaxHeight();
if (Cursor.NW_RESIZE.equals(cursorEvent) || Cursor.N_RESIZE.equals(cursorEvent) || Cursor.NE_RESIZE.equals(cursorEvent)) {
double newHeight = stage.getHeight() - (mouseEvent.getScreenY() - stage.getY());
if (newHeight >= minHeight && newHeight <= maxHeight) {
stage.setHeight(newHeight);
stage.setY(mouseEvent.getScreenY());
} else {
newHeight = Math.min(Math.max(newHeight, minHeight), maxHeight);
// y1 + h1 = y2 + h2
// y1 = y2 + h2 - h1
stage.setY(stage.getY() + stage.getHeight() - newHeight);
stage.setHeight(newHeight);
}
} else {
stage.setHeight(Math.min(Math.max(mouseEventY + startY, minHeight), maxHeight));
}
}
if (!Cursor.N_RESIZE.equals(cursorEvent) && !Cursor.S_RESIZE.equals(cursorEvent)) {
double minWidth = stage.getMinWidth() > (border * 2) ? stage.getMinWidth() : (border * 2);
double maxWidth = stage.getMaxWidth();
if (Cursor.NW_RESIZE.equals(cursorEvent) || Cursor.W_RESIZE.equals(cursorEvent) || Cursor.SW_RESIZE.equals(cursorEvent)) {
double newWidth = stage.getWidth() - (mouseEvent.getScreenX() - stage.getX());
if (newWidth >= minWidth && newWidth <= maxWidth) {
stage.setWidth(newWidth);
stage.setX(mouseEvent.getScreenX());
} else {
newWidth = Math.min(Math.max(newWidth, minWidth), maxWidth);
// x1 + w1 = x2 + w2
// x1 = x2 + w2 - w1
stage.setX(stage.getX() + stage.getWidth() - newWidth);
stage.setWidth(newWidth);
}
} else {
stage.setWidth(Math.min(Math.max(mouseEventX + startX, minWidth), maxWidth));
}
}
}
}
if (MouseEvent.MOUSE_PRESSED.equals(mouseEventType)) {
startScreenX = mouseEvent.getScreenX();
startScreenY = mouseEvent.getScreenY();
} else if (MouseEvent.MOUSE_DRAGGED.equals(mouseEventType)) {
if (Cursor.DEFAULT.equals(cursorEvent)) {
stage.setX(stage.getX() + mouseEvent.getScreenX() - startScreenX);
startScreenX = mouseEvent.getScreenX();
stage.setY(stage.getY() + mouseEvent.getScreenY() - startScreenY);
startScreenY = mouseEvent.getScreenY();
}
}
}
}
}
Here is an updated version of ResizeHelper posted by @Alexander.Berg, which supports window drag:
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
//created by Alexander Berg
public class ResizeHelper {
private static double xOffset = 0;
private static double yOffset = 0;
public static void addResizeListener(Stage stage) {
ResizeListener resizeListener = new ResizeListener(stage);
stage.getScene().addEventHandler(MouseEvent.MOUSE_MOVED, resizeListener);
stage.getScene().addEventHandler(MouseEvent.MOUSE_PRESSED, resizeListener);
stage.getScene().addEventHandler(MouseEvent.MOUSE_DRAGGED, resizeListener);
stage.getScene().addEventHandler(MouseEvent.MOUSE_EXITED, resizeListener);
stage.getScene().addEventHandler(MouseEvent.MOUSE_EXITED_TARGET, resizeListener);
ObservableList<Node> children = stage.getScene().getRoot().getChildrenUnmodifiable();
for (Node child : children) {
addListenerDeeply(child, resizeListener);
}
}
public static void addListenerDeeply(Node node, EventHandler<MouseEvent> listener) {
node.addEventHandler(MouseEvent.MOUSE_MOVED, listener);
node.addEventHandler(MouseEvent.MOUSE_PRESSED, listener);
node.addEventHandler(MouseEvent.MOUSE_DRAGGED, listener);
node.addEventHandler(MouseEvent.MOUSE_EXITED, listener);
node.addEventHandler(MouseEvent.MOUSE_EXITED_TARGET, listener);
if (node instanceof Parent) {
Parent parent = (Parent) node;
ObservableList<Node> children = parent.getChildrenUnmodifiable();
for (Node child : children) {
addListenerDeeply(child, listener);
}
}
}
static class ResizeListener implements EventHandler<MouseEvent> {
private Stage stage;
private Cursor cursorEvent = Cursor.DEFAULT;
private int border = 4;
private double startX = 0;
private double startY = 0;
public ResizeListener(Stage stage) {
this.stage = stage;
}
@Override
public void handle(MouseEvent mouseEvent) {
EventType<? extends MouseEvent> mouseEventType = mouseEvent.getEventType();
Scene scene = stage.getScene();
double mouseEventX = mouseEvent.getSceneX(), mouseEventY = mouseEvent.getSceneY(), sceneWidth = scene.getWidth(), sceneHeight = scene.getHeight();
if (MouseEvent.MOUSE_MOVED.equals(mouseEventType) == true) {
if (mouseEventX < border && mouseEventY < border) {
cursorEvent = Cursor.NW_RESIZE;
} else if (mouseEventX < border && mouseEventY > sceneHeight - border) {
cursorEvent = Cursor.SW_RESIZE;
} else if (mouseEventX > sceneWidth - border && mouseEventY < border) {
cursorEvent = Cursor.NE_RESIZE;
} else if (mouseEventX > sceneWidth - border && mouseEventY > sceneHeight - border) {
cursorEvent = Cursor.SE_RESIZE;
} else if (mouseEventX < border) {
cursorEvent = Cursor.W_RESIZE;
} else if (mouseEventX > sceneWidth - border) {
cursorEvent = Cursor.E_RESIZE;
} else if (mouseEventY < border) {
cursorEvent = Cursor.N_RESIZE;
} else if (mouseEventY > sceneHeight - border) {
cursorEvent = Cursor.S_RESIZE;
} else {
cursorEvent = Cursor.DEFAULT;
}
scene.setCursor(cursorEvent);
} else if (MouseEvent.MOUSE_EXITED.equals(mouseEventType) || MouseEvent.MOUSE_EXITED_TARGET.equals(mouseEventType)) {
scene.setCursor(Cursor.DEFAULT);
} else if (MouseEvent.MOUSE_PRESSED.equals(mouseEventType) == true) {
startX = stage.getWidth() - mouseEventX;
startY = stage.getHeight() - mouseEventY;
xOffset = mouseEvent.getSceneX();
yOffset = mouseEvent.getSceneY();
} else if (MouseEvent.MOUSE_DRAGGED.equals(mouseEventType) == true) {
if (Cursor.DEFAULT.equals(cursorEvent) == false) {
if (Cursor.W_RESIZE.equals(cursorEvent) == false && Cursor.E_RESIZE.equals(cursorEvent) == false) {
double minHeight = stage.getMinHeight() > (border * 2) ? stage.getMinHeight() : (border * 2);
if (Cursor.NW_RESIZE.equals(cursorEvent) == true || Cursor.N_RESIZE.equals(cursorEvent) == true || Cursor.NE_RESIZE.equals(cursorEvent) == true) {
if (stage.getHeight() > minHeight || mouseEventY < 0) {
stage.setHeight(stage.getY() - mouseEvent.getScreenY() + stage.getHeight());
stage.setY(mouseEvent.getScreenY());
}
} else {
if (stage.getHeight() > minHeight || mouseEventY + startY - stage.getHeight() > 0) {
stage.setHeight(mouseEventY + startY);
}
}
}
if (Cursor.N_RESIZE.equals(cursorEvent) == false && Cursor.S_RESIZE.equals(cursorEvent) == false) {
double minWidth = stage.getMinWidth() > (border * 2) ? stage.getMinWidth() : (border * 2);
if (Cursor.NW_RESIZE.equals(cursorEvent) == true || Cursor.W_RESIZE.equals(cursorEvent) == true || Cursor.SW_RESIZE.equals(cursorEvent) == true) {
if (stage.getWidth() > minWidth || mouseEventX < 0) {
stage.setWidth(stage.getX() - mouseEvent.getScreenX() + stage.getWidth());
stage.setX(mouseEvent.getScreenX());
}
} else {
if (stage.getWidth() > minWidth || mouseEventX + startX - stage.getWidth() > 0) {
stage.setWidth(mouseEventX + startX);
}
}
}
} else if (mouseEvent.getSceneY() < 70) {
stage.setX(mouseEvent.getScreenX() - xOffset);
stage.setY(mouseEvent.getScreenY() - yOffset);
}
}
}
}
}
I've looked into some threads where they discuss about it before I tried myself to "solve" it, but in the end there was no Problem.
I created a button in the bottom-right corner, gave the Button an OnDraggedMethod and simply used the mouseevent and set the Height/Width on MousePosition - stage X/Y position.
double newX = event.getScreenX() - stage.getX() + 13;
double newY = event.getScreenY() - stage.getY() + 10;
if (newX % 5 == 0 || newY % 5 == 0) {
if (newX > 550) {
stage.setWidth(newX);
} else {
stage.setWidth(550);
}
if (newY > 200) {
stage.setHeight(newY);
} else {
stage.setHeight(200);
}
}
Also I set a minimal Width and Height. And because i think that the laggy resize-animation is annoying i surrounded the statement with the if and just resize the stage every 5th pixel.
Looks much better!
PS: The +13 and +10 is just for the MousePosition. Otherweise the Mouse is on the Corner not on the Button^^.
What can I say... I'm a slow learner. Took me a bit to really go through all the code and understand it. Its not hard... just a lot going on. I decided to make some changes to use absolute mouse position as opposed to relative mouse position, I also changed the class overall from using static members and whatnot. Here's what I came up with and works quite well for myself. For those searching for similar solutions, enjoy my copious comments... Using this version of this class is as simple as:
ResizeListener listener = new ResizeListener(stage);
import draco_logger.LeafLogger;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.stage.Screen;
import javafx.stage.Stage;
//originally created by Alexander Berg
//https://stackoverflow.com/questions/19455059/allow-user-to-resize-an-undecorated-stage
//
//modified by Joseph Adomatis
//extracted ResizeListener class and made public, added arg for CWinMaxButton for detecting Maximized Window, using my own logger
//changed MouseDragged routines and private variables to make use of screen absolute values instead of relative values
//MouseDragged also updated to respect Min/Max sizes
public class ResizeListener implements EventHandler<MouseEvent> {
public ResizeListener(Stage stage) {
LeafLogger log = new LeafLogger("ResizeListener", "Constructor(Stage)");
this.stage = stage;
this._max = null;
isPressed = false;
cursorEvent = Cursor.DEFAULT;
border = 3;
stageStartH = 0;
stageStartW = 0;
stageStartX = 0;
stageStartY = 0;
this.addResizeListener();
log.Wither();
}
public void AddMaxButton(CWinMaxButton max){ this._max = max; }
private void addResizeListener() {
LeafLogger log = new LeafLogger("ResizeListener", "addResizeListener");
this.stage.getScene().addEventHandler(MouseEvent.MOUSE_MOVED, this);
this.stage.getScene().addEventHandler(MouseEvent.MOUSE_PRESSED, this);
this.stage.getScene().addEventHandler(MouseEvent.MOUSE_DRAGGED, this);
this.stage.getScene().addEventHandler(MouseEvent.MOUSE_ENTERED, this);
this.stage.getScene().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, this);
this.stage.getScene().addEventHandler(MouseEvent.MOUSE_EXITED, this);
this.stage.getScene().addEventHandler(MouseEvent.MOUSE_EXITED_TARGET, this);
ObservableList<Node> children = this.stage.getScene().getRoot().getChildrenUnmodifiable();
for (Node child : children) {
addListenerDeeply(child);
}
log.Wither();
}
private void addListenerDeeply(Node node) {
LeafLogger log = new LeafLogger("ResizeListener", "addListenerDeeply");
node.addEventHandler(MouseEvent.MOUSE_MOVED, this);
node.addEventHandler(MouseEvent.MOUSE_PRESSED, this);
node.addEventHandler(MouseEvent.MOUSE_DRAGGED, this);
node.addEventHandler(MouseEvent.MOUSE_ENTERED, this);
node.addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, this);
node.addEventHandler(MouseEvent.MOUSE_EXITED, this);
node.addEventHandler(MouseEvent.MOUSE_EXITED_TARGET, this);
if (node instanceof Parent) {
Parent parent = (Parent) node;
ObservableList<Node> children = parent.getChildrenUnmodifiable();
for (Node child : children) {
addListenerDeeply(child);
}
}
log.Wither();
}
@Override
public void handle(MouseEvent mouseEvent) {
LeafLogger log = new LeafLogger("ResizeListener","handle");
// Check if we registered a maximize button
if(this._max != null){
// Check with the maximize button to see if window is currently maximized
if(this._max.GetMaximized()){
// We do not resize Maximized windows
log.Wither();
return;
}
}
EventType<? extends MouseEvent> mouseEventType = mouseEvent.getEventType();
Scene scene = stage.getScene();
// set minHeight vars in such a way as to ensure that there is always a border over which we can continue to resize again
// if stage MinHeight were 0 and you resized to 0, the draggable zone is gone and you cannot resize anymore
// so regardless of app preference, we artificially set min sizes to leave all borders
double minHeight = stage.getMinHeight() > (border*2) ? stage.getMinHeight() : (border*2);
double minWidth = stage.getMinWidth() > (border*2) ? stage.getMinWidth() : (border*2);
double maxHeight = stage.getMaxHeight();
double maxWidth = stage.getMaxWidth();
// capture the position of the mouse cursor relative to the stage anchor point at the time of the event
double mouseEventX = mouseEvent.getSceneX();
double mouseEventY = mouseEvent.getSceneY();
// capture the current scene Height and Width
double sceneHeight = scene.getHeight();
double sceneWidth = scene.getWidth();
// capture the screen max visual Height and Width
double screenHeight = Screen.getPrimary().getVisualBounds().getHeight();
double screenWidth = Screen.getPrimary().getVisualBounds().getWidth();
// if MOUSE_MOVED and its new position is over one of the stage borders, we want to update the cursor to be one of the resize variety
if (MouseEvent.MOUSE_MOVED.equals(mouseEventType)) {
if (mouseEventX < border && mouseEventY < border) {
cursorEvent = Cursor.NW_RESIZE;
} else if (mouseEventX < border && mouseEventY > sceneHeight - border) {
cursorEvent = Cursor.SW_RESIZE;
} else if (mouseEventX > sceneWidth - border && mouseEventY < border) {
cursorEvent = Cursor.NE_RESIZE;
} else if (mouseEventX > sceneWidth - border && mouseEventY > sceneHeight - border) {
cursorEvent = Cursor.SE_RESIZE;
} else if (mouseEventX < border) {
cursorEvent = Cursor.W_RESIZE;
} else if (mouseEventX > sceneWidth - border) {
cursorEvent = Cursor.E_RESIZE;
} else if (mouseEventY < border) {
cursorEvent = Cursor.N_RESIZE;
} else if (mouseEventY > sceneHeight - border) {
cursorEvent = Cursor.S_RESIZE;
} else {
cursorEvent = Cursor.DEFAULT;
}
scene.setCursor(cursorEvent);
// if MOUSE_EXITED the stage screen area and we'd pressed but did not release the mouse button, then we want to maintain our current cursor
// otherwise, since the mouse is outside our stage, we return it to the default cursor
} else if(MouseEvent.MOUSE_EXITED.equals(mouseEventType) || MouseEvent.MOUSE_EXITED_TARGET.equals(mouseEventType)){
if(!isPressed){
scene.setCursor(Cursor.DEFAULT);
}
// similarly, if MOUSE ENTERED the stage screen area and we'd pressed but did not release the mouse button, then we want to maintain the current cursor
// otherwise, since the mouse is coming back to us, we dont want to keep whatever other cursor may have been set by other windows so we return to default
} else if(MouseEvent.MOUSE_ENTERED.equals(mouseEventType) || MouseEvent.MOUSE_ENTERED_TARGET.equals(mouseEventType)){
if(!isPressed){
scene.setCursor(Cursor.DEFAULT);
}
// if MOUSE_PRESSED we might need to keep track that we pressed it and are initiating a potential drag/resize event
} else if (MouseEvent.MOUSE_PRESSED.equals(mouseEventType)) {
// right now we dont care if mouse was pressed, but we might
boolean iCare = false;
// check the cursor type, if it is a resize cursor then mouse is over a border and we DO care that we pressed the mouse
if(Cursor.N_RESIZE.equals(cursorEvent) || Cursor.S_RESIZE.equals(cursorEvent) || Cursor.E_RESIZE.equals(cursorEvent) || Cursor.W_RESIZE.equals(cursorEvent)){
iCare = true;
} else if(Cursor.NE_RESIZE.equals(cursorEvent) || Cursor.NW_RESIZE.equals(cursorEvent) || Cursor.SE_RESIZE.equals(cursorEvent) || Cursor.SW_RESIZE.equals(cursorEvent)){
iCare = true;
}
// if we care that we pressed the mouse, we need to capture the initial data that will be used by our drag event handler to actually resize the window
if(iCare){
stageStartH = stage.getHeight();
stageStartW = stage.getWidth();
stageStartX = stage.getX();
stageStartY = stage.getY();
mouseStartX = mouseEvent.getScreenX();
mouseStartY = mouseEvent.getScreenY();
isPressed = true;
}
// if MOUSE_RELEASED, we don't care what the mouse does anymore so release our flag
} else if(MouseEvent.MOUSE_RELEASED.equals(mouseEventType)){
isPressed = false;
// if MOUSE_DRAGGED, this handler might have something to do
} else if (MouseEvent.MOUSE_DRAGGED.equals(mouseEventType)) {
// if the cursor is still default, then this handler doesnt care about the drag event so ignore everything else
// this handler only cares if the cursor is of the resize variety
if(Cursor.DEFAULT.equals(cursorEvent)){
return;
}
// Check if there is a vertical component to the window resize
// The only time there isn't a vertical component is if the mouse is strictly on the west or east side of the stage
if (!Cursor.W_RESIZE.equals(cursorEvent) && !Cursor.E_RESIZE.equals(cursorEvent)) {
// There is a vertical component.
// If we are resizing the north side however, we will be resetting both the Y coordinate of the stage anchor as well as the stage height
if (Cursor.NW_RESIZE.equals(cursorEvent) || Cursor.N_RESIZE.equals(cursorEvent) || Cursor.NE_RESIZE.equals(cursorEvent)) {
double mouseDifY = mouseStartY - stageStartY;
// we are moving the north side
// figure out where the south side of the stage is
double finalY = stageStartY + stage.getHeight();
// we are free to move the north side until it reaches the point where the distance to the south side is greater than maxHeight
// OR, we run into the top of the screen
double minStageY = (finalY - maxHeight) > 0 ? (finalY - maxHeight): 0;
double minMouseY = minStageY + mouseDifY;
// we are free to move the north side until it reaches the point where the distance to the south side is less than minHeight
double maxStageY = finalY - minHeight;
double maxMouseY = maxStageY + mouseDifY;
// capture the absolute position of the mouse at the time of the event
double curMouseY = mouseEvent.getScreenY();
if(curMouseY < minMouseY){
stage.setY(minStageY);
// Our mouse passed the value at which we would breach max height
// We dont want the curMouseY to update any more until the mouse is back over the border.
// Otherwise, the window border would resize relative to mouse movement, not relative to absolute mouse position
curMouseY = minMouseY;
} else if(curMouseY > maxMouseY){
stage.setY(maxStageY);
// Our mouse passed the value at which we would breach min height
// We dont want the curMouseY to update any more until the mouse is back over the border.
// Otherwise, the window border would resize relative to mouse movement, not relative to absolute mouse position
curMouseY = maxMouseY;
} else {
stage.setY(curMouseY - mouseDifY);
}
double newY = stage.getY();
double newHeight = finalY - newY;
stage.setHeight(newHeight);
// Our stage and mouse start variables were set via the mouse pressed event handle
// If we did above procedure in the mouse released event handle, it would work, but there would be no display update till mouse released.
// By using mouse dragged event handle, we get display update each event cycle... but we have to constantly update our start variables for the next cycle
// While dragging mouse, you aren't releasing and re-pressing it to update the variables....
stageStartY = stage.getY();
stageStartH = stage.getHeight();
mouseStartY = curMouseY;
} else {
// Else, we are resizing the south side, and the Y coordinate remains fixed. We only change the stage height
// figure out where the current south side actually is
double curFinalY = stageStartY + stageStartH;
double mouseDifY = mouseStartY - curFinalY;
// we are free to move the north side until it reaches the point where the distance to the south side is greater than maxHeight
// OR, we run into the bottom of the screen
double maxFinalY = (stageStartY + maxHeight) < screenHeight ? (stageStartY + maxHeight) : screenHeight;
double maxMouseY = maxFinalY + mouseDifY;
// we are free to move the south side until the point where the distance from anchor to south side is less than minHeight
double minFinalY = stageStartY + minHeight;
double minMouseY = minFinalY + mouseDifY;
// capture the absolute position of the mouse at the time of the event
double curMouseY = mouseEvent.getScreenY();
if (curMouseY < minMouseY) {
stage.setHeight(minHeight);
// Our mouse passed the value at which we would breach min height
// We don't want the curMouseY to update any more until the mouse is back over the border.
// Otherwise, the window border would resize relative to mouse movement, not relative to absolute mouse position
curMouseY = minMouseY;
} else if(curMouseY > maxMouseY){
double newFinalY = maxMouseY - mouseDifY;
double newHeight = newFinalY - stageStartY;
stage.setHeight(newHeight);
// Our mouse passed the value at which we would breach max height
// We don't want the curMouseY to update any more until the mouse is back over the border.
// Otherwise, the window border would resize relative to mouse movement, not relative to absolute mouse position
curMouseY = maxMouseY;
} else {
double newFinalY = curMouseY - mouseDifY;
double newHeight = newFinalY - stageStartY;
stage.setHeight(newHeight);
}
// Our stage and mouse start variables were set via the mouse pressed event handle
// If we did above procedure in the mouse released event handle, it would work, but there would be no display update till mouse released.
// By using mouse dragged event handle, we get display update each event cycle... but we have to constantly update our start variables for the next cycle
// While dragging mouse, you aren't releasing and re-pressing it to update the variables....
stageStartY = stage.getY();
stageStartH = stage.getHeight();
mouseStartY = curMouseY;
}
}
// Check if there is a horizontal component to the window resize
// The only time there isn't a horizontal component is if the mouse is strictly on the north or south side of the stage.
if (!Cursor.N_RESIZE.equals(cursorEvent) && !Cursor.S_RESIZE.equals(cursorEvent)) {
// There is a horizontal component.
// If we are resizing the west side however, we will be resetting both the X coordinate of the stage anchor as well as the stage width.
if (Cursor.NW_RESIZE.equals(cursorEvent) || Cursor.W_RESIZE.equals(cursorEvent) || Cursor.SW_RESIZE.equals(cursorEvent)) {
// we are moving the west side
// figure out where the east side of the stage is
double mouseDifX = mouseStartX - stageStartX;
double finalX = stageStartX + stageStartW;
// we are free to move the west side until it reaches the point where the distance to the east side is greater than maxWidth
// OR, we run into the left of the screen
double minStageX = (finalX - maxHeight) > 0 ? (finalX - maxHeight): 0;
double minMouseX = minStageX + mouseDifX;
// we are free to move the west side until it reaches the point where the distance to the east side is less than minWidth
double maxStageX = finalX - minWidth;
double maxMouseX = maxStageX + mouseDifX;
// capture the absolute position of the mouse at the time of the event
double curMouseX = mouseEvent.getScreenX();
if(curMouseX < minMouseX){
stage.setX(minStageX);
// Our mouse passed the value at which we would breach max width
// We don't want the curMouseX to update any more until the mouse is back over the border.
// Otherwise, the window border would resize relative to mouse movement, not relative to absolute mouse position
curMouseX = minMouseX;
} else if(curMouseX > maxMouseX){
stage.setX(maxStageX);
curMouseX = maxMouseX;
// Our mouse passed the value at which we would breach min width
// We don't want the curMouseX to update any more until the mouse is back over the border.
// Otherwise, the window border would resize relative to mouse movement, not relative to absolute mouse position
} else {
stage.setX(curMouseX - mouseDifX);
}
double newX = stage.getX();
double newWidth = finalX - newX;
stage.setWidth(newWidth);
// Our stage and mouse start variables were set via the mouse pressed event handle
// If we did above procedure in the mouse released event handle, it would work, but there would be no display update till mouse released.
// By using mouse dragged event handle, we get display update each event cycle... but we have to constantly update our start variables for the next cycle
// While dragging mouse, you aren't releasing and re-pressing it to update the variables....
stageStartX = stage.getX();
stageStartW = stage.getWidth();
mouseStartX = curMouseX;
} else {
// Else, we are resizing the east side, and the X coordinate remains fixed. We only change the stage width.
// figure out where the current east side actually is
double curFinalX = stageStartX + stageStartW;
double mouseDifX = mouseStartX - curFinalX;
// we are free to move the east side until the point where the distance from anchor to east side is less than minWidth
double minFinalX = stageStartX + minWidth;
double minMouseX = minFinalX + mouseDifX;
// we are free to move the east side until it reaches the point where the distance to the west side is greater than maxWidth
// OR, we run into the right of the screen
double maxFinalX = (stageStartX + maxWidth) < screenWidth ? (stageStartX + maxWidth) : screenWidth;
double maxMouseX = maxFinalX + mouseDifX;
// capture the absolute position of the mouse at the time of the event
double curMouseX = mouseEvent.getScreenX();
if (curMouseX < minMouseX) {
stage.setWidth(minWidth);
curMouseX = minMouseX;
// Our mouse passed the value at which we would breach min width
// We don't want the curMouseX to update any more until the mouse is back over the border.
// Otherwise, the window border would resize relative to mouse movement, not relative to absolute mouse position
} else if(curMouseX > maxMouseX){
double newFinalX = maxMouseX - mouseDifX;
double newWidth = newFinalX - stageStartX;
stage.setWidth(newWidth);
// Our mouse passed the value at which we would breach max width
// We don't want the curMouseY to update any more until the mouse is back over the border.
// Otherwise, the window border would resize relative to mouse movement, not relative to absolute mouse position
curMouseX = maxMouseX;
} else {
double newFinalX = curMouseX - mouseDifX;
double newWidth = newFinalX - stageStartX;
stage.setWidth(newWidth);
}
// Our stage and mouse start variables were set via the mouse pressed event handle
// If we did above procedure in the mouse released event handle, it would work, but there would be no display update till mouse released.
// By using mouse dragged event handle, we get display update each event cycle... but we have to constantly update our start variables for the next cycle
// While dragging mouse, you aren't releasing and re-pressing it to update the variables....
stageStartX = stage.getX();
stageStartW = stage.getWidth();
mouseStartX = curMouseX;
}
}
}
log.Wither();
}
// <editor-fold defaultstate="collapsed" desc="***** Private Variable Declarations *****">
private boolean isPressed;
private Cursor cursorEvent;
private CWinMaxButton _max;
private double mouseStartX;
private double mouseStartY;
private double stageStartH;
private double stageStartW;
private double stageStartX;
private double stageStartY;
private final int border;
private final Stage stage;
// </editor-fold>
}
I updated @Yevhenii.Kanivets ' updated version of @Alexander.Berg 's ResizeHelper class. This includes the ability to drag the scene, with the best implementation I've found of ResizeHelper. Also set minimum stage size to a height of 1 and width of 1 instead of 0, because there are bugs if they stay at 0.
Someone still can improve on this answer. The SW, W, NW, N, and NE edges do not resize smoothly, although it's a minor issue. When resizing from those edges, the scene also can shift slightly. They should resize smoothly like the S, SE, and E edges do. The latter edges do not shift the scene.
I cannot speak for @M.K 's implementation as I am not using Kotlin, but I'd give that a shot first if you are.
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
/**
* Util class to handle window resizing when a stage style set to StageStyle.UNDECORATED.
* Includes dragging of the stage.
* Original on 6/13/14.
* Updated on 8/15/17.
* Updated on 12/19/19.
*
* @author Alexander.Berg
* @author Evgenii Kanivets
* @author Zachary Perales
*/
public class ResizeHelper {
public static void addResizeListener(Stage stage) {
addResizeListener(stage, 1, 1, Double.MAX_VALUE, Double.MAX_VALUE);
}
public static void addResizeListener(Stage stage, double minWidth, double minHeight, double maxWidth, double maxHeight) {
ResizeListener resizeListener = new ResizeListener(stage);
stage.getScene().addEventHandler(MouseEvent.MOUSE_MOVED, resizeListener);
stage.getScene().addEventHandler(MouseEvent.MOUSE_PRESSED, resizeListener);
stage.getScene().addEventHandler(MouseEvent.MOUSE_DRAGGED, resizeListener);
stage.getScene().addEventHandler(MouseEvent.MOUSE_EXITED, resizeListener);
stage.getScene().addEventHandler(MouseEvent.MOUSE_EXITED_TARGET, resizeListener);
resizeListener.setMinWidth(minWidth);
resizeListener.setMinHeight(minHeight);
resizeListener.setMaxWidth(maxWidth);
resizeListener.setMaxHeight(maxHeight);
ObservableList<Node> children = stage.getScene().getRoot().getChildrenUnmodifiable();
for (Node child : children) {
addListenerDeeply(child, resizeListener);
}
}
private static void addListenerDeeply(Node node, EventHandler<MouseEvent> listener) {
node.addEventHandler(MouseEvent.MOUSE_MOVED, listener);
node.addEventHandler(MouseEvent.MOUSE_PRESSED, listener);
node.addEventHandler(MouseEvent.MOUSE_DRAGGED, listener);
node.addEventHandler(MouseEvent.MOUSE_EXITED, listener);
node.addEventHandler(MouseEvent.MOUSE_EXITED_TARGET, listener);
if (node instanceof Parent) {
Parent parent = (Parent) node;
ObservableList<Node> children = parent.getChildrenUnmodifiable();
for (Node child : children) {
addListenerDeeply(child, listener);
}
}
}
static class ResizeListener implements EventHandler<MouseEvent> {
private Stage stage;
private Cursor cursorEvent = Cursor.DEFAULT;
private boolean resizing = true;
private int border = 4;
private double startX = 0;
private double startY = 0;
private double screenOffsetX = 0;
private double screenOffsetY = 0;
// Max and min sizes for controlled stage
private double minWidth;
private double maxWidth;
private double minHeight;
private double maxHeight;
public ResizeListener(Stage stage) {
this.stage = stage;
}
public void setMinWidth(double minWidth) {
this.minWidth = minWidth;
}
public void setMaxWidth(double maxWidth) {
this.maxWidth = maxWidth;
}
public void setMinHeight(double minHeight) {
this.minHeight = minHeight;
}
public void setMaxHeight(double maxHeight) {
this.maxHeight = maxHeight;
}
@Override
public void handle(MouseEvent mouseEvent) {
EventType<? extends MouseEvent> mouseEventType = mouseEvent.getEventType();
Scene scene = stage.getScene();
double mouseEventX = mouseEvent.getSceneX(),
mouseEventY = mouseEvent.getSceneY(),
sceneWidth = scene.getWidth(),
sceneHeight = scene.getHeight();
if (MouseEvent.MOUSE_MOVED.equals(mouseEventType)) {
if (mouseEventX < border && mouseEventY < border) {
cursorEvent = Cursor.NW_RESIZE;
} else if (mouseEventX < border && mouseEventY > sceneHeight - border) {
cursorEvent = Cursor.SW_RESIZE;
} else if (mouseEventX > sceneWidth - border && mouseEventY < border) {
cursorEvent = Cursor.NE_RESIZE;
} else if (mouseEventX > sceneWidth - border && mouseEventY > sceneHeight - border) {
cursorEvent = Cursor.SE_RESIZE;
} else if (mouseEventX < border) {
cursorEvent = Cursor.W_RESIZE;
} else if (mouseEventX > sceneWidth - border) {
cursorEvent = Cursor.E_RESIZE;
} else if (mouseEventY < border) {
cursorEvent = Cursor.N_RESIZE;
} else if (mouseEventY > sceneHeight - border) {
cursorEvent = Cursor.S_RESIZE;
} else {
cursorEvent = Cursor.DEFAULT;
}
scene.setCursor(cursorEvent);
} else if (MouseEvent.MOUSE_EXITED.equals(mouseEventType) || MouseEvent.MOUSE_EXITED_TARGET.equals(mouseEventType)) {
scene.setCursor(Cursor.DEFAULT);
} else if (MouseEvent.MOUSE_PRESSED.equals(mouseEventType)) {
startX = stage.getWidth() - mouseEventX;
startY = stage.getHeight() - mouseEventY;
} else if (MouseEvent.MOUSE_DRAGGED.equals(mouseEventType)) {
if (!Cursor.DEFAULT.equals(cursorEvent)) {
resizing = true;
if (!Cursor.W_RESIZE.equals(cursorEvent) && !Cursor.E_RESIZE.equals(cursorEvent)) {
double minHeight = stage.getMinHeight() > (border * 2) ? stage.getMinHeight() : (border * 2);
if (Cursor.NW_RESIZE.equals(cursorEvent) || Cursor.N_RESIZE.equals(cursorEvent)
|| Cursor.NE_RESIZE.equals(cursorEvent)) {
if (stage.getHeight() > minHeight || mouseEventY < 0) {
setStageHeight(stage.getY() - mouseEvent.getScreenY() + stage.getHeight());
stage.setY(mouseEvent.getScreenY() );
}
} else {
if (stage.getHeight() > minHeight || mouseEventY + startY - stage.getHeight() > 0) {
setStageHeight(mouseEventY + startY);
}
}
}
if (!Cursor.N_RESIZE.equals(cursorEvent) && !Cursor.S_RESIZE.equals(cursorEvent)) {
double minWidth = stage.getMinWidth() > (border * 2) ? stage.getMinWidth() : (border * 2);
if (Cursor.NW_RESIZE.equals(cursorEvent) || Cursor.W_RESIZE.equals(cursorEvent)
|| Cursor.SW_RESIZE.equals(cursorEvent)) {
if (stage.getWidth() > minWidth || mouseEventX < 0) {
setStageWidth(stage.getX() - mouseEvent.getScreenX() + stage.getWidth());
stage.setX(mouseEvent.getScreenX());
}
} else {
if (stage.getWidth() > minWidth || mouseEventX + startX - stage.getWidth() > 0) {
setStageWidth(mouseEventX + startX);
}
}
}
resizing = false;
}
}
if (MouseEvent.MOUSE_PRESSED.equals(mouseEventType) && Cursor.DEFAULT.equals(cursorEvent)) {
resizing = false;
screenOffsetX = stage.getX() - mouseEvent.getScreenX();
screenOffsetY = stage.getY() - mouseEvent.getScreenY();
}
if (MouseEvent.MOUSE_DRAGGED.equals(mouseEventType) && Cursor.DEFAULT.equals(cursorEvent) && resizing == false) {
stage.setX(mouseEvent.getScreenX() + screenOffsetX);
stage.setY(mouseEvent.getScreenY() + screenOffsetY);
}
}
private void setStageWidth(double width) {
width = Math.min(width, maxWidth);
width = Math.max(width, minWidth);
stage.setWidth(width);
}
private void setStageHeight(double height) {
height = Math.min(height, maxHeight);
height = Math.max(height, minHeight);
stage.setHeight(height);
}
}
}
Edit:
I've updated this code to not drag any scrollbars, as nobody wants that functionality and I needed to remove it myself. It should be easy to disallow additional controls from being dragged if you come across the need, by comparing my two code submissions on here.
Note that if the disallowed control is on the edge of the scene, you need to wrap it in something draggable to access resizing on those edges.
public class ResizeHelper {
static boolean isScrollbar = false;
public static void addResizeListener(Stage stage) {
addResizeListener(stage, 1, 1, Double.MAX_VALUE, Double.MAX_VALUE);
}
public static void addResizeListener(Stage stage, double minWidth, double minHeight, double maxWidth, double maxHeight) {
ResizeListener resizeListener = new ResizeListener(stage);
stage.getScene().addEventHandler(MouseEvent.MOUSE_MOVED, resizeListener);
stage.getScene().addEventHandler(MouseEvent.MOUSE_PRESSED, resizeListener);
stage.getScene().addEventHandler(MouseEvent.MOUSE_DRAGGED, resizeListener);
stage.getScene().addEventHandler(MouseEvent.MOUSE_EXITED, resizeListener);
stage.getScene().addEventHandler(MouseEvent.MOUSE_EXITED_TARGET, resizeListener);
resizeListener.setMinWidth(minWidth);
resizeListener.setMinHeight(minHeight);
resizeListener.setMaxWidth(maxWidth);
resizeListener.setMaxHeight(maxHeight);
ObservableList<Node> children = stage.getScene().getRoot().getChildrenUnmodifiable();
for (Node child : children) {
if (child instanceof ScrollBar) {
isScrollbar = true;
} else if (!(child instanceof ScrollBar)) {
isScrollbar = false;
addListenerDeeply(child, resizeListener);
}
}
}
private static void addListenerDeeply(Node node, EventHandler<MouseEvent> listener) {
node.addEventHandler(MouseEvent.MOUSE_MOVED, listener);
node.addEventHandler(MouseEvent.MOUSE_PRESSED, listener);
node.addEventHandler(MouseEvent.MOUSE_DRAGGED, listener);
node.addEventHandler(MouseEvent.MOUSE_EXITED, listener);
node.addEventHandler(MouseEvent.MOUSE_EXITED_TARGET, listener);
if (node instanceof Parent) {
Parent parent = (Parent) node;
ObservableList<Node> children = parent.getChildrenUnmodifiable();
for (Node child : children) {
if (child instanceof ScrollBar) {
isScrollbar = true;
} else if (!(child instanceof ScrollBar)) {
isScrollbar = false;
addListenerDeeply(child, listener);
}
}
}
}
static class ResizeListener implements EventHandler<MouseEvent> {
private Stage stage;
private Cursor cursorEvent = Cursor.DEFAULT;
private boolean resizing = true;
private int border = 4;
private double startX = 0;
private double startY = 0;
private double screenOffsetX = 0;
private double screenOffsetY = 0;
// Max and min sizes for controlled stage
private double minWidth;
private double maxWidth;
private double minHeight;
private double maxHeight;
public ResizeListener(Stage stage) {
this.stage = stage;
}
public void setMinWidth(double minWidth) {
this.minWidth = minWidth;
}
public void setMaxWidth(double maxWidth) {
this.maxWidth = maxWidth;
}
public void setMinHeight(double minHeight) {
this.minHeight = minHeight;
}
public void setMaxHeight(double maxHeight) {
this.maxHeight = maxHeight;
}
@Override
public void handle(MouseEvent mouseEvent) {
EventType<? extends MouseEvent> mouseEventType = mouseEvent.getEventType();
Scene scene = stage.getScene();
double mouseEventX = mouseEvent.getSceneX(),
mouseEventY = mouseEvent.getSceneY(),
sceneWidth = scene.getWidth(),
sceneHeight = scene.getHeight();
if (MouseEvent.MOUSE_MOVED.equals(mouseEventType) && stage.isMaximized() == false ) {
if (mouseEventX < border && mouseEventY < border) {
cursorEvent = Cursor.NW_RESIZE;
} else if (mouseEventX < border && mouseEventY > sceneHeight - border) {
cursorEvent = Cursor.SW_RESIZE;
} else if (mouseEventX > sceneWidth - border && mouseEventY < border) {
cursorEvent = Cursor.NE_RESIZE;
} else if (mouseEventX > sceneWidth - border && mouseEventY > sceneHeight - border) {
cursorEvent = Cursor.SE_RESIZE;
} else if (mouseEventX < border) {
cursorEvent = Cursor.W_RESIZE;
} else if (mouseEventX > sceneWidth - border) {
cursorEvent = Cursor.E_RESIZE;
} else if (mouseEventY < border) {
cursorEvent = Cursor.N_RESIZE;
} else if (mouseEventY > sceneHeight - border) {
cursorEvent = Cursor.S_RESIZE;
} else {
cursorEvent = Cursor.DEFAULT;
}
scene.setCursor(cursorEvent);
} else if (MouseEvent.MOUSE_EXITED.equals(mouseEventType) || MouseEvent.MOUSE_EXITED_TARGET.equals(mouseEventType)) {
scene.setCursor(Cursor.DEFAULT);
} else if (MouseEvent.MOUSE_PRESSED.equals(mouseEventType)) {
startX = stage.getWidth() - mouseEventX;
startY = stage.getHeight() - mouseEventY;
} else if (MouseEvent.MOUSE_DRAGGED.equals(mouseEventType)) {
if (!Cursor.DEFAULT.equals(cursorEvent)) {
resizing = true;
if (!Cursor.W_RESIZE.equals(cursorEvent) && !Cursor.E_RESIZE.equals(cursorEvent)) {
double minHeight = stage.getMinHeight() > (border * 2) ? stage.getMinHeight() : (border * 2);
if (Cursor.NW_RESIZE.equals(cursorEvent) || Cursor.N_RESIZE.equals(cursorEvent)
|| Cursor.NE_RESIZE.equals(cursorEvent)) {
if (stage.getHeight() > minHeight || mouseEventY < 0) {
setStageHeight(stage.getY() - mouseEvent.getScreenY() + stage.getHeight());
stage.setY(mouseEvent.getScreenY() );
}
} else {
if (stage.getHeight() > minHeight || mouseEventY + startY - stage.getHeight() > 0) {
setStageHeight(mouseEventY + startY);
}
}
}
if (!Cursor.N_RESIZE.equals(cursorEvent) && !Cursor.S_RESIZE.equals(cursorEvent)) {
double minWidth = stage.getMinWidth() > (border * 2) ? stage.getMinWidth() : (border * 2);
if (Cursor.NW_RESIZE.equals(cursorEvent) || Cursor.W_RESIZE.equals(cursorEvent)
|| Cursor.SW_RESIZE.equals(cursorEvent)) {
if (stage.getWidth() > minWidth || mouseEventX < 0) {
setStageWidth(stage.getX() - mouseEvent.getScreenX() + stage.getWidth());
stage.setX(mouseEvent.getScreenX());
}
} else {
if (stage.getWidth() > minWidth || mouseEventX + startX - stage.getWidth() > 0) {
setStageWidth(mouseEventX + startX);
}
}
}
resizing = false;
}
}
if (MouseEvent.MOUSE_PRESSED.equals(mouseEventType) && Cursor.DEFAULT.equals(cursorEvent) ) {
resizing = false;
screenOffsetX = stage.getX() - mouseEvent.getScreenX();
screenOffsetY = stage.getY() - mouseEvent.getScreenY();
}
if (MouseEvent.MOUSE_DRAGGED.equals(mouseEventType) && Cursor.DEFAULT.equals(cursorEvent) && resizing == false) {
stage.setX(mouseEvent.getScreenX() + screenOffsetX);
stage.setY(mouseEvent.getScreenY() + screenOffsetY);
}
}
private void setStageWidth(double width) {
width = Math.min(width, maxWidth);
width = Math.max(width, minWidth);
stage.setWidth(width);
}
private void setStageHeight(double height) {
height = Math.min(height, maxHeight);
height = Math.max(height, minHeight);
stage.setHeight(height);
}
}
}
Here is an updated version of ResizeHelper posted by @Alexander.Berg, which supports min and max stage sizes.
package sem.helper;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
/**
* Util class to handle window resizing when a stage style set to StageStyle.UNDECORATED.
* Created on 8/15/17.
*
* @author Evgenii Kanivets
*/
public class ResizeHelper {
public static void addResizeListener(Stage stage) {
addResizeListener(stage, 0, 0, Double.MAX_VALUE, Double.MAX_VALUE);
}
public static void addResizeListener(Stage stage, double minWidth, double minHeight, double maxWidth, double maxHeight) {
ResizeListener resizeListener = new ResizeListener(stage);
stage.getScene().addEventHandler(MouseEvent.MOUSE_MOVED, resizeListener);
stage.getScene().addEventHandler(MouseEvent.MOUSE_PRESSED, resizeListener);
stage.getScene().addEventHandler(MouseEvent.MOUSE_DRAGGED, resizeListener);
stage.getScene().addEventHandler(MouseEvent.MOUSE_EXITED, resizeListener);
stage.getScene().addEventHandler(MouseEvent.MOUSE_EXITED_TARGET, resizeListener);
resizeListener.setMinWidth(minWidth);
resizeListener.setMinHeight(minHeight);
resizeListener.setMaxWidth(maxWidth);
resizeListener.setMaxHeight(maxHeight);
ObservableList<Node> children = stage.getScene().getRoot().getChildrenUnmodifiable();
for (Node child : children) {
addListenerDeeply(child, resizeListener);
}
}
private static void addListenerDeeply(Node node, EventHandler<MouseEvent> listener) {
node.addEventHandler(MouseEvent.MOUSE_MOVED, listener);
node.addEventHandler(MouseEvent.MOUSE_PRESSED, listener);
node.addEventHandler(MouseEvent.MOUSE_DRAGGED, listener);
node.addEventHandler(MouseEvent.MOUSE_EXITED, listener);
node.addEventHandler(MouseEvent.MOUSE_EXITED_TARGET, listener);
if (node instanceof Parent) {
Parent parent = (Parent) node;
ObservableList<Node> children = parent.getChildrenUnmodifiable();
for (Node child : children) {
addListenerDeeply(child, listener);
}
}
}
static class ResizeListener implements EventHandler<MouseEvent> {
private Stage stage;
private Cursor cursorEvent = Cursor.DEFAULT;
private int border = 4;
private double startX = 0;
private double startY = 0;
// Max and min sizes for controlled stage
private double minWidth;
private double maxWidth;
private double minHeight;
private double maxHeight;
public ResizeListener(Stage stage) {
this.stage = stage;
}
public void setMinWidth(double minWidth) {
this.minWidth = minWidth;
}
public void setMaxWidth(double maxWidth) {
this.maxWidth = maxWidth;
}
public void setMinHeight(double minHeight) {
this.minHeight = minHeight;
}
public void setMaxHeight(double maxHeight) {
this.maxHeight = maxHeight;
}
@Override
public void handle(MouseEvent mouseEvent) {
EventType<? extends MouseEvent> mouseEventType = mouseEvent.getEventType();
Scene scene = stage.getScene();
double mouseEventX = mouseEvent.getSceneX(),
mouseEventY = mouseEvent.getSceneY(),
sceneWidth = scene.getWidth(),
sceneHeight = scene.getHeight();
if (MouseEvent.MOUSE_MOVED.equals(mouseEventType)) {
if (mouseEventX < border && mouseEventY < border) {
cursorEvent = Cursor.NW_RESIZE;
} else if (mouseEventX < border && mouseEventY > sceneHeight - border) {
cursorEvent = Cursor.SW_RESIZE;
} else if (mouseEventX > sceneWidth - border && mouseEventY < border) {
cursorEvent = Cursor.NE_RESIZE;
} else if (mouseEventX > sceneWidth - border && mouseEventY > sceneHeight - border) {
cursorEvent = Cursor.SE_RESIZE;
} else if (mouseEventX < border) {
cursorEvent = Cursor.W_RESIZE;
} else if (mouseEventX > sceneWidth - border) {
cursorEvent = Cursor.E_RESIZE;
} else if (mouseEventY < border) {
cursorEvent = Cursor.N_RESIZE;
} else if (mouseEventY > sceneHeight - border) {
cursorEvent = Cursor.S_RESIZE;
} else {
cursorEvent = Cursor.DEFAULT;
}
scene.setCursor(cursorEvent);
} else if (MouseEvent.MOUSE_EXITED.equals(mouseEventType) || MouseEvent.MOUSE_EXITED_TARGET.equals(mouseEventType)) {
scene.setCursor(Cursor.DEFAULT);
} else if (MouseEvent.MOUSE_PRESSED.equals(mouseEventType)) {
startX = stage.getWidth() - mouseEventX;
startY = stage.getHeight() - mouseEventY;
} else if (MouseEvent.MOUSE_DRAGGED.equals(mouseEventType)) {
if (!Cursor.DEFAULT.equals(cursorEvent)) {
if (!Cursor.W_RESIZE.equals(cursorEvent) && !Cursor.E_RESIZE.equals(cursorEvent)) {
double minHeight = stage.getMinHeight() > (border * 2) ? stage.getMinHeight() : (border * 2);
if (Cursor.NW_RESIZE.equals(cursorEvent) || Cursor.N_RESIZE.equals(cursorEvent)
|| Cursor.NE_RESIZE.equals(cursorEvent)) {
if (stage.getHeight() > minHeight || mouseEventY < 0) {
setStageHeight(stage.getY() - mouseEvent.getScreenY() + stage.getHeight());
stage.setY(mouseEvent.getScreenY());
}
} else {
if (stage.getHeight() > minHeight || mouseEventY + startY - stage.getHeight() > 0) {
setStageHeight(mouseEventY + startY);
}
}
}
if (!Cursor.N_RESIZE.equals(cursorEvent) && !Cursor.S_RESIZE.equals(cursorEvent)) {
double minWidth = stage.getMinWidth() > (border * 2) ? stage.getMinWidth() : (border * 2);
if (Cursor.NW_RESIZE.equals(cursorEvent) || Cursor.W_RESIZE.equals(cursorEvent)
|| Cursor.SW_RESIZE.equals(cursorEvent)) {
if (stage.getWidth() > minWidth || mouseEventX < 0) {
setStageWidth(stage.getX() - mouseEvent.getScreenX() + stage.getWidth());
stage.setX(mouseEvent.getScreenX());
}
} else {
if (stage.getWidth() > minWidth || mouseEventX + startX - stage.getWidth() > 0) {
setStageWidth(mouseEventX + startX);
}
}
}
}
}
}
private void setStageWidth(double width) {
width = Math.min(width, maxWidth);
width = Math.max(width, minWidth);
stage.setWidth(width);
}
private void setStageHeight(double height) {
height = Math.min(height, maxHeight);
height = Math.max(height, minHeight);
stage.setHeight(height);
}
}
}