diff --git a/core/src/main/java/software/coley/bentofx/building/StageBuilding.java b/core/src/main/java/software/coley/bentofx/building/StageBuilding.java index 63493872..c13c0e23 100644 --- a/core/src/main/java/software/coley/bentofx/building/StageBuilding.java +++ b/core/src/main/java/software/coley/bentofx/building/StageBuilding.java @@ -1,8 +1,14 @@ package software.coley.bentofx.building; +import javafx.collections.ObservableList; +import javafx.geometry.Bounds; +import javafx.geometry.Point2D; +import javafx.geometry.Rectangle2D; import javafx.geometry.Side; import javafx.scene.Scene; import javafx.scene.layout.Region; +import javafx.scene.robot.Robot; +import javafx.stage.Screen; import javafx.stage.Stage; import javafx.stage.Window; import org.jspecify.annotations.Nullable; @@ -22,6 +28,8 @@ public class StageBuilding { private final Bento bento; private StageFactory stageFactory = DEFAULT_STAGE_FACTORY; private SceneFactory sceneFactory = DEFAULT_SCENE_FACTORY; + private boolean applyMousePosition = false; + private boolean applySourceAsOwner = true; public StageBuilding(Bento bento) { this.bento = bento; @@ -121,7 +129,30 @@ public DragDropStage newStageForDockable(@Nullable Scene sourceScene, // Copy properties from the source scene/stage. if (sourceScene != null) - initializeFromSource(sourceScene, scene, sourceStage, stage, true); + initializeFromSource(sourceScene, scene, sourceStage, stage, applySourceAsOwner); + + // Position the stage at the mouse position, if enabled. + if (applyMousePosition) { + final Robot robot = new Robot(); + final Point2D mousePosition = robot.getMousePosition(); + + // Clamp the position to the screen bounds. + // We don't want a new stage that spawns on the bottom or right edge of the screen to be inaccessible. + double x = mousePosition.getX(); + double y = mousePosition.getY(); + ObservableList screens = Screen.getScreensForRectangle(x, y, x + 1, y + 1); + if (!screens.isEmpty()) { + final Bounds dockableContainerBounds = leaf.getBoundsInLocal(); + final Rectangle2D screenBounds = screens.getFirst().getVisualBounds(); + final double maxX = screenBounds.getMaxX() - dockableContainerBounds.getWidth(); + final double maxY = screenBounds.getMaxY() - dockableContainerBounds.getHeight(); + x = Math.min(x, maxX); + y = Math.min(y, maxY); + } + + stage.setX(x); + stage.setY(y); + } return stage; } @@ -183,4 +214,25 @@ public void setSceneFactory(@Nullable SceneFactory factory) { factory = DEFAULT_SCENE_FACTORY; sceneFactory = factory; } + + /** + * @param applySourceAsOwner + * {@code true} to make newly created stages have their owner set to the source stage the dockable is being dragged out of. + * {@code false} to not set an owner, allowing the new stage to be handled independently of the source stage. + * + * @see Stage#initOwner(Window) + */ + public void setApplySourceAsOwner(boolean applySourceAsOwner) { + this.applySourceAsOwner = applySourceAsOwner; + } + + /** + * + * @param applyMousePosition + * {@code true} to position newly created stages at the current mouse position. + * {@code false} to not apply any special positioning, allowing the stage to be positioned automatically (Generally centered). + */ + public void setApplyMousePosition(boolean applyMousePosition) { + this.applyMousePosition = applyMousePosition; + } }