diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java index bfb55f95be7..e4233f9ba8a 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java @@ -30,6 +30,9 @@ import com.sun.javafx.scene.layout.HeaderButtonBehavior; import com.sun.javafx.stage.HeaderButtonMetrics; import com.sun.javafx.stage.StageHelper; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanPropertyBase; import javafx.beans.property.ObjectProperty; @@ -38,7 +41,6 @@ import javafx.beans.property.ReadOnlyDoubleWrapper; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectWrapper; -import javafx.beans.value.ObservableValue; import javafx.css.StyleableDoubleProperty; import javafx.event.Event; import javafx.geometry.Dimension2D; @@ -65,14 +67,14 @@ * method. *
* {@code HeaderBar} is a layout container that allows applications to place scene graph nodes in three areas: - * {@link #leadingProperty() leading}, {@link #centerProperty() center}, and {@link #trailingProperty() trailing}. + * {@link #leftProperty() left}, {@link #centerProperty() center}, and {@link #rightProperty() right}. * All areas can be {@code null}. The default {@link #minHeightProperty() minHeight} of the {@code HeaderBar} is * set to match the height of the platform-specific default header buttons. * *
+ * By default, {@code HeaderBar}.{@link #minHeightProperty() minHeight} is set to the value of
+ * {@code minSystemHeight}, unless {@code minHeight} is explicitly set by a stylesheet or application code.
+ *
+ * @param stage the {@code Stage}
+ * @return the {@code minSystemHeight} property
+ */
+ public static ReadOnlyDoubleProperty minSystemHeightProperty(Stage stage) {
+ return AttachedProperties.of(stage).minSystemHeight.getReadOnlyProperty();
+ }
+
+ /**
+ * Gets the value of the {@link #minSystemHeightProperty(Stage) minSystemHeight} property.
+ *
+ * @param stage the {@code Stage}
+ * @return the system-provided minimum recommended height for the {@code HeaderBar}
+ */
+ public static double getMinSystemHeight(Stage stage) {
+ return AttachedProperties.of(stage).minSystemHeight.get();
+ }
+
/**
* Sets the alignment for the child when contained in a {@code HeaderBar}.
* If set, will override the header bar's default alignment for the child's position.
@@ -314,9 +391,7 @@ public static Insets getMargin(Node child) {
return (Insets)Pane.getConstraint(child, MARGIN);
}
- private Subscription subscription = Subscription.EMPTY;
- private HeaderButtonMetrics currentMetrics;
- private boolean currentFullScreen;
+ private Subscription subscriptions = Subscription.EMPTY;
/**
* Creates a new {@code HeaderBar}.
@@ -328,157 +403,43 @@ public HeaderBar() {
// user code changes the property value before we set it to the height of the native title bar.
minHeightProperty();
- ObservableValue
- * Note that the left system inset refers to the left side of the window, independent of layout orientation.
- */
- private final ReadOnlyObjectWrapper
- * Note that the right system inset refers to the right side of the window, independent of layout orientation.
- */
- private final ReadOnlyObjectWrapper
- * By default, {@link #minHeightProperty() minHeight} is set to the value of {@code minSystemHeight},
- * unless {@code minHeight} is explicitly set by a stylesheet or application code.
- */
- private final ReadOnlyDoubleWrapper minSystemHeight =
- new ReadOnlyDoubleWrapper(this, "minSystemHeight") {
- @Override
- protected void invalidated() {
- double height = get();
- var minHeight = (StyleableDoubleProperty)minHeightProperty();
-
- // Only change minHeight if it was not set by a stylesheet or application code.
- if (minHeight.getStyleOrigin() == null) {
- minHeight.applyStyle(null, height);
- }
- }
- };
-
- public final ReadOnlyDoubleProperty minSystemHeightProperty() {
- return minSystemHeight.getReadOnlyProperty();
- }
-
- public final double getMinSystemHeight() {
- return minSystemHeight.get();
- }
-
- /**
- * The leading area of the {@code HeaderBar}.
- *
- * The leading area corresponds to the left area in a left-to-right layout, and to the right area
- * in a right-to-left layout.
+ * The left area of the {@code HeaderBar}.
*
* @defaultValue {@code null}
*/
- private final ObjectProperty
- * The trailing area corresponds to the right area in a left-to-right layout, and to the left area
- * in a right-to-left layout.
+ * The right area of the {@code HeaderBar}.
*
* @defaultValue {@code null}
*/
- private final ObjectProperty
* Applications that use a single {@code HeaderBar} extending the entire width of the window should
* set this property to {@code true} to prevent the header buttons from overlapping the content of the
* {@code HeaderBar}.
*
* @defaultValue {@code true}
- * @see #trailingSystemPaddingProperty() trailingSystemPadding
+ * @see #rightSystemPaddingProperty() rightSystemPadding
*/
- private final BooleanProperty leadingSystemPadding = new BooleanPropertyBase(true) {
+ private final BooleanProperty leftSystemPadding = new BooleanPropertyBase(true) {
@Override
public Object getBean() {
return HeaderBar.this;
@@ -543,7 +501,7 @@ public Object getBean() {
@Override
public String getName() {
- return "leadingSystemPadding";
+ return "leftSystemPadding";
}
@Override
@@ -552,32 +510,32 @@ protected void invalidated() {
}
};
- public final BooleanProperty leadingSystemPaddingProperty() {
- return leadingSystemPadding;
+ public final BooleanProperty leftSystemPaddingProperty() {
+ return leftSystemPadding;
}
- public final boolean isLeadingSystemPadding() {
- return leadingSystemPadding.get();
+ public final boolean isLeftSystemPadding() {
+ return leftSystemPadding.get();
}
- public final void setLeadingSystemPadding(boolean value) {
- leadingSystemPadding.set(value);
+ public final void setLeftSystemPadding(boolean value) {
+ leftSystemPadding.set(value);
}
/**
- * Specifies whether additional padding should be added to the trailing side of the {@code HeaderBar}.
+ * Specifies whether additional padding should be added to the right side of the {@code HeaderBar}.
* The size of the additional padding corresponds to the size of the system-reserved area that contains
* the default header buttons (iconify, maximize, and close). If the system-reserved area contains no
- * header buttons, no additional padding is added to the trailing side of the {@code HeaderBar}.
+ * header buttons, no additional padding is added to the right side of the {@code HeaderBar}.
*
* Applications that use a single {@code HeaderBar} extending the entire width of the window should
* set this property to {@code true} to prevent the header buttons from overlapping the content of the
* {@code HeaderBar}.
*
* @defaultValue {@code true}
- * @see #leadingSystemPaddingProperty() leadingSystemPadding
+ * @see #leftSystemPaddingProperty() leftSystemPadding
*/
- private final BooleanProperty trailingSystemPadding = new BooleanPropertyBase(true) {
+ private final BooleanProperty rightSystemPadding = new BooleanPropertyBase(true) {
@Override
public Object getBean() {
return HeaderBar.this;
@@ -585,7 +543,7 @@ public Object getBean() {
@Override
public String getName() {
- return "trailingSystemPadding";
+ return "rightSystemPadding";
}
@Override
@@ -594,61 +552,59 @@ protected void invalidated() {
}
};
- public final BooleanProperty trailingSystemPaddingProperty() {
- return trailingSystemPadding;
- }
-
- public final boolean isTrailingSystemPadding() {
- return trailingSystemPadding.get();
- }
-
- public final void setTrailingSystemPadding(boolean value) {
- trailingSystemPadding.set(value);
+ public final BooleanProperty rightSystemPaddingProperty() {
+ return rightSystemPadding;
}
- private boolean isLeftSystemPadding(NodeOrientation nodeOrientation) {
- return nodeOrientation == NodeOrientation.LEFT_TO_RIGHT && isLeadingSystemPadding()
- || nodeOrientation == NodeOrientation.RIGHT_TO_LEFT && isTrailingSystemPadding();
+ public final boolean isRightSystemPadding() {
+ return rightSystemPadding.get();
}
- private boolean isRightSystemPadding(NodeOrientation nodeOrientation) {
- return nodeOrientation == NodeOrientation.LEFT_TO_RIGHT && isTrailingSystemPadding()
- || nodeOrientation == NodeOrientation.RIGHT_TO_LEFT && isLeadingSystemPadding();
+ public final void setRightSystemPadding(boolean value) {
+ rightSystemPadding.set(value);
}
@Override
protected double computeMinWidth(double height) {
- Node leading = getLeading();
+ Node left = getLeft();
Node center = getCenter();
- Node trailing = getTrailing();
+ Node right = getRight();
Insets insets = getInsets();
double leftPrefWidth;
double rightPrefWidth;
double centerMinWidth;
- double systemPaddingWidth = 0;
+ double leftSystemPaddingWidth = 0;
+ double rightSystemPaddingWidth = 0;
if (height != -1
- && (childHasContentBias(leading, Orientation.VERTICAL) ||
- childHasContentBias(trailing, Orientation.VERTICAL) ||
+ && (childHasContentBias(left, Orientation.VERTICAL) ||
+ childHasContentBias(right, Orientation.VERTICAL) ||
childHasContentBias(center, Orientation.VERTICAL))) {
double areaHeight = Math.max(0, height);
- leftPrefWidth = getAreaWidth(leading, areaHeight, false);
- rightPrefWidth = getAreaWidth(trailing, areaHeight, false);
+ leftPrefWidth = getAreaWidth(left, areaHeight, false);
+ rightPrefWidth = getAreaWidth(right, areaHeight, false);
centerMinWidth = getAreaWidth(center, areaHeight, true);
} else {
- leftPrefWidth = getAreaWidth(leading, -1, false);
- rightPrefWidth = getAreaWidth(trailing, -1, false);
+ leftPrefWidth = getAreaWidth(left, -1, false);
+ rightPrefWidth = getAreaWidth(right, -1, false);
centerMinWidth = getAreaWidth(center, -1, true);
}
- NodeOrientation nodeOrientation = getEffectiveNodeOrientation();
+ Scene scene = getScene();
+ Stage stage = scene != null
+ ? scene.getWindow() instanceof Stage s ? s : null
+ : null;
- if (isLeftSystemPadding(nodeOrientation)) {
- systemPaddingWidth += getLeftSystemInset().getWidth();
- }
+ if (stage != null) {
+ var attachedProperties = AttachedProperties.of(stage);
- if (isRightSystemPadding(nodeOrientation)) {
- systemPaddingWidth += getRightSystemInset().getWidth();
+ if (scene.getEffectiveNodeOrientation() != getEffectiveNodeOrientation()) {
+ leftSystemPaddingWidth = isLeftSystemPadding() ? attachedProperties.rightSystemInset.get().getWidth() : 0;
+ rightSystemPaddingWidth = isRightSystemPadding() ? attachedProperties.leftSystemInset.get().getWidth() : 0;
+ } else {
+ leftSystemPaddingWidth = isLeftSystemPadding() ? attachedProperties.leftSystemInset.get().getWidth() : 0;
+ rightSystemPaddingWidth = isRightSystemPadding() ? attachedProperties.rightSystemInset.get().getWidth() : 0;
+ }
}
return insets.getLeft()
@@ -656,91 +612,93 @@ protected double computeMinWidth(double height) {
+ centerMinWidth
+ rightPrefWidth
+ insets.getRight()
- + systemPaddingWidth;
+ + leftSystemPaddingWidth
+ + rightSystemPaddingWidth;
}
@Override
protected double computeMinHeight(double width) {
- Node leading = getLeading();
+ Node left = getLeft();
Node center = getCenter();
- Node trailing = getTrailing();
+ Node right = getRight();
Insets insets = getInsets();
- double leadingMinHeight = getAreaHeight(leading, -1, true);
- double trailingMinHeight = getAreaHeight(trailing, -1, true);
+ double leftMinHeight = getAreaHeight(left, -1, true);
+ double rightMinHeight = getAreaHeight(right, -1, true);
double centerMinHeight;
if (width != -1 && childHasContentBias(center, Orientation.HORIZONTAL)) {
- double leadingPrefWidth = getAreaWidth(leading, -1, false);
- double trailingPrefWidth = getAreaWidth(trailing, -1, false);
- centerMinHeight = getAreaHeight(center, Math.max(0, width - leadingPrefWidth - trailingPrefWidth), true);
+ double leftPrefWidth = getAreaWidth(left, -1, false);
+ double rightPrefWidth = getAreaWidth(right, -1, false);
+ centerMinHeight = getAreaHeight(center, Math.max(0, width - leftPrefWidth - rightPrefWidth), true);
} else {
centerMinHeight = getAreaHeight(center, -1, true);
}
return insets.getTop()
+ insets.getBottom()
- + Math.max(centerMinHeight, Math.max(trailingMinHeight, leadingMinHeight));
+ + Math.max(centerMinHeight, Math.max(rightMinHeight, leftMinHeight));
}
@Override
protected double computePrefHeight(double width) {
- Node leading = getLeading();
+ Node left = getLeft();
Node center = getCenter();
- Node trailing = getTrailing();
+ Node right = getRight();
Insets insets = getInsets();
- double leadingPrefHeight = getAreaHeight(leading, -1, false);
- double trailingPrefHeight = getAreaHeight(trailing, -1, false);
+ double leftPrefHeight = getAreaHeight(left, -1, false);
+ double rightPrefHeight = getAreaHeight(right, -1, false);
double centerPrefHeight;
if (width != -1 && childHasContentBias(center, Orientation.HORIZONTAL)) {
- double leadingPrefWidth = getAreaWidth(leading, -1, false);
- double trailingPrefWidth = getAreaWidth(trailing, -1, false);
- centerPrefHeight = getAreaHeight(center, Math.max(0, width - leadingPrefWidth - trailingPrefWidth), false);
+ double leftPrefWidth = getAreaWidth(left, -1, false);
+ double rightPrefWidth = getAreaWidth(right, -1, false);
+ centerPrefHeight = getAreaHeight(center, Math.max(0, width - leftPrefWidth - rightPrefWidth), false);
} else {
centerPrefHeight = getAreaHeight(center, -1, false);
}
return insets.getTop()
+ insets.getBottom()
- + Math.max(centerPrefHeight, Math.max(trailingPrefHeight, leadingPrefHeight));
- }
-
- @Override
- public boolean usesMirroring() {
- return false;
+ + Math.max(centerPrefHeight, Math.max(rightPrefHeight, leftPrefHeight));
}
@Override
protected void layoutChildren() {
+ Node left = getLeft();
Node center = getCenter();
- Node left, right;
+ Node right = getRight();
Insets insets = getInsets();
- NodeOrientation nodeOrientation = getEffectiveNodeOrientation();
- boolean rtl = nodeOrientation == NodeOrientation.RIGHT_TO_LEFT;
double width = Math.max(getWidth(), minWidth(-1));
double height = Math.max(getHeight(), minHeight(-1));
double leftWidth = 0;
double rightWidth = 0;
double insideY = insets.getTop();
double insideHeight = height - insideY - insets.getBottom();
- double insideX, insideWidth;
- double leftSystemPaddingWidth = isLeftSystemPadding(nodeOrientation) ? getLeftSystemInset().getWidth() : 0;
- double rightSystemPaddingWidth = isRightSystemPadding(nodeOrientation) ? getRightSystemInset().getWidth() : 0;
-
- if (rtl) {
- left = getTrailing();
- right = getLeading();
- insideX = insets.getRight() + leftSystemPaddingWidth;
- insideWidth = width - insideX - insets.getLeft() - rightSystemPaddingWidth;
- } else {
- left = getLeading();
- right = getTrailing();
- insideX = insets.getLeft() + leftSystemPaddingWidth;
- insideWidth = width - insideX - insets.getRight() - rightSystemPaddingWidth;
+ double rightSystemPaddingWidth = 0;
+ double leftSystemPaddingWidth = 0;
+
+ Scene scene = getScene();
+ Stage stage = scene != null
+ ? scene.getWindow() instanceof Stage s ? s : null
+ : null;
+
+ if (stage != null) {
+ AttachedProperties attachedProperties = AttachedProperties.of(stage);
+
+ if (scene.getEffectiveNodeOrientation() != getEffectiveNodeOrientation()) {
+ leftSystemPaddingWidth = isLeftSystemPadding() ? attachedProperties.rightSystemInset.get().getWidth() : 0;
+ rightSystemPaddingWidth = isRightSystemPadding() ? attachedProperties.leftSystemInset.get().getWidth() : 0;
+ } else {
+ leftSystemPaddingWidth = isLeftSystemPadding() ? attachedProperties.leftSystemInset.get().getWidth() : 0;
+ rightSystemPaddingWidth = isRightSystemPadding() ? attachedProperties.rightSystemInset.get().getWidth() : 0;
+ }
}
+ double insideX = insets.getLeft() + leftSystemPaddingWidth;
+ double insideWidth = width - insideX - insets.getRight() - rightSystemPaddingWidth;
+
if (left != null && left.isManaged()) {
- Insets leftMargin = adjustMarginForRTL(getNodeMargin(left), rtl);
+ Insets leftMargin = getNodeMargin(left);
double adjustedWidth = adjustWidthByMargin(insideWidth, leftMargin);
double childWidth = resizeChild(left, adjustedWidth, false, insideHeight, leftMargin);
leftWidth = snapSpaceX(leftMargin.getLeft()) + childWidth + snapSpaceX(leftMargin.getRight());
@@ -756,7 +714,7 @@ protected void layoutChildren() {
}
if (right != null && right.isManaged()) {
- Insets rightMargin = adjustMarginForRTL(getNodeMargin(right), rtl);
+ Insets rightMargin = getNodeMargin(right);
double adjustedWidth = adjustWidthByMargin(insideWidth - leftWidth, rightMargin);
double childWidth = resizeChild(right, adjustedWidth, false, insideHeight, rightMargin);
rightWidth = snapSpaceX(rightMargin.getLeft()) + childWidth + snapSpaceX(rightMargin.getRight());
@@ -772,7 +730,7 @@ protected void layoutChildren() {
}
if (center != null && center.isManaged()) {
- Insets centerMargin = adjustMarginForRTL(getNodeMargin(center), rtl);
+ Insets centerMargin = getNodeMargin(center);
Pos alignment = getAlignment(center);
if (alignment == null || alignment.getHpos() == HPos.CENTER) {
@@ -809,16 +767,6 @@ protected void layoutChildren() {
}
}
- private Insets adjustMarginForRTL(Insets margin, boolean rtl) {
- if (margin == null) {
- return null;
- }
-
- return rtl
- ? new Insets(margin.getTop(), margin.getLeft(), margin.getBottom(), margin.getRight())
- : margin;
- }
-
private boolean childHasContentBias(Node child, Orientation orientation) {
if (child != null && child.isManaged()) {
return child.getContentBias() == orientation;
@@ -864,6 +812,26 @@ private Insets getNodeMargin(Node child) {
return margin != null ? margin : Insets.EMPTY;
}
+ private void onStageChanged(Stage stage) {
+ subscriptions.unsubscribe();
+
+ if (stage != null) {
+ var attachedProperties = AttachedProperties.of(stage);
+
+ subscriptions = Subscription.combine(
+ attachedProperties.minSystemHeight.subscribe(height -> {
+ var minHeight = (StyleableDoubleProperty)minHeightProperty();
+
+ // Only change minHeight if it was not set by a stylesheet or application code.
+ if (minHeight.getStyleOrigin() == null) {
+ minHeight.applyStyle(null, height);
+ }
+ }),
+ attachedProperties.subscribeLayoutInvalidated(this::requestLayout)
+ );
+ }
+ }
+
private final class NodeProperty extends ObjectPropertyBase
+ * For layout containers, the layout orientation determines the visual order of their children. If a layout container
+ * has named areas, the names always retain their default meaning. For example, the {@code left} and {@code right}
+ * areas of {@link javafx.scene.layout.BorderPane BorderPane} or {@link javafx.scene.layout.HeaderBar HeaderBar}
+ * are always left and right in regard to the default layout orientation (left-to-right). If the layout orientation
+ * is right-to-left, the left area will appear to the right, and the right area will appear to the left.
*/
package javafx.scene.layout;
diff --git a/modules/javafx.graphics/src/test/java/test/javafx/scene/layout/HeaderBarTest.java b/modules/javafx.graphics/src/test/java/test/javafx/scene/layout/HeaderBarTest.java
index 7732f6a58b7..976912be506 100644
--- a/modules/javafx.graphics/src/test/java/test/javafx/scene/layout/HeaderBarTest.java
+++ b/modules/javafx.graphics/src/test/java/test/javafx/scene/layout/HeaderBarTest.java
@@ -28,6 +28,7 @@
import com.sun.javafx.scene.SceneHelper;
import com.sun.javafx.tk.HeaderAreaType;
import com.sun.javafx.tk.TKSceneListener;
+import java.lang.reflect.Method;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.geometry.Dimension2D;
@@ -40,6 +41,7 @@
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
+import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@@ -52,23 +54,45 @@
@SuppressWarnings("deprecation")
public class HeaderBarTest {
+ Stage stage;
+ Scene scene;
HeaderBar headerBar;
@BeforeEach
void setup() {
headerBar = new HeaderBar();
+ scene = new Scene(headerBar);
+ stage = new Stage();
+ stage.setScene(scene);
+ stage.show();
+ }
+
+ @AfterEach
+ void teardown() {
+ stage.close();
+ }
+
+ Layout Orientation
+ * The layout orientation of the scene graph is determined by the {@link javafx.scene.Scene#nodeOrientationProperty() Scene.nodeOrientation}
+ * and {@link javafx.scene.Node#nodeOrientationProperty() Node.nodeOrientation} properties.
+ * It is effectively left-to-right by default, but can be right-to-left depending on the locale of the operating system.
+ * If a layout orientation is set on the scene or on any node of the scene graph, descendants of the scene or node will
+ * inherit the specified orientation.
+ *