javajavafx-2stagescene

Allow user to resize an undecorated Stage


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 managed to make an undecorated , semi-transparent Stage that can be dragged around to define the area and added a close button to let the user confirm the area which is to be recorded.

Now, how do I let the user resize the stage by dragging it by its edges ?

SSCCE:

package draggable;

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.SceneBuilder;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonBuilder;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.StackPaneBuilder;
import javafx.scene.paint.Color;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.StageStyle;

public class DraggableStage extends Application{

    Button close;
    StackPane holder;
    Rectangle2D maxBounds;
    Scene theScene;

    double pressedX;
    double pressedY;
    double draggedX;
    double draggedY;

    @Override
    public void start(Stage stage) throws Exception {
        final Stage theStage = stage;

        // determine how big the screen is
        maxBounds = Screen.getPrimary().getVisualBounds(); 

        //create the close button
        close = ButtonBuilder
                .create()
                .text("Close")
                .build();

        //create the StackPane holder for the button
        holder = StackPaneBuilder
                    .create()
                    .alignment(Pos.CENTER)
                    .children(close)
                    .build();

        // you cannot resize the screen beyond the max resolution of the screen
        holder.setMaxSize(maxBounds.getWidth(), maxBounds.getHeight());

        //you cannot resize under half the width and height of the screen
        holder.setMinSize(maxBounds.getWidth() / 2,maxBounds.getHeight() / 2);

        //the scene where it all happens
        theScene = SceneBuilder
                      .create()
                      .root(holder)
                      .width(maxBounds.getWidth() / 2)
                      .height(maxBounds.getHeight() / 2)
                      .build();

        // add the button listeners
        close.setOnAction(new EventHandler<ActionEvent>(){
            @Override
            public void handle(ActionEvent event) {
                theStage.close();
            }
        });

        // add the drag and press listener for the StackPane
        holder.setOnMousePressed(new EventHandler<MouseEvent>(){
            @Override
            public void handle(MouseEvent e) {
                pressedX = e.getX();
                pressedY = e.getY();
            }
        });

        holder.setOnMouseDragged(new EventHandler<MouseEvent>(){
            @Override
            public void handle(MouseEvent e) {
                draggedX = e.getX();
                draggedY = e.getY();

                double differenceX = draggedX - pressedX;
                double differenceY = draggedY - pressedY;

                theStage.setX(theStage.getX() + differenceX);
                theStage.setY(theStage.getY() + differenceY); 
            }
        });

        //the mandatory mumbo jumbo
        theScene.setFill(Color.rgb(128, 128, 128, 0.5));
        stage.initStyle(StageStyle.TRANSPARENT);
        stage.setScene(theScene);
        stage.sizeToScene();
        stage.show();
    }

    public static void main(String[] args) {
        Application.launch("draggable.DraggableStage");
    }
}  

Image:
enter image description here


Solution

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