diff --git a/modules/javafx.graphics/src/main/docs/javafx/scene/doc-files/cssref.html b/modules/javafx.graphics/src/main/docs/javafx/scene/doc-files/cssref.html index 11ae849727b..20a3ef4e4cb 100644 --- a/modules/javafx.graphics/src/main/docs/javafx/scene/doc-files/cssref.html +++ b/modules/javafx.graphics/src/main/docs/javafx/scene/doc-files/cssref.html @@ -86,7 +86,7 @@ .csspropertytable th, .csspropertytable td { padding: 2px; } - .csspropertytable thead th, .csspropertytable thead td { + .csspropertytable thead th, .csspropertytable thead td, .csspropertytable tbody th.subheader { font-size: 10px; text-align: center; background-color: #CCC; @@ -539,8 +539,7 @@

CSS and the JavaFX Scene Graph

JavaFX CSS also supports pseudo‑classes, but does not implement the full range of pseudo‑classes as specified in Pseudo‑classes. The pseudo‑classes - supported by each Node type are given in the tables within this reference. Note that JavaFX does not currently - support structural pseudo‑classes. + supported by each Node type are given in the tables within this reference.

Each node honors a set of properties that depends on the node's JavaFX @@ -869,7 +868,6 @@

Limitations

syntax not specified in this document.
  • With the exception of @font‑face and @import, @-keyword statements are ignored.
  • The <media-query-list> of the @import statement is not parsed.
  • -
  • The structural pseudo‑classes are not supported.
  • The ":active" and ":focus" dynamic pseudo‑classes are not supported. However, Nodes do support the ":pressed" and ":focused" pseudo‑classes, which are similar.
  • @@ -1529,7 +1527,7 @@

    Looked-up Colors <looke with the "style" property on a node.

    In the following example, all background color of all buttons uses the looked up color "abc".

    -

    .root { abc: #f00 }
    +

    :root { abc: #f00 }
    .button { -fx-background-color: abc }

    RGB Colors <rgb-color>

    The RGB color model is used in numerical color specifications. It has a @@ -1825,11 +1823,11 @@

    Stage

    Group

    -

    Style class: .root.popup

    +

    Style class: :root.popup

    PopupWindow does not have any properties that can be styled by CSS, but a PopupWindow does have its own Scene. - Scene's root gets the .root style-class by default. If the Scene is the root scene of a PopupWindow, then the + Scene's root gets the :root pseudo-class by default. If the Scene is the root scene of a PopupWindow, then the .popup style-class is also added. This allows the root scene of a PopupWindow to have distinct styles via - the CSS rule .root.popup { /* declarations */ } + the CSS rule :root.popup { /* declarations */ }

    Nodes

    @@ -2002,41 +2000,63 @@

    Node

    Pseudo-classes

    - - - - - - - - + + - + - + - + - + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Available CSS Pseudo-classes
    CSS Pseudo-classComments
    disabledapplies when the disabled variable is trueUser Action Pseudo-classesComments
    focusedfocused applies when the focused variable is true
    focus-visiblefocus-visible applies when the focusVisible variable is true
    focus-withinfocus-within applies when the focusWithin variable is true
    hoverhover applies when the hover variable is true
    pressedpressed applies when the pressed variable is true
    show-mnemonicapples when the mnemonic affordance (typically an underscore) - should be shown.Input Pseudo-classesComments
    disabledapplies when the disabled variable is true
    show-mnemonicapplies when the mnemonic affordance (typically an underscore) should be shown
    Tree-Structural Pseudo-classesComments
    first-childapplies when the node is the first child in its Parent container
    last-childapplies when the node is the last child in its Parent container
    only-childapplies when the node is the only child in its Parent container
    nth-child()applies when the node is the n-th child in its Parent container + (the only acceptable arguments are "even" and "odd")
    @@ -2062,6 +2082,25 @@

    Parent

    +

    Pseudo-classes

    + + + + + + + + + + + + + + + + + +
    Available CSS Pseudo-classes
    CSS Pseudo-classComments
    rootapplies when the Parent is the root node of a Scene or SubScene
    Also has all pseudo‑classes of Node

    Scene

    Style class: not applicable
    @@ -4294,7 +4333,7 @@

    PasswordField

    The PasswordField control has all the properties of TextField

    PopupControl

    PopupControl is also a PopupWindow and as such, its root node has the - style-class .root.popup

    + style-class :root.popup

    ProgressBar

    Style class: progress-bar

    diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/Parent.java b/modules/javafx.graphics/src/main/java/javafx/scene/Parent.java index 5fc016b3d7a..2e15da5fd9d 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/Parent.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/Parent.java @@ -40,6 +40,7 @@ import com.sun.javafx.util.Utils; import com.sun.javafx.collections.TrackableObservableList; import com.sun.javafx.collections.VetoableListDecorator; +import javafx.css.PseudoClass; import javafx.css.Selector; import com.sun.javafx.css.StyleManager; import com.sun.javafx.geom.BaseBounds; @@ -316,7 +317,25 @@ private List getOrderedChildren() { private boolean geomChanged; private boolean childSetModified; private final ObservableList children = new VetoableListDecorator(new TrackableObservableList() { + private static final PseudoClass FIRST_CHILD_PSEUDO_CLASS = PseudoClass.getPseudoClass("first-child"); + private static final PseudoClass LAST_CHILD_PSEUDO_CLASS = PseudoClass.getPseudoClass("last-child"); + private static final PseudoClass ONLY_CHILD_PSEUDO_CLASS = PseudoClass.getPseudoClass("only-child"); + private static final PseudoClass NTH_EVEN_CHILD_PSEUDO_CLASS = PseudoClass.getPseudoClass("nth-child(even)"); + private static final PseudoClass NTH_ODD_CHILD_PSEUDO_CLASS = PseudoClass.getPseudoClass("nth-child(odd)"); + private static final List ONLY_CHILD_CLASSES = List.of(ONLY_CHILD_PSEUDO_CLASS, + FIRST_CHILD_PSEUDO_CLASS, + LAST_CHILD_PSEUDO_CLASS); + + private static final List FIRST_CHILD_CLASSES = List.of(FIRST_CHILD_PSEUDO_CLASS); + + private static void toggleStructuralPseudoClasses(Node node, List active) { + node.pseudoClassStateChanged(FIRST_CHILD_PSEUDO_CLASS, active.contains(FIRST_CHILD_PSEUDO_CLASS)); + node.pseudoClassStateChanged(LAST_CHILD_PSEUDO_CLASS, active.contains(LAST_CHILD_PSEUDO_CLASS)); + node.pseudoClassStateChanged(ONLY_CHILD_PSEUDO_CLASS, active.contains(ONLY_CHILD_PSEUDO_CLASS)); + node.pseudoClassStateChanged(NTH_EVEN_CHILD_PSEUDO_CLASS, active.contains(NTH_EVEN_CHILD_PSEUDO_CLASS)); + node.pseudoClassStateChanged(NTH_ODD_CHILD_PSEUDO_CLASS, active.contains(NTH_ODD_CHILD_PSEUDO_CLASS)); + } @Override protected void onChanged(Change c) { @@ -324,6 +343,7 @@ protected void onChanged(Change c) { unmodifiableManagedChildren = null; boolean relayout = false; boolean viewOrderChildrenDirty = false; + int firstDirtyChildIndex = -1; if (childSetModified) { while (c.next()) { @@ -351,6 +371,14 @@ protected void onChanged(Change c) { if (n.isManaged()) { relayout = true; } + + toggleStructuralPseudoClasses(n, List.of()); + } + + // Sub-changes are sorted by their 'from' index, so it is sufficient to record + // the index of the first change to separate unchanged from changed elements. + if (firstDirtyChildIndex < 0) { + firstDirtyChildIndex = from; } // Mark viewOrderChildrenDirty if there is modification to children list @@ -402,6 +430,12 @@ protected void onChanged(Change c) { // If childSet was not modified, we still need to check whether the permutation // did change the layout layout_loop:while (c.next()) { + // Sub-changes are sorted by their 'from' index, so it is sufficient to record + // the index of the first change to separate unchanged from changed elements. + if (firstDirtyChildIndex < 0) { + firstDirtyChildIndex = c.getFrom(); + } + List removed = c.getRemoved(); for (int i = 0, removedSize = removed.size(); i < removedSize; ++i) { if (removed.get(i).isManaged()) { @@ -419,6 +453,31 @@ protected void onChanged(Change c) { } } + // Toggle the "only-child" / "first-child" pseudo-classes on the first child. + if (size() == 1) { + toggleStructuralPseudoClasses(getFirst(), ONLY_CHILD_CLASSES); + } else if (size() > 1 && firstDirtyChildIndex == 0) { + toggleStructuralPseudoClasses(getFirst(), FIRST_CHILD_CLASSES); + } + + // Clear the "last-child" pseudo-class if it was set on the last non-modified child. + if (firstDirtyChildIndex > 0) { + get(firstDirtyChildIndex - 1).pseudoClassStateChanged(LAST_CHILD_PSEUDO_CLASS, false); + } + + // Add the "last-child" pseudo-class to the last child. + if (size() > 0) { + getLast().pseudoClassStateChanged(LAST_CHILD_PSEUDO_CLASS, true); + } + + // Toggle the "nth-child(even)" and "nth-child(odd)" pseudo-classes on all modified children. + if (firstDirtyChildIndex >= 0) { + for (int i = firstDirtyChildIndex, max = size(); i < max; ++i) { + Node n = get(i); + n.pseudoClassStateChanged(NTH_EVEN_CHILD_PSEUDO_CLASS, i % 2 != 0); + n.pseudoClassStateChanged(NTH_ODD_CHILD_PSEUDO_CLASS, i % 2 == 0); + } + } // // Note that the styles of a child do not affect the parent or @@ -449,10 +508,8 @@ protected void onChanged(Change 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();