c) {
// Note the starting index at which we need to update the
// PGGroup on the next update, and mark the children dirty
- c.reset();
- c.next();
- if (startIdx > c.getFrom()) {
- startIdx = c.getFrom();
+ if (startIdx > firstDirtyChildIndex) {
+ startIdx = firstDirtyChildIndex;
}
NodeHelper.markDirty(Parent.this, DirtyBits.PARENT_CHILDREN);
diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java b/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java
index cd1097eac88..81b4dfda83f 100644
--- a/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java
+++ b/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java
@@ -70,6 +70,7 @@
import javafx.collections.ObservableList;
import javafx.collections.ObservableMap;
import javafx.css.CssMetaData;
+import javafx.css.PseudoClass;
import javafx.css.StyleableObjectProperty;
import javafx.css.Stylesheet;
import javafx.event.*;
@@ -1183,7 +1184,9 @@ public String getName() {
* layout of the scene graph. If a resizable node (layout {@code Region} or
* {@code Control}) is set as the root, then the root's size will track the
* scene's size, causing the contents to be relayed out as necessary.
- *
+ *
+ * The {@code :root} pseudo-class matches the root node.
+ *
* Scene doesn't accept null root.
*
*/
@@ -1201,7 +1204,6 @@ public final Parent getRoot() {
public final ObjectProperty rootProperty() {
if (root == null) {
root = new ObjectPropertyBase<>() {
-
private void forceUnbind() {
System.err.println("Unbinding illegal root.");
unbind();
@@ -1235,9 +1237,11 @@ protected void invalidated() {
if (oldRoot != null) {
oldRoot.setScenes(null, null);
oldRoot.getStyleClass().remove("root");
+ oldRoot.pseudoClassStateChanged(PseudoClass.getPseudoClass("root"), false);
}
oldRoot = _value;
_value.getStyleClass().add(0, "root");
+ _value.pseudoClassStateChanged(PseudoClass.getPseudoClass("root"), true);
_value.setScenes(Scene.this, null);
markDirty(DirtyBits.ROOT_DIRTY);
_value.resize(getWidth(), getHeight()); // maybe no-op if root is not resizable
diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/SubScene.java b/modules/javafx.graphics/src/main/java/javafx/scene/SubScene.java
index 402b4afda30..c965e61e9f5 100644
--- a/modules/javafx.graphics/src/main/java/javafx/scene/SubScene.java
+++ b/modules/javafx.graphics/src/main/java/javafx/scene/SubScene.java
@@ -34,6 +34,7 @@
import javafx.application.Platform;
import javafx.beans.NamedArg;
import javafx.beans.property.*;
+import javafx.css.PseudoClass;
import javafx.css.Stylesheet;
import javafx.geometry.NodeOrientation;
import javafx.geometry.Point3D;
@@ -256,7 +257,9 @@ private boolean isDepthBufferInternal() {
* Defines the root {@code Node} of the {@code SubScene} scene graph.
* If a {@code Group} is used as the root, the
* contents of the scene graph will be clipped by the {@code SubScene}'s width and height.
- *
+ *
+ * The {@code :root} pseudo-class matches the root node.
+ *
* {@code SubScene} doesn't accept null root.
*
*/
@@ -317,9 +320,11 @@ protected void invalidated() {
StyleManager.getInstance().forget(SubScene.this);
oldRoot.setScenes(null, null);
oldRoot.getStyleClass().remove("root");
+ oldRoot.pseudoClassStateChanged(PseudoClass.getPseudoClass("root"), false);
}
oldRoot = _value;
_value.getStyleClass().add(0, "root");
+ _value.pseudoClassStateChanged(PseudoClass.getPseudoClass("root"), true);
_value.setScenes(getScene(), SubScene.this);
markDirty(SubSceneDirtyBits.ROOT_SG_DIRTY);
_value.resize(getWidth(), getHeight()); // maybe no-op if root is not resizable
diff --git a/modules/javafx.graphics/src/test/java/test/javafx/css/StylesheetTest.java b/modules/javafx.graphics/src/test/java/test/javafx/css/StylesheetTest.java
index 570e59f5efa..486e7ecbe3f 100644
--- a/modules/javafx.graphics/src/test/java/test/javafx/css/StylesheetTest.java
+++ b/modules/javafx.graphics/src/test/java/test/javafx/css/StylesheetTest.java
@@ -40,6 +40,7 @@
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
+import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
@@ -62,6 +63,7 @@
import javafx.geometry.VPos;
import javafx.scene.Group;
import javafx.scene.Scene;
+import javafx.scene.layout.Background;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
@@ -73,10 +75,7 @@
import javafx.stage.Stage;
import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assertions.fail;
+import static org.junit.jupiter.api.Assertions.*;
public class StylesheetTest {
@@ -778,4 +777,21 @@ public void testStyleRevertsWhenDataURIStylesheetIsRemoved() {
assertEquals(Color.BLUE, rect.getFill());
}
+ @Test
+ public void testRootPseudoClassSelectsRootNode() {
+ var root = new StackPane();
+
+ root.getStylesheets().add("data:base64," + Base64.getEncoder().encodeToString("""
+ :root {
+ -fx-background-color: red;
+ }
+ """.getBytes(StandardCharsets.UTF_8)));
+
+ assertNotEquals(Background.fill(Color.RED), root.getBackground());
+
+ Scene scene = new Scene(root);
+ scene.getRoot().applyCss();
+
+ assertEquals(Background.fill(Color.RED), root.getBackground());
+ }
}
diff --git a/modules/javafx.graphics/src/test/java/test/javafx/scene/Parent_pseudoClasses_Test.java b/modules/javafx.graphics/src/test/java/test/javafx/scene/Parent_pseudoClasses_Test.java
new file mode 100644
index 00000000000..e344f7d513d
--- /dev/null
+++ b/modules/javafx.graphics/src/test/java/test/javafx/scene/Parent_pseudoClasses_Test.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2024, 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 test.javafx.scene;
+
+import javafx.css.PseudoClass;
+import javafx.scene.Group;
+import javafx.scene.Node;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class Parent_pseudoClasses_Test {
+
+ static final PseudoClass FIRST_CHILD = PseudoClass.getPseudoClass("first-child");
+ static final PseudoClass LAST_CHILD = PseudoClass.getPseudoClass("last-child");
+ static final PseudoClass ONLY_CHILD = PseudoClass.getPseudoClass("only-child");
+ static final PseudoClass NTH_EVEN_CHILD = PseudoClass.getPseudoClass("nth-child(even)");
+ static final PseudoClass NTH_ODD_CHILD = PseudoClass.getPseudoClass("nth-child(odd)");
+
+ @Test
+ void onlyChildPseudoClass() {
+ var group = new Group();
+ var child1 = new Group();
+ var child2 = new Group();
+ assertNotPseudoClass(ONLY_CHILD, child1);
+ assertNotPseudoClass(ONLY_CHILD, child2);
+ group.getChildren().add(child1);
+ assertPseudoClass(ONLY_CHILD, child1);
+ group.getChildren().removeFirst();
+ assertNotPseudoClass(ONLY_CHILD, child1);
+ group.getChildren().addAll(child1, child2);
+ assertNotPseudoClass(ONLY_CHILD, child1);
+ assertNotPseudoClass(ONLY_CHILD, child2);
+ group.getChildren().removeFirst();
+ assertNotPseudoClass(ONLY_CHILD, child1);
+ assertPseudoClass(ONLY_CHILD, child2);
+ }
+
+ @Test
+ void firstChildPseudoClass() {
+ var group = new Group();
+ var child1 = new Group();
+ var child2 = new Group();
+ assertNotPseudoClass(FIRST_CHILD, child1);
+ assertNotPseudoClass(FIRST_CHILD, child2);
+ group.getChildren().add(child1);
+ assertPseudoClass(FIRST_CHILD, child1);
+ group.getChildren().add(child2);
+ assertPseudoClass(FIRST_CHILD, child1);
+ assertNotPseudoClass(FIRST_CHILD, child2);
+ group.getChildren().removeFirst();
+ assertNotPseudoClass(FIRST_CHILD, child1);
+ assertPseudoClass(FIRST_CHILD, child2);
+ }
+
+ @Test
+ void lastChildPseudoClass() {
+ var group = new Group();
+ var child1 = new Group();
+ var child2 = new Group();
+ assertNotPseudoClass(LAST_CHILD, child1);
+ assertNotPseudoClass(LAST_CHILD, child2);
+ group.getChildren().add(child1);
+ assertPseudoClass(LAST_CHILD, child1);
+ group.getChildren().add(child2);
+ assertNotPseudoClass(LAST_CHILD, child1);
+ assertPseudoClass(LAST_CHILD, child2);
+ group.getChildren().removeFirst();
+ assertNotPseudoClass(LAST_CHILD, child1);
+ assertPseudoClass(LAST_CHILD, child2);
+ group.getChildren().removeFirst();
+ assertNotPseudoClass(LAST_CHILD, child2);
+ }
+
+ @Test
+ void nthChildEvenOddPseudoClass() {
+ var group = new Group();
+ var child1 = new Group();
+ var child2 = new Group();
+ var child3 = new Group();
+ var child4 = new Group();
+
+ // [child1, child2, child3, child4]
+ group.getChildren().addAll(child1, child2, child3, child4);
+ assertPseudoClass(NTH_EVEN_CHILD, child2, child4);
+ assertNotPseudoClass(NTH_EVEN_CHILD, child1, child3);
+ assertPseudoClass(NTH_ODD_CHILD, child1, child3);
+ assertNotPseudoClass(NTH_ODD_CHILD, child2, child4);
+
+ // [child1, child2, child2b, child3, child4]
+ var child2b = new Group();
+ group.getChildren().add(2, child2b);
+ assertPseudoClass(NTH_EVEN_CHILD, child2, child3);
+ assertNotPseudoClass(NTH_EVEN_CHILD, child1, child2b, child4);
+ assertPseudoClass(NTH_ODD_CHILD, child1, child2b, child4);
+ assertNotPseudoClass(NTH_ODD_CHILD, child2, child3);
+
+ // [child1, child3, child4]
+ group.getChildren().remove(1, 3);
+ assertPseudoClass(NTH_EVEN_CHILD, child3);
+ assertNotPseudoClass(NTH_EVEN_CHILD, child1, child2, child2b, child4);
+ assertPseudoClass(NTH_ODD_CHILD, child1, child4);
+ assertNotPseudoClass(NTH_ODD_CHILD, child2, child2b, child3);
+ }
+
+ private void assertPseudoClass(PseudoClass pseudoClass, Node... nodes) {
+ for (Node node : nodes) {
+ assertTrue(node.getPseudoClassStates().contains(pseudoClass));
+ }
+ }
+
+ private void assertNotPseudoClass(PseudoClass pseudoClass, Node... nodes) {
+ for (Node node : nodes) {
+ assertFalse(node.getPseudoClassStates().contains(pseudoClass));
+ }
+ }
+}
diff --git a/modules/javafx.graphics/src/test/java/test/javafx/scene/SceneTest.java b/modules/javafx.graphics/src/test/java/test/javafx/scene/SceneTest.java
index c3708474041..fe0924b0f0c 100644
--- a/modules/javafx.graphics/src/test/java/test/javafx/scene/SceneTest.java
+++ b/modules/javafx.graphics/src/test/java/test/javafx/scene/SceneTest.java
@@ -31,6 +31,7 @@
import com.sun.javafx.scene.SceneHelper;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
+import javafx.css.PseudoClass;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.TilePane;
@@ -237,6 +238,18 @@ public void testRootStyleClassIsClearedWhenRootNodeIsRemovedFromScene() {
assertFalse(g.getStyleClass().contains("root"));
}
+ @Test
+ public void testRootPseudoClassIsSetOnRootNode() {
+ var root = PseudoClass.getPseudoClass("root");
+ Scene scene = new Scene(new Group());
+ Group g = new Group();
+ assertFalse(g.getPseudoClassStates().contains(root));
+ scene.setRoot(g);
+ assertTrue(g.getPseudoClassStates().contains(root));
+ scene.setRoot(new Group());
+ assertFalse(g.getPseudoClassStates().contains(root));
+ }
+
@Test
public void testNodeUpdatedWhenAddedToScene() {
Group root = new Group();
diff --git a/modules/javafx.graphics/src/test/java/test/javafx/scene/SubSceneTest.java b/modules/javafx.graphics/src/test/java/test/javafx/scene/SubSceneTest.java
index 76547d589b4..ac18c365923 100644
--- a/modules/javafx.graphics/src/test/java/test/javafx/scene/SubSceneTest.java
+++ b/modules/javafx.graphics/src/test/java/test/javafx/scene/SubSceneTest.java
@@ -25,6 +25,7 @@
package test.javafx.scene;
import com.sun.javafx.scene.NodeHelper;
+import javafx.css.PseudoClass;
import javafx.stage.Stage;
import com.sun.javafx.sg.prism.NGCamera;
import com.sun.javafx.sg.prism.NGSubScene;
@@ -149,6 +150,18 @@ public void testRootStyleClassIsClearedWhenRootNodeIsRemovedFromSubScene() {
assertFalse(g.getStyleClass().contains("root"));
}
+ @Test
+ public void testRootPseudoClassIsSetOnRootNode() {
+ var root = PseudoClass.getPseudoClass("root");
+ SubScene scene = new SubScene(new Group(), 10, 10);
+ Group g = new Group();
+ assertFalse(g.getPseudoClassStates().contains(root));
+ scene.setRoot(g);
+ assertTrue(g.getPseudoClassStates().contains(root));
+ scene.setRoot(new Group());
+ assertFalse(g.getPseudoClassStates().contains(root));
+ }
+
@Test
public void testSetCamera() {
Camera camera = new PerspectiveCamera();