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
Undecorater seems to be the only solution.
I know this thread is old, but i think new people are stumbling against this problem and no true way was made possible by the code provided above, i played around 2 days with the solutions above and had no hope for perfect draggable and resizable window, until now.
https://github.com/goxr3plus/FX-BorderlessScene
This library saved my sanity, using 3 lines of code i managed to give the proper behaviour to my scene.
I created a ResizeHelper class which can help you in that case, usage:
ResizeHelper.addResizeListener(yourStage);
the helper class:
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 {
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;
} 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);
}
}
}
}
}
}
}
}
I made the original solution also support padding in the root container of the stage. Otherwise the cursor wouldn't be set properly and the resizing also. The resizing also works better by taking into account the mouse position inside the stage when starting to drag, to avoid "jumps" in the repositioning of the Stage.
package apro2.canbustool.ui.util;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.geometry.Insets;
import javafx.scene.Cursor;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
//created by Alexander Berg
public class ResizeHelper {
public static ResizeListener addResizeListener (Stage stage) {
ResizeListener resizeListener = new ResizeListener(stage);
Scene scene = stage.getScene();
scene.addEventHandler(MouseEvent.MOUSE_MOVED, resizeListener);
scene.addEventHandler(MouseEvent.MOUSE_PRESSED, resizeListener);
scene.addEventHandler(MouseEvent.MOUSE_DRAGGED, resizeListener);
scene.addEventHandler(MouseEvent.MOUSE_EXITED, resizeListener);
scene.addEventHandler(MouseEvent.MOUSE_EXITED_TARGET, resizeListener);
return resizeListener;
}
public static class ResizeListener implements EventHandler<MouseEvent> {
private Stage stage;
private Scene scene;
private Cursor cursorEvent = Cursor.DEFAULT;
private int border = 4;
private double startX = 0;
private double startY = 0;
private double sceneOffsetX = 0;
private double sceneOffsetY = 0;
private double padTop = 0;
private double padRight = 0;
private double padBottom = 0;
private double padLeft = 0;
public ResizeListener (Stage stage) {
this.stage = stage;
this.scene = stage.getScene();
}
public void setPadding (Insets padding) {
padTop = padding.getTop();
padRight = padding.getRight();
padBottom = padding.getBottom();
padLeft = padding.getLeft();
}
@Override
public void handle(MouseEvent mouseEvent) {
EventType<? extends MouseEvent> mouseEventType = mouseEvent.getEventType();
double mouseEventX = mouseEvent.getSceneX(),
mouseEventY = mouseEvent.getSceneY(),
viewWidth = stage.getWidth() - padLeft - padRight,
viewHeight = stage.getHeight() - padTop - padBottom;
if (MouseEvent.MOUSE_MOVED.equals(mouseEventType)) {
if (mouseEventX < border + padLeft && mouseEventY < border + padTop) {
cursorEvent = Cursor.NW_RESIZE;
}
else if (mouseEventX < border + padLeft && mouseEventY > viewHeight - border + padTop) {
cursorEvent = Cursor.SW_RESIZE;
}
else if (mouseEventX > viewWidth - border + padLeft && mouseEventY < border + padTop) {
cursorEvent = Cursor.NE_RESIZE;
}
else if (mouseEventX > viewWidth - border + padLeft && mouseEventY > viewHeight - border + padTop) {
cursorEvent = Cursor.SE_RESIZE;
}
else if (mouseEventX < border + padLeft) {
cursorEvent = Cursor.W_RESIZE;
}
else if (mouseEventX > viewWidth - border + padLeft) {
cursorEvent = Cursor.E_RESIZE;
}
else if (mouseEventY < border + padTop) {
cursorEvent = Cursor.N_RESIZE;
}
else if (mouseEventY > viewHeight - border + padTop) {
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 = viewWidth - mouseEventX;
startY = viewHeight - mouseEventY;
sceneOffsetX = mouseEvent.getSceneX();
sceneOffsetY = mouseEvent.getSceneY();
}
else if (MouseEvent.MOUSE_DRAGGED.equals(mouseEventType) && !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) {
double height = stage.getY() - mouseEvent.getScreenY() + stage.getHeight() + sceneOffsetY;
double y = mouseEvent.getScreenY() - sceneOffsetY;
stage.setHeight(height);
stage.setY(y);
}
} else {
if (stage.getHeight() > minHeight || mouseEventY + startY - stage.getHeight() > 0) {
stage.setHeight(mouseEventY + startY + padBottom + padTop);
}
}
}
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) {
double width = stage.getX() - mouseEvent.getScreenX() + stage.getWidth() + sceneOffsetX;
double x = mouseEvent.getScreenX() - sceneOffsetX;
stage.setWidth(width);
stage.setX(x);
}
} else {
if (stage.getWidth() > minWidth || mouseEventX + startX - stage.getWidth() > 0) {
stage.setWidth(mouseEventX + startX + padLeft + padRight);
}
}
}
}
}
}
}
Thanks @Alexander.Berg for providing this utility class (1st version). It helped me to get the desired functionality for my Undecorated Stage.
However, I have few queries regarding some points:
I tried to modify your code to address all the above queries. Please find below the updated ResizeHelper. I am only trying to simplify the things a bit further. Once again thanks for providing the code and letting me to think with a starting point.
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.scene.Cursor;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
/**
* Helper class to set the resizing implementation for any given undecorated stage.
*/
public class ResizeHelper {
/**
* Handler to process the resizing of the the given stage.
*/
static class ResizeHandler implements EventHandler<MouseEvent> {
/** Space to consider around the stage border for resizing */
private static final int BORDER = 10;
/** Stage to which the handler is implemented */
private final Stage stage;
/** Current cursor reference to the scene */
private Cursor cursor = Cursor.DEFAULT;
/** X position of the drag start */
private double startX = 0;
/** Y position of the drag start */
private double startY = 0;
/**
* Constructor.
*
* @param stageTmp Stage to which resizing to be set.
*/
public ResizeHandler(final Stage stageTmp) {
stage = stageTmp;
}
@Override
public void handle(final MouseEvent event) {
final EventType<? extends MouseEvent> eventType = event.getEventType();
final Scene scene = stage.getScene();
final double mouseEventX = event.getSceneX();
final double mouseEventY = event.getSceneY();
final double sceneWidth = scene.getWidth();
final double sceneHeight = scene.getHeight();
if (MouseEvent.MOUSE_MOVED.equals(eventType)) {
setCursor(mouseEventX, mouseEventY, sceneWidth, sceneHeight);
scene.setCursor(cursor);
} else if (MouseEvent.MOUSE_PRESSED.equals(eventType)) {
startX = stage.getWidth() - mouseEventX;
startY = stage.getHeight() - mouseEventY;
consumeEventIfNotDefaultCursor(event);
} else if (MouseEvent.MOUSE_DRAGGED.equals(eventType) && !Cursor.DEFAULT.equals(cursor)) {
consumeEventIfNotDefaultCursor(event);
if (!Cursor.W_RESIZE.equals(cursor) && !Cursor.E_RESIZE.equals(cursor)) {
processVerticalDrag(event);
}
if (!Cursor.N_RESIZE.equals(cursor) && !Cursor.S_RESIZE.equals(cursor)) {
processHorizontalDrag(event);
}
}
}
/**
* Consumes the mouse event if the cursor is not the DEFAULT cursor.
*
* @param event MouseEvent instance.
*/
private void consumeEventIfNotDefaultCursor(final MouseEvent event) {
if (!cursor.equals(Cursor.DEFAULT)) {
event.consume();
}
}
/**
* Processes the horizontal drag movement and resizes the window width.
*
* @param event MouseEvent instance.
*/
private void processHorizontalDrag(final MouseEvent event) {
final double minWidth =
stage.getMinWidth() > BORDER * 2 ? stage.getMinWidth() : BORDER * 2;
final double mouseEventX = event.getSceneX();
if (Cursor.NW_RESIZE.equals(cursor)
|| Cursor.W_RESIZE.equals(cursor)
|| Cursor.SW_RESIZE.equals(cursor)) {
if (stage.getWidth() > minWidth || mouseEventX < 0) {
stage.setWidth(stage.getX() - event.getScreenX() + stage.getWidth());
stage.setX(event.getScreenX());
}
} else if (stage.getWidth() > minWidth || mouseEventX + startX - stage.getWidth() > 0) {
stage.setWidth(mouseEventX + startX);
}
}
/**
* Processes the vertical drag movement and resizes the window height.
*
* @param event MouseEvent instance.
*/
private void processVerticalDrag(final MouseEvent event) {
final double minHeight =
stage.getMinHeight() > BORDER * 2 ? stage.getMinHeight() : BORDER * 2;
final double mouseEventY = event.getSceneY();
if (Cursor.NW_RESIZE.equals(cursor)
|| Cursor.N_RESIZE.equals(cursor)
|| Cursor.NE_RESIZE.equals(cursor)) {
if (stage.getHeight() > minHeight || mouseEventY < 0) {
stage.setHeight(stage.getY() - event.getScreenY() + stage.getHeight());
stage.setY(event.getScreenY());
}
} else if (stage.getHeight() > minHeight || mouseEventY + startY - stage.getHeight() > 0) {
stage.setHeight(mouseEventY + startY);
}
}
/**
* Determines and sets the appropriate cursor based on the mouse position in relative to scene bounds.
*
* @param mouseEventX X position of mouse in the scene.
* @param mouseEventY Y position of mouse in the scene.
* @param sceneWidth Width of the scene.
* @param sceneHeight Height of the scene.
*/
private void setCursor(final double mouseEventX, final double mouseEventY, final double sceneWidth,
final double sceneHeight) {
final Cursor cursor1;
if (mouseEventX < BORDER && mouseEventY < BORDER) {
cursor1 = Cursor.NW_RESIZE;
} else if (mouseEventX < BORDER && mouseEventY > sceneHeight - BORDER) {
cursor1 = Cursor.SW_RESIZE;
} else if (mouseEventX > sceneWidth - BORDER && mouseEventY < BORDER) {
cursor1 = Cursor.NE_RESIZE;
} else if (mouseEventX > sceneWidth - BORDER && mouseEventY > sceneHeight - BORDER) {
cursor1 = Cursor.SE_RESIZE;
} else if (mouseEventX < BORDER) {
cursor1 = Cursor.W_RESIZE;
} else if (mouseEventX > sceneWidth - BORDER) {
cursor1 = Cursor.E_RESIZE;
} else if (mouseEventY < BORDER) {
cursor1 = Cursor.N_RESIZE;
} else if (mouseEventY > sceneHeight - BORDER) {
cursor1 = Cursor.S_RESIZE;
} else {
cursor1 = Cursor.DEFAULT;
}
cursor = cursor1;
}
}
/**
* Constructor.
*
*/
private ResizeHelper() {
}
/**
* Adds the resize handler to the provided stage.
*
* @param stage Stage to which the resizing should be implemented.
*/
public static void addResizeHandler(final Stage stage) {
ResizeHandler resizeHandler = new ResizeHandler(stage);
stage.setMinHeight(stage.getHeight());
stage.setMinWidth(stage.getWidth());
stage.setMaxHeight(stage.getMaxHeight());
stage.setMaxWidth(stage.getMaxWidth());
stage.getScene().addEventFilter(MouseEvent.ANY, resizeHandler);
}
}
Inspired by the many answers here, I wrote my own implementation that has all the necessary features that I could think of:
Draggability
Fullscreen
Resizeable
Quick setup:
StageInteractor(this)
.makeDraggable()
.makeFullscreenable()
.makeResizable()
Custom interaction areas:
StageInteractor(this)
// custom width / height for drag areas (top, right, bottom, left)
.makeDraggable(50, 20, 50, 20)
// custom width / height for double-clickable areas (top, right, bottom, left)
.makeFullscreenable(50, 20, 50, 20)
// custom width / height for resize areas (top, right, bottom, left)
.makeResizable(20, 20, 20, 20)
import javafx.scene.Cursor
import javafx.scene.Scene
import javafx.scene.input.MouseButton
import javafx.scene.input.MouseEvent
import javafx.stage.Screen
import javafx.stage.Stage
class StageInteractor {
private val stage: Stage
private val scene: Scene
private var isDraggable = false
private var isDragging = false
private var allowDragging = true
private var dragMarginTop = 0.0
private var dragMarginRight = 0.0
private var dragMarginBottom = 0.0
private var dragMarginLeft = 0.0
private var isFullscreenable = false
private var isFullscreen = false
private var allowFullscreen = true
private var fullscreenMarginTop = 0.0
private var fullscreenMarginRight = 0.0
private var fullscreenMarginBottom = 0.0
private var fullscreenMarginLeft = 0.0
private var stageWidthBeforeFullscreen = 0.0
private var stageHeightBeforeFullscreen = 0.0
private var stageXBeforeFullscreen = 0.0
private var stageYBeforeFullscreen = 0.0
private var isResizeable = false
private var isResizing = false
private var allowResizing = true
private var resizeDirection: ResizeDirection? = null
private var resizeMarginTop = 0.0
private var resizeMarginRight = 0.0
private var resizeMarginBottom = 0.0
private var resizeMarginLeft = 0.0
constructor(stage: Stage) {
this.stage = stage
this.scene = stage.scene
}
fun makeDraggable(
marginTop: Double = 50.0,
marginRight: Double = 0.0,
marginBottom: Double = 0.0,
marginLeft: Double = 0.0
): StageInteractor {
dragMarginTop = marginTop
dragMarginRight = marginRight
dragMarginBottom = marginBottom
dragMarginLeft = marginLeft
if (!isDraggable) {
isDraggable = true
var dragStartOffsetX = 0.0
var dragStartOffsetY = 0.0
scene.addEventHandler(MouseEvent.MOUSE_MOVED) {
val isWithinBounds = detectDraggingBounds(it)
if (isDraggable && allowDragging && isWithinBounds) {
scene.cursor = Cursor.OPEN_HAND
} else {
if (scene.cursor == Cursor.OPEN_HAND) {
scene.cursor = Cursor.DEFAULT
}
}
}
scene.addEventHandler(MouseEvent.MOUSE_PRESSED) {
dragStartOffsetX = stage.x - it.screenX
dragStartOffsetY = stage.y - it.screenY
}
scene.addEventHandler(MouseEvent.MOUSE_DRAGGED) {
val isWithinBounds = detectDraggingBounds(it)
if (isDraggable && allowDragging && isWithinBounds) {
isDragging = true
scene.cursor = Cursor.CLOSED_HAND
}
if (isDragging) {
stage.x = it.screenX + dragStartOffsetX
stage.y = it.screenY + dragStartOffsetY
}
}
scene.addEventHandler(MouseEvent.MOUSE_RELEASED) {
if (isDragging) {
isDragging = false
scene.cursor = Cursor.DEFAULT
}
}
}
return this
}
private fun detectDraggingBounds(event: MouseEvent): Boolean {
return event.sceneY <= dragMarginTop
|| scene.height - event.sceneY <= dragMarginBottom
|| event.sceneX <= dragMarginLeft
|| scene.width - event.sceneX <= dragMarginRight
}
fun makeFullscreenable(
marginTop: Double = 50.0,
marginRight: Double = 0.0,
marginBottom: Double = 0.0,
marginLeft: Double = 0.0
): StageInteractor {
fullscreenMarginTop = marginTop
fullscreenMarginRight = marginRight
fullscreenMarginBottom = marginBottom
fullscreenMarginLeft = marginLeft
if (!isFullscreenable) {
isFullscreenable = true
scene.addEventHandler(MouseEvent.MOUSE_PRESSED) {
val isDoubleClick = it.button == MouseButton.PRIMARY && it.clickCount >= 2
if (isFullscreenable && allowFullscreen && isDoubleClick && detectFullscreenBounds(it)) {
if (isFullscreen) {
isFullscreen = false
allowDragging = true
allowResizing = true
stage.x = stageXBeforeFullscreen
stage.y = stageYBeforeFullscreen
stage.width = stageWidthBeforeFullscreen
stage.height = stageHeightBeforeFullscreen
} else {
isFullscreen = true
allowDragging = false
allowResizing = false
stageWidthBeforeFullscreen = stage.width
stageHeightBeforeFullscreen = stage.height
stageXBeforeFullscreen = stage.x
stageYBeforeFullscreen = stage.y
val screenBounds = Screen.getPrimary().visualBounds
val newWidth = if (stage.maxWidth < screenBounds.width) {
stage.maxWidth
} else {
screenBounds.width
}
val newHeight = if (stage.maxHeight < screenBounds.height) {
stage.maxHeight
} else {
screenBounds.height
}
stage.width = newWidth
stage.height = newHeight
stage.x = screenBounds.minX
stage.y = screenBounds.minY
}
}
}
}
return this
}
private fun detectFullscreenBounds(event: MouseEvent): Boolean {
val isWithinBounds = event.sceneY <= fullscreenMarginTop
|| scene.height - event.sceneY <= fullscreenMarginBottom
|| event.sceneX <= fullscreenMarginLeft
|| scene.width - event.sceneX <= fullscreenMarginRight
val resizeDirection = detectResizeDirection(event)
return isWithinBounds && resizeDirection == null
}
fun makeResizable(
marginTop: Double = 10.0,
marginRight: Double = 10.0,
marginBottom: Double = 10.0,
marginLeft: Double = 10.0
): StageInteractor {
resizeMarginTop = marginTop
resizeMarginRight = marginRight
resizeMarginBottom = marginBottom
resizeMarginLeft = marginLeft
if (!isResizeable) {
isResizeable = true
scene.addEventHandler(MouseEvent.MOUSE_MOVED) {
if (isResizeable && allowResizing && !isResizing) {
when (detectResizeDirection(it)) {
ResizeDirection.NORTH_WEST -> scene.cursor = Cursor.NW_RESIZE
ResizeDirection.NORTH_EAST -> scene.cursor = Cursor.NE_RESIZE
ResizeDirection.SOUTH_WEST -> scene.cursor = Cursor.SW_RESIZE
ResizeDirection.SOUTH_EAST -> scene.cursor = Cursor.SE_RESIZE
ResizeDirection.NORTH -> scene.cursor = Cursor.N_RESIZE
ResizeDirection.SOUTH -> scene.cursor = Cursor.S_RESIZE
ResizeDirection.WEST -> scene.cursor = Cursor.W_RESIZE
ResizeDirection.EAST -> scene.cursor = Cursor.E_RESIZE
else -> {
val cursors = listOf(
Cursor.NW_RESIZE,
Cursor.NE_RESIZE,
Cursor.SW_RESIZE,
Cursor.SE_RESIZE,
Cursor.N_RESIZE,
Cursor.S_RESIZE,
Cursor.W_RESIZE,
Cursor.E_RESIZE
)
if (cursors.contains(scene.cursor)) {
scene.cursor = Cursor.DEFAULT
}
}
}
}
}
var resizeStartFromSceneX = 0.0
var resizeStartFromSceneY = 0.0
var resizeStartFromScreenX = 0.0
var resizeStartFromScreenY = 0.0
var resizeStartStageWidth = 0.0
var resizeStartStageHeight = 0.0
scene.addEventHandler(MouseEvent.MOUSE_PRESSED) {
if (isResizeable && allowResizing && !isResizing) {
resizeDirection = detectResizeDirection(it)
if (resizeDirection != null) {
if (it.button == MouseButton.PRIMARY && it.clickCount >= 2) {
val screenBounds = Screen.getPrimary().visualBounds
if (resizeDirection == ResizeDirection.NORTH || resizeDirection == ResizeDirection.NORTH_WEST || resizeDirection == ResizeDirection.NORTH_EAST) {
stage.height = ensureStageHeightIsWithinLimits(
stage.height + stage.y - screenBounds.minY
)
stage.y = 0.0
}
if (resizeDirection == ResizeDirection.SOUTH || resizeDirection == ResizeDirection.SOUTH_WEST || resizeDirection == ResizeDirection.SOUTH_EAST) {
stage.height = ensureStageHeightIsWithinLimits(
screenBounds.height - stage.y + screenBounds.minY
)
if (stage.height == screenBounds.height) {
stage.y = 0.0
}
}
if (resizeDirection == ResizeDirection.WEST || resizeDirection == ResizeDirection.NORTH_WEST || resizeDirection == ResizeDirection.SOUTH_WEST) {
stage.width = ensureStageWidthIsWithinLimits(
stage.width + stage.x
)
stage.x = 0.0
}
if (resizeDirection == ResizeDirection.EAST || resizeDirection == ResizeDirection.NORTH_EAST || resizeDirection == ResizeDirection.SOUTH_EAST) {
stage.width = ensureStageWidthIsWithinLimits(
screenBounds.width - stage.x
)
if (stage.width == screenBounds.width) {
stage.x = 0.0
}
}
} else {
isResizing = true
isDraggable = false
isFullscreenable = false
resizeStartFromScreenX = it.screenX
resizeStartFromScreenY = it.screenY
resizeStartFromSceneX = it.sceneX
resizeStartFromSceneY = it.sceneY
resizeStartStageWidth = stage.width
resizeStartStageHeight = stage.height
}
}
}
}
scene.addEventHandler(MouseEvent.MOUSE_DRAGGED) {
if (isResizing) {
if (resizeDirection == ResizeDirection.NORTH || resizeDirection == ResizeDirection.NORTH_WEST || resizeDirection == ResizeDirection.NORTH_EAST) {
val newHeight = ensureStageHeightIsWithinLimits(
resizeStartStageHeight + (resizeStartFromScreenY - it.screenY)
)
val newY = when (newHeight) {
stage.maxHeight, stage.minHeight -> stage.y
else -> it.screenY - resizeStartFromSceneY
}
stage.height = newHeight
stage.y = newY
}
if (resizeDirection == ResizeDirection.SOUTH || resizeDirection == ResizeDirection.SOUTH_WEST || resizeDirection == ResizeDirection.SOUTH_EAST) {
val newHeight = ensureStageHeightIsWithinLimits(
resizeStartStageHeight + (it.screenY - resizeStartFromScreenY)
)
stage.height = newHeight
}
if (resizeDirection == ResizeDirection.WEST || resizeDirection == ResizeDirection.NORTH_WEST || resizeDirection == ResizeDirection.SOUTH_WEST) {
val newWidth = ensureStageWidthIsWithinLimits(
resizeStartStageWidth + (resizeStartFromScreenX - it.screenX)
)
val newX = when (newWidth) {
stage.maxWidth, stage.minWidth -> stage.x
else -> it.screenX - resizeStartFromSceneX
}
stage.width = newWidth
stage.x = newX
}
if (resizeDirection == ResizeDirection.EAST || resizeDirection == ResizeDirection.NORTH_EAST || resizeDirection == ResizeDirection.SOUTH_EAST) {
val newWidth = ensureStageWidthIsWithinLimits(
resizeStartStageWidth + (it.screenX - resizeStartFromScreenX)
)
stage.width = newWidth
}
}
}
scene.addEventHandler(MouseEvent.MOUSE_RELEASED) {
if (isResizing) {
isResizing = false
isDraggable = true
isFullscreenable = true
}
}
}
return this
}
private fun detectResizeDirection(event: MouseEvent): ResizeDirection? {
val isNorthResize = event.sceneY <= resizeMarginTop
val isSouthResize = scene.height - event.sceneY <= resizeMarginBottom
val isWestResize = event.sceneX <= resizeMarginLeft
val isEastResize = scene.width - event.sceneX <= resizeMarginRight
val isNorthWestResize = isNorthResize && isWestResize
val isNorthEastResize = isNorthResize && isEastResize
val isSouthWestResize = isSouthResize && isWestResize
val isSouthEastResize = isSouthResize && isEastResize
return when {
isNorthWestResize -> ResizeDirection.NORTH_WEST
isNorthEastResize -> ResizeDirection.NORTH_EAST
isSouthWestResize -> ResizeDirection.SOUTH_WEST
isSouthEastResize -> ResizeDirection.SOUTH_EAST
isNorthResize -> ResizeDirection.NORTH
isSouthResize -> ResizeDirection.SOUTH
isWestResize -> ResizeDirection.WEST
isEastResize -> ResizeDirection.EAST
else -> null
}
}
private fun ensureStageWidthIsWithinLimits(width: Double): Double {
val screenBounds = Screen.getPrimary().visualBounds
return when {
width > stage.maxWidth -> stage.maxWidth
width < stage.minWidth -> stage.minWidth
width > screenBounds.width -> screenBounds.width
else -> width
}
}
private fun ensureStageHeightIsWithinLimits(height: Double): Double {
val screenBounds = Screen.getPrimary().visualBounds
return when {
height > stage.maxHeight -> stage.maxHeight
height < stage.minHeight -> stage.minHeight
height > screenBounds.height -> screenBounds.height
else -> height
}
}
enum class ResizeDirection {
NORTH, NORTH_EAST, NORTH_WEST,
SOUTH, SOUTH_EAST, SOUTH_WEST,
EAST, WEST;
}
}