From a3867840d87945ac910c24d24f77646128b37aed Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Tue, 25 Nov 2025 19:52:51 +0100 Subject: [PATCH 01/12] Implementation of positioning anchor for Stage --- .../src/main/java/javafx/stage/Stage.java | 229 +++++++++++++++++- .../src/main/java/javafx/stage/Window.java | 5 + .../java/test/javafx/stage/StageTest.java | 205 +++++++++++++++- 3 files changed, 431 insertions(+), 8 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java b/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java index f3016ebd5ea..3e8b48895b4 100644 --- a/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java +++ b/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; import javafx.application.ColorScheme; import javafx.application.Platform; @@ -37,6 +38,7 @@ import javafx.collections.ListChangeListener.Change; import javafx.collections.ObservableList; import javafx.geometry.NodeOrientation; +import javafx.geometry.Rectangle2D; import javafx.scene.Scene; import javafx.scene.image.Image; import javafx.scene.input.KeyCombination; @@ -52,6 +54,7 @@ import com.sun.javafx.stage.StagePeerListener; import com.sun.javafx.tk.TKStage; import com.sun.javafx.tk.Toolkit; +import com.sun.javafx.util.Utils; import javafx.beans.NamedArg; import javafx.beans.property.DoubleProperty; import javafx.beans.property.DoublePropertyBase; @@ -297,6 +300,33 @@ public Stage(@NamedArg(value="style", defaultValue="DECORATED") StageStyle style super.show(); } + /** + * Shows this stage at the specified location and adjusts the position as needed to keep the stage + * visible on screen. If the stage is already showing, it is moved to the computed position instead. + *
+ * Positioning is done using an {@code anchor} point on the stage. The specified location is interpreted + * as the desired screen coordinates of that anchor, and the stage location is derived from that. + * For example, if the anchor is {@code (0.5, 0.5)}, the stage is positioned so its center lies at + * {@code (anchorX, anchorY)}; if the anchor is {@code (0, 0)}, the stage's top-left corner is placed + * at {@code (anchorX, anchorY)}. The final stage location is clamped to the screen bounds. + *
+ * After the stage is shown, its {@link #xProperty() X} and {@link #yProperty() Y} properties will + * reflect the adjusted position. + * + * @param anchorX the requested horizontal location of the anchor point on the screen + * @param anchorY the requested vertical location of the anchor point on the screen + * @param anchor the point on the stage that should coincide with {@code (anchorX, anchorY)} on the screen + * @since 26 + */ + public final void show(double anchorX, double anchorY, Anchor anchor) { + if (isShowing()) { + new PositionRequest(anchorX, anchorY, anchor).apply(this); + } else { + positionRequest = new PositionRequest(anchorX, anchorY, anchor); + super.show(); + } + } + private boolean primary = false; //------------------------------------------------------------------ @@ -431,7 +461,46 @@ private boolean isImportant() { * @since JavaFX 2.2 */ public void showAndWait() { + verifyCanShowAndWait(); + super.show(); + inNestedEventLoop = true; + Toolkit.getToolkit().enterNestedEventLoop(this); + } + + /** + * Shows this stage at the specified location and adjusts the position as needed to keep the stage + * visible on screen. This method blocks until the stage is hidden before returning to the caller. + * This method temporarily blocks processing of the current event and starts a nested event loop + * to handle other events. This method must be called on the JavaFX application thread. + *
+ * Positioning is done using an {@code anchor} point on the stage. The specified location is interpreted + * as the desired screen coordinates of that anchor, and the stage location is derived from that. + * For example, if the anchor is {@code (0.5, 0.5)}, the stage is positioned so its center lies at + * {@code (anchorX, anchorY)}; if the anchor is {@code (0, 0)}, the stage's top-left corner is placed + * at {@code (anchorX, anchorY)}. The final stage location is clamped to the screen bounds. + *
+ * After the stage is shown, its {@link #xProperty() X} and {@link #yProperty() Y} properties will + * reflect the adjusted position. + * + * @param anchorX the requested horizontal location of the anchor point on the screen + * @param anchorY the requested vertical location of the anchor point on the screen + * @param anchor the point on the stage that should coincide with {@code (anchorX, anchorY)} on the screen + * @throws IllegalStateException if this method is called on a thread other than the JavaFX application thread + * @throws IllegalStateException if this method is called during animation or layout processing + * @throws IllegalStateException if this call would exceed the maximum number of nested event loops + * @throws IllegalStateException if this method is called on the primary stage + * @throws IllegalStateException if this stage is already showing + * @since 26 + */ + public final void showAndWait(double anchorX, double anchorY, Anchor anchor) { + verifyCanShowAndWait(); + positionRequest = new PositionRequest(anchorX, anchorY, anchor); + super.show(); + inNestedEventLoop = true; + Toolkit.getToolkit().enterNestedEventLoop(this); + } + private void verifyCanShowAndWait() { Toolkit.getToolkit().checkFxUserThread(); if (isPrimary()) { @@ -450,10 +519,6 @@ public void showAndWait() { // method is called from an event handler that is listening to a // WindowEvent.WINDOW_HIDING event. assert !inNestedEventLoop; - - show(); - inNestedEventLoop = true; - Toolkit.getToolkit().enterNestedEventLoop(this); } private StageStyle style; // default is set in constructor @@ -1315,4 +1380,160 @@ private void setPrefHeaderButtonHeight(double height) { peer.setPrefHeaderButtonHeight(height); } } + + @Override + final void fixBounds() { + if (positionRequest != null) { + positionRequest.apply(this); + positionRequest = null; + } + } + + private PositionRequest positionRequest; + + private record PositionRequest(double screenX, double screenY, Anchor anchor) { + + void apply(Stage stage) { + Screen currentScreen = Utils.getScreenForPoint(screenX, screenY); + Rectangle2D screenBounds = Utils.hasFullScreenStage(currentScreen) + ? currentScreen.getBounds() + : currentScreen.getVisualBounds(); + + double width = stage.getWidth(); + double height = stage.getHeight(); + double anchorX, anchorY; + double anchorRelX, anchorRelY; + + if (anchor.relative) { + anchorX = width * anchor.x; + anchorY = height * anchor.y; + anchorRelX = anchor.x; + anchorRelY = anchor.y; + } else { + anchorX = anchor.x; + anchorY = anchor.y; + anchorRelX = anchor.x / width; + anchorRelY = anchor.y / height; + } + + double minX = screenBounds.getMinX(); + double minY = screenBounds.getMinY(); + double maxX = screenBounds.getMaxX() - width; + double maxY = screenBounds.getMaxY() - height; + double x, y; + + if (maxX >= minX) { + x = Utils.clamp(minX, screenX - anchorX, maxX); + } else { + x = anchorRelX > 0.5 ? maxX : minX; + } + + if (maxY >= minY) { + y = Utils.clamp(minY, screenY - anchorY, maxY); + } else { + y = anchorRelY > 0.5 ? maxY : minY; + } + + stage.setX(x); + stage.setY(y); + } + } + + /** + * Defines an anchor point that is used to position a stage with {@link #show(double, double, Anchor)} + * or {@link #showAndWait(double, double, Anchor)}. The anchor is the point on the stage that should + * coincide with a given screen location. + *
+ * Anchors can be specified in one of two coordinate systems: + *
+ * {@code (0,0)} is the top-left; {@code (1,1)} is the bottom-right; {@code (0.5,0.5)} is the center.
+ *
+ * @param x x fraction of the stage width
+ * @param y y fraction of the stage height
+ * @return a relative {@code Anchor}
+ */
+ public static Anchor ofRelative(double x, double y) {
+ return new Anchor(x, y, true);
+ }
+
+ /**
+ * Creates an absolute anchor expressed as pixel offsets from the stage's top-left corner.
+ * The values can be less than 0 or greater than the stage's size; in this case the anchor
+ * is located outside the stage.
+ *
+ * @param x x offset in pixels from the stage's left edge
+ * @param y y offset in pixels from the stage's top edge
+ * @return an absolute {@code Anchor}
+ */
+ public static Anchor ofAbsolute(double x, double y) {
+ return new Anchor(x, y, false);
+ }
+
+ /**
+ * Gets the horizontal location of the anchor.
+ *
+ * @return the horizontal location of the anchor
+ */
+ public double getX() {
+ return x;
+ }
+
+ /**
+ * Gets the vertical location of the anchor.
+ *
+ * @return the vertical location of the anchor
+ */
+ public double getY() {
+ return y;
+ }
+
+ /**
+ * Returns whether the anchor is expressed as a fraction of the stage size.
+ *
+ * @return {@code true} if the anchor is expressed as a fraction of the stage size,
+ * {@code false} otherwise
+ */
+ public boolean isRelative() {
+ return relative;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof Anchor other
+ && other.x == x
+ && other.y == y
+ && other.relative == relative;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(x, y, relative);
+ }
+ }
}
diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/Window.java b/modules/javafx.graphics/src/main/java/javafx/stage/Window.java
index 9f518cfe7ff..a947e69e241 100644
--- a/modules/javafx.graphics/src/main/java/javafx/stage/Window.java
+++ b/modules/javafx.graphics/src/main/java/javafx/stage/Window.java
@@ -1117,6 +1117,9 @@ public final ObjectProperty
+ * An {@code AnchorPoint} provides a {@code (x, y)} coordinate together with a flag indicating
+ * how those coordinates should be interpreted:
+ *
+ * In proportional coordinates, {@code (0, 0)} refers to the top-left corner of the target area and
+ * {@code (1, 1)} refers to the bottom-right corner. Values outside the {@code [0..1]} range represent
+ * points outside the bounds.
+ *
+ * @param x the horizontal fraction of the target width
+ * @param y the vertical fraction of the target height
+ * @return a proportional {@code AnchorPoint}
+ */
+ public static AnchorPoint proportional(double x, double y) {
+ return new AnchorPoint(x, y, true);
+ }
+
+ /**
+ * Creates an absolute anchor point, expressed as pixel offsets from the top-left corner of the target area.
+ *
+ * @param x the horizontal offset in pixels from the left edge of the target area
+ * @param y the vertical offset in pixels from the top edge of the target area
+ * @return an absolute {@code AnchorPoint}
+ */
+ public static AnchorPoint absolute(double x, double y) {
+ return new AnchorPoint(x, y, false);
+ }
+
+ /**
+ * Returns the horizontal coordinate of this anchor point.
+ *
+ * @return the horizontal coordinate of this anchor point
+ */
+ public double getX() {
+ return x;
+ }
+
+ /**
+ * Returns the vertical coordinate of this anchor point.
+ *
+ * @return the vertical coordinate of this anchor point
+ */
+ public double getY() {
+ return y;
+ }
+
+ /**
+ * Indicates whether the {@code x} and {@code y} coordinates are proportional to the size of the target area.
+ *
+ * @return {@code true} if the coordinates are proportional, {@code false} otherwise
+ */
+ public boolean isProportional() {
+ return proportional;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof AnchorPoint other
+ && x == other.x
+ && y == other.y
+ && proportional == other.proportional;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 37 * hash + Double.hashCode(this.x);
+ hash = 37 * hash + Double.hashCode(this.y);
+ hash = 37 * hash + Boolean.hashCode(this.proportional);
+ return hash;
+ }
+
+ /**
+ * Anchor at the top-left corner of the target area.
+ *
+ * This constant is equivalent to {@code AnchorPoint.proportional(0, 0)}.
+ */
+ public static final AnchorPoint TOP_LEFT = new AnchorPoint(0, 0, true);
+
+ /**
+ * Anchor at the top-center midpoint of the target area.
+ *
+ * This constant is equivalent to {@code AnchorPoint.proportional(0.5, 0)}.
+ */
+ public static final AnchorPoint TOP_CENTER = new AnchorPoint(0.5, 0, true);
+
+ /**
+ * Anchor at the top-right corner of the target area.
+ *
+ * This constant is equivalent to {@code AnchorPoint.proportional(1, 0)}.
+ */
+ public static final AnchorPoint TOP_RIGHT = new AnchorPoint(1, 0, true);
+
+ /**
+ * Anchor at the center-left midpoint of the target area.
+ *
+ * This constant is equivalent to {@code AnchorPoint.proportional(0, 0.5)}.
+ */
+ public static final AnchorPoint CENTER_LEFT = new AnchorPoint(0, 0.5, true);
+
+ /**
+ * Anchor at the center of the target area.
+ *
+ * This constant is equivalent to {@code AnchorPoint.proportional(0.5, 0.5)}.
+ */
+ public static final AnchorPoint CENTER = new AnchorPoint(0.5, 0.5, true);
+
+ /**
+ * Anchor at the center-right midpoint of the target area.
+ *
+ * This constant is equivalent to {@code AnchorPoint.proportional(1, 0.5)}.
+ */
+ public static final AnchorPoint CENTER_RIGHT = new AnchorPoint(1, 0.5, true);
+
+ /**
+ * Anchor at the bottom-left corner of the target area.
+ *
+ * This constant is equivalent to {@code AnchorPoint.proportional(0, 1)}.
+ */
+ public static final AnchorPoint BOTTOM_LEFT = new AnchorPoint(0, 1, true);
+
+ /**
+ * Anchor at the bottom-center midpoint of the target area.
+ *
+ * This constant is equivalent to {@code AnchorPoint.proportional(0.5, 1)}.
+ */
+ public static final AnchorPoint BOTTOM_CENTER = new AnchorPoint(0.5, 1, true);
+
+ /**
+ * Anchor at the bottom-right corner of the target area.
+ *
+ * This constant is equivalent to {@code AnchorPoint.proportional(1, 1)}.
+ */
+ public static final AnchorPoint BOTTOM_RIGHT = new AnchorPoint(1, 1, true);
+}
diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java b/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java
index 3e8b48895b4..90f2ebc6e4d 100644
--- a/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java
+++ b/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java
@@ -27,7 +27,6 @@
import java.util.ArrayList;
import java.util.List;
-import java.util.Objects;
import javafx.application.ColorScheme;
import javafx.application.Platform;
@@ -37,6 +36,7 @@
import javafx.beans.property.StringPropertyBase;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
+import javafx.geometry.AnchorPoint;
import javafx.geometry.NodeOrientation;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
@@ -318,7 +318,7 @@ public Stage(@NamedArg(value="style", defaultValue="DECORATED") StageStyle style
* @param anchor the point on the stage that should coincide with {@code (anchorX, anchorY)} on the screen
* @since 26
*/
- public final void show(double anchorX, double anchorY, Anchor anchor) {
+ public final void show(double anchorX, double anchorY, AnchorPoint anchor) {
if (isShowing()) {
new PositionRequest(anchorX, anchorY, anchor).apply(this);
} else {
@@ -492,7 +492,7 @@ public void showAndWait() {
* @throws IllegalStateException if this stage is already showing
* @since 26
*/
- public final void showAndWait(double anchorX, double anchorY, Anchor anchor) {
+ public final void showAndWait(double anchorX, double anchorY, AnchorPoint anchor) {
verifyCanShowAndWait();
positionRequest = new PositionRequest(anchorX, anchorY, anchor);
super.show();
@@ -1391,7 +1391,13 @@ final void fixBounds() {
private PositionRequest positionRequest;
- private record PositionRequest(double screenX, double screenY, Anchor anchor) {
+ private record PositionRequest(double screenX, double screenY, AnchorPoint anchor) {
+
+ PositionRequest {
+ if (anchor == null) {
+ throw new NullPointerException("anchor cannot be null");
+ }
+ }
void apply(Stage stage) {
Screen currentScreen = Utils.getScreenForPoint(screenX, screenY);
@@ -1404,16 +1410,16 @@ void apply(Stage stage) {
double anchorX, anchorY;
double anchorRelX, anchorRelY;
- if (anchor.relative) {
- anchorX = width * anchor.x;
- anchorY = height * anchor.y;
- anchorRelX = anchor.x;
- anchorRelY = anchor.y;
+ if (anchor.isProportional()) {
+ anchorX = width * anchor.getX();
+ anchorY = height * anchor.getY();
+ anchorRelX = anchor.getX();
+ anchorRelY = anchor.getY();
} else {
- anchorX = anchor.x;
- anchorY = anchor.y;
- anchorRelX = anchor.x / width;
- anchorRelY = anchor.y / height;
+ anchorX = anchor.getX();
+ anchorY = anchor.getY();
+ anchorRelX = anchor.getX() / width;
+ anchorRelY = anchor.getY() / height;
}
double minX = screenBounds.getMinX();
@@ -1438,102 +1444,4 @@ void apply(Stage stage) {
stage.setY(y);
}
}
-
- /**
- * Defines an anchor point that is used to position a stage with {@link #show(double, double, Anchor)}
- * or {@link #showAndWait(double, double, Anchor)}. The anchor is the point on the stage that should
- * coincide with a given screen location.
- *
- * Anchors can be specified in one of two coordinate systems:
- *
- * {@code (0,0)} is the top-left; {@code (1,1)} is the bottom-right; {@code (0.5,0.5)} is the center.
- *
- * @param x x fraction of the stage width
- * @param y y fraction of the stage height
- * @return a relative {@code Anchor}
- */
- public static Anchor ofRelative(double x, double y) {
- return new Anchor(x, y, true);
- }
-
- /**
- * Creates an absolute anchor expressed as pixel offsets from the stage's top-left corner.
- * The values can be less than 0 or greater than the stage's size; in this case the anchor
- * is located outside the stage.
- *
- * @param x x offset in pixels from the stage's left edge
- * @param y y offset in pixels from the stage's top edge
- * @return an absolute {@code Anchor}
- */
- public static Anchor ofAbsolute(double x, double y) {
- return new Anchor(x, y, false);
- }
-
- /**
- * Gets the horizontal location of the anchor.
- *
- * @return the horizontal location of the anchor
- */
- public double getX() {
- return x;
- }
-
- /**
- * Gets the vertical location of the anchor.
- *
- * @return the vertical location of the anchor
- */
- public double getY() {
- return y;
- }
-
- /**
- * Returns whether the anchor is expressed as a fraction of the stage size.
- *
- * @return {@code true} if the anchor is expressed as a fraction of the stage size,
- * {@code false} otherwise
- */
- public boolean isRelative() {
- return relative;
- }
-
- @Override
- public boolean equals(Object obj) {
- return obj instanceof Anchor other
- && other.x == x
- && other.y == y
- && other.relative == relative;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(x, y, relative);
- }
- }
}
From d44562346b641efd632e6f58f1afe07bb05725a4 Mon Sep 17 00:00:00 2001
From: mstr2 <43553916+mstr2@users.noreply.github.com>
Date: Wed, 26 Nov 2025 14:33:28 +0100
Subject: [PATCH 03/12] update tests
---
.../java/test/javafx/stage/StageTest.java | 27 ++++++++++---------
1 file changed, 14 insertions(+), 13 deletions(-)
diff --git a/modules/javafx.graphics/src/test/java/test/javafx/stage/StageTest.java b/modules/javafx.graphics/src/test/java/test/javafx/stage/StageTest.java
index d3eea95ea03..8872d273367 100644
--- a/modules/javafx.graphics/src/test/java/test/javafx/stage/StageTest.java
+++ b/modules/javafx.graphics/src/test/java/test/javafx/stage/StageTest.java
@@ -27,15 +27,16 @@
import java.util.ArrayList;
import java.util.stream.Stream;
+import javafx.geometry.AnchorPoint;
import javafx.scene.image.Image;
-import com.sun.javafx.stage.WindowHelper;
import javafx.scene.Group;
import javafx.scene.Scene;
+import javafx.stage.Stage;
import test.com.sun.javafx.pgstub.StubStage;
import test.com.sun.javafx.pgstub.StubToolkit;
import test.com.sun.javafx.pgstub.StubToolkit.ScreenConfiguration;
+import com.sun.javafx.stage.WindowHelper;
import com.sun.javafx.tk.Toolkit;
-import javafx.stage.Stage;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@@ -557,7 +558,7 @@ public void testAddAndSetNullIcon() {
public void showWithAnchorClampsWindowToScreenEdges(
@SuppressWarnings("unused") String edge,
@SuppressWarnings("unused") String anchorName,
- Stage.Anchor anchor,
+ AnchorPoint anchor,
double screenW, double screenH,
double stageW, double stageH,
double requestX, double requestY) {
@@ -587,13 +588,13 @@ private static Stream
+ * Clamping adjusts the computed window position so that the window is shown within the screen bounds.
+ * Clamping can be applied independently on the horizontal axis, the vertical axis, both axes, or not at all.
+ *
+ * @since 26
+ */
+public enum ClampPolicy {
+
+ /**
+ * Do not clamp the computed position.
+ *
+ * The window is placed exactly as specified by the requested screen coordinates, even if this
+ * causes parts of the window to extend beyond the bounds of the screen.
+ */
+ NONE,
+
+ /**
+ * Clamp the computed position horizontally only.
+ *
+ * The {@code x} coordinate of the window is adjusted as needed to keep the window within the screen
+ * bounds, while the {@code y} coordinate is left unchanged.
+ */
+ HORIZONTAL,
+
+ /**
+ * Clamp the computed position vertically only.
+ *
+ * The {@code y} coordinate of the window is adjusted as needed to keep the window within the screen
+ * bounds, while the {@code x} coordinate is left unchanged.
+ */
+ VERTICAL,
+
+ /**
+ * Clamp the computed position both horizontally and vertically.
+ *
+ * Both the {@code x} and {@code y} coordinates of the window are adjusted as needed to keep the
+ * window within the screen bounds.
+ */
+ BOTH
+}
diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java b/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java
index 90f2ebc6e4d..ef1db2427a6 100644
--- a/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java
+++ b/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java
@@ -27,6 +27,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import javafx.application.ColorScheme;
import javafx.application.Platform;
@@ -37,6 +38,7 @@
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.geometry.AnchorPoint;
+import javafx.geometry.Insets;
import javafx.geometry.NodeOrientation;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
@@ -304,25 +306,67 @@ public Stage(@NamedArg(value="style", defaultValue="DECORATED") StageStyle style
* Shows this stage at the specified location and adjusts the position as needed to keep the stage
* visible on screen. If the stage is already showing, it is moved to the computed position instead.
*
- * Positioning is done using an {@code anchor} point on the stage. The specified location is interpreted
- * as the desired screen coordinates of that anchor, and the stage location is derived from that.
- * For example, if the anchor is {@code (0.5, 0.5)}, the stage is positioned so its center lies at
- * {@code (anchorX, anchorY)}; if the anchor is {@code (0, 0)}, the stage's top-left corner is placed
- * at {@code (anchorX, anchorY)}. The final stage location is clamped to the screen bounds.
+ * The {@code anchor} identifies a point on the stage that should coincide with the requested screen
+ * coordinates {@code (anchorX, anchorY)}. The stage location is computed from the anchor and the
+ * stage size, and is then adjusted to keep the stage within the screen bounds.
*
* After the stage is shown, its {@link #xProperty() X} and {@link #yProperty() Y} properties will
* reflect the adjusted position.
+ *
+ * Calling this method is equivalent to calling
+ * {@code show(anchorX, anchorY, anchor, ClampPolicy.BOTH, Insets.EMPTY)}.
*
* @param anchorX the requested horizontal location of the anchor point on the screen
* @param anchorY the requested vertical location of the anchor point on the screen
* @param anchor the point on the stage that should coincide with {@code (anchorX, anchorY)} on the screen
+ * @throws NullPointerException if {@code anchor} is {@code null}
* @since 26
*/
public final void show(double anchorX, double anchorY, AnchorPoint anchor) {
+ show(anchorX, anchorY, anchor, ClampPolicy.BOTH, Insets.EMPTY);
+ }
+
+ /**
+ * Shows this stage at the specified location using the given anchor and clamping options.
+ * If the stage is already showing, it is moved to the computed position instead.
+ *
+ * The {@code anchor} identifies a point on the stage that should coincide with the requested screen
+ * coordinates {@code (anchorX, anchorY)}. The stage location is computed from the anchor and the
+ * stage size, and is then adjusted according to {@code clampPolicy}.
+ *
+ * The {@code clampPolicy} controls which axes are clamped to the screen bounds:
+ *
+ * After the stage is shown, its {@link #xProperty() X} and {@link #yProperty() Y} properties will
+ * reflect the adjusted position.
+ *
+ * @param anchorX the requested horizontal location of the anchor point on the screen
+ * @param anchorY the requested vertical location of the anchor point on the screen
+ * @param anchor the point on the stage that should coincide with {@code (anchorX, anchorY)} on the screen
+ * @param clampPolicy controls whether clamping is performed horizontally, vertically, both, or not at all
+ * @param screenPadding the minimum padding to maintain between the stage edges and the screen edges
+ * when clamping is performed
+ * @throws NullPointerException if {@code anchor}, {@code clampPolicy}, or {@code screenPadding} is {@code null}
+ * @since 26
+ */
+ public final void show(double anchorX, double anchorY, AnchorPoint anchor,
+ ClampPolicy clampPolicy, Insets screenPadding) {
+ var positionRequest = new PositionRequest(anchorX, anchorY, anchor, clampPolicy, screenPadding);
+
if (isShowing()) {
- new PositionRequest(anchorX, anchorY, anchor).apply(this);
+ positionRequest.apply(this);
} else {
- positionRequest = new PositionRequest(anchorX, anchorY, anchor);
+ this.positionRequest = positionRequest;
super.show();
}
}
@@ -470,21 +514,23 @@ public void showAndWait() {
/**
* Shows this stage at the specified location and adjusts the position as needed to keep the stage
* visible on screen. This method blocks until the stage is hidden before returning to the caller.
- * This method temporarily blocks processing of the current event and starts a nested event loop
- * to handle other events. This method must be called on the JavaFX application thread.
+ * It also temporarily blocks processing of the current event and starts a nested event loop to
+ * handle other events. This method must be called on the JavaFX application thread.
*
- * Positioning is done using an {@code anchor} point on the stage. The specified location is interpreted
- * as the desired screen coordinates of that anchor, and the stage location is derived from that.
- * For example, if the anchor is {@code (0.5, 0.5)}, the stage is positioned so its center lies at
- * {@code (anchorX, anchorY)}; if the anchor is {@code (0, 0)}, the stage's top-left corner is placed
- * at {@code (anchorX, anchorY)}. The final stage location is clamped to the screen bounds.
+ * The {@code anchor} identifies a point on the stage that should coincide with the requested screen
+ * coordinates {@code (anchorX, anchorY)}. The stage location is computed from the anchor and the
+ * stage size, and is then adjusted to keep the stage within the screen bounds.
*
* After the stage is shown, its {@link #xProperty() X} and {@link #yProperty() Y} properties will
* reflect the adjusted position.
+ *
+ * Calling this method is equivalent to calling
+ * {@code showAndWait(anchorX, anchorY, anchor, ClampPolicy.BOTH, Insets.EMPTY)}.
*
* @param anchorX the requested horizontal location of the anchor point on the screen
* @param anchorY the requested vertical location of the anchor point on the screen
* @param anchor the point on the stage that should coincide with {@code (anchorX, anchorY)} on the screen
+ * @throws NullPointerException if {@code anchor} is {@code null}
* @throws IllegalStateException if this method is called on a thread other than the JavaFX application thread
* @throws IllegalStateException if this method is called during animation or layout processing
* @throws IllegalStateException if this call would exceed the maximum number of nested event loops
@@ -493,8 +539,53 @@ public void showAndWait() {
* @since 26
*/
public final void showAndWait(double anchorX, double anchorY, AnchorPoint anchor) {
+ showAndWait(anchorX, anchorY, anchor, ClampPolicy.BOTH, Insets.EMPTY);
+ }
+
+ /**
+ * Shows this stage at the specified location using the given anchor and clamping options.
+ * This method blocks until the stage is hidden before returning to the caller.
+ * It also temporarily blocks processing of the current event and starts a nested event loop to
+ * handle other events. This method must be called on the JavaFX application thread.
+ *
+ * The {@code anchor} identifies a point on the stage that should coincide with the requested screen
+ * coordinates {@code (anchorX, anchorY)}. The stage location is computed from the anchor and the
+ * stage size, and is then adjusted according to {@code clampPolicy}.
+ *
+ * The {@code clampPolicy} controls which axes are clamped to the screen bounds:
+ *
+ * After the stage is shown, its {@link #xProperty() X} and {@link #yProperty() Y} properties will
+ * reflect the adjusted position.
+ *
+ * @param anchorX the requested horizontal location of the anchor point on the screen
+ * @param anchorY the requested vertical location of the anchor point on the screen
+ * @param anchor the point on the stage that should coincide with {@code (anchorX, anchorY)} on the screen
+ * @param clampPolicy controls whether clamping is performed horizontally, vertically, both, or not at all
+ * @param screenPadding the minimum padding to maintain between the stage edges and the screen edges
+ * when clamping is performed
+ * @throws NullPointerException if {@code anchor}, {@code clampPolicy}, or {@code screenPadding} is {@code null}
+ * @throws IllegalStateException if this method is called on a thread other than the JavaFX application thread
+ * @throws IllegalStateException if this method is called during animation or layout processing
+ * @throws IllegalStateException if this call would exceed the maximum number of nested event loops
+ * @throws IllegalStateException if this method is called on the primary stage
+ * @throws IllegalStateException if this stage is already showing
+ * @since 26
+ */
+ public final void showAndWait(double anchorX, double anchorY, AnchorPoint anchor,
+ ClampPolicy clampPolicy, Insets screenPadding) {
verifyCanShowAndWait();
- positionRequest = new PositionRequest(anchorX, anchorY, anchor);
+ positionRequest = new PositionRequest(anchorX, anchorY, anchor, clampPolicy, screenPadding);
super.show();
inNestedEventLoop = true;
Toolkit.getToolkit().enterNestedEventLoop(this);
@@ -1391,12 +1482,13 @@ final void fixBounds() {
private PositionRequest positionRequest;
- private record PositionRequest(double screenX, double screenY, AnchorPoint anchor) {
+ private record PositionRequest(double screenX, double screenY, AnchorPoint anchor,
+ ClampPolicy clampPolicy, Insets screenPadding) {
PositionRequest {
- if (anchor == null) {
- throw new NullPointerException("anchor cannot be null");
- }
+ Objects.requireNonNull(anchor, "anchor cannot be null");
+ Objects.requireNonNull(clampPolicy, "clampPolicy cannot be null");
+ Objects.requireNonNull(screenPadding, "screenPadding cannot be null");
}
void apply(Stage stage) {
@@ -1418,26 +1510,44 @@ void apply(Stage stage) {
} else {
anchorX = anchor.getX();
anchorY = anchor.getY();
- anchorRelX = anchor.getX() / width;
- anchorRelY = anchor.getY() / height;
+ anchorRelX = width != 0 ? anchor.getX() / width : 0;
+ anchorRelY = height != 0 ? anchor.getY() / height : 0;
}
- double minX = screenBounds.getMinX();
- double minY = screenBounds.getMinY();
- double maxX = screenBounds.getMaxX() - width;
- double maxY = screenBounds.getMaxY() - height;
- double x, y;
+ // Raw (unclamped) top-left position derived from the requested screen point + anchor
+ double rawX = screenX - anchorX;
+ double rawY = screenY - anchorY;
- if (maxX >= minX) {
- x = Utils.clamp(minX, screenX - anchorX, maxX);
- } else {
- x = anchorRelX > 0.5 ? maxX : minX;
+ // Start with raw coordinates; clamp per policy below
+ double x = rawX;
+ double y = rawY;
+
+ // Only compute clamp ranges for axes that are being clamped
+ boolean clampH = clampPolicy == ClampPolicy.BOTH || clampPolicy == ClampPolicy.HORIZONTAL;
+ boolean clampV = clampPolicy == ClampPolicy.BOTH || clampPolicy == ClampPolicy.VERTICAL;
+
+ if (clampH) {
+ double minX = screenBounds.getMinX() + screenPadding.getLeft();
+ double maxX = screenBounds.getMaxX() - screenPadding.getRight() - width;
+
+ if (maxX >= minX) {
+ x = Utils.clamp(minX, rawX, maxX);
+ } else {
+ // Window (plus padding) doesn't fit horizontally: pick a side based on anchor
+ x = anchorRelX > 0.5 ? maxX : minX;
+ }
}
- if (maxY >= minY) {
- y = Utils.clamp(minY, screenY - anchorY, maxY);
- } else {
- y = anchorRelY > 0.5 ? maxY : minY;
+ if (clampV) {
+ double minY = screenBounds.getMinY() + screenPadding.getTop();
+ double maxY = screenBounds.getMaxY() - screenPadding.getBottom() - height;
+
+ if (maxY >= minY) {
+ y = Utils.clamp(minY, rawY, maxY);
+ } else {
+ // Window (plus padding) doesn't fit vertically: pick a side based on anchor
+ y = anchorRelY > 0.5 ? maxY : minY;
+ }
}
stage.setX(x);
diff --git a/modules/javafx.graphics/src/test/java/test/javafx/stage/StageTest.java b/modules/javafx.graphics/src/test/java/test/javafx/stage/StageTest.java
index 8872d273367..161fca102d1 100644
--- a/modules/javafx.graphics/src/test/java/test/javafx/stage/StageTest.java
+++ b/modules/javafx.graphics/src/test/java/test/javafx/stage/StageTest.java
@@ -28,9 +28,11 @@
import java.util.ArrayList;
import java.util.stream.Stream;
import javafx.geometry.AnchorPoint;
+import javafx.geometry.Insets;
import javafx.scene.image.Image;
import javafx.scene.Group;
import javafx.scene.Scene;
+import javafx.stage.ClampPolicy;
import javafx.stage.Stage;
import test.com.sun.javafx.pgstub.StubStage;
import test.com.sun.javafx.pgstub.StubToolkit;
@@ -43,6 +45,7 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -551,14 +554,17 @@ public void testAddAndSetNullIcon() {
/**
* Tests that a stage that is shown with an anchor and placed such that it extends slightly beyond
- * the edges of the screen is repositioned so that it fits within the screen.
+ * the edges of the screen is repositioned so that it fits within the screen, taking into account
+ * padding around the screen.
*/
@ParameterizedTest(name = "Clamps to {0} edges with {1} anchor")
@MethodSource("showWithAnchorClampsWindowToScreenEdges_arguments")
- public void showWithAnchorClampsWindowToScreenEdges(
+ public void showAndClampToScreenEdgesWithPadding(
@SuppressWarnings("unused") String edge,
@SuppressWarnings("unused") String anchorName,
AnchorPoint anchor,
+ ClampPolicy clampPolicy,
+ Insets screenPadding,
double screenW, double screenH,
double stageW, double stageH,
double requestX, double requestY) {
@@ -571,10 +577,10 @@ public void showWithAnchorClampsWindowToScreenEdges(
try {
s.setWidth(stageW);
s.setHeight(stageH);
- s.show(requestX, requestY, anchor);
+ s.show(requestX, requestY, anchor, clampPolicy, screenPadding);
pulse();
- assertWithinScreenBounds(peer, toolkit.getScreens().getFirst());
+ assertWithinScreenBounds(peer, toolkit.getScreens().getFirst(), screenPadding);
} finally {
toolkit.resetScreens();
}
@@ -588,70 +594,65 @@ private static Stream
+ * Screen edge constraints are specified by {@code screenPadding}:
+ * values {@code >= 0} enable a constraint for the corresponding edge (minimum distance to keep),
+ * values {@code < 0} disable the constraint for that edge. Enabled constraints reduce the usable area
+ * for placement by the given insets.
+ */
+ public static Consumer
+ * The requested screen coordinates {@code (screenX, screenY)} are interpreted as the desired location
+ * of {@code anchor} on the window. The raw (unadjusted) window position is derived from the anchor and
+ * the given {@code width}/{@code height}. If that raw position violates any enabled constraints, the
+ * method considers alternative anchors depending on {@code policy} (for example, horizontally and/or
+ * vertically flipped anchors) and chooses the alternative that yields the smallest adjustment after
+ * constraints are applied.
+ *
+ * Screen edge constraints are specified by {@code screenPadding}:
+ * values {@code >= 0} enable a constraint for the corresponding edge (minimum distance to keep),
+ * values {@code < 0} disable the constraint for that edge. Enabled constraints reduce the usable area
+ * for placement by the given insets.
+ */
+ public static Point2D computeAdjustedLocation(double screenX, double screenY,
+ double width, double height,
+ AnchorPoint anchor, AnchorPolicy policy,
+ Rectangle2D screenBounds, Insets screenPadding) {
+ Constraints constraints = computeConstraints(screenBounds, width, height, screenPadding);
+ Position preferredRaw = getRawForAnchor(screenX, screenY, anchor, width, height);
+ boolean validH = isHorizontalValid(preferredRaw, constraints);
+ boolean validV = isVerticalValid(preferredRaw, constraints);
+ if (validH && validV) {
+ return new Point2D(preferredRaw.x, preferredRaw.y);
+ }
+
+ List
+ * For each inset value:
+ *
+ * The result is the position at which the window would be located if no edge constraints were applied.
+ */
+ private static Position getRawForAnchor(double screenX, double screenY, AnchorPoint anchor,
+ double width, double height) {
+ double x, y, relX, relY;
+
+ if (anchor.isProportional()) {
+ x = width * anchor.getX();
+ y = height * anchor.getY();
+ relX = anchor.getX();
+ relY = anchor.getY();
+ } else {
+ x = anchor.getX();
+ y = anchor.getY();
+ relX = width != 0 ? anchor.getX() / width : 0;
+ relY = height != 0 ? anchor.getY() / height : 0;
+ }
+
+ return new Position(screenX - x, screenY - y, relX, relY);
+ }
+
+ /**
+ * Computes the list of alternative candidate anchors to consider, based on the requested policy
+ * and which constraint the preferred placement violates.
+ *
+ * Candidates are ordered from most preferred to least preferred for the given policy.
+ */
+ private static List
+ * Constraints may be disabled per edge (via negative inset values). When both edges for an axis
+ * are enabled, the position is constrained to the resulting interval. When only one edge is enabled,
+ * a one-sided minimum or maximum constraint is applied. If the constrained interval is too small to
+ * fit the window, a side is chosen based on the relative anchor location.
+ */
+ private static Point2D applyConstraints(Position raw, Constraints c) {
+ double x = raw.x;
+ double y = raw.y;
+
+ if (c.hasMinX && c.hasMaxX) {
+ if (c.maxX >= c.minX) {
+ x = Utils.clamp(c.minX, x, c.maxX);
+ } else {
+ // Constrained space too small: choose a side based on anchor
+ x = raw.relX > 0.5 ? c.maxX : c.minX;
+ }
+ } else if (c.hasMinX) {
+ x = Math.max(x, c.minX);
+ } else if (c.hasMaxX) {
+ x = Math.min(x, c.maxX);
+ }
+
+ if (c.hasMinY && c.hasMaxY) {
+ if (c.maxY >= c.minY) {
+ y = Utils.clamp(c.minY, y, c.maxY);
+ } else {
+ // Constrained space too small: choose a side based on anchor
+ y = raw.relY > 0.5 ? c.maxY : c.minY;
+ }
+ } else if (c.hasMinY) {
+ y = Math.max(y, c.minY);
+ } else if (c.hasMaxY) {
+ y = Math.min(y, c.maxY);
+ }
+
+ return new Point2D(x, y);
+ }
+
+ /**
+ * Computes a scalar "adjustment cost" used to select between candidate anchors.
+ *
+ * The current implementation uses Manhattan distance (|dx| + |dy|) between the raw and adjusted positions.
+ * Lower values indicate that fewer or smaller constraint adjustments were required.
+ */
+ private static double getAdjustmentCost(Position raw, Point2D adjusted) {
+ return Math.abs(adjusted.getX() - raw.x) + Math.abs(adjusted.getY() - raw.y);
+ }
+
+ private static boolean isHorizontalValid(Position raw, Constraints c) {
+ return !(c.hasMinX && raw.x < c.minX) && !(c.hasMaxX && raw.x > c.maxX);
+ }
+
+ private static boolean isVerticalValid(Position raw, Constraints c) {
+ return !(c.hasMinY && raw.y < c.minY) && !(c.hasMaxY && raw.y > c.maxY);
+ }
+
+ private static AnchorPoint flipAnchor(AnchorPoint anchor,
+ double width, double height,
+ boolean flipH, boolean flipV) {
+ if (anchor.isProportional()) {
+ double x = anchor.getX();
+ double y = anchor.getY();
+ double nx = flipH ? (1.0 - x) : x;
+ double ny = flipV ? (1.0 - y) : y;
+ return AnchorPoint.proportional(nx, ny);
+ } else {
+ double x = anchor.getX();
+ double y = anchor.getY();
+ double nx = flipH ? (width - x) : x;
+ double ny = flipV ? (height - y) : y;
+ return AnchorPoint.absolute(nx, ny);
+ }
+ }
+
+ private record Constraints(boolean hasMinX, boolean hasMaxX,
+ boolean hasMinY, boolean hasMaxY,
+ double minX, double maxX,
+ double minY, double maxY) {}
+
+ private record Position(double x, double y, double relX, double relY) {}
+}
diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/AnchorPolicy.java b/modules/javafx.graphics/src/main/java/javafx/stage/AnchorPolicy.java
new file mode 100644
index 00000000000..0a9ec0c2ad2
--- /dev/null
+++ b/modules/javafx.graphics/src/main/java/javafx/stage/AnchorPolicy.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package javafx.stage;
+
+import javafx.geometry.AnchorPoint;
+import javafx.geometry.Insets;
+
+/**
+ * Specifies how a repositioning operation may adjust an anchor point when the preferred placement
+ * would violate the screen bounds constraints.
+ *
+ * The anchor passed to {@link Stage#relocate(double, double, AnchorPoint, AnchorPolicy, Insets)} or specified
+ * by {@link PopupWindow#anchorLocationProperty() PopupWindow.anchorLocation} identifies the point on the
+ * window that should coincide with the requested screen coordinates. When the preferred anchor would place
+ * the window outside the allowed screen area (as defined by the screen bounds and any configured insets),
+ * an {@code AnchorPolicy} can be used to select an alternative anchor before applying any final position
+ * adjustment.
+ *
+ * @since 26
+ */
+public enum AnchorPolicy {
+
+ /**
+ * Always use the preferred anchor and never select an alternative anchor.
+ *
+ * If the preferred placement violates the allowed screen area, the window position is adjusted
+ * for the window to fall within the allowed screen area. If this is not possible, the window is
+ * biased towards the edge that is closer to the anchor.
+ */
+ FIXED,
+
+ /**
+ * If the preferred placement violates horizontal constraints, attempt a horizontally flipped anchor.
+ *
+ * A horizontal flip mirrors the anchor across the vertical center line of the window
+ * (for example, {@code TOP_LEFT} becomes {@code TOP_RIGHT}).
+ *
+ * If the horizontally flipped anchor does not improve the placement, the original anchor is used
+ * and the final position is adjusted for the window to fall within the allowed screen area.
+ * If this is not possible, the window is biased towards the edge that is closer to the anchor.
+ */
+ FLIP_HORIZONTAL,
+
+ /**
+ * If the preferred placement violates vertical constraints, attempt a vertically flipped anchor.
+ *
+ * A vertical flip mirrors the anchor across the horizontal center line of the window
+ * (for example, {@code TOP_LEFT} becomes {@code BOTTOM_LEFT}).
+ *
+ * If the vertically flipped anchor does not improve the placement, the original anchor is used
+ * and the final position is adjusted for the window to fall within the allowed screen area.
+ * If this is not possible, the window is biased towards the edge that is closer to the anchor.
+ */
+ FLIP_VERTICAL,
+
+ /**
+ * Automatically chooses an alternative anchor based on which constraints are violated.
+ *
+ * This policy selects the "most natural" flip for the current situation:
+ *
- * Clamping adjusts the computed window position so that the window is shown within the screen bounds.
- * Clamping can be applied independently on the horizontal axis, the vertical axis, both axes, or not at all.
- *
- * @since 26
- */
-public enum ClampPolicy {
-
- /**
- * Do not clamp the computed position.
- *
- * The window is placed exactly as specified by the requested screen coordinates, even if this
- * causes parts of the window to extend beyond the bounds of the screen.
- */
- NONE,
-
- /**
- * Clamp the computed position horizontally only.
- *
- * The {@code x} coordinate of the window is adjusted as needed to keep the window within the screen
- * bounds, while the {@code y} coordinate is left unchanged.
- */
- HORIZONTAL,
-
- /**
- * Clamp the computed position vertically only.
- *
- * The {@code y} coordinate of the window is adjusted as needed to keep the window within the screen
- * bounds, while the {@code x} coordinate is left unchanged.
- */
- VERTICAL,
-
- /**
- * Clamp the computed position both horizontally and vertically.
- *
- * Both the {@code x} and {@code y} coordinates of the window are adjusted as needed to keep the
- * window within the screen bounds.
- */
- BOTH
-}
diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/PopupWindow.java b/modules/javafx.graphics/src/main/java/javafx/stage/PopupWindow.java
index a7b68f0fa54..b47978fc294 100644
--- a/modules/javafx.graphics/src/main/java/javafx/stage/PopupWindow.java
+++ b/modules/javafx.graphics/src/main/java/javafx/stage/PopupWindow.java
@@ -44,8 +44,11 @@
import javafx.collections.ObservableList;
import javafx.event.Event;
import javafx.event.EventHandler;
+import javafx.geometry.AnchorPoint;
import javafx.geometry.BoundingBox;
import javafx.geometry.Bounds;
+import javafx.geometry.Insets;
+import javafx.geometry.Point2D;
import javafx.geometry.Rectangle2D;
import javafx.scene.Group;
import javafx.scene.Node;
@@ -59,6 +62,7 @@
import com.sun.javafx.scene.SceneHelper;
import com.sun.javafx.stage.FocusUngrabEvent;
import com.sun.javafx.stage.PopupWindowPeerListener;
+import com.sun.javafx.stage.WindowBoundsUtil;
import com.sun.javafx.stage.WindowCloseRequestHandler;
import com.sun.javafx.stage.WindowEventDispatcher;
import com.sun.javafx.tk.Toolkit;
@@ -631,6 +635,32 @@ public final ReadOnlyDoubleProperty anchorYProperty() {
return anchorY.getReadOnlyProperty();
}
+ /**
+ * Controls whether an alternative anchor location may be used when the preferred
+ * {@link #anchorLocationProperty() anchorLocation} would place the popup window outside the screen bounds.
+ * Depending on the policy, the preferred anchor location may be mirrored to the other side of the window
+ * horizontally or vertically, or an anchor location may be selected automatically.
+ *
+ * If no alternative anchor location yields a better placement, the specified {@code anchorLocation} is used.
+ *
+ * @defaultValue {@link AnchorPolicy#FIXED}
+ * @since 26
+ */
+ private final ObjectProperty
- * The {@code anchor} identifies a point on the stage that should coincide with the requested screen
- * coordinates {@code (anchorX, anchorY)}. The stage location is computed from the anchor and the
- * stage size, and is then adjusted to keep the stage within the screen bounds.
- *
- * After the stage is shown, its {@link #xProperty() X} and {@link #yProperty() Y} properties will
- * reflect the adjusted position.
- *
- * Calling this method is equivalent to calling
- * {@code show(anchorX, anchorY, anchor, ClampPolicy.BOTH, Insets.EMPTY)}.
- *
- * @param anchorX the requested horizontal location of the anchor point on the screen
- * @param anchorY the requested vertical location of the anchor point on the screen
- * @param anchor the point on the stage that should coincide with {@code (anchorX, anchorY)} on the screen
- * @throws NullPointerException if {@code anchor} is {@code null}
- * @since 26
- */
- public final void show(double anchorX, double anchorY, AnchorPoint anchor) {
- show(anchorX, anchorY, anchor, ClampPolicy.BOTH, Insets.EMPTY);
- }
-
- /**
- * Shows this stage at the specified location using the given anchor and clamping options.
- * If the stage is already showing, it is moved to the computed position instead.
- *
- * The {@code anchor} identifies a point on the stage that should coincide with the requested screen
- * coordinates {@code (anchorX, anchorY)}. The stage location is computed from the anchor and the
- * stage size, and is then adjusted according to {@code clampPolicy}.
- *
- * The {@code clampPolicy} controls which axes are clamped to the screen bounds:
- *
- * After the stage is shown, its {@link #xProperty() X} and {@link #yProperty() Y} properties will
- * reflect the adjusted position.
- *
- * @param anchorX the requested horizontal location of the anchor point on the screen
- * @param anchorY the requested vertical location of the anchor point on the screen
- * @param anchor the point on the stage that should coincide with {@code (anchorX, anchorY)} on the screen
- * @param clampPolicy controls whether clamping is performed horizontally, vertically, both, or not at all
- * @param screenPadding the minimum padding to maintain between the stage edges and the screen edges
- * when clamping is performed
- * @throws NullPointerException if {@code anchor}, {@code clampPolicy}, or {@code screenPadding} is {@code null}
- * @throws IllegalArgumentException if {@code screenPadding} is negative
- * @since 26
- */
- public final void show(double anchorX, double anchorY, AnchorPoint anchor,
- ClampPolicy clampPolicy, Insets screenPadding) {
- var positionRequest = new PositionRequest(anchorX, anchorY, anchor, clampPolicy, screenPadding);
-
- if (isShowing()) {
- positionRequest.apply(this);
- } else {
- this.positionRequest = positionRequest;
- super.show();
- }
- }
-
private boolean primary = false;
//------------------------------------------------------------------
@@ -506,94 +435,7 @@ private boolean isImportant() {
* @since JavaFX 2.2
*/
public void showAndWait() {
- verifyCanShowAndWait();
- super.show();
- inNestedEventLoop = true;
- Toolkit.getToolkit().enterNestedEventLoop(this);
- }
- /**
- * Shows this stage at the specified location and adjusts the position as needed to keep the stage
- * visible on screen. This method blocks until the stage is hidden before returning to the caller.
- * It also temporarily blocks processing of the current event and starts a nested event loop to
- * handle other events. This method must be called on the JavaFX application thread.
- *
- * The {@code anchor} identifies a point on the stage that should coincide with the requested screen
- * coordinates {@code (anchorX, anchorY)}. The stage location is computed from the anchor and the
- * stage size, and is then adjusted to keep the stage within the screen bounds.
- *
- * After the stage is shown, its {@link #xProperty() X} and {@link #yProperty() Y} properties will
- * reflect the adjusted position.
- *
- * Calling this method is equivalent to calling
- * {@code showAndWait(anchorX, anchorY, anchor, ClampPolicy.BOTH, Insets.EMPTY)}.
- *
- * @param anchorX the requested horizontal location of the anchor point on the screen
- * @param anchorY the requested vertical location of the anchor point on the screen
- * @param anchor the point on the stage that should coincide with {@code (anchorX, anchorY)} on the screen
- * @throws NullPointerException if {@code anchor} is {@code null}
- * @throws IllegalStateException if this method is called on a thread other than the JavaFX application thread
- * @throws IllegalStateException if this method is called during animation or layout processing
- * @throws IllegalStateException if this call would exceed the maximum number of nested event loops
- * @throws IllegalStateException if this method is called on the primary stage
- * @throws IllegalStateException if this stage is already showing
- * @since 26
- */
- public final void showAndWait(double anchorX, double anchorY, AnchorPoint anchor) {
- showAndWait(anchorX, anchorY, anchor, ClampPolicy.BOTH, Insets.EMPTY);
- }
-
- /**
- * Shows this stage at the specified location using the given anchor and clamping options.
- * This method blocks until the stage is hidden before returning to the caller.
- * It also temporarily blocks processing of the current event and starts a nested event loop to
- * handle other events. This method must be called on the JavaFX application thread.
- *
- * The {@code anchor} identifies a point on the stage that should coincide with the requested screen
- * coordinates {@code (anchorX, anchorY)}. The stage location is computed from the anchor and the
- * stage size, and is then adjusted according to {@code clampPolicy}.
- *
- * The {@code clampPolicy} controls which axes are clamped to the screen bounds:
- *
- * After the stage is shown, its {@link #xProperty() X} and {@link #yProperty() Y} properties will
- * reflect the adjusted position.
- *
- * @param anchorX the requested horizontal location of the anchor point on the screen
- * @param anchorY the requested vertical location of the anchor point on the screen
- * @param anchor the point on the stage that should coincide with {@code (anchorX, anchorY)} on the screen
- * @param clampPolicy controls whether clamping is performed horizontally, vertically, both, or not at all
- * @param screenPadding the minimum padding to maintain between the stage edges and the screen edges
- * when clamping is performed
- * @throws NullPointerException if {@code anchor}, {@code clampPolicy}, or {@code screenPadding} is {@code null}
- * @throws IllegalArgumentException if {@code screenPadding} is negative
- * @throws IllegalStateException if this method is called on a thread other than the JavaFX application thread
- * @throws IllegalStateException if this method is called during animation or layout processing
- * @throws IllegalStateException if this call would exceed the maximum number of nested event loops
- * @throws IllegalStateException if this method is called on the primary stage
- * @throws IllegalStateException if this stage is already showing
- * @since 26
- */
- public final void showAndWait(double anchorX, double anchorY, AnchorPoint anchor,
- ClampPolicy clampPolicy, Insets screenPadding) {
- verifyCanShowAndWait();
- positionRequest = new PositionRequest(anchorX, anchorY, anchor, clampPolicy, screenPadding);
- super.show();
- inNestedEventLoop = true;
- Toolkit.getToolkit().enterNestedEventLoop(this);
- }
-
- private void verifyCanShowAndWait() {
Toolkit.getToolkit().checkFxUserThread();
if (isPrimary()) {
@@ -612,6 +454,10 @@ private void verifyCanShowAndWait() {
// method is called from an event handler that is listening to a
// WindowEvent.WINDOW_HIDING event.
assert !inNestedEventLoop;
+
+ show();
+ inNestedEventLoop = true;
+ Toolkit.getToolkit().enterNestedEventLoop(this);
}
private StageStyle style; // default is set in constructor
@@ -1371,6 +1217,88 @@ public void toBack() {
}
}
+ /**
+ * Moves this stage to the specified screen location using the given anchor.
+ *
+ * The {@code anchor} identifies a point on the stage that should coincide with the requested screen
+ * coordinates {@code (anchorX, anchorY)}. The stage location is derived from this anchor and is then
+ * adjusted to keep the stage within the screen bounds.
+ *
+ * This method may be called either before or after {@link #show()} or {@link #showAndWait()}.
+ * If called before the stage is shown, then
+ *
+ * The {@code anchor} identifies a point on the stage that should coincide with the requested screen
+ * coordinates {@code (anchorX, anchorY)}. The stage location is derived from this anchor and is then
+ * optionally adjusted to keep the stage within screen edge constraints.
+ *
+ * The {@code anchorPolicy} controls whether an alternative anchor may be used when the preferred anchor
+ * would violate screen edge constraints. Depending on the policy, the preferred anchor location may be
+ * mirrored to the other side of the window horizontally or vertically, or an anchor might be selected
+ * automatically. If no alternative anchor yields a better placement, the specified {@code anchor} is used.
+ *
+ * The {@code screenPadding} parameter defines per-edge constraints against the current screen bounds.
+ * Each inset value specifies the minimum distance to maintain between the stage edge and the corresponding
+ * screen edge. A value {@code >= 0} enables the corresponding edge constraint; a negative value disables
+ * the constraint for that edge. Enabled constraints effectively shrink the usable screen area by the
+ * given insets. For example, a left inset of {@code 10} ensures the stage will not be placed closer than
+ * 10 pixels to the left screen edge.
+ *
+ * This method may be called either before or after {@link #show()} or {@link #showAndWait()}.
+ * If called before the stage is shown, then
+ *
diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/AnchorPolicy.java b/modules/javafx.graphics/src/main/java/javafx/stage/AnchorPolicy.java
index 0a9ec0c2ad2..a161a393307 100644
--- a/modules/javafx.graphics/src/main/java/javafx/stage/AnchorPolicy.java
+++ b/modules/javafx.graphics/src/main/java/javafx/stage/AnchorPolicy.java
@@ -91,4 +91,4 @@ public enum AnchorPolicy {
* If this is not possible, the window is biased towards the edge that is closer to the anchor.
*/
AUTO
-}
\ No newline at end of file
+}
diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java b/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java
index 65e2b6aa0a6..cc6f735bf45 100644
--- a/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java
+++ b/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java
@@ -1267,7 +1267,7 @@ public final void relocate(double anchorX, double anchorY, AnchorPoint anchor) {
* This method may be called either before or after {@link #show()} or {@link #showAndWait()}.
* If called before the stage is shown, then
*
* For each inset value:
*
* The anchor passed to {@link Stage#relocate(double, double, AnchorPoint, AnchorPolicy, Insets)} or specified
* by {@link PopupWindow#anchorLocationProperty() PopupWindow.anchorLocation} identifies the point on the
* window that should coincide with the requested screen coordinates. When the preferred anchor would place
- * the window outside the allowed screen area (as defined by the screen bounds and any configured insets),
+ * the window outside the usable screen area (as defined by the screen bounds and any configured insets),
* an {@code AnchorPolicy} can be used to select an alternative anchor before applying any final position
* adjustment.
*
@@ -53,7 +53,7 @@ public enum AnchorPolicy {
FIXED,
/**
- * If the preferred placement violates horizontal constraints, attempt a horizontally flipped anchor.
+ * If the preferred anchor violates horizontal constraints, attempt a horizontally flipped anchor.
*
* A horizontal flip mirrors the anchor across the vertical center line of the window
* (for example, {@code TOP_LEFT} becomes {@code TOP_RIGHT}).
@@ -65,7 +65,7 @@ public enum AnchorPolicy {
FLIP_HORIZONTAL,
/**
- * If the preferred placement violates vertical constraints, attempt a vertically flipped anchor.
+ * If the preferred anchor violates vertical constraints, attempt a vertically flipped anchor.
*
* A vertical flip mirrors the anchor across the horizontal center line of the window
* (for example, {@code TOP_LEFT} becomes {@code BOTTOM_LEFT}).
diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java b/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java
index cc6f735bf45..883131cfd5d 100644
--- a/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java
+++ b/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java
@@ -1227,7 +1227,7 @@ public void toBack() {
* This method may be called either before or after {@link #show()} or {@link #showAndWait()}.
* If called before the stage is shown, then
*
* The {@code anchor} identifies a point on the stage that should coincide with the requested screen
* coordinates {@code (anchorX, anchorY)}. The stage location is derived from this anchor and is then
- * optionally adjusted to keep the stage within screen edge constraints.
+ * optionally adjusted to keep the stage within the usable screen area.
*
* The {@code anchorPolicy} controls whether an alternative anchor may be used when the preferred anchor
- * would violate screen edge constraints. Depending on the policy, the preferred anchor location may be
- * mirrored to the other side of the window horizontally or vertically, or an anchor might be selected
- * automatically. If no alternative anchor yields a better placement, the specified {@code anchor} is used.
+ * would place the stage outside the usable screen area. Depending on the policy, the preferred anchor
+ * location may be mirrored to the other side of the window horizontally or vertically, or an anchor might
+ * be selected automatically. If no alternative anchor yields a better placement, the specified
+ * {@code anchor} is used.
*
* The {@code screenPadding} parameter defines per-edge constraints against the current screen bounds.
* Each inset value specifies the minimum distance to maintain between the stage edge and the corresponding
diff --git a/modules/javafx.graphics/src/test/java/test/javafx/stage/StageTest.java b/modules/javafx.graphics/src/test/java/test/javafx/stage/StageTest.java
index 65fbf53c411..7b98929ad36 100644
--- a/modules/javafx.graphics/src/test/java/test/javafx/stage/StageTest.java
+++ b/modules/javafx.graphics/src/test/java/test/javafx/stage/StageTest.java
@@ -822,7 +822,7 @@ public void relocateWhenStageDoesNotFitInConstrainedSpanUsesAnchorToChooseSide()
pulse();
assertEquals(-100, peer.x, 0.0001); // maxX = 200 - 300 = -100
- assertEquals(0, peer.y, 0.0001); // y still chooses minY because TOP_RIGHT has ayRel = 0
+ assertEquals(0, peer.y, 0.0001); // choose minY because TOP_RIGHT has y = 0
}
@Test
@@ -834,7 +834,7 @@ public void relocateUsesSecondScreenBoundsForConstraints() {
s.setWidth(400);
s.setHeight(300);
- // Point on 2nd screen, but near its bottom-right corner.
+ // Point on screen 2, but near its bottom-right corner.
double px = 1920 + 1440 - 1;
double py = 160 + 900 - 1;
@@ -842,7 +842,7 @@ public void relocateUsesSecondScreenBoundsForConstraints() {
s.show();
pulse();
- // Clamp within screen2: x <= 1920+1440-400 = 2960, y <= 160+900-300 = 760
+ // Clamp within screen 2: x <= 1920+1440-400 = 2960, y <= 160+900-300 = 760
assertEquals(2960, peer.x, 0.0001);
assertEquals(760, peer.y, 0.0001);
assertNotWithinBounds(peer, toolkit.getScreens().get(0), Insets.EMPTY);
@@ -872,13 +872,13 @@ public void relocateWithZeroSizeAndImpossibleConstraintsChoosesSideUsingAnchorPo
s.setWidth(0);
s.setHeight(0);
- // Make the constrained space impossible even for a 0-size window:
+ // Make the constrained space impossible even for a zero-size window:
// Horizontal: minX = 500, maxX = 800 - 400 - 0 = 400 => maxX < minX
// Vertical: minY = 300, maxY = 600 - 400 - 0 = 200 => maxY < minY
var constraints = new Insets(300, 400, 400, 500);
- // axRel = 0.25 => choose minX (since axRel <= 0.5)
- // ayRel = 0.75 => choose maxY (since ayRel > 0.5)
+ // x = 0.25 => choose minX (since x <= 0.5)
+ // y = 0.75 => choose maxY (since y > 0.5)
var anchor = AnchorPoint.proportional(0.25, 0.75);
s.relocate(0, 0, anchor, AnchorPolicy.FIXED, constraints);
From 8d9138b795ebbb63c55f79002ed65e00c52c8262 Mon Sep 17 00:00:00 2001
From: mstr2 <43553916+mstr2@users.noreply.github.com>
Date: Mon, 8 Dec 2025 23:33:04 +0100
Subject: [PATCH 10/12] small changes
---
...owBoundsUtil.java => WindowRelocator.java} | 43 +++---
.../java/javafx/geometry/AnchorPoint.java | 135 +++++++++---------
.../main/java/javafx/stage/AnchorPolicy.java | 34 +++--
.../main/java/javafx/stage/PopupWindow.java | 8 +-
.../src/main/java/javafx/stage/Stage.java | 15 +-
5 files changed, 117 insertions(+), 118 deletions(-)
rename modules/javafx.graphics/src/main/java/com/sun/javafx/stage/{WindowBoundsUtil.java => WindowRelocator.java} (90%)
diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/stage/WindowBoundsUtil.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/stage/WindowRelocator.java
similarity index 90%
rename from modules/javafx.graphics/src/main/java/com/sun/javafx/stage/WindowBoundsUtil.java
rename to modules/javafx.graphics/src/main/java/com/sun/javafx/stage/WindowRelocator.java
index 0937cdb57d6..7b23ad8dfdc 100644
--- a/modules/javafx.graphics/src/main/java/com/sun/javafx/stage/WindowBoundsUtil.java
+++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/stage/WindowRelocator.java
@@ -37,22 +37,24 @@
import java.util.Objects;
import java.util.function.Consumer;
-public final class WindowBoundsUtil {
+public final class WindowRelocator {
- private WindowBoundsUtil() {}
+ private WindowRelocator() {}
/**
- * Creates a relocation operation that positions a {@link Window} at the requested screen coordinates
+ * Creates a relocator that positions a {@link Window} at the requested screen coordinates
* using an {@link AnchorPoint}, {@link AnchorPolicy}, and per-edge screen constraints.
*
* Screen edge constraints are specified by {@code screenPadding}:
- * values {@code >= 0} enable a constraint for the corresponding edge (minimum distance to keep),
- * values {@code < 0} disable the constraint for that edge. Enabled constraints reduce the usable area
- * for placement by the given insets.
+ *
+ * This constant is equivalent to {@code AnchorPoint.proportional(0, 0)}.
+ */
+ public static final AnchorPoint TOP_LEFT = new AnchorPoint(0, 0, true);
+
+ /**
+ * Anchor at the top-center midpoint of the target area.
+ *
+ * This constant is equivalent to {@code AnchorPoint.proportional(0.5, 0)}.
+ */
+ public static final AnchorPoint TOP_CENTER = new AnchorPoint(0.5, 0, true);
+
+ /**
+ * Anchor at the top-right corner of the target area.
+ *
+ * This constant is equivalent to {@code AnchorPoint.proportional(1, 0)}.
+ */
+ public static final AnchorPoint TOP_RIGHT = new AnchorPoint(1, 0, true);
+
+ /**
+ * Anchor at the center-left midpoint of the target area.
+ *
+ * This constant is equivalent to {@code AnchorPoint.proportional(0, 0.5)}.
+ */
+ public static final AnchorPoint CENTER_LEFT = new AnchorPoint(0, 0.5, true);
+
+ /**
+ * Anchor at the center of the target area.
+ *
+ * This constant is equivalent to {@code AnchorPoint.proportional(0.5, 0.5)}.
+ */
+ public static final AnchorPoint CENTER = new AnchorPoint(0.5, 0.5, true);
+
+ /**
+ * Anchor at the center-right midpoint of the target area.
+ *
+ * This constant is equivalent to {@code AnchorPoint.proportional(1, 0.5)}.
+ */
+ public static final AnchorPoint CENTER_RIGHT = new AnchorPoint(1, 0.5, true);
+
+ /**
+ * Anchor at the bottom-left corner of the target area.
+ *
+ * This constant is equivalent to {@code AnchorPoint.proportional(0, 1)}.
+ */
+ public static final AnchorPoint BOTTOM_LEFT = new AnchorPoint(0, 1, true);
+
+ /**
+ * Anchor at the bottom-center midpoint of the target area.
+ *
+ * This constant is equivalent to {@code AnchorPoint.proportional(0.5, 1)}.
+ */
+ public static final AnchorPoint BOTTOM_CENTER = new AnchorPoint(0.5, 1, true);
+
+ /**
+ * Anchor at the bottom-right corner of the target area.
+ *
+ * This constant is equivalent to {@code AnchorPoint.proportional(1, 1)}.
+ */
+ public static final AnchorPoint BOTTOM_RIGHT = new AnchorPoint(1, 1, true);
+
private final double x;
private final double y;
private final boolean proportional;
@@ -69,10 +131,10 @@ public static AnchorPoint proportional(double x, double y) {
}
/**
- * Creates an absolute anchor point, expressed as pixel offsets from the top-left corner of the target area.
+ * Creates an absolute anchor point, expressed as offsets from the top-left corner of the target area.
*
- * @param x the horizontal offset in pixels from the left edge of the target area
- * @param y the vertical offset in pixels from the top edge of the target area
+ * @param x the horizontal offset from the left edge of the target area
+ * @param y the vertical offset from the top edge of the target area
* @return an absolute {@code AnchorPoint}
*/
public static AnchorPoint absolute(double x, double y) {
@@ -127,67 +189,4 @@ public int hashCode() {
public String toString() {
return "AnchorPoint [x = " + x + ", y = " + y + ", proportional = " + proportional + "]";
}
-
- /**
- * Anchor at the top-left corner of the target area.
- *
- * This constant is equivalent to {@code AnchorPoint.proportional(0, 0)}.
- */
- public static final AnchorPoint TOP_LEFT = new AnchorPoint(0, 0, true);
-
- /**
- * Anchor at the top-center midpoint of the target area.
- *
- * This constant is equivalent to {@code AnchorPoint.proportional(0.5, 0)}.
- */
- public static final AnchorPoint TOP_CENTER = new AnchorPoint(0.5, 0, true);
-
- /**
- * Anchor at the top-right corner of the target area.
- *
- * This constant is equivalent to {@code AnchorPoint.proportional(1, 0)}.
- */
- public static final AnchorPoint TOP_RIGHT = new AnchorPoint(1, 0, true);
-
- /**
- * Anchor at the center-left midpoint of the target area.
- *
- * This constant is equivalent to {@code AnchorPoint.proportional(0, 0.5)}.
- */
- public static final AnchorPoint CENTER_LEFT = new AnchorPoint(0, 0.5, true);
-
- /**
- * Anchor at the center of the target area.
- *
- * This constant is equivalent to {@code AnchorPoint.proportional(0.5, 0.5)}.
- */
- public static final AnchorPoint CENTER = new AnchorPoint(0.5, 0.5, true);
-
- /**
- * Anchor at the center-right midpoint of the target area.
- *
- * This constant is equivalent to {@code AnchorPoint.proportional(1, 0.5)}.
- */
- public static final AnchorPoint CENTER_RIGHT = new AnchorPoint(1, 0.5, true);
-
- /**
- * Anchor at the bottom-left corner of the target area.
- *
- * This constant is equivalent to {@code AnchorPoint.proportional(0, 1)}.
- */
- public static final AnchorPoint BOTTOM_LEFT = new AnchorPoint(0, 1, true);
-
- /**
- * Anchor at the bottom-center midpoint of the target area.
- *
- * This constant is equivalent to {@code AnchorPoint.proportional(0.5, 1)}.
- */
- public static final AnchorPoint BOTTOM_CENTER = new AnchorPoint(0.5, 1, true);
-
- /**
- * Anchor at the bottom-right corner of the target area.
- *
- * This constant is equivalent to {@code AnchorPoint.proportional(1, 1)}.
- */
- public static final AnchorPoint BOTTOM_RIGHT = new AnchorPoint(1, 1, true);
}
diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/AnchorPolicy.java b/modules/javafx.graphics/src/main/java/javafx/stage/AnchorPolicy.java
index dd6b3dd93cf..f80d19594a2 100644
--- a/modules/javafx.graphics/src/main/java/javafx/stage/AnchorPolicy.java
+++ b/modules/javafx.graphics/src/main/java/javafx/stage/AnchorPolicy.java
@@ -46,33 +46,31 @@ public enum AnchorPolicy {
/**
* Always use the preferred anchor and never select an alternative anchor.
*
- * If the preferred placement violates the allowed screen area, the window position is adjusted
- * for the window to fall within the allowed screen area. If this is not possible, the window is
- * biased towards the edge that is closer to the anchor.
+ * If the preferred anchor places the window outside the usable screen area, the window position is
+ * adjusted for the window to fall within the usable screen area. If this is not possible, the window
+ * is biased towards the edge that is closer to the anchor.
*/
FIXED,
/**
* If the preferred anchor violates horizontal constraints, attempt a horizontally flipped anchor.
*
- * A horizontal flip mirrors the anchor across the vertical center line of the window
- * (for example, {@code TOP_LEFT} becomes {@code TOP_RIGHT}).
- *
- * If the horizontally flipped anchor does not improve the placement, the original anchor is used
- * and the final position is adjusted for the window to fall within the allowed screen area.
- * If this is not possible, the window is biased towards the edge that is closer to the anchor.
+ * A horizontal flip mirrors the anchor across the vertical center line of the window (for example,
+ * {@code TOP_LEFT} becomes {@code TOP_RIGHT}). If the horizontally flipped anchor does not improve
+ * the placement, the original anchor is used and the final position is adjusted for the window to
+ * fall within the usable screen area. If this is not possible, the window is biased towards the
+ * edge that is closer to the anchor.
*/
FLIP_HORIZONTAL,
/**
* If the preferred anchor violates vertical constraints, attempt a vertically flipped anchor.
*
- * A vertical flip mirrors the anchor across the horizontal center line of the window
- * (for example, {@code TOP_LEFT} becomes {@code BOTTOM_LEFT}).
- *
- * If the vertically flipped anchor does not improve the placement, the original anchor is used
- * and the final position is adjusted for the window to fall within the allowed screen area.
- * If this is not possible, the window is biased towards the edge that is closer to the anchor.
+ * A vertical flip mirrors the anchor across the horizontal center line of the window (for example,
+ * {@code TOP_LEFT} becomes {@code BOTTOM_LEFT}). If the vertically flipped anchor does not improve
+ * the placement, the original anchor is used and the final position is adjusted for the window to
+ * fall within the usable screen area. If this is not possible, the window is biased towards the
+ * edge that is closer to the anchor.
*/
FLIP_VERTICAL,
@@ -83,11 +81,11 @@ public enum AnchorPolicy {
*
- * The {@code anchor} identifies a point on the stage that should coincide with the requested screen
- * coordinates {@code (anchorX, anchorY)}. The stage location is derived from this anchor and is then
- * optionally adjusted to keep the stage within the usable screen area.
+ * The {@code anchor} identifies a point in stage coordinates that should coincide with the requested
+ * screen coordinates {@code (anchorX, anchorY)}. The stage location is derived from this anchor and is
+ * then optionally adjusted to keep the stage within the usable screen area.
*
* The {@code anchorPolicy} controls whether an alternative anchor may be used when the preferred anchor
* would place the stage outside the usable screen area. Depending on the policy, the preferred anchor
@@ -1275,7 +1275,8 @@ public final void relocate(double anchorX, double anchorY, AnchorPoint anchor) {
*
* @param anchorX the requested horizontal location of the anchor point on the screen
* @param anchorY the requested vertical location of the anchor point on the screen
- * @param anchor the point on the stage that should coincide with {@code (anchorX, anchorY)} on the screen
+ * @param anchor the point in stage coordinates that should coincide with {@code (anchorX, anchorY)}
+ * in screen coordinates
* @param anchorPolicy controls alternative anchor selection if the preferred placement violates
* enabled screen constraints
* @param screenPadding per-edge minimum distance constraints relative to the screen edges;
@@ -1285,7 +1286,7 @@ public final void relocate(double anchorX, double anchorY, AnchorPoint anchor) {
*/
public final void relocate(double anchorX, double anchorY, AnchorPoint anchor,
AnchorPolicy anchorPolicy, Insets screenPadding) {
- var request = WindowBoundsUtil.newDeferredRelocation(anchorX, anchorY, anchor, anchorPolicy, screenPadding);
+ var request = WindowRelocator.newDeferredRelocator(anchorX, anchorY, anchor, anchorPolicy, screenPadding);
if (isShowing()) {
request.accept(this);
From a20128713cf0f5f620611cf9d09a6494117f0539 Mon Sep 17 00:00:00 2001
From: mstr2 <43553916+mstr2@users.noreply.github.com>
Date: Tue, 9 Dec 2025 21:32:11 +0100
Subject: [PATCH 11/12] refactor
---
.../com/sun/javafx/stage/WindowHelper.java | 7 +-
.../javafx/stage/WindowLocationAlgorithm.java | 37 +++++
.../com/sun/javafx/stage/WindowRelocator.java | 61 ++++++--
.../main/java/com/sun/javafx/util/Utils.java | 19 +++
.../main/java/javafx/stage/AnchorPolicy.java | 8 +-
.../main/java/javafx/stage/PopupWindow.java | 8 +-
.../src/main/java/javafx/stage/Stage.java | 105 +++++++------
.../src/main/java/javafx/stage/Window.java | 138 +++++++++---------
.../java/test/javafx/stage/StageTest.java | 124 +++++++++-------
9 files changed, 308 insertions(+), 199 deletions(-)
create mode 100644 modules/javafx.graphics/src/main/java/com/sun/javafx/stage/WindowLocationAlgorithm.java
diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/stage/WindowHelper.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/stage/WindowHelper.java
index a4393c7664f..a98fc99b7b6 100644
--- a/modules/javafx.graphics/src/main/java/com/sun/javafx/stage/WindowHelper.java
+++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/stage/WindowHelper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -90,6 +90,10 @@ protected void visibleChangedImpl(Window window, boolean visible) {
* Methods used by Window (base) class only
*/
+ public static Window getWindowOwner(Window window) {
+ return windowAccessor.getWindowOwner(window);
+ }
+
public static TKStage getPeer(Window window) {
return windowAccessor.getPeer(window);
}
@@ -145,6 +149,7 @@ public interface WindowAccessor {
void setHelper(Window window, WindowHelper windowHelper);
void doVisibleChanging(Window window, boolean visible);
void doVisibleChanged(Window window, boolean visible);
+ Window getWindowOwner(Window window);
TKStage getPeer(Window window);
void setPeer(Window window, TKStage peer);
WindowPeerListener getPeerListener(Window window);
diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/stage/WindowLocationAlgorithm.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/stage/WindowLocationAlgorithm.java
new file mode 100644
index 00000000000..83a09d94a64
--- /dev/null
+++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/stage/WindowLocationAlgorithm.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.javafx.stage;
+
+import javafx.stage.Screen;
+
+public interface WindowLocationAlgorithm {
+
+ record ComputedLocation(
+ double x, double y,
+ double xGravity, double yGravity) {}
+
+ ComputedLocation compute(Screen screen, double windowWidth, double windowHeight);
+}
diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/stage/WindowRelocator.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/stage/WindowRelocator.java
index 7b23ad8dfdc..ea48ec0c3fd 100644
--- a/modules/javafx.graphics/src/main/java/com/sun/javafx/stage/WindowRelocator.java
+++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/stage/WindowRelocator.java
@@ -35,15 +35,14 @@
import javafx.stage.Window;
import java.util.List;
import java.util.Objects;
-import java.util.function.Consumer;
public final class WindowRelocator {
private WindowRelocator() {}
/**
- * Creates a relocator that positions a {@link Window} at the requested screen coordinates
- * using an {@link AnchorPoint}, {@link AnchorPolicy}, and per-edge screen constraints.
+ * Creates a location algorithm that computes the position of the {@link Window} at the requested screen
+ * coordinates using an {@link AnchorPoint}, {@link AnchorPolicy}, and per-edge screen constraints.
*
* Screen edge constraints are specified by {@code screenPadding}:
*
- * The anchor passed to {@link Stage#relocate(double, double, AnchorPoint, AnchorPolicy, Insets)} or specified
- * by {@link PopupWindow#anchorLocationProperty() PopupWindow.anchorLocation} identifies the point on the
- * window that should coincide with the requested screen coordinates. When the preferred anchor would place
- * the window outside the usable screen area (as defined by the screen bounds and any configured insets),
+ * The stage anchor passed to {@link Stage#relocate(AnchorPoint, AnchorPoint, AnchorPolicy, Insets)} or
+ * specified by {@link PopupWindow#anchorLocationProperty() PopupWindow.anchorLocation} identifies the point
+ * on the window that should coincide with the requested screen coordinates. When the preferred anchor would
+ * place the window outside the usable screen area (as defined by the screen bounds and any configured insets),
* an {@code AnchorPolicy} can be used to select an alternative anchor before applying any final position
* adjustment.
*
diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/PopupWindow.java b/modules/javafx.graphics/src/main/java/javafx/stage/PopupWindow.java
index 1e2ea8bd9e7..7c97f2994d9 100644
--- a/modules/javafx.graphics/src/main/java/javafx/stage/PopupWindow.java
+++ b/modules/javafx.graphics/src/main/java/javafx/stage/PopupWindow.java
@@ -752,12 +752,12 @@ boolean isContentLocation() {
}
@Override
- void setXInternal(final double value) {
+ void setXInternal(double value, float xGravity) {
updateWindow(windowToAnchorX(value), getAnchorY());
}
@Override
- void setYInternal(final double value) {
+ void setYInternal(double value, float yGravity) {
updateWindow(getAnchorX(), windowToAnchorY(value));
}
@@ -842,11 +842,11 @@ private void updateWindow(final double newAnchorX,
// update popup position
// don't set Window.xExplicit unnecessarily
if (!Double.isNaN(windowScrMinX)) {
- super.setXInternal(windowScrMinX);
+ super.setXInternal(windowScrMinX, 0);
}
// don't set Window.yExplicit unnecessarily
if (!Double.isNaN(windowScrMinY)) {
- super.setYInternal(windowScrMinY);
+ super.setYInternal(windowScrMinY, 0);
}
// set anchor x, anchor y
diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java b/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java
index f472e9d652c..1fd906c23e1 100644
--- a/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java
+++ b/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java
@@ -27,7 +27,6 @@
import java.util.ArrayList;
import java.util.List;
-import java.util.function.Consumer;
import javafx.application.ColorScheme;
import javafx.application.Platform;
@@ -53,6 +52,7 @@
import com.sun.javafx.stage.HeaderButtonMetrics;
import com.sun.javafx.stage.StageHelper;
import com.sun.javafx.stage.StagePeerListener;
+import com.sun.javafx.stage.WindowLocationAlgorithm;
import com.sun.javafx.stage.WindowRelocator;
import com.sun.javafx.tk.TKStage;
import com.sun.javafx.tk.Toolkit;
@@ -1218,11 +1218,8 @@ public void toBack() {
}
/**
- * Moves this stage to the specified screen location using the given anchor.
- *
- * The {@code anchor} identifies a point on the stage that should coincide with the requested screen
- * coordinates {@code (anchorX, anchorY)}. The stage location is derived from this anchor and is then
- * adjusted to keep the stage within the screen bounds.
+ * Positions this stage so that a point on the stage ({@code stageAnchor}) coincides with a point on
+ * the screen ({@code screenAnchor}), and ensures that the stage is not placed off-screen.
*
* This method may be called either before or after {@link #show()} or {@link #showAndWait()}.
* If called before the stage is shown, then
@@ -1232,38 +1229,25 @@ public void toBack() {
* immediately; instead, they are updated after the stage is shown.
*
* Calling this method is equivalent to calling
- * {@code relocate(anchorX, anchorY, anchor, AnchorPolicy.FIXED, Insets.EMPTY)}.
+ * {@code relocate(screenAnchor, stageAnchor, AnchorPolicy.FIXED, Insets.EMPTY)}.
*
- * @param anchorX the requested horizontal location of the anchor point on the screen
- * @param anchorY the requested vertical location of the anchor point on the screen
- * @param anchor the point on the stage that should coincide with {@code (anchorX, anchorY)} on the screen
- * @throws NullPointerException if {@code anchor} is {@code null}
+ * @param screenAnchor An anchor point in absolute or proportional screen coordinates. If the screen anchor
+ * is {@linkplain AnchorPoint#proportional(double, double) proportional}, it is first
+ * resolved against the {@linkplain Screen#getVisualBounds() visual bounds} of the screen
+ * that currently contains this stage; if the stage does not have a location yet, the
+ * primary screen is used. If a full-screen stage is showing on the screen, the screen
+ * anchor is resolved against its complete {@linkplain Screen#getBounds() bounds}.
+ * @param stageAnchor An anchor point in absolute or proportional stage coordinates.
+ * @throws NullPointerException if any of the parameters is {@code null}
* @since 26
*/
- public final void relocate(double anchorX, double anchorY, AnchorPoint anchor) {
- relocate(anchorX, anchorY, anchor, AnchorPolicy.FIXED, Insets.EMPTY);
+ public final void relocate(AnchorPoint screenAnchor, AnchorPoint stageAnchor) {
+ relocate(screenAnchor, stageAnchor, AnchorPolicy.FIXED, Insets.EMPTY);
}
/**
- * Moves this stage to the specified screen location using the given anchor, anchor-selection policy,
- * and screen edge constraints that may restrict the usable screen area.
- *
- * The {@code anchor} identifies a point in stage coordinates that should coincide with the requested
- * screen coordinates {@code (anchorX, anchorY)}. The stage location is derived from this anchor and is
- * then optionally adjusted to keep the stage within the usable screen area.
- *
- * The {@code anchorPolicy} controls whether an alternative anchor may be used when the preferred anchor
- * would place the stage outside the usable screen area. Depending on the policy, the preferred anchor
- * location may be mirrored to the other side of the window horizontally or vertically, or an anchor might
- * be selected automatically. If no alternative anchor yields a better placement, the specified
- * {@code anchor} is used.
- *
- * The {@code screenPadding} parameter defines per-edge constraints against the current screen bounds.
- * Each inset value specifies the minimum distance to maintain between the stage edge and the corresponding
- * screen edge. A value {@code >= 0} enables the corresponding edge constraint; a negative value disables
- * the constraint for that edge. Enabled constraints effectively shrink the usable screen area by the
- * given insets. For example, a left inset of {@code 10} ensures the stage will not be placed closer than
- * 10 pixels to the left screen edge.
+ * Positions this stage so that a point on the stage ({@code stageAnchor}) coincides with a point on
+ * the screen ({@code screenAnchor}), subject to the specified anchor policy and screen padding.
*
* This method may be called either before or after {@link #show()} or {@link #showAndWait()}.
* If called before the stage is shown, then
@@ -1273,34 +1257,41 @@ public final void relocate(double anchorX, double anchorY, AnchorPoint anchor) {
* immediately; instead, they are updated after the stage is shown.
*
*
- * @param anchorX the requested horizontal location of the anchor point on the screen
- * @param anchorY the requested vertical location of the anchor point on the screen
- * @param anchor the point in stage coordinates that should coincide with {@code (anchorX, anchorY)}
- * in screen coordinates
- * @param anchorPolicy controls alternative anchor selection if the preferred placement violates
- * enabled screen constraints
- * @param screenPadding per-edge minimum distance constraints relative to the screen edges;
- * values {@code < 0} disable the constraint for the respective edge
- * @throws NullPointerException if {@code anchor}, {@code anchorPolicy}, or {@code screenPadding} is {@code null}
+ * @param screenAnchor An anchor point in absolute or proportional screen coordinates. If the screen anchor
+ * is {@linkplain AnchorPoint#proportional(double, double) proportional}, it is first
+ * resolved against the {@linkplain Screen#getVisualBounds() visual bounds} of the screen
+ * that currently contains this stage; if the stage does not have a location yet, the
+ * primary screen is used. If a full-screen stage is showing on the screen, the screen
+ * anchor is resolved against its complete {@linkplain Screen#getBounds() bounds}.
+ * @param stageAnchor An anchor point in absolute or proportional stage coordinates.
+ * @param anchorPolicy Controls whether an alternative stage anchor may be used when the preferred anchor would
+ * place the stage outside the usable screen area. Depending on the policy, the preferred
+ * anchor location may be mirrored across the vertical/horizontal center line of the stage,
+ * or an anchor might be selected automatically. If no alternative anchor yields a better
+ * placement, the specified {@code stageAnchor} is used.
+ * @param screenPadding Defines per-edge constraints against the screen bounds. Each inset value specifies the
+ * minimum distance to maintain between the stage edge and the corresponding screen edge.
+ * A value {@code >= 0} enables the corresponding edge constraint; a negative value disables
+ * the constraint for that edge. Enabled constraints effectively shrink the usable screen
+ * area by the given insets. For example, a left inset of {@code 10} ensures the stage will
+ * not be placed closer than 10 pixels to the left screen edge.
+ * @throws NullPointerException if any of the parameters is {@code null}
* @since 26
*/
- public final void relocate(double anchorX, double anchorY, AnchorPoint anchor,
+ public final void relocate(AnchorPoint screenAnchor, AnchorPoint stageAnchor,
AnchorPolicy anchorPolicy, Insets screenPadding) {
- var request = WindowRelocator.newDeferredRelocator(anchorX, anchorY, anchor, anchorPolicy, screenPadding);
+ clearLocationExplicit();
+
+ WindowLocationAlgorithm algorithm = WindowRelocator.newRelocationAlgorithm(
+ screenAnchor, stageAnchor, anchorPolicy,screenPadding);
if (isShowing()) {
- request.accept(this);
+ applyLocationAlgorithm(algorithm);
} else {
- this.relocationRequest = request;
+ this.locationAlgorithm = algorithm;
}
}
- @Override
- public void centerOnScreen() {
- relocationRequest = null; // cancel previous relocation request
- super.centerOnScreen();
- }
-
/**
* Closes this {@code Stage}.
* This call is equivalent to {@code hide()}.
@@ -1405,9 +1396,15 @@ private void setPrefHeaderButtonHeight(double height) {
}
@Override
- final Consumer
* Screen edge constraints are specified by {@code screenPadding}:
*
- * The stage anchor passed to {@link Stage#relocate(AnchorPoint, AnchorPoint, AnchorPolicy, Insets)} or
- * specified by {@link PopupWindow#anchorLocationProperty() PopupWindow.anchorLocation} identifies the point
- * on the window that should coincide with the requested screen coordinates. When the preferred anchor would
- * place the window outside the usable screen area (as defined by the screen bounds and any configured insets),
+ * The stage anchor passed to {@link Stage#relocate(AnchorPoint, AnchorPoint)} or specified by
+ * {@link PopupWindow#anchorLocationProperty() PopupWindow.anchorLocation} identifies the point on the
+ * window that should coincide with the requested screen coordinates. When the preferred anchor would place
+ * the window outside the usable screen area (as defined by the screen bounds and any configured insets),
* an {@code AnchorPolicy} can be used to select an alternative anchor before applying any final position
* adjustment.
*
diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java b/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java
index 1fd906c23e1..8aa4b05be48 100644
--- a/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java
+++ b/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java
@@ -27,6 +27,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import javafx.application.ColorScheme;
import javafx.application.Platform;
@@ -1224,25 +1225,26 @@ public void toBack() {
* This method may be called either before or after {@link #show()} or {@link #showAndWait()}.
* If called before the stage is shown, then
*
+ * This method may be called either before or after {@link #show()} or {@link #showAndWait()}.
+ * If called before the stage is shown, then
+ *
+ *
+ *
+ * @since 26
+ */
+public final class AnchorPoint {
+
+ private final double x;
+ private final double y;
+ private final boolean proportional;
+
+ private AnchorPoint(double x, double y, boolean proportional) {
+ this.x = x;
+ this.y = y;
+ this.proportional = proportional;
+ }
+
+ /**
+ * Creates a proportional anchor point, expressed as fractions of the target area's width and height.
+ *
- *
- *
- * @since 26
- */
- public static final class Anchor {
-
- private final double x;
- private final double y;
- private final boolean relative;
-
- private Anchor(double x, double y, boolean relative) {
- this.x = x;
- this.y = y;
- this.relative = relative;
- }
-
- /**
- * Creates a relative anchor expressed as a fraction of the stage size.
- * The values can be less than 0 or greater than 1; in this case the anchor is located outside the stage.
- *
+ *
+ * If clamping is performed, {@code screenPadding} specifies additional space to maintain between the
+ * stage edges and the screen edges. The padding is applied per edge (top/right/bottom/left) and
+ * effectively shrinks the usable screen area for clamping by the given insets. For example, a left
+ * padding of {@code 10} ensures that, after clamping, the stage will not be placed closer than
+ * 10 pixels to the left screen edge (and similarly for the other edges).
+ *
+ *
+ * If clamping is performed, {@code screenPadding} specifies additional space to maintain between the
+ * stage edges and the screen edges. The padding is applied per edge (top/right/bottom/left) and
+ * effectively shrinks the usable screen area for clamping by the given insets. For example, a left
+ * padding of {@code 10} ensures that, after clamping, the stage will not be placed closer than
+ * 10 pixels to the left screen edge (and similarly for the other edges).
+ *
+ *
+ * Enabled constraints shrink the usable region by the given amounts. The computed {@code maxX}
+ * and {@code maxY} incorporate the window size (i.e., they are the maximum allowed top-left
+ * coordinates that still keep the window within the constrained region).
+ */
+ private static Constraints computeConstraints(Rectangle2D screenBounds,
+ double width, double height,
+ Insets screenPadding) {
+ boolean hasMinX = screenPadding.getLeft() >= 0;
+ boolean hasMaxX = screenPadding.getRight() >= 0;
+ boolean hasMinY = screenPadding.getTop() >= 0;
+ boolean hasMaxY = screenPadding.getBottom() >= 0;
+
+ double minX = screenBounds.getMinX() + (hasMinX ? screenPadding.getLeft() : 0);
+ double maxX = screenBounds.getMaxX() - (hasMaxX ? screenPadding.getRight() : 0) - width;
+ double minY = screenBounds.getMinY() + (hasMinY ? screenPadding.getTop() : 0);
+ double maxY = screenBounds.getMaxY() - (hasMaxY ? screenPadding.getBottom() : 0) - height;
+
+ return new Constraints(hasMinX, hasMaxX, hasMinY, hasMaxY, minX, maxX, minY, maxY);
+ }
+
+ /**
+ * Computes the raw (unadjusted) top-left position for the given anchor.
+ *
+ *
+ * If no alternative anchor yields a better placement, the original anchor is used and the final
+ * position is adjusted for the window to fall within the allowed screen area.
+ * If this is not possible, the window is biased towards the edge that is closer to the anchor.
+ */
+ AUTO
+}
\ No newline at end of file
diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/ClampPolicy.java b/modules/javafx.graphics/src/main/java/javafx/stage/ClampPolicy.java
deleted file mode 100644
index 7a921642d18..00000000000
--- a/modules/javafx.graphics/src/main/java/javafx/stage/ClampPolicy.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package javafx.stage;
-
-/**
- * Specifies how a {@link Stage} should be clamped to the screen bounds.
- *
- *
- * If clamping is performed, {@code screenPadding} specifies additional space to maintain between the
- * stage edges and the screen edges. The padding is applied per edge (top/right/bottom/left) and
- * effectively shrinks the usable screen area for clamping by the given insets. For example, a left
- * padding of {@code 10} ensures that, after clamping, the stage will not be placed closer than
- * 10 pixels to the left screen edge (and similarly for the other edges).
- *
- *
- * If clamping is performed, {@code screenPadding} specifies additional space to maintain between the
- * stage edges and the screen edges. The padding is applied per edge (top/right/bottom/left) and
- * effectively shrinks the usable screen area for clamping by the given insets. For example, a left
- * padding of {@code 10} ensures that, after clamping, the stage will not be placed closer than
- * 10 pixels to the left screen edge (and similarly for the other edges).
- *
+ *
+ * Calling this method is equivalent to calling
+ * {@code relocate(anchorX, anchorY, anchor, AnchorPolicy.FIXED, Insets.EMPTY)}.
+ *
+ * @param anchorX the requested horizontal location of the anchor point on the screen
+ * @param anchorY the requested vertical location of the anchor point on the screen
+ * @param anchor the point on the stage that should coincide with {@code (anchorX, anchorY)} on the screen
+ * @throws NullPointerException if {@code anchor} is {@code null}
+ * @since 26
+ */
+ public final void relocate(double anchorX, double anchorY, AnchorPoint anchor) {
+ relocate(anchorX, anchorY, anchor, AnchorPolicy.FIXED, Insets.EMPTY);
+ }
+
+ /**
+ * Moves this stage to the specified screen location using the given anchor, anchor-selection policy,
+ * and screen edge constraints.
+ *
+ *
+ *
+ * @param anchorX the requested horizontal location of the anchor point on the screen
+ * @param anchorY the requested vertical location of the anchor point on the screen
+ * @param anchor the point on the stage that should coincide with {@code (anchorX, anchorY)} on the screen
+ * @param anchorPolicy controls alternative anchor selection if the preferred placement violates
+ * enabled screen constraints
+ * @param screenPadding per-edge minimum distance constraints relative to the screen edges;
+ * values {@code < 0} disable the constraint for the respective edge
+ * @throws NullPointerException if {@code anchor}, {@code anchorPolicy}, or {@code screenPadding} is {@code null}
+ * @since 26
+ */
+ public final void relocate(double anchorX, double anchorY, AnchorPoint anchor,
+ AnchorPolicy anchorPolicy, Insets screenPadding) {
+ var request = WindowBoundsUtil.newDeferredRelocation(anchorX, anchorY, anchor, anchorPolicy, screenPadding);
+
+ if (isShowing()) {
+ request.accept(this);
+ } else {
+ this.relocationRequest = request;
+ }
+ }
+
+ @Override
+ public void centerOnScreen() {
+ relocationRequest = null; // cancel previous relocation request
+ super.centerOnScreen();
+ }
+
/**
* Closes this {@code Stage}.
* This call is equivalent to {@code hide()}.
@@ -1475,92 +1403,9 @@ private void setPrefHeaderButtonHeight(double height) {
}
@Override
- final void fixBounds() {
- if (positionRequest != null) {
- positionRequest.apply(this);
- positionRequest = null;
- }
+ final Consumer
- *
From 007d485ed015ef29224a2e37072c862285dacfe8 Mon Sep 17 00:00:00 2001
From: mstr2 <43553916+mstr2@users.noreply.github.com>
Date: Tue, 2 Dec 2025 04:38:30 +0100
Subject: [PATCH 09/12] doc fixes
---
.../java/com/sun/javafx/stage/WindowBoundsUtil.java | 2 +-
.../src/main/java/javafx/geometry/AnchorPoint.java | 4 ++--
.../src/main/java/javafx/stage/AnchorPolicy.java | 10 +++++-----
.../src/main/java/javafx/stage/Stage.java | 11 ++++++-----
.../src/test/java/test/javafx/stage/StageTest.java | 12 ++++++------
5 files changed, 20 insertions(+), 19 deletions(-)
diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/stage/WindowBoundsUtil.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/stage/WindowBoundsUtil.java
index dbfd98dbb3b..0937cdb57d6 100644
--- a/modules/javafx.graphics/src/main/java/com/sun/javafx/stage/WindowBoundsUtil.java
+++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/stage/WindowBoundsUtil.java
@@ -124,7 +124,7 @@ public static Point2D computeAdjustedLocation(double screenX, double screenY,
*
- *
* Enabled constraints shrink the usable region by the given amounts. The computed {@code maxX}
diff --git a/modules/javafx.graphics/src/main/java/javafx/geometry/AnchorPoint.java b/modules/javafx.graphics/src/main/java/javafx/geometry/AnchorPoint.java
index 66de156b310..91418c01ef6 100644
--- a/modules/javafx.graphics/src/main/java/javafx/geometry/AnchorPoint.java
+++ b/modules/javafx.graphics/src/main/java/javafx/geometry/AnchorPoint.java
@@ -32,10 +32,10 @@
* An {@code AnchorPoint} provides a {@code (x, y)} coordinate together with a flag indicating
* how those coordinates should be interpreted:
*
- *
*
diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/AnchorPolicy.java b/modules/javafx.graphics/src/main/java/javafx/stage/AnchorPolicy.java
index a161a393307..dd6b3dd93cf 100644
--- a/modules/javafx.graphics/src/main/java/javafx/stage/AnchorPolicy.java
+++ b/modules/javafx.graphics/src/main/java/javafx/stage/AnchorPolicy.java
@@ -29,13 +29,13 @@
import javafx.geometry.Insets;
/**
- * Specifies how a repositioning operation may adjust an anchor point when the preferred placement
- * would violate the screen bounds constraints.
+ * Specifies how a window repositioning operation may adjust an anchor point when the preferred anchor
+ * would place the window outside the usable screen area.
*
- *
@@ -1250,12 +1250,13 @@ public final void relocate(double anchorX, double anchorY, AnchorPoint anchor) {
*
+ *
+ * Enabled constraints reduce the usable area for placement by the given insets.
*/
- public static Consumer
*
* If no alternative anchor yields a better placement, the original anchor is used and the final
- * position is adjusted for the window to fall within the allowed screen area.
+ * position is adjusted for the window to fall within the usable screen area.
* If this is not possible, the window is biased towards the edge that is closer to the anchor.
*/
AUTO
diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/PopupWindow.java b/modules/javafx.graphics/src/main/java/javafx/stage/PopupWindow.java
index b47978fc294..1e2ea8bd9e7 100644
--- a/modules/javafx.graphics/src/main/java/javafx/stage/PopupWindow.java
+++ b/modules/javafx.graphics/src/main/java/javafx/stage/PopupWindow.java
@@ -30,6 +30,7 @@
import com.sun.javafx.event.DirectEvent;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
@@ -62,9 +63,9 @@
import com.sun.javafx.scene.SceneHelper;
import com.sun.javafx.stage.FocusUngrabEvent;
import com.sun.javafx.stage.PopupWindowPeerListener;
-import com.sun.javafx.stage.WindowBoundsUtil;
import com.sun.javafx.stage.WindowCloseRequestHandler;
import com.sun.javafx.stage.WindowEventDispatcher;
+import com.sun.javafx.stage.WindowRelocator;
import com.sun.javafx.tk.Toolkit;
import com.sun.javafx.stage.PopupWindowHelper;
@@ -813,11 +814,12 @@ private void updateWindow(final double newAnchorX,
? currentScreen.getBounds()
: currentScreen.getVisualBounds();
- Point2D location = WindowBoundsUtil.computeAdjustedLocation(
+ Point2D location = WindowRelocator.computeAdjustedLocation(
newAnchorX, newAnchorY,
anchorBounds.getWidth(), anchorBounds.getHeight(),
AnchorPoint.proportional(anchorXCoef, anchorYCoef),
- getAnchorPolicy(), screenBounds, Insets.EMPTY);
+ Objects.requireNonNullElse(getAnchorPolicy(), AnchorPolicy.FIXED),
+ screenBounds, Insets.EMPTY);
anchorScrMinX = location.getX();
anchorScrMinY = location.getY();
diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java b/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java
index 883131cfd5d..f472e9d652c 100644
--- a/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java
+++ b/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java
@@ -53,7 +53,7 @@
import com.sun.javafx.stage.HeaderButtonMetrics;
import com.sun.javafx.stage.StageHelper;
import com.sun.javafx.stage.StagePeerListener;
-import com.sun.javafx.stage.WindowBoundsUtil;
+import com.sun.javafx.stage.WindowRelocator;
import com.sun.javafx.tk.TKStage;
import com.sun.javafx.tk.Toolkit;
import javafx.beans.NamedArg;
@@ -1246,11 +1246,11 @@ public final void relocate(double anchorX, double anchorY, AnchorPoint anchor) {
/**
* Moves this stage to the specified screen location using the given anchor, anchor-selection policy,
- * and screen edge constraints.
+ * and screen edge constraints that may restrict the usable screen area.
*
@@ -52,27 +51,57 @@ private WindowRelocator() {}
*
* Enabled constraints reduce the usable area for placement by the given insets.
*/
- public static Consumer
@@ -51,32 +54,33 @@ private WindowRelocator() {}
*
* Enabled constraints reduce the usable area for placement by the given insets.
*/
- public static WindowLocationAlgorithm newRelocationAlgorithm(AnchorPoint screenAnchor,
+ public static WindowLocationAlgorithm newRelocationAlgorithm(Screen userScreen,
+ AnchorPoint screenAnchor,
+ Insets screenPadding,
AnchorPoint stageAnchor,
- AnchorPolicy anchorPolicy,
- Insets screenPadding) {
+ AnchorPolicy anchorPolicy) {
Objects.requireNonNull(screenAnchor, "screenAnchor cannot be null");
+ Objects.requireNonNull(screenPadding, "screenPadding cannot be null");
Objects.requireNonNull(stageAnchor, "stageAnchor cannot be null");
Objects.requireNonNull(anchorPolicy, "anchorPolicy cannot be null");
- Objects.requireNonNull(screenPadding, "screenPadding cannot be null");
return (windowScreen, windowWidth, windowHeight) -> {
double screenX, screenY;
double gravityX, gravityY;
+ Screen currentScreen = Objects.requireNonNullElse(userScreen, windowScreen);
+ Rectangle2D currentBounds = Utils.hasFullScreenStage(currentScreen)
+ ? currentScreen.getBounds()
+ : currentScreen.getVisualBounds();
// Compute the absolute coordinates of the screen anchor.
- // If the screen anchor is specified in proportional coordinates, it refers to the complete
+ // If the screen anchor is specified in proportional coordinates, it is proportional to the complete
// screen bounds when a full-screen stage is showing on the screen, and the visual bounds otherwise.
if (screenAnchor.isProportional()) {
- Rectangle2D bounds = Utils.hasFullScreenStage(windowScreen)
- ? windowScreen.getBounds()
- : windowScreen.getVisualBounds();
-
- screenX = bounds.getMinX() + screenAnchor.getX() * bounds.getWidth();
- screenY = bounds.getMinY() + screenAnchor.getY() * bounds.getHeight();
+ screenX = currentBounds.getMinX() + screenAnchor.getX() * currentBounds.getWidth();
+ screenY = currentBounds.getMinY() + screenAnchor.getY() * currentBounds.getHeight();
} else {
- screenX = screenAnchor.getX();
- screenY = screenAnchor.getY();
+ screenX = currentBounds.getMinX() + screenAnchor.getX();
+ screenY = currentBounds.getMinY() + screenAnchor.getY();
}
// The absolute screen anchor might be on a different screen than the current window, so we
diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/AnchorPolicy.java b/modules/javafx.graphics/src/main/java/javafx/stage/AnchorPolicy.java
index 3e1b4638311..f4a96d0bb1c 100644
--- a/modules/javafx.graphics/src/main/java/javafx/stage/AnchorPolicy.java
+++ b/modules/javafx.graphics/src/main/java/javafx/stage/AnchorPolicy.java
@@ -26,16 +26,15 @@
package javafx.stage;
import javafx.geometry.AnchorPoint;
-import javafx.geometry.Insets;
/**
* Specifies how a window repositioning operation may adjust an anchor point when the preferred anchor
* would place the window outside the usable screen area.
*
- *
- * Calling this method is equivalent to calling
- * {@code relocate(screenAnchor, stageAnchor, AnchorPolicy.FIXED, Insets.EMPTY)}.
+ * Calling this method is equivalent to calling {@link #relocate(AnchorPoint, Insets, AnchorPoint, AnchorPolicy)
+ * relocate(screenAnchor, Insets.EMPTY, stageAnchor, AnchorPolicy.FIXED)}.
*
- * @param screenAnchor An anchor point in absolute or proportional screen coordinates. If the screen anchor
- * is {@linkplain AnchorPoint#proportional(double, double) proportional}, it is first
- * resolved against the {@linkplain Screen#getVisualBounds() visual bounds} of the screen
- * that currently contains this stage; if the stage does not have a location yet, the
- * primary screen is used. If a full-screen stage is showing on the screen, the screen
- * anchor is resolved against its complete {@linkplain Screen#getBounds() bounds}.
+ * @param screenAnchor An anchor point in absolute or proportional coordinates relative to the screen that
+ * currently contains this stage (current screen); if the stage does not have a location
+ * yet, the primary screen is implied. If the screen anchor is
+ * {@linkplain AnchorPoint#proportional(double, double) proportional}, it is first resolved
+ * against the {@linkplain Screen#getVisualBounds() visual bounds} of the current screen;
+ * if a full-screen stage is showing on the current screen, the screen anchor is resolved
+ * against its complete {@linkplain Screen#getBounds() bounds}.
* @param stageAnchor An anchor point in absolute or proportional stage coordinates.
* @throws NullPointerException if any of the parameters is {@code null}
* @since 26
*/
public final void relocate(AnchorPoint screenAnchor, AnchorPoint stageAnchor) {
- relocate(screenAnchor, stageAnchor, AnchorPolicy.FIXED, Insets.EMPTY);
+ relocateImpl(null, screenAnchor, Insets.EMPTY, stageAnchor, AnchorPolicy.FIXED);
}
/**
@@ -1252,38 +1254,83 @@ public final void relocate(AnchorPoint screenAnchor, AnchorPoint stageAnchor) {
* This method may be called either before or after {@link #show()} or {@link #showAndWait()}.
* If called before the stage is shown, then
*
- *
*
- * @param screenAnchor An anchor point in absolute or proportional screen coordinates. If the screen anchor
- * is {@linkplain AnchorPoint#proportional(double, double) proportional}, it is first
- * resolved against the {@linkplain Screen#getVisualBounds() visual bounds} of the screen
- * that currently contains this stage; if the stage does not have a location yet, the
- * primary screen is used. If a full-screen stage is showing on the screen, the screen
- * anchor is resolved against its complete {@linkplain Screen#getBounds() bounds}.
+ * @param screenAnchor An anchor point in absolute or proportional coordinates relative to the screen that
+ * currently contains this stage (current screen); if the stage does not have a location
+ * yet, the primary screen is implied. If the screen anchor is
+ * {@linkplain AnchorPoint#proportional(double, double) proportional}, it is first resolved
+ * against the {@linkplain Screen#getVisualBounds() visual bounds} of the current screen;
+ * if a full-screen stage is showing on the current screen, the screen anchor is resolved
+ * against its complete {@linkplain Screen#getBounds() bounds}.
+ * @param screenPadding Defines per-edge constraints against the screen bounds. Each inset value specifies the
+ * minimum distance to maintain between the stage edge and the corresponding screen edge.
+ * A value {@code >= 0} enables the corresponding edge constraint; a negative value disables
+ * the constraint for that edge. Enabled constraints effectively shrink the usable screen
+ * area by the given insets. For example, a left inset of {@code 10} ensures the stage will
+ * not be placed closer than 10 pixels to the left screen edge.
* @param stageAnchor An anchor point in absolute or proportional stage coordinates.
* @param anchorPolicy Controls whether an alternative stage anchor may be used when the preferred anchor would
* place the stage outside the usable screen area. Depending on the policy, the preferred
* anchor location may be mirrored across the vertical/horizontal center line of the stage,
* or an anchor might be selected automatically. If no alternative anchor yields a better
* placement, the specified {@code stageAnchor} is used.
+ * @throws NullPointerException if any of the parameters is {@code null}
+ * @since 26
+ */
+ public final void relocate(AnchorPoint screenAnchor, Insets screenPadding,
+ AnchorPoint stageAnchor, AnchorPolicy anchorPolicy) {
+ relocateImpl(null, screenAnchor, screenPadding, stageAnchor, anchorPolicy);
+ }
+
+ /**
+ * Positions this stage so that a point on the stage ({@code stageAnchor}) coincides with a point on
+ * the screen ({@code screenAnchor}), subject to the specified anchor policy and screen padding.
+ *
+ *
+ *
+ * @param screen The reference screen that defines the coordinate space for {@code screenAnchor}.
+ * @param screenAnchor An anchor point in absolute or proportional coordinates relative to {@code screen}.
+ * If the screen anchor is {@linkplain AnchorPoint#proportional(double, double) proportional},
+ * it is first resolved against the {@linkplain Screen#getVisualBounds() visual bounds} of
+ * the screen; if a full-screen stage is showing on the screen, the screen anchor is resolved
+ * against its complete {@linkplain Screen#getBounds() bounds}.
* @param screenPadding Defines per-edge constraints against the screen bounds. Each inset value specifies the
* minimum distance to maintain between the stage edge and the corresponding screen edge.
* A value {@code >= 0} enables the corresponding edge constraint; a negative value disables
* the constraint for that edge. Enabled constraints effectively shrink the usable screen
* area by the given insets. For example, a left inset of {@code 10} ensures the stage will
* not be placed closer than 10 pixels to the left screen edge.
+ * @param stageAnchor An anchor point in absolute or proportional stage coordinates.
+ * @param anchorPolicy Controls whether an alternative stage anchor may be used when the preferred anchor would
+ * place the stage outside the usable screen area. Depending on the policy, the preferred
+ * anchor location may be mirrored across the vertical/horizontal center line of the stage,
+ * or an anchor might be selected automatically. If no alternative anchor yields a better
+ * placement, the specified {@code stageAnchor} is used.
* @throws NullPointerException if any of the parameters is {@code null}
* @since 26
*/
- public final void relocate(AnchorPoint screenAnchor, AnchorPoint stageAnchor,
- AnchorPolicy anchorPolicy, Insets screenPadding) {
+ public final void relocate(Screen screen, AnchorPoint screenAnchor, Insets screenPadding,
+ AnchorPoint stageAnchor, AnchorPolicy anchorPolicy) {
+ Objects.requireNonNull(screen, "screen cannot be null");
+ relocateImpl(screen, screenAnchor, screenPadding, stageAnchor, anchorPolicy);
+ }
+
+ private void relocateImpl(Screen screen, AnchorPoint screenAnchor, Insets screenPadding,
+ AnchorPoint stageAnchor, AnchorPolicy anchorPolicy) {
clearLocationExplicit();
WindowLocationAlgorithm algorithm = WindowRelocator.newRelocationAlgorithm(
- screenAnchor, stageAnchor, anchorPolicy,screenPadding);
+ screen, screenAnchor, screenPadding, stageAnchor, anchorPolicy);
if (isShowing()) {
applyLocationAlgorithm(algorithm);
diff --git a/modules/javafx.graphics/src/test/java/test/javafx/stage/StageTest.java b/modules/javafx.graphics/src/test/java/test/javafx/stage/StageTest.java
index 91f2e481ddf..ba4d35da1eb 100644
--- a/modules/javafx.graphics/src/test/java/test/javafx/stage/StageTest.java
+++ b/modules/javafx.graphics/src/test/java/test/javafx/stage/StageTest.java
@@ -33,6 +33,7 @@
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.AnchorPolicy;
+import javafx.stage.Screen;
import javafx.stage.Stage;
import test.com.sun.javafx.pgstub.StubStage;
import test.com.sun.javafx.pgstub.StubToolkit;
@@ -556,17 +557,28 @@ public void testAddAndSetNullIcon() {
public void relocateNullArgumentsThrowNPE() {
s.show();
assertNotNull(peer);
- assertThrows(NullPointerException.class, () -> s.relocate(null, AnchorPoint.TOP_LEFT, AnchorPolicy.FIXED, Insets.EMPTY));
- assertThrows(NullPointerException.class, () -> s.relocate(AnchorPoint.TOP_LEFT, null, AnchorPolicy.FIXED, Insets.EMPTY));
- assertThrows(NullPointerException.class, () -> s.relocate(AnchorPoint.TOP_LEFT, AnchorPoint.TOP_LEFT, null, Insets.EMPTY));
- assertThrows(NullPointerException.class, () -> s.relocate(AnchorPoint.TOP_LEFT, AnchorPoint.TOP_LEFT, AnchorPolicy.FIXED, null));
+ assertThrows(NullPointerException.class, () -> s.relocate(null, AnchorPoint.TOP_LEFT));
+ assertThrows(NullPointerException.class, () -> s.relocate(AnchorPoint.TOP_LEFT, null));
+
+ assertThrows(NullPointerException.class, () -> s.relocate(null, Insets.EMPTY, AnchorPoint.TOP_LEFT, AnchorPolicy.FIXED));
+ assertThrows(NullPointerException.class, () -> s.relocate(AnchorPoint.TOP_LEFT, Insets.EMPTY, null, AnchorPolicy.FIXED));
+ assertThrows(NullPointerException.class, () -> s.relocate(AnchorPoint.TOP_LEFT, Insets.EMPTY, AnchorPoint.TOP_LEFT, null));
+ assertThrows(NullPointerException.class, () -> s.relocate(AnchorPoint.TOP_LEFT, null, AnchorPoint.TOP_LEFT, AnchorPolicy.FIXED));
+
+ assertThrows(NullPointerException.class, () -> s.relocate(null, null, Insets.EMPTY, AnchorPoint.TOP_LEFT, AnchorPolicy.FIXED));
+ assertThrows(NullPointerException.class, () -> s.relocate(null, AnchorPoint.TOP_LEFT, Insets.EMPTY, null, AnchorPolicy.FIXED));
+ assertThrows(NullPointerException.class, () -> s.relocate(null, AnchorPoint.TOP_LEFT, Insets.EMPTY, AnchorPoint.TOP_LEFT, null));
+ assertThrows(NullPointerException.class, () -> s.relocate(null, AnchorPoint.TOP_LEFT, null, AnchorPoint.TOP_LEFT, AnchorPolicy.FIXED));
}
+ /**
+ * Tests that {@code relocate()} called before {@code show()} is applied when the stage is shown.
+ */
@Test
public void relocateBeforeShowPositionsStageOnShow() {
s.setWidth(300);
s.setHeight(200);
- s.relocate(AnchorPoint.absolute(100, 120), AnchorPoint.TOP_LEFT, AnchorPolicy.FIXED, Insets.EMPTY);
+ s.relocate(AnchorPoint.absolute(100, 120), Insets.EMPTY, AnchorPoint.TOP_LEFT, AnchorPolicy.FIXED);
s.show();
assertEquals(100, peer.x, 0.0001);
@@ -574,12 +586,15 @@ public void relocateBeforeShowPositionsStageOnShow() {
assertWithinBounds(peer, toolkit.getScreens().getFirst(), Insets.EMPTY);
}
+ /**
+ * Tests that {@code relocate()} called after {@code show()} updates the stage position immediately.
+ */
@Test
public void relocateAfterShowMovesStageImmediately() {
s.setWidth(300);
s.setHeight(200);
s.show();
- s.relocate(AnchorPoint.absolute(200, 220), AnchorPoint.TOP_LEFT, AnchorPolicy.FIXED, Insets.EMPTY);
+ s.relocate(AnchorPoint.absolute(200, 220), Insets.EMPTY, AnchorPoint.TOP_LEFT, AnchorPolicy.FIXED);
pulse();
assertEquals(200, peer.x, 0.0001);
@@ -587,6 +602,9 @@ public void relocateAfterShowMovesStageImmediately() {
assertWithinBounds(peer, toolkit.getScreens().getFirst(), Insets.EMPTY);
}
+ /**
+ * Tests that proportional screen anchors resolve against visual bounds.
+ */
@Test
public void relocateWithProportionalScreenAnchorResolvesAgainstVisualBounds() {
// Visual bounds differ from full bounds (e.g., task bar / menu bar reserved area).
@@ -596,7 +614,7 @@ public void relocateWithProportionalScreenAnchorResolvesAgainstVisualBounds() {
s.setHeight(100);
// Proportional screen anchors are resolved against visual bounds when no fullscreen stage is present.
- s.relocate(AnchorPoint.proportional(0, 0), AnchorPoint.TOP_LEFT, AnchorPolicy.FIXED, Insets.EMPTY);
+ s.relocate(AnchorPoint.proportional(0, 0), Insets.EMPTY, AnchorPoint.TOP_LEFT, AnchorPolicy.FIXED);
s.show();
assertEquals(0, peer.x, 0.0001);
@@ -604,6 +622,9 @@ public void relocateWithProportionalScreenAnchorResolvesAgainstVisualBounds() {
assertWithinBounds(peer, toolkit.getScreens().getFirst(), Insets.EMPTY);
}
+ /**
+ * Tests that proportional screen anchors resolve against the stage's current screen.
+ */
@Test
public void relocateWithProportionalScreenAnchorUsesCurrentScreen() {
toolkit.setScreens(
@@ -619,15 +640,122 @@ public void relocateWithProportionalScreenAnchorUsesCurrentScreen() {
// Center stage on screen 2's visual bounds:
// screen center = (800 + 0.5*800, 40 + 0.5*560) = (1200, 320)
// stage top-left = center - (100, 100) = (1100, 220)
- s.relocate(AnchorPoint.proportional(0.5, 0.5), AnchorPoint.CENTER, AnchorPolicy.FIXED, Insets.EMPTY);
+ s.relocate(AnchorPoint.proportional(0.5, 0.5), Insets.EMPTY, AnchorPoint.CENTER, AnchorPolicy.FIXED);
+ s.show();
+
+ assertEquals(1100, peer.x, 0.0001);
+ assertEquals(220, peer.y, 0.0001);
+ assertNotWithinBounds(peer, toolkit.getScreens().get(0), Insets.EMPTY);
+ assertWithinBounds(peer, toolkit.getScreens().get(1), Insets.EMPTY);
+ }
+
+ /**
+ * Absolute screenAnchor is relative to the specified screen's reference rectangle.
+ */
+ @Test
+ public void relocateWithScreenParameterAbsoluteAnchorIsRelativeToScreenVisualBounds() {
+ toolkit.setScreens(
+ new ScreenConfiguration(0, 0, 800, 600, 0, 0, 800, 600, 96),
+ new ScreenConfiguration(800, 0, 800, 600, 800, 40, 800, 560, 96));
+
+ s.setWidth(100);
+ s.setHeight(100);
+
+ Screen screen2 = Screen.getScreens().get(1);
+
+ // Absolute coordinates are relative to the reference rectangle of screen2.
+ // Here: visual min = (800, 40), so (10, 20) => (810, 60)
+ s.relocate(screen2, AnchorPoint.absolute(10, 20), Insets.EMPTY, AnchorPoint.TOP_LEFT, AnchorPolicy.FIXED);
+ s.show();
+
+ assertEquals(810, peer.x, 0.0001);
+ assertEquals(60, peer.y, 0.0001);
+ assertNotWithinBounds(peer, toolkit.getScreens().get(0), Insets.EMPTY);
+ assertWithinBounds(peer, toolkit.getScreens().get(1), Insets.EMPTY);
+ }
+
+ /**
+ * Proportional screenAnchor uses the specified screen even if the stage is currently on another screen.
+ */
+ @Test
+ public void relocateWithScreenParameterProportionalAnchorUsesSpecifiedScreen() {
+ toolkit.setScreens(
+ new ScreenConfiguration(0, 0, 800, 600, 0, 0, 800, 600, 96),
+ new ScreenConfiguration(800, 0, 800, 600, 800, 40, 800, 560, 96));
+
+ s.setX(10);
+ s.setY(10);
+ s.setWidth(200);
+ s.setHeight(200);
s.show();
+ Screen screen2 = Screen.getScreens().get(1);
+
+ // Center of screen2 visual bounds:
+ // (800 + 0.5*800, 40 + 0.5*560) = (1200, 320)
+ // Stage anchor CENTER => top-left = (1200-100, 320-100) = (1100, 220)
+ s.relocate(screen2, AnchorPoint.proportional(0.5, 0.5), Insets.EMPTY, AnchorPoint.CENTER, AnchorPolicy.FIXED);
+ pulse();
+
assertEquals(1100, peer.x, 0.0001);
assertEquals(220, peer.y, 0.0001);
assertNotWithinBounds(peer, toolkit.getScreens().get(0), Insets.EMPTY);
assertWithinBounds(peer, toolkit.getScreens().get(1), Insets.EMPTY);
}
+ /**
+ * Constraints (screenPadding) are applied against the specified screen's usable bounds.
+ */
+ @Test
+ public void relocateWithScreenParameterHonorsPaddingOnSpecifiedScreen() {
+ toolkit.setScreens(
+ new ScreenConfiguration(0, 0, 800, 600, 0, 0, 800, 600, 96),
+ new ScreenConfiguration(800, 0, 800, 600, 800, 40, 800, 560, 96));
+
+ s.setWidth(300);
+ s.setHeight(200);
+
+ Screen screen2 = Screen.getScreens().get(1);
+ Insets padding = new Insets(10, 20, 30, 40); // top, right, bottom, left
+
+ // Request a TOP_LEFT placement beyond bottom-right; should clamp within padded usable area.
+ // Screen2 visual: min=(800,40), size=(800,560)
+ // Padded usable maxX = 800+800-20 = 1580; maxY = 40+560-30 = 570
+ // Stage max top-left: x = 1580-300 = 1280; y = 570-200 = 370
+ s.relocate(screen2, AnchorPoint.absolute(800, 560), padding, AnchorPoint.TOP_LEFT, AnchorPolicy.FIXED);
+ s.show();
+
+ assertEquals(1280, peer.x, 0.0001);
+ assertEquals(370, peer.y, 0.0001);
+ assertNotWithinBounds(peer, toolkit.getScreens().get(0), padding);
+ assertWithinBounds(peer, toolkit.getScreens().get(1), padding);
+ }
+
+ /**
+ * "Before show" path works with the screen overload too (deferred application).
+ */
+ @Test
+ public void relocateWithScreenParameterBeforeShowPositionsStageOnShow() {
+ toolkit.setScreens(
+ new ScreenConfiguration(0, 0, 800, 600, 0, 0, 800, 600, 96),
+ new ScreenConfiguration(800, 0, 800, 600, 800, 40, 800, 560, 96));
+
+ Screen screen2 = Screen.getScreens().get(1);
+
+ s.setWidth(120);
+ s.setHeight(80);
+ s.relocate(screen2, AnchorPoint.TOP_LEFT, Insets.EMPTY, AnchorPoint.TOP_LEFT, AnchorPolicy.FIXED);
+ s.show();
+
+ assertEquals(800, peer.x, 0.0001);
+ assertEquals(40, peer.y, 0.0001);
+ assertNotWithinBounds(peer, toolkit.getScreens().get(0), Insets.EMPTY);
+ assertWithinBounds(peer, toolkit.getScreens().get(1), Insets.EMPTY);
+ }
+
+ /**
+ * Tests that {@code relocate()} called before show overrides any prior {@code centerOnScreen()} request.
+ */
@Test
public void relocateCancelsCenterOnScreenWhenCalledBeforeShow() {
s.setWidth(200);
@@ -636,7 +764,7 @@ public void relocateCancelsCenterOnScreenWhenCalledBeforeShow() {
// If centerOnScreen were honored, we'd expect (300, 200) on 800x600.
// relocate should override/cancel it.
- s.relocate(AnchorPoint.absolute(0, 0), AnchorPoint.TOP_LEFT, AnchorPolicy.FIXED, Insets.EMPTY);
+ s.relocate(AnchorPoint.absolute(0, 0), Insets.EMPTY, AnchorPoint.TOP_LEFT, AnchorPolicy.FIXED);
s.show();
assertEquals(0, peer.x, 0.0001);
@@ -644,6 +772,9 @@ public void relocateCancelsCenterOnScreenWhenCalledBeforeShow() {
assertWithinBounds(peer, toolkit.getScreens().getFirst(), Insets.EMPTY);
}
+ /**
+ * Tests that enabled padding insets constrain the resulting stage position.
+ */
@Test
public void relocateHonorsPaddingForEnabledEdges() {
s.setWidth(200);
@@ -652,7 +783,7 @@ public void relocateHonorsPaddingForEnabledEdges() {
var padding = new Insets(10, 20, 30, 40); // top, right, bottom, left
// Ask to place the TOP_LEFT anchor beyond the bottom-right safe area to force adjustment
- s.relocate(AnchorPoint.absolute(800, 600), AnchorPoint.TOP_LEFT, AnchorPolicy.FIXED, padding);
+ s.relocate(AnchorPoint.absolute(800, 600), padding, AnchorPoint.TOP_LEFT, AnchorPolicy.FIXED);
s.show();
// Allowed top-left: x <= 800 - 20 - 200 = 580, y <= 600 - 30 - 200 = 370
@@ -661,6 +792,9 @@ public void relocateHonorsPaddingForEnabledEdges() {
assertWithinBounds(peer, toolkit.getScreens().getFirst(), padding);
}
+ /**
+ * Tests that negative insets disable constraints for the corresponding edges.
+ */
@Test
public void relocateNegativeInsetsDisableConstraintsPerEdge() {
s.setWidth(300);
@@ -668,7 +802,7 @@ public void relocateNegativeInsetsDisableConstraintsPerEdge() {
// Disable right and bottom constraints (negative), keep left/top enabled at 0.
var padding = new Insets(0, -1, -1, 0);
- s.relocate(AnchorPoint.absolute(790, 590), AnchorPoint.TOP_LEFT, AnchorPolicy.FIXED, padding);
+ s.relocate(AnchorPoint.absolute(790, 590), padding, AnchorPoint.TOP_LEFT, AnchorPolicy.FIXED);
s.show();
assertEquals(790, peer.x, 0.0001);
@@ -676,6 +810,9 @@ public void relocateNegativeInsetsDisableConstraintsPerEdge() {
assertNotWithinBounds(peer, toolkit.getScreens().getFirst(), padding);
}
+ /**
+ * Tests that a single enabled left-edge constraint is honored.
+ */
@Test
public void relocateOneSidedLeftConstraintOnly() {
s.setWidth(300);
@@ -683,7 +820,7 @@ public void relocateOneSidedLeftConstraintOnly() {
// Enable left constraint (10), disable others
var padding = new Insets(-1, -1, -1, 10);
- s.relocate(AnchorPoint.absolute(0, 100), AnchorPoint.TOP_LEFT, AnchorPolicy.FIXED, padding);
+ s.relocate(AnchorPoint.absolute(0, 100), padding, AnchorPoint.TOP_LEFT, AnchorPolicy.FIXED);
s.show();
assertEquals(10, peer.x, 0.0001);
@@ -691,6 +828,9 @@ public void relocateOneSidedLeftConstraintOnly() {
assertWithinBounds(peer, toolkit.getScreens().getFirst(), padding);
}
+ /**
+ * Tests that {@link AnchorPolicy#FLIP_HORIZONTAL} selects a horizontally flipped anchor to avoid right overflow.
+ */
@Test
public void relocateFlipHorizontalFitsWithoutAdjustment() {
s.setWidth(300);
@@ -698,7 +838,7 @@ public void relocateFlipHorizontalFitsWithoutAdjustment() {
// TOP_LEFT at (790,10) overflows to the right.
// TOP_RIGHT at (790,10) => rawX=790-300=490 fits.
- s.relocate(AnchorPoint.absolute(790, 10), AnchorPoint.TOP_LEFT, AnchorPolicy.FLIP_HORIZONTAL, Insets.EMPTY);
+ s.relocate(AnchorPoint.absolute(790, 10), Insets.EMPTY, AnchorPoint.TOP_LEFT, AnchorPolicy.FLIP_HORIZONTAL);
s.show();
assertEquals(490, peer.x, 0.0001);
@@ -706,6 +846,10 @@ public void relocateFlipHorizontalFitsWithoutAdjustment() {
assertWithinBounds(peer, toolkit.getScreens().getFirst(), Insets.EMPTY);
}
+ /**
+ * Tests that {@link AnchorPolicy#AUTO} prefers a diagonal flip when both horizontal and vertical
+ * constraints are violated.
+ */
@Test
public void relocateAutoDiagonalBeatsAdjustOnly() {
s.setWidth(300);
@@ -713,7 +857,7 @@ public void relocateAutoDiagonalBeatsAdjustOnly() {
// TOP_LEFT at (790,590) overflows right and bottom.
// AUTO should choose BOTTOM_RIGHT (diagonal flip) => raw=(490,390) fits with no adjustment.
- s.relocate(AnchorPoint.absolute(790, 590), AnchorPoint.TOP_LEFT, AnchorPolicy.AUTO, Insets.EMPTY);
+ s.relocate(AnchorPoint.absolute(790, 590), Insets.EMPTY, AnchorPoint.TOP_LEFT, AnchorPolicy.AUTO);
s.show();
assertEquals(490, peer.x, 0.0001);
@@ -721,6 +865,9 @@ public void relocateAutoDiagonalBeatsAdjustOnly() {
assertWithinBounds(peer, toolkit.getScreens().getFirst(), Insets.EMPTY);
}
+ /**
+ * Tests that {@link AnchorPolicy#FLIP_HORIZONTAL} may still require vertical clamping after flipping.
+ */
@Test
public void relocateFlipHorizontalStillRequiresVerticalAdjustment() {
s.setWidth(300);
@@ -728,7 +875,7 @@ public void relocateFlipHorizontalStillRequiresVerticalAdjustment() {
// Flip horizontally resolves X, but Y still needs adjustment.
// TOP_RIGHT raw = (490,590) => y clamps to 400.
- s.relocate(AnchorPoint.absolute(790, 590), AnchorPoint.TOP_LEFT, AnchorPolicy.FLIP_HORIZONTAL, Insets.EMPTY);
+ s.relocate(AnchorPoint.absolute(790, 590), Insets.EMPTY, AnchorPoint.TOP_LEFT, AnchorPolicy.FLIP_HORIZONTAL);
s.show();
assertEquals(490, peer.x, 0.0001);
@@ -736,6 +883,9 @@ public void relocateFlipHorizontalStillRequiresVerticalAdjustment() {
assertWithinBounds(peer, toolkit.getScreens().getFirst(), Insets.EMPTY);
}
+ /**
+ * Tests that {@link AnchorPolicy#FLIP_VERTICAL} may still require horizontal clamping after flipping.
+ */
@Test
public void relocateFlipVerticalStillRequiresHorizontalAdjustment() {
s.setWidth(300);
@@ -743,7 +893,7 @@ public void relocateFlipVerticalStillRequiresHorizontalAdjustment() {
// Flip vertically resolves Y, but X still needs adjustment.
// BOTTOM_LEFT raw = (790,390) => x clamps to 500.
- s.relocate(AnchorPoint.absolute(790, 590), AnchorPoint.TOP_LEFT, AnchorPolicy.FLIP_VERTICAL, Insets.EMPTY);
+ s.relocate(AnchorPoint.absolute(790, 590), Insets.EMPTY, AnchorPoint.TOP_LEFT, AnchorPolicy.FLIP_VERTICAL);
s.show();
assertEquals(500, peer.x, 0.0001);
@@ -751,7 +901,10 @@ public void relocateFlipVerticalStillRequiresHorizontalAdjustment() {
assertWithinBounds(peer, toolkit.getScreens().getFirst(), Insets.EMPTY);
}
- @Test
+ /**
+ * Tests that {@link AnchorPolicy#AUTO} flips horizontally when only the right constraint is enabled and violated.
+ */
+ @Test
public void relocateAutoWithRightOnlyConstraintFlipsHorizontally() {
s.setWidth(300);
s.setHeight(200);
@@ -761,7 +914,7 @@ public void relocateAutoWithRightOnlyConstraintFlipsHorizontally() {
// Preferred TOP_LEFT: rawX=790 => violates right constraint (maxX=500)
// AUTO should choose TOP_RIGHT: rawX = 790-300 = 490 (fits without adjustment)
- s.relocate(AnchorPoint.absolute(790, 10), AnchorPoint.TOP_LEFT, AnchorPolicy.AUTO, constraints);
+ s.relocate(AnchorPoint.absolute(790, 10), constraints, AnchorPoint.TOP_LEFT, AnchorPolicy.AUTO);
s.show();
assertEquals(490, peer.x, 0.0001);
@@ -769,6 +922,9 @@ public void relocateAutoWithRightOnlyConstraintFlipsHorizontally() {
assertWithinBounds(peer, toolkit.getScreens().getFirst(), Insets.EMPTY);
}
+ /**
+ * Tests that {@link AnchorPolicy#AUTO} keeps the preferred anchor when flipping would worsen the adjustment.
+ */
@Test
public void relocateAutoWithLeftOnlyConstraintDoesNotFlipWhenFlipWouldBeWorse() {
s.setWidth(300);
@@ -780,7 +936,7 @@ public void relocateAutoWithLeftOnlyConstraintDoesNotFlipWhenFlipWouldBeWorse()
// Preferred TOP_LEFT: rawX = 0 -> adjusted to 10 (cost 10)
// Flipped TOP_RIGHT: rawX = 0-300 = -300 -> adjusted to 10 (cost 310)
// AUTO may consider the flip, but should keep the original anchor as "better".
- s.relocate(AnchorPoint.absolute(0, 10), AnchorPoint.TOP_LEFT, AnchorPolicy.AUTO, constraints);
+ s.relocate(AnchorPoint.absolute(0, 10), constraints, AnchorPoint.TOP_LEFT, AnchorPolicy.AUTO);
s.show();
assertEquals(10, peer.x, 0.0001);
@@ -788,6 +944,9 @@ public void relocateAutoWithLeftOnlyConstraintDoesNotFlipWhenFlipWouldBeWorse()
assertWithinBounds(peer, toolkit.getScreens().getFirst(), Insets.EMPTY);
}
+ /**
+ * Tests that {@link AnchorPolicy#AUTO} flips vertically when only the bottom constraint is enabled and violated.
+ */
@Test
public void relocateAutoWithBottomOnlyConstraintFlipsVertically() {
s.setWidth(300);
@@ -798,7 +957,7 @@ public void relocateAutoWithBottomOnlyConstraintFlipsVertically() {
// Preferred TOP_LEFT at y=590 => rawY=590 violates bottom maxY=400
// Vertical flip to BOTTOM_LEFT yields rawY=590-200=390 (fits)
- s.relocate(AnchorPoint.absolute(100, 590), AnchorPoint.TOP_LEFT, AnchorPolicy.AUTO, constraints);
+ s.relocate(AnchorPoint.absolute(100, 590), constraints, AnchorPoint.TOP_LEFT, AnchorPolicy.AUTO);
s.show();
assertEquals(100, peer.x, 0.0001);
@@ -806,6 +965,9 @@ public void relocateAutoWithBottomOnlyConstraintFlipsVertically() {
assertWithinBounds(peer, toolkit.getScreens().getFirst(), Insets.EMPTY);
}
+ /**
+ * Tests that {@link AnchorPolicy#AUTO} ignores disabled edge constraints when deciding whether to flip.
+ */
@Test
public void relocateAutoIgnoresDisabledEdgesWhenDecidingWhetherToFlip() {
s.setWidth(300);
@@ -816,7 +978,7 @@ public void relocateAutoIgnoresDisabledEdgesWhenDecidingWhetherToFlip() {
// just because rawX would exceed the screen width.
var constraints = new Insets(-1, -1, -1, 0);
- s.relocate(AnchorPoint.absolute(790, 10), AnchorPoint.TOP_LEFT, AnchorPolicy.AUTO, constraints);
+ s.relocate(AnchorPoint.absolute(790, 10), constraints, AnchorPoint.TOP_LEFT, AnchorPolicy.AUTO);
s.show();
// With only left constraint, rawX=790 is allowed (since right is disabled).
@@ -825,6 +987,9 @@ public void relocateAutoIgnoresDisabledEdgesWhenDecidingWhetherToFlip() {
assertNotWithinBounds(peer, toolkit.getScreens().getFirst(), Insets.EMPTY);
}
+ /**
+ * Tests side selection when the stage cannot fit in the constrained span.
+ */
@Test
public void relocateWhenStageDoesNotFitInConstrainedSpanUsesAnchorToChooseSide() {
// Make a screen smaller than the stage, so maxX < minX (and maxY < minY).
@@ -833,7 +998,7 @@ public void relocateWhenStageDoesNotFitInConstrainedSpanUsesAnchorToChooseSide()
s.setHeight(250);
// With TOP_LEFT, choose minX/minY in non-fit scenario.
- s.relocate(AnchorPoint.absolute(0, 0), AnchorPoint.TOP_LEFT, AnchorPolicy.FIXED, Insets.EMPTY);
+ s.relocate(AnchorPoint.absolute(0, 0), Insets.EMPTY, AnchorPoint.TOP_LEFT, AnchorPolicy.FIXED);
s.show();
assertEquals(0, peer.x, 0.0001);
assertEquals(0, peer.y, 0.0001);
@@ -842,13 +1007,16 @@ public void relocateWhenStageDoesNotFitInConstrainedSpanUsesAnchorToChooseSide()
s.hide();
s.setWidth(300);
s.setHeight(250);
- s.relocate(AnchorPoint.absolute(0, 0), AnchorPoint.TOP_RIGHT, AnchorPolicy.FIXED, Insets.EMPTY);
+ s.relocate(AnchorPoint.absolute(0, 0), Insets.EMPTY, AnchorPoint.TOP_RIGHT, AnchorPolicy.FIXED);
s.show();
assertEquals(-100, peer.x, 0.0001); // maxX = 200 - 300 = -100
assertEquals(0, peer.y, 0.0001); // choose minY because TOP_RIGHT has y = 0
}
+ /**
+ * Tests that {@code relocate()} uses the screen containing the request point to apply constraints.
+ */
@Test
public void relocateUsesSecondScreenBoundsForConstraints() {
toolkit.setScreens(
@@ -860,7 +1028,7 @@ public void relocateUsesSecondScreenBoundsForConstraints() {
// Point on screen 2, but near its bottom-right corner.
var p = AnchorPoint.absolute(1920 + 1440 - 1, 160 + 900 - 1);
- s.relocate(p, AnchorPoint.TOP_LEFT, AnchorPolicy.FIXED, Insets.EMPTY);
+ s.relocate(p, Insets.EMPTY, AnchorPoint.TOP_LEFT, AnchorPolicy.FIXED);
s.show();
// Clamp within screen 2: x <= 1920+1440-400 = 2960, y <= 160+900-300 = 760
@@ -870,6 +1038,9 @@ public void relocateUsesSecondScreenBoundsForConstraints() {
assertWithinBounds(peer, toolkit.getScreens().get(1), Insets.EMPTY);
}
+ /**
+ * Tests {@code relocate()} behavior for a zero-size stage with proportional anchors (no NaN/Infinity).
+ */
@Test
public void relocateWithZeroSizeAndProportionalAnchorDoesNotProduceNaNAndConstrainsNormally() {
// Force zero size at positioning time.
@@ -877,7 +1048,7 @@ public void relocateWithZeroSizeAndProportionalAnchorDoesNotProduceNaNAndConstra
s.setHeight(0);
// Enable all edges (Insets.EMPTY), so negative coordinate requests are constrained.
- s.relocate(AnchorPoint.absolute(-10, -20), AnchorPoint.CENTER, AnchorPolicy.AUTO, Insets.EMPTY);
+ s.relocate(AnchorPoint.absolute(-10, -20), Insets.EMPTY, AnchorPoint.CENTER, AnchorPolicy.AUTO);
s.show();
// With width/height == 0, maxX == 800, and maxY == 600; raw is (-10, -20) => constrained to (0,0)
@@ -887,6 +1058,9 @@ public void relocateWithZeroSizeAndProportionalAnchorDoesNotProduceNaNAndConstra
assertFalse(Double.isNaN(peer.y) || Double.isInfinite(peer.y));
}
+ /**
+ * Tests side selection when constraints define an impossible usable area for a zero-size stage.
+ */
@Test
public void relocateWithZeroSizeAndImpossibleConstraintsChoosesSideUsingAnchorPosition() {
s.setWidth(0);
@@ -901,13 +1075,16 @@ public void relocateWithZeroSizeAndImpossibleConstraintsChoosesSideUsingAnchorPo
// y = 0.75 => choose maxY (since y > 0.5)
var anchor = AnchorPoint.proportional(0.25, 0.75);
- s.relocate(AnchorPoint.absolute(0, 0), anchor, AnchorPolicy.FIXED, constraints);
+ s.relocate(AnchorPoint.absolute(0, 0), constraints, anchor, AnchorPolicy.FIXED);
s.show();
assertEquals(500, peer.x, 0.0001);
assertEquals(200, peer.y, 0.0001);
}
+ /**
+ * Tests that zero-size relocation with absolute anchors does not divide by zero in fallback paths.
+ */
@Test
public void relocateWithZeroSizeAndAbsoluteAnchorDoesNotDivideByZero() {
s.setWidth(0);
@@ -917,13 +1094,16 @@ public void relocateWithZeroSizeAndAbsoluteAnchorDoesNotDivideByZero() {
var constraints = new Insets(300, 400, 400, 500);
var anchor = AnchorPoint.absolute(10, 10);
- s.relocate(AnchorPoint.absolute(0, 0), anchor, AnchorPolicy.FIXED, constraints);
+ s.relocate(AnchorPoint.absolute(0, 0), constraints, anchor, AnchorPolicy.FIXED);
s.show();
assertEquals(500, peer.x, 0.0001); // minX
assertEquals(300, peer.y, 0.0001); // minY
}
+ /**
+ * Tests that {@link AnchorPolicy#FIXED} constrains the stage within screen bounds for several anchors.
+ */
@ParameterizedTest
@MethodSource("relocateHonorsScreenBounds_arguments")
public void relocateWithFixedAnchorPolicyHonorsScreenBounds(
@@ -933,12 +1113,15 @@ public void relocateWithFixedAnchorPolicyHonorsScreenBounds(
double requestX, double requestY) {
s.setWidth(stageW);
s.setHeight(stageH);
- s.relocate(AnchorPoint.absolute(requestX, requestY), stageAnchor, AnchorPolicy.FIXED, screenPadding);
+ s.relocate(AnchorPoint.absolute(requestX, requestY), screenPadding, stageAnchor, AnchorPolicy.FIXED);
s.show();
assertWithinBounds(peer, toolkit.getScreens().getFirst(), screenPadding);
}
+ /**
+ * Tests that {@link AnchorPolicy#FIXED} constrains the stage within padded usable bounds for several anchors.
+ */
@ParameterizedTest
@MethodSource("relocateHonorsScreenBoundsWithPadding_arguments")
public void relocateWithFixedAnchorPolicyHonorsScreenBoundsWithPadding(
@@ -948,7 +1131,7 @@ public void relocateWithFixedAnchorPolicyHonorsScreenBoundsWithPadding(
double requestX, double requestY) {
s.setWidth(stageW);
s.setHeight(stageH);
- s.relocate(AnchorPoint.absolute(requestX, requestY), stageAnchor, AnchorPolicy.FIXED, screenPadding);
+ s.relocate(AnchorPoint.absolute(requestX, requestY), screenPadding, stageAnchor, AnchorPolicy.FIXED);
s.show();
assertWithinBounds(peer, toolkit.getScreens().getFirst(), screenPadding);