Allow user to resize an undecorated Stage

后端 未结 12 1437
悲&欢浪女
悲&欢浪女 2020-12-24 02:21

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

相关标签:
12条回答
  • 2020-12-24 03:09

    Undecorater seems to be the only solution.

    0 讨论(0)
  • 2020-12-24 03:10

    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.

    0 讨论(0)
  • 2020-12-24 03:16

    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);
                                }
                            }
                        }
                    }
    
                }
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-24 03:18

    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);
                        }
                    }
                }
            }
        }
    }
    

    }

    0 讨论(0)
  • 2020-12-24 03:20

    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:

    1. What is need to traverse through all the child nodes and set the handler to each and every parent node. Is is not sufficient to put the handler on Scene only?
    2. If we are anyway checking for the EventType in handler, what is need in setting the same handler to different event types. Can it be set on one super mouse event (MouseEvent.ANY) and can skip the rest.
    3. If point#1 is handled, then there is no need to consider MOUSE_EXITED and MOUSE_EXITED_TARGET to set the default cursor back.
    4. There is every possible chance to trigger the event on underlying node(s) in the drag space(border space) if the handler is added as event handler. The most common case can be if the custom stage close button is very near to corner which comes under the border space. Trying to resize at that space will eventually trigger the close button. Should it need to be implement by adding it as filter (and consume the event when neccessary) rather than handler?

    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);
        }
    }
    
    0 讨论(0)
  • 2020-12-24 03:20

    Kotlin Version

    Inspired by the many answers here, I wrote my own implementation that has all the necessary features that I could think of:

    Features

    • Draggability

      • Window is dragable
      • Customizable interaction areas (top, right, bottom, left)
      • Custom cursor when hovering over a drag area
    • Fullscreen

      • Window can be set into fullscreen
      • Window restores back to its previous size and position
      • Customizable interaction areas (top, right, bottom, left)
    • Resizeable

      • Window can be resized by the window border in any direction (n, ne, e, se, s, se, w, nw)
      • Customizable interaction areas (n, e, w, s)
      • Double clicking on a window border will resize the window and take up all the available space in that direction
      • Custom cursor when hovering over a resize area

    Usage

    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)
    

    Code

    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;
        }
    }
    
    0 讨论(0)
提交回复
热议问题