1 /*
   2  * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package test.javafx.scene;
  27 
  28 import test.javafx.scene.shape.TestUtils;
  29 import test.javafx.scene.shape.CircleTest;
  30 import com.sun.javafx.geom.BoxBounds;
  31 import com.sun.javafx.geom.PickRay;
  32 import com.sun.javafx.geom.transform.Affine2D;
  33 import com.sun.javafx.geom.transform.Affine3D;
  34 import com.sun.javafx.geom.transform.BaseTransform;
  35 import com.sun.javafx.geom.transform.Translate2D;
  36 import test.com.sun.javafx.pgstub.StubStage;
  37 import test.com.sun.javafx.pgstub.StubToolkit;
  38 import com.sun.javafx.scene.DirtyBits;
  39 import com.sun.javafx.scene.NodeHelper;
  40 import com.sun.javafx.scene.input.PickResultChooser;
  41 import com.sun.javafx.scene.shape.RectangleHelper;
  42 import com.sun.javafx.sg.prism.NGGroup;
  43 import com.sun.javafx.sg.prism.NGNode;
  44 import com.sun.javafx.sg.prism.NGRectangle;
  45 import test.com.sun.javafx.test.objects.TestScene;
  46 import test.com.sun.javafx.test.objects.TestStage;
  47 import com.sun.javafx.tk.Toolkit;
  48 import com.sun.javafx.util.Utils;
  49 import javafx.beans.property.*;
  50 import javafx.geometry.BoundingBox;
  51 import javafx.geometry.Bounds;
  52 import javafx.geometry.NodeOrientation;
  53 import javafx.geometry.Point2D;
  54 import javafx.geometry.Point3D;
  55 import javafx.scene.effect.DropShadow;
  56 import javafx.scene.effect.Effect;
  57 import javafx.scene.shape.*;
  58 import javafx.scene.transform.Rotate;
  59 import javafx.scene.transform.Transform;
  60 import org.junit.Rule;
  61 import org.junit.Test;
  62 import org.junit.rules.ExpectedException;
  63 
  64 import java.lang.reflect.Method;
  65 import java.util.Comparator;
  66 import javafx.scene.Group;
  67 import javafx.scene.Node;
  68 import javafx.scene.NodeShim;
  69 import javafx.scene.ParallelCamera;
  70 import javafx.scene.ParentShim;
  71 import javafx.scene.PerspectiveCamera;
  72 import javafx.scene.Scene;
  73 import javafx.scene.SceneShim;
  74 import javafx.scene.SubScene;
  75 import javafx.scene.layout.AnchorPane;
  76 import javafx.scene.transform.Affine;
  77 import javafx.scene.transform.Scale;
  78 import javafx.scene.transform.Shear;
  79 import javafx.scene.transform.Translate;
  80 import javafx.stage.Stage;
  81 
  82 import static org.junit.Assert.*;
  83 import org.junit.Ignore;
  84 /**
  85  * Tests various aspects of Node.
  86  *
  87  */
  88 public class NodeTest {
  89     @Rule
  90     public ExpectedException thrown = ExpectedException.none();
  91 
  92     // Things to test:
  93         // When parent is changed, should cursor on toolkit change as well if
  94         // the current node has the mouse over it and didn't explicitly set a
  95         // cursor??
  96 
  97         // Test CSS integration
  98 
  99         // Events:
 100             // Events should *not* be delivered to invisible nodes as per the
 101             // specification for visible
 102 
 103         // A Node must lose focus when it is no longer visible
 104 
 105         // A node made invisible must cause the cursor to be updated
 106 
 107         // Setting the cursor should override the parent cursor when hover
 108         // (test that this happens both when the node already has hover set and
 109         // when hover is changed to true)
 110 
 111         // Setting the cursor to null should revert to parent cursor when hover
 112         // (test that this happens both when the node already has hover set and
 113         // when hover is changed to true)
 114 
 115         // Clip:
 116             // Test setting/clearing the clip affects the bounds
 117             // Test changing bounds / smooth / etc on clip updates bounds of clipped Node
 118 
 119         // Effect:
 120             // Test setting/clearing the effect affects the bounds
 121             // Test changing state on Effect updates bounds of Node
 122 
 123         // Test that a disabled Group affects the disabled property of child nodes
 124 
 125         // Test contains, intersects methods
 126         // Test parentToLocal/localToStage/etc
 127 
 128         // Test computeCompleteBounds
 129         // (other bounds test situtations explicitly tested in BoundsTest)
 130 
 131         // Test transforms end up setting the correct matrix on the peer
 132         // In particular, test that pivots are taken correctly into account
 133 
 134         // Test hover is updated when mouse enters
 135         // Test hover is updated when mouse exists
 136         // Test hover is updated when mouse was over but a higher node then
 137         // turns on blocks mouse
 138         // Test hover is updated when node moves out from under the cursor
 139         // TODO most of these cases cannot be handled until/unless we update
 140         // the list of nodes under the cursor on pulse events
 141 
 142         // Test pressed is updated when mouse is pressed
 143         // Test pressed is updated when mouse is released
 144         // TODO shoudl pressed obey the semantics of a button that is armed & pressed?
 145         // Or should "armed" be put on Node? What to do here?
 146 
 147         // Test various onMouseXXX event handlers
 148 
 149         // Test onKeyXXX handlers
 150 
 151         // Test focused is updated?
 152         // Test nodes which are not focusable are not focused!
 153         // Test focus... (SHOULD NOT DEPEND ON KEY LISTENERS BEING INSTALLED!!)
 154 
 155         // Test that clip is taken into account for both "contains" and
 156         // "intersects". See http://javafx-jira.kenai.com/browse/RT-646
 157 
 158 
 159 
 160     /***************************************************************************
 161      *                                                                         *
 162      *                              Basic Node Tests                           *
 163      *                                                                         *
 164      **************************************************************************/
 165 
 166 // TODO disable this because it depends on TestNode
 167 //    @Test public void testPeerNotifiedOfVisibilityChanges() {
 168 //        Rectangle rect = new Rectangle();
 169 //        Node peer = rect.impl_getPGNode();
 170 //        assertEquals(peer.visible, rect.visible);
 171 //
 172 //        rect.visible = false;
 173 //        assertEquals(peer.visible, rect.visible);
 174 //
 175 //        rect.visible = true;
 176 //        assertEquals(peer.visible, rect.visible);
 177 //    }
 178 
 179     /***************************************************************************
 180      *                                                                         *
 181      *                            Testing Node Bounds                          *
 182      *                                                                         *
 183      **************************************************************************/
 184 
 185 // TODO disable this because it depends on TestNode
 186 //     public function testContainsCallsPeer():Void {
 187 //         var rect = Rectangle { };
 188 //         var peer = rect.impl_getPGNode() as TestNode;
 189 //         peer.numTimesContainsCalled = 0;
 190 //
 191 //         rect.contains(0, 0);
 192 //         assertEquals(1, peer.numTimesContainsCalled);
 193 //
 194 //         rect.contains(Point2D { x:10, y:10 });
 195 //         assertEquals(2, peer.numTimesContainsCalled);
 196 //     }
 197 
 198 // TODO disable this because it depends on TestNode
 199 //     public function testIntersectsCallsPeer():Void {
 200 //         var rect = Rectangle { };
 201 //         var peer = rect.impl_getPGNode() as TestNode;
 202 //         peer.numTimesIntersectsCalled = 0;
 203 //
 204 //         rect.intersects(0, 0, 10, 10);
 205 //         assertEquals(1, peer.numTimesIntersectsCalled);
 206 //
 207 //         rect.intersects(BoundingBox { minX:10, minY:10, width:100, height:100 });
 208 //         assertEquals(2, peer.numTimesIntersectsCalled);
 209 //     }
 210 
 211     /***************************************************************************
 212      *                                                                         *
 213      *                          Testing Node transforms                        *
 214      *                                                                         *
 215      **************************************************************************/
 216 
 217     /**
 218      * Tests that the function which converts a com.sun.javafx.geom.Point2D
 219      * in parent coords to local coords works properly.
 220      */
 221     @Test public void testParentToLocalGeomPoint() {
 222         Rectangle rect = new Rectangle();
 223         rect.setTranslateX(10);
 224         rect.setTranslateY(10);
 225         rect.setWidth(100);
 226         rect.setHeight(100);
 227         rect.getTransforms().clear();
 228         rect.getTransforms().addAll(Transform.scale(2, 2), Transform.translate(30, 30));
 229 
 230         Point2D pt = new Point2D(0, 0);
 231         pt = rect.parentToLocal(pt);
 232         assertEquals(new Point2D(-35, -35), pt);
 233     }
 234 
 235     // TODO need to test with some observableArrayList of transforms which cannot be
 236     // cleanly inverted so that we can test that code path
 237 
 238     @Test public void testLocalToParentGeomPoint() {
 239         Rectangle rect = new Rectangle();
 240         rect.setTranslateX(10);
 241         rect.setTranslateY(10);
 242         rect.setWidth(100);
 243         rect.setHeight(100);
 244         rect.getTransforms().clear();
 245         rect.getTransforms().addAll(Transform.scale(2, 2), Transform.translate(30, 30));
 246 
 247         Point2D pt = new Point2D(0, 0);
 248         pt = rect.localToParent(pt);
 249         assertEquals(new Point2D(70, 70), pt);
 250     }
 251 
 252     @Test public void testPickingNodeDirectlyNoTransforms() {
 253         Rectangle rect = new Rectangle();
 254         rect.setX(10);
 255         rect.setY(10);
 256         rect.setWidth(100);
 257         rect.setHeight(100);
 258 
 259         // needed since picking doesn't work unless rooted in a scene and visible
 260         Scene scene = new Scene(new Group());
 261         ParentShim.getChildren(scene.getRoot()).add(rect);
 262 
 263         PickResultChooser res = new PickResultChooser();
 264         rect.impl_pickNode(new PickRay(50, 50, 1, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY), res);
 265         assertSame(rect, res.getIntersectedNode());
 266         res = new PickResultChooser();
 267         rect.impl_pickNode(new PickRay(0, 0, 1, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY), res);
 268         assertNull(res.getIntersectedNode());
 269     }
 270 
 271     @Test public void testPickingNodeDirectlyWithTransforms() {
 272         Rectangle rect = new Rectangle();
 273         rect.setTranslateX(10);
 274         rect.setTranslateY(10);
 275         rect.setWidth(100);
 276         rect.setHeight(100);
 277 
 278         // needed since picking doesn't work unless rooted in a scene and visible
 279         Scene scene = new Scene(new Group());
 280         ParentShim.getChildren(scene.getRoot()).add(rect);
 281 
 282         PickResultChooser res = new PickResultChooser();
 283         rect.impl_pickNode(new PickRay(50, 50, 1, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY), res);
 284         assertSame(rect, res.getIntersectedNode());
 285         res = new PickResultChooser();
 286         rect.impl_pickNode(new PickRay(0, 0, 1, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY), res);
 287         assertNull(res.getIntersectedNode());
 288     }
 289 
 290     @Test public void testEffectSharedOnNodes() {
 291         Effect effect = new DropShadow();
 292         Rectangle node = new Rectangle();
 293         node.setEffect(effect);
 294 
 295         Rectangle node2 = new Rectangle();
 296         node2.setEffect(effect);
 297 
 298         assertEquals(effect, node.getEffect());
 299         assertEquals(effect, node2.getEffect());
 300     }
 301 
 302     public static void testBooleanPropertyPropagation(
 303         final Node node,
 304         final String propertyName,
 305         final boolean initialValue,
 306         final boolean newValue) throws Exception {
 307 
 308         final StringBuilder propertyNameBuilder = new StringBuilder(propertyName);
 309         propertyNameBuilder.setCharAt(0, Character.toUpperCase(propertyName.charAt(0)));
 310         final String setterName = new StringBuilder("set").append(propertyNameBuilder).toString();
 311         final String getterName = new StringBuilder("is").append(propertyNameBuilder).toString();
 312 
 313         final Class<? extends Node> nodeClass = node.getClass();
 314         final Method setter = nodeClass.getMethod(setterName, boolean.class);
 315         final Method getter = nodeClass.getMethod(getterName);
 316 
 317         final NGNode peer = NodeHelper.getPeer(node);
 318         final Class<? extends NGNode> impl_class = peer.getClass();
 319         final Method impl_getter = impl_class.getMethod(getterName);
 320 
 321 
 322         // 1. Create test scene
 323         final Scene scene = new Scene(new Group());
 324         ParentShim.getChildren(scene.getRoot()).add(node);
 325 
 326         // 2. Initial setup
 327         setter.invoke(node, initialValue);
 328         NodeHelper.syncPeer(node);
 329         assertEquals(initialValue, getter.invoke(node));
 330         assertEquals(initialValue, impl_getter.invoke(peer));
 331 
 332         // 3. Change value of the property
 333         setter.invoke(node, newValue);
 334 
 335         // 4. Check that the property value has changed but has not propagated to PGNode
 336         assertEquals(newValue, getter.invoke(node));
 337         assertEquals(initialValue, impl_getter.invoke(peer));
 338 
 339         // 5. Propagate the property value to PGNode
 340         NodeHelper.syncPeer(node);
 341 
 342         // 6. Check that the value has been propagated to PGNode
 343         assertEquals(newValue, impl_getter.invoke(peer));
 344     }
 345 
 346 
 347     public static void testFloatPropertyPropagation(
 348         final Node node,
 349         final String propertyName,
 350         final float initialValue,
 351         final float newValue) throws Exception {
 352 
 353         testFloatPropertyPropagation(node, propertyName, propertyName, initialValue, newValue);
 354     }
 355 
 356     public static void syncNode(Node node) {
 357         NodeShim.updateBounds(node);
 358         NodeHelper.syncPeer(node);
 359     }
 360 
 361     public static void assertBooleanPropertySynced(
 362             final Node node,
 363             final String propertyName,
 364             final String pgPropertyName,
 365             final boolean value) throws Exception {
 366 
 367         final Scene scene = new Scene(new Group(), 500, 500);
 368 
 369         final StringBuilder propertyNameBuilder = new StringBuilder(propertyName);
 370         propertyNameBuilder.setCharAt(0, Character.toUpperCase(propertyName.charAt(0)));
 371         final String getterName = new StringBuilder("is").append(propertyNameBuilder).toString();
 372         Method getterMethod = node.getClass().getMethod(getterName, new Class[]{});
 373         Boolean defaultValue = (Boolean)getterMethod.invoke(node);
 374         BooleanProperty v = new SimpleBooleanProperty(defaultValue);
 375 
 376         Method modelMethod = node.getClass().getMethod(
 377                 propertyName + "Property",
 378                 new Class[]{});
 379         BooleanProperty model = (BooleanProperty)modelMethod.invoke(node);
 380         model.bind(v);
 381 
 382         ParentShim.getChildren(scene.getRoot()).add(node);
 383 
 384         NodeTest.syncNode(node);
 385         assertEquals(defaultValue, TestUtils.getBooleanValue(node, pgPropertyName));
 386 
 387         v.set(value);
 388         NodeTest.syncNode(node);
 389         assertEquals(value, TestUtils.getBooleanValue(node, pgPropertyName));
 390     }
 391 
 392     public static void assertIntPropertySynced(
 393             final Node node,
 394             final String propertyName,
 395             final String pgPropertyName,
 396             final int value) throws Exception {
 397 
 398         final Scene scene = new Scene(new Group(), 500, 500);
 399 
 400         final StringBuilder propertyNameBuilder = new StringBuilder(propertyName);
 401         propertyNameBuilder.setCharAt(0, Character.toUpperCase(propertyName.charAt(0)));
 402         final String getterName = new StringBuilder("get").append(propertyNameBuilder).toString();
 403         Method getterMethod = node.getClass().getMethod(getterName, new Class[]{});
 404         Integer defaultValue = (Integer)getterMethod.invoke(node);
 405         IntegerProperty v = new SimpleIntegerProperty(defaultValue);
 406 
 407         Method modelMethod = node.getClass().getMethod(
 408                 propertyName + "Property",
 409                 new Class[]{});
 410         IntegerProperty model = (IntegerProperty)modelMethod.invoke(node);
 411         model.bind(v);
 412 
 413         ParentShim.getChildren(scene.getRoot()).add(node);
 414 
 415         NodeTest.syncNode(node);
 416         assertTrue(numbersEquals(defaultValue,
 417                 (Number)TestUtils.getObjectValue(node, pgPropertyName)));
 418 
 419         v.set(value);
 420         NodeTest.syncNode(node);
 421         assertTrue(numbersEquals(new Integer(value),
 422                 (Number)TestUtils.getObjectValue(node, pgPropertyName)));
 423     }
 424 
 425     public static boolean numbersEquals(Number expected, Number value) {
 426         return numbersEquals(expected, value, 0.001);
 427     }
 428 
 429     public static boolean numbersEquals(Number expected, Number value, double delta) {
 430         boolean res = (Math.abs(expected.doubleValue() - value.doubleValue()) < delta);
 431         if (!res) {
 432             System.err.println("expected=" + expected + ", value=" + value);
 433         }
 434         return res;
 435     }
 436 
 437     public static void assertDoublePropertySynced(
 438             final Node node,
 439             final String propertyName,
 440             final String pgPropertyName,
 441             final double value) throws Exception {
 442 
 443         final Scene scene = new Scene(new Group(), 500, 500);
 444 
 445         final StringBuilder propertyNameBuilder = new StringBuilder(propertyName);
 446         propertyNameBuilder.setCharAt(0, Character.toUpperCase(propertyName.charAt(0)));
 447         final String getterName = new StringBuilder("get").append(propertyNameBuilder).toString();
 448         Method getterMethod = node.getClass().getMethod(getterName, new Class[]{});
 449         Double defaultValue = (Double)getterMethod.invoke(node);
 450         DoubleProperty v = new SimpleDoubleProperty(defaultValue);
 451 
 452         Method modelMethod = node.getClass().getMethod(
 453                 propertyName + "Property",
 454                 new Class[]{});
 455         DoubleProperty model = (DoubleProperty)modelMethod.invoke(node);
 456         model.bind(v);
 457 
 458         ParentShim.getChildren(scene.getRoot()).add(node);
 459 
 460         NodeTest.syncNode(node);
 461         assertTrue(numbersEquals(defaultValue,
 462                 (Number)TestUtils.getObjectValue(node, pgPropertyName)));
 463 
 464         v.set(value);
 465         NodeTest.syncNode(node);
 466         assertTrue(numbersEquals(new Double(value),
 467                 (Number)TestUtils.getObjectValue(node, pgPropertyName)));
 468     }
 469 
 470 
 471     public static void assertObjectPropertySynced(
 472             final Node node,
 473             final String propertyName,
 474             final String pgPropertyName,
 475             final Object value) throws Exception {
 476 
 477         final Scene scene = new Scene(new Group(), 500, 500);
 478 
 479         final StringBuilder propertyNameBuilder = new StringBuilder(propertyName);
 480         propertyNameBuilder.setCharAt(0, Character.toUpperCase(propertyName.charAt(0)));
 481         final String getterName = new StringBuilder("get").append(propertyNameBuilder).toString();
 482         Method getterMethod = node.getClass().getMethod(getterName, new Class[]{});
 483         Object defaultValue = getterMethod.invoke(node);
 484         ObjectProperty v = new SimpleObjectProperty(defaultValue);
 485 
 486         Method modelMethod = node.getClass().getMethod(
 487                 propertyName + "Property",
 488                 new Class[]{});
 489         ObjectProperty model = (ObjectProperty)modelMethod.invoke(node);
 490         model.bind(v);
 491 
 492         ParentShim.getChildren(scene.getRoot()).add(node);
 493 
 494         NodeTest.syncNode(node);
 495         // sometimes enum is used on node but int on PGNode
 496         Object result1 = TestUtils.getObjectValue(node, pgPropertyName);
 497         if (result1 instanceof Integer) {
 498             assertTrue(((Enum)defaultValue).ordinal() == ((Integer)result1).intValue());
 499         } else {
 500             assertEquals(defaultValue, TestUtils.getObjectValue(node, pgPropertyName));
 501         }
 502 
 503         v.set(value);
 504         NodeTest.syncNode(node);
 505 
 506         Object result2 = TestUtils.getObjectValue(node, pgPropertyName);
 507         if (result2 instanceof Integer) {
 508             assertTrue(((Enum)value).ordinal() == ((Integer)result2).intValue());
 509         } else {
 510             assertEquals(value, TestUtils.getObjectValue(node, pgPropertyName));
 511         }
 512     }
 513 
 514 
 515 
 516     public static void assertObjectProperty_AsStringSynced(
 517             final Node node,
 518             final String propertyName,
 519             final String pgPropertyName,
 520             final Object value) throws Exception {
 521 
 522         final Scene scene = new Scene(new Group(), 500, 500);
 523 
 524         final StringBuilder propertyNameBuilder = new StringBuilder(propertyName);
 525         propertyNameBuilder.setCharAt(0, Character.toUpperCase(propertyName.charAt(0)));
 526         final String getterName = new StringBuilder("get").append(propertyNameBuilder).toString();
 527         Method getterMethod = node.getClass().getMethod(getterName, new Class[]{});
 528         Object defaultValue = getterMethod.invoke(node);
 529         ObjectProperty v = new SimpleObjectProperty(defaultValue);
 530 
 531         Method modelMethod = node.getClass().getMethod(
 532                 propertyName + "Property",
 533                 new Class[]{});
 534         ObjectProperty model = (ObjectProperty)modelMethod.invoke(node);
 535         model.bind(v);
 536 
 537         ParentShim.getChildren(scene.getRoot()).add(node);
 538 
 539         NodeTest.syncNode(node);
 540         assertEquals(
 541                 defaultValue.toString(),
 542                 TestUtils.getObjectValue(node, pgPropertyName).toString());
 543 
 544         v.set(value);
 545         NodeTest.syncNode(node);
 546 
 547         assertEquals(
 548                 value.toString(),
 549                 TestUtils.getObjectValue(node, pgPropertyName).toString());
 550     }
 551 
 552     public static void assertStringPropertySynced(
 553             final Node node,
 554             final String propertyName,
 555             final String pgPropertyName,
 556             final String value) throws Exception {
 557 
 558         final Scene scene = new Scene(new Group(), 500, 500);
 559 
 560         final StringBuilder propertyNameBuilder = new StringBuilder(propertyName);
 561         propertyNameBuilder.setCharAt(0, Character.toUpperCase(propertyName.charAt(0)));
 562         final String getterName = new StringBuilder("get").append(propertyNameBuilder).toString();
 563         Method getterMethod = node.getClass().getMethod(getterName, new Class[]{});
 564         String defaultValue = (String)getterMethod.invoke(node);
 565         StringProperty v = new SimpleStringProperty(defaultValue);
 566 
 567         Method modelMethod = node.getClass().getMethod(
 568                 propertyName + "Property",
 569                 new Class[]{});
 570         StringProperty model = (StringProperty)modelMethod.invoke(node);
 571         model.bind(v);
 572 
 573         ParentShim.getChildren(scene.getRoot()).add(node);
 574 
 575         NodeTest.syncNode(node);
 576         assertEquals(
 577                 defaultValue,
 578                 TestUtils.getStringValue(node, pgPropertyName));
 579 
 580         v.set(value);
 581         NodeTest.syncNode(node);
 582 
 583         assertEquals(
 584                 value,
 585                 TestUtils.getStringValue(node, pgPropertyName));
 586     }
 587 
 588     public static void testFloatPropertyPropagation(
 589         final Node node,
 590         final String propertyName,
 591         final String pgPropertyName,
 592         final float initialValue,
 593         final float newValue) throws Exception {
 594 
 595         final StringBuilder propertyNameBuilder = new StringBuilder(propertyName);
 596         propertyNameBuilder.setCharAt(0, Character.toUpperCase(propertyName.charAt(0)));
 597 
 598         final StringBuilder pgPropertyNameBuilder = new StringBuilder(pgPropertyName);
 599         pgPropertyNameBuilder.setCharAt(0, Character.toUpperCase(pgPropertyName.charAt(0)));
 600 
 601         final String setterName = new StringBuilder("set").append(propertyNameBuilder).toString();
 602         final String getterName = new StringBuilder("get").append(propertyNameBuilder).toString();
 603         final String pgGetterName = new StringBuilder("get").append(pgPropertyNameBuilder).toString();
 604 
 605         final Class<? extends Node> nodeClass = node.getClass();
 606         final Method setter = nodeClass.getMethod(setterName, float.class);
 607         final Method getter = nodeClass.getMethod(getterName);
 608 
 609         final NGNode peer = NodeHelper.getPeer(node);
 610         final Class<? extends NGNode> impl_class = peer.getClass();
 611         final Method impl_getter = impl_class.getMethod(pgGetterName);
 612 
 613 
 614         // 1. Create test scene
 615         final Scene scene = new Scene(new Group());
 616         ParentShim.getChildren(scene.getRoot()).add(node);
 617 
 618         // 2. Initial setup
 619         setter.invoke(node, initialValue);
 620         NodeHelper.syncPeer(node);
 621         assertEquals(initialValue, (Float) getter.invoke(node), 1e-100);
 622         assertEquals(initialValue, (Float) impl_getter.invoke(peer), 1e-100);
 623 
 624         // 3. Change value of the property
 625         setter.invoke(node, newValue);
 626 
 627         // 4. Check that the property value has changed but has not propagated to PGNode
 628         assertEquals(newValue, (Float) getter.invoke(node), 1e-100);
 629         assertEquals(initialValue, (Float) impl_getter.invoke(peer), 1e-100);
 630 
 631         // 5. Propagate the property value to PGNode
 632         NodeHelper.syncPeer(node);
 633 
 634         // 6. Check that the value has been propagated to PGNode
 635         assertEquals(newValue, (Float) impl_getter.invoke(peer), 1e-100);
 636     }
 637 
 638     public static void testDoublePropertyPropagation(
 639         final Node node,
 640         final String propertyName,
 641         final double initialValue,
 642         final double newValue) throws Exception {
 643 
 644         testDoublePropertyPropagation(node, propertyName, propertyName, initialValue, newValue);
 645     }
 646 
 647 
 648     public static void testDoublePropertyPropagation(
 649         final Node node,
 650         final String propertyName,
 651         final String pgPropertyName,
 652         final double initialValue,
 653         final double newValue) throws Exception {
 654 
 655         final StringBuilder propertyNameBuilder = new StringBuilder(propertyName);
 656         propertyNameBuilder.setCharAt(0, Character.toUpperCase(propertyName.charAt(0)));
 657 
 658         final StringBuilder pgPropertyNameBuilder = new StringBuilder(pgPropertyName);
 659         pgPropertyNameBuilder.setCharAt(0, Character.toUpperCase(pgPropertyName.charAt(0)));
 660 
 661         final String setterName = new StringBuilder("set").append(propertyNameBuilder).toString();
 662         final String getterName = new StringBuilder("get").append(propertyNameBuilder).toString();
 663         final String pgGetterName = new StringBuilder("get").append(pgPropertyNameBuilder).toString();
 664 
 665         final Class<? extends Node> nodeClass = node.getClass();
 666         final Method setter = nodeClass.getMethod(setterName, double.class);
 667         final Method getter = nodeClass.getMethod(getterName);
 668 
 669         final NGNode peer = NodeHelper.getPeer(node);
 670         final Class<? extends NGNode> impl_class = peer.getClass();
 671         final Method impl_getter = impl_class.getMethod(pgGetterName);
 672 
 673 
 674         // 1. Create test scene
 675         final Scene scene = new Scene(new Group());
 676         ParentShim.getChildren(scene.getRoot()).add(node);
 677 
 678         // 2. Initial setup
 679         setter.invoke(node, initialValue);
 680         NodeHelper.syncPeer(node);
 681         assertEquals(initialValue, (Double) getter.invoke(node), 1e-100);
 682         assertEquals((float) initialValue, (Float) impl_getter.invoke(peer), 1e-100);
 683 
 684         // 3. Change value of the property
 685         setter.invoke(node, newValue);
 686 
 687         // 4. Check that the property value has changed but has not propagated to PGNode
 688         assertEquals(newValue, (Double) getter.invoke(node), 1e-100);
 689         assertEquals((float) initialValue, (Float) impl_getter.invoke(peer), 1e-100);
 690 
 691         // 5. Propagate the property value to PGNode
 692         NodeHelper.syncPeer(node);
 693 
 694         // 6. Check that the value has been propagated to PGNode
 695         assertEquals((float) newValue, (Float) impl_getter.invoke(peer), 1e-100);
 696     }
 697 
 698     public interface ObjectValueConvertor {
 699         Object toSg(Object pgValue);
 700     }
 701 
 702     public static final Comparator DEFAULT_OBJ_COMPARATOR =
 703             (sgValue, pgValue) -> {
 704                 assertEquals(sgValue, pgValue);
 705                 return 0;
 706             };
 707 
 708     public static void testObjectPropertyPropagation(
 709         final Node node,
 710         final String propertyName,
 711         final Object initialValue,
 712         final Object newValue) throws Exception {
 713 
 714         testObjectPropertyPropagation(node, propertyName, propertyName, initialValue, newValue);
 715     }
 716 
 717     public static void testObjectPropertyPropagation(
 718             final Node node,
 719             final String propertyName,
 720             final String pgPropertyName,
 721             final Object initialValue,
 722             final Object newValue) throws Exception {
 723         testObjectPropertyPropagation(node, propertyName, pgPropertyName,
 724                 initialValue, newValue, DEFAULT_OBJ_COMPARATOR);
 725     }
 726 
 727     public static void testObjectPropertyPropagation(
 728             final Node node,
 729             final String propertyName,
 730             final String pgPropertyName,
 731             final Object initialValue,
 732             final Object newValue,
 733             final ObjectValueConvertor convertor) throws Exception {
 734         testObjectPropertyPropagation(
 735                 node, propertyName, pgPropertyName,
 736                 initialValue, newValue,
 737                 (sgValue, pgValue) -> {
 738                     assertEquals(sgValue, convertor.toSg(pgValue));
 739                     return 0;
 740                 }
 741         );
 742     }
 743 
 744     public static void testObjectPropertyPropagation(
 745             final Node node,
 746             final String propertyName,
 747             final String pgPropertyName,
 748             final Object initialValue,
 749             final Object newValue,
 750             final Comparator comparator) throws Exception {
 751         final StringBuilder propertyNameBuilder = new StringBuilder(propertyName);
 752         propertyNameBuilder.setCharAt(0, Character.toUpperCase(propertyName.charAt(0)));
 753 
 754         final StringBuilder pgPropertyNameBuilder = new StringBuilder(pgPropertyName);
 755         pgPropertyNameBuilder.setCharAt(0, Character.toUpperCase(pgPropertyName.charAt(0)));
 756 
 757         final String setterName = new StringBuilder("set").append(propertyNameBuilder).toString();
 758         final String getterName = new StringBuilder("get").append(propertyNameBuilder).toString();
 759         final String pgGetterName = new StringBuilder("get").append(pgPropertyNameBuilder).toString();
 760 
 761         final Class<? extends Node> nodeClass = node.getClass();
 762         final Method getter = nodeClass.getMethod(getterName);
 763         final Method setter = nodeClass.getMethod(setterName, getter.getReturnType());
 764 
 765         final NGNode peer = NodeHelper.getPeer(node);
 766         final Class<? extends NGNode> impl_class = peer.getClass();
 767         final Method impl_getter = impl_class.getMethod(pgGetterName);
 768 
 769 
 770         // 1. Create test scene
 771         final Scene scene = new Scene(new Group());
 772         ParentShim.getChildren(scene.getRoot()).add(node);
 773 
 774         // 2. Initial setup
 775         setter.invoke(node, initialValue);
 776         NodeHelper.syncPeer(node);
 777         assertEquals(initialValue, getter.invoke(node));
 778         assertEquals(0, comparator.compare(initialValue,
 779                                            impl_getter.invoke(peer)));
 780 
 781         // 3. Change value of the property
 782         setter.invoke(node, newValue);
 783 
 784         // 4. Check that the property value has changed but has not propagated to PGNode
 785         assertEquals(newValue, getter.invoke(node));
 786         assertEquals(0, comparator.compare(initialValue,
 787                                            impl_getter.invoke(peer)));
 788 
 789         // 5. Propagate the property value to PGNode
 790         NodeHelper.syncPeer(node);
 791 
 792         // 6. Check that the value has been propagated to PGNode
 793         assertEquals(0, comparator.compare(newValue,
 794                                            impl_getter.invoke(peer)));
 795     }
 796 
 797 
 798     public static void testIntPropertyPropagation(
 799         final Node node,
 800         final String propertyName,
 801         final int initialValue,
 802         final int newValue) throws Exception {
 803 
 804         testIntPropertyPropagation(node, propertyName, propertyName, initialValue, newValue);
 805     }
 806 
 807 
 808     public static void testIntPropertyPropagation(
 809         final Node node,
 810         final String propertyName,
 811         final String pgPropertyName,
 812         final int initialValue,
 813         final int newValue) throws Exception {
 814 
 815         final StringBuilder propertyNameBuilder = new StringBuilder(propertyName);
 816         propertyNameBuilder.setCharAt(0, Character.toUpperCase(propertyName.charAt(0)));
 817 
 818         final StringBuilder pgPropertyNameBuilder = new StringBuilder(pgPropertyName);
 819         pgPropertyNameBuilder.setCharAt(0, Character.toUpperCase(pgPropertyName.charAt(0)));
 820 
 821         final String setterName = new StringBuilder("set").append(propertyNameBuilder).toString();
 822         final String getterName = new StringBuilder("get").append(propertyNameBuilder).toString();
 823         final String pgGetterName = new StringBuilder("get").append(pgPropertyNameBuilder).toString();
 824 
 825         final Class<? extends Node> nodeClass = node.getClass();
 826         final Method getter = nodeClass.getMethod(getterName);
 827         final Method setter = nodeClass.getMethod(setterName, getter.getReturnType());
 828 
 829         final NGNode peer = NodeHelper.getPeer(node);
 830         final Class<? extends NGNode> impl_class = peer.getClass();
 831         final Method impl_getter = impl_class.getMethod(pgGetterName);
 832 
 833 
 834         // 1. Create test scene
 835         final Scene scene = new Scene(new Group());
 836         ParentShim.getChildren(scene.getRoot()).add(node);
 837 
 838         // 2. Initial setup
 839         setter.invoke(node, initialValue);
 840         assertEquals(initialValue, getter.invoke(node));
 841         NodeHelper.syncPeer(node);
 842         assertEquals(initialValue, ((Number) impl_getter.invoke(peer)).intValue());
 843 
 844         // 3. Change value of the property
 845         setter.invoke(node, newValue);
 846 
 847         // 4. Check that the property value has changed but has not propagated to PGNode
 848         assertEquals(newValue, getter.invoke(node));
 849         assertEquals(initialValue, ((Number) impl_getter.invoke(peer)).intValue());
 850 
 851         // 5. Propagate the property value to PGNode
 852         NodeHelper.syncPeer(node);
 853 
 854         // 6. Check that the value has been propagated to PGNode
 855         assertEquals(newValue, ((Number) impl_getter.invoke(peer)).intValue());
 856     }
 857 
 858     public static void callSyncPGNode(final Node node) {
 859         NodeHelper.syncPeer(node);
 860     }
 861 
 862     @Test
 863     public void testToFront() {
 864         Rectangle rect1 = new Rectangle();
 865         Rectangle rect2 = new Rectangle();
 866         Group g = new Group();
 867 
 868         Scene scene = new Scene(g);
 869         ParentShim.getChildren(g).add(rect1);
 870         ParentShim.getChildren(g).add(rect2);
 871 
 872         rect1.toFront();
 873         rect2.toFront();
 874 
 875         // toFront should not remove rectangle from scene
 876         assertEquals(scene, rect2.getScene());
 877         assertEquals(scene, rect1.getScene());
 878         // test corect order of scene content
 879         assertEquals(rect2, ParentShim.getChildren(g).get(1));
 880         assertEquals(rect1, ParentShim.getChildren(g).get(0));
 881 
 882         rect1.toFront();
 883         assertEquals(scene, rect2.getScene());
 884         assertEquals(scene, rect1.getScene());
 885         assertEquals(rect1, ParentShim.getChildren(g).get(1));
 886         assertEquals(rect2, ParentShim.getChildren(g).get(0));
 887     }
 888 
 889     @Test
 890     public void testClip() {
 891         Rectangle rect1 = new Rectangle();
 892         Rectangle rect2 = new Rectangle();
 893         rect1.setClip(rect2);
 894 
 895         Scene scene = new Scene(new Group());
 896         ParentShim.getChildren(scene.getRoot()).add(rect1);
 897         assertEquals(rect2, rect1.getClip());
 898         assertEquals(scene, rect2.getScene());
 899 
 900     }
 901 
 902     @Test
 903     public void testInvalidClip() {
 904         Rectangle rectA = new Rectangle(300, 300);
 905         Rectangle clip1 = new Rectangle(10, 10);
 906         Rectangle clip2 = new Rectangle(100, 100);
 907         clip2.setClip(rectA);
 908         rectA.setClip(clip1);
 909         assertEquals(rectA.getClip(), clip1);
 910         thrown.expect(IllegalArgumentException.class);
 911         try {
 912             rectA.setClip(clip2);
 913         } catch (final IllegalArgumentException e) {
 914             assertNotSame(rectA.getClip(), clip2);
 915             throw e;
 916         }
 917     }
 918 
 919     @Test public void testProperties() {
 920         Rectangle node = new Rectangle();
 921         javafx.collections.ObservableMap<Object, Object> properties = node.getProperties();
 922 
 923         /* If we ask for it, we should get it.
 924          */
 925         assertNotNull(properties);
 926 
 927         /* What we put in, we should get out.
 928          */
 929         properties.put("MyKey", "MyValue");
 930         assertEquals("MyValue", properties.get("MyKey"));
 931 
 932         /* If we ask for it again, we should get the same thing.
 933          */
 934         javafx.collections.ObservableMap<Object, Object> properties2 = node.getProperties();
 935         assertEquals(properties2, properties);
 936 
 937         /* What we put in to the other one, we should get out of this one because
 938          * they should be the same thing.
 939          */
 940         assertEquals("MyValue", properties2.get("MyKey"));
 941     }
 942 
 943     public static boolean isDirty(Node node, DirtyBits[] dbs) {
 944         for(DirtyBits db:dbs) {
 945             if (!NodeShim.isDirty(node, db)) {
 946                 System.out.printf("@NodeTest:check dirty: %s [%d]\n",db,db.ordinal());
 947                 return false;
 948             }
 949         }
 950         return true;
 951     }
 952 
 953     @Test
 954     public void testDefaultValueForViewOrderIsZeroWhenReadFromGetter() {
 955         final Node node = new Rectangle();
 956         assertEquals(0, node.getViewOrder(), .005);
 957     }
 958 
 959     @Test
 960     public void testDefaultValueForViewOrderIsZeroWhenReadFromProperty() {
 961         final Node node = new Rectangle();
 962         assertEquals(0, node.viewOrderProperty().get(), .005);
 963     }
 964 
 965     @Test
 966     public void settingViewOrderThroughSetterShouldAffectBothGetterAndProperty() {
 967         final Node node = new Rectangle();
 968         node.setViewOrder(.5);
 969         assertEquals(.5, node.getViewOrder(), .005);
 970         assertEquals(.5, node.viewOrderProperty().get(), .005);
 971     }
 972 
 973     @Test
 974     public void settingViewOrderThroughPropertyShouldAffectBothGetterAndProperty() {
 975         final Node node = new Rectangle();
 976         node.viewOrderProperty().set(.5);
 977         assertEquals(.5, node.getViewOrder(), .005);
 978         assertEquals(.5, node.viewOrderProperty().get(), .005);
 979     }
 980 
 981     @Test
 982     public void testDefaultValueForOpacityIsOneWhenReadFromGetter() {
 983         final Node node = new Rectangle();
 984         assertEquals(1, node.getOpacity(), .005);
 985     }
 986 
 987     @Test
 988     public void testDefaultValueForOpacityIsOneWhenReadFromProperty() {
 989         final Node node = new Rectangle();
 990         assertEquals(1, node.opacityProperty().get(), .005);
 991     }
 992 
 993     @Test
 994     public void settingOpacityThroughSetterShouldAffectBothGetterAndProperty() {
 995         final Node node = new Rectangle();
 996         node.setOpacity(.5);
 997         assertEquals(.5, node.getOpacity(), .005);
 998         assertEquals(.5, node.opacityProperty().get(), .005);
 999     }
1000 
1001     @Test
1002     public void settingOpacityThroughPropertyShouldAffectBothGetterAndProperty() {
1003         final Node node = new Rectangle();
1004         node.opacityProperty().set(.5);
1005         assertEquals(.5, node.getOpacity(), .005);
1006         assertEquals(.5, node.opacityProperty().get(), .005);
1007     }
1008 
1009     @Test
1010     public void testDefaultValueForVisibleIsTrueWhenReadFromGetter() {
1011         final Node node = new Rectangle();
1012         assertTrue(node.isVisible());
1013     }
1014 
1015     @Test
1016     public void testDefaultValueForVisibleIsTrueWhenReadFromProperty() {
1017         final Node node = new Rectangle();
1018         assertTrue(node.visibleProperty().get());
1019     }
1020 
1021     @Test
1022     public void settingVisibleThroughSetterShouldAffectBothGetterAndProperty() {
1023         final Node node = new Rectangle();
1024         node.setVisible(false);
1025         assertFalse(node.isVisible());
1026         assertFalse(node.visibleProperty().get());
1027     }
1028 
1029     @Test
1030     public void settingVisibleThroughPropertyShouldAffectBothGetterAndProperty() {
1031         final Node node = new Rectangle();
1032         node.visibleProperty().set(false);
1033         assertFalse(node.isVisible());
1034         assertFalse(node.visibleProperty().get());
1035     }
1036 
1037     @Test
1038     public void testDefaultStyleIsEmptyString() {
1039         final Node node = new Rectangle();
1040         assertEquals("", node.getStyle());
1041         assertEquals("", node.styleProperty().get());
1042         node.setStyle(null);
1043         assertEquals("", node.styleProperty().get());
1044         assertEquals("", node.getStyle());
1045     }
1046 
1047     @Test
1048     public void testSynchronizationOfInvisibleNodes() {
1049         final Group g = new Group();
1050         final Circle c = new CircleTest.StubCircle(50);
1051         final NGGroup sg = NodeHelper.getPeer(g);
1052         final CircleTest.StubNGCircle sc = NodeHelper.getPeer(c);
1053         ParentShim.getChildren(g).add(c);
1054 
1055         syncNode(g);
1056         syncNode(c);
1057         assertFalse(sg.getChildren().isEmpty());
1058         assertEquals(50.0, sc.getRadius(), 0.01);
1059 
1060         g.setVisible(false);
1061 
1062         syncNode(g);
1063         syncNode(c);
1064         assertFalse(sg.isVisible());
1065 
1066         final Rectangle r = new Rectangle();
1067         ParentShim.getChildren(g).add(r);
1068         c.setRadius(100);
1069 
1070         syncNode(g);
1071         syncNode(c);
1072         // Group with change in children will always be synced even if it is invisible
1073         assertEquals(2, sg.getChildren().size());
1074         assertEquals(50.0, sc.getRadius(), 0.01);
1075 
1076         g.setVisible(true);
1077 
1078         syncNode(g);
1079         syncNode(c);
1080         assertEquals(2, sg.getChildren().size());
1081         assertEquals(100.0, sc.getRadius(), 0.01);
1082 
1083     }
1084 
1085     @Test
1086     public void testSynchronizationOfInvisibleNodes_2() {
1087         final Group g = new Group();
1088         final Circle c = new CircleTest.StubCircle(50);
1089 
1090         Scene s = new Scene(g);
1091         Stage st = new Stage();
1092         st.show();
1093         st.setScene(s);
1094 
1095         final NGGroup sg = NodeHelper.getPeer(g);
1096         final CircleTest.StubNGCircle sc = NodeHelper.getPeer(c);
1097 
1098         ParentShim.getChildren(g).add(c);
1099 
1100         SceneShim.scenePulseListener_pulse(s);
1101 
1102         g.setVisible(false);
1103 
1104         SceneShim.scenePulseListener_pulse(s);
1105 
1106         assertFalse(sg.isVisible());
1107         assertTrue(sc.isVisible());
1108 
1109         c.setCenterX(10);             // Make the circle dirty. It won't be synchronized as it is practically invisible (through the parent)
1110 
1111         SceneShim.scenePulseListener_pulse(s);
1112 
1113         c.setVisible(false);         // As circle is invisible and dirty, this won't trigger a synchronization
1114 
1115         SceneShim.scenePulseListener_pulse(s);
1116 
1117         assertFalse(sg.isVisible());
1118         assertTrue(sc.isVisible()); // This has not been synchronized, as it's not necessary
1119                                     // The rendering will stop at the Group, which is invisible
1120 
1121         g.setVisible(true);
1122 
1123         SceneShim.scenePulseListener_pulse(s);
1124 
1125         assertTrue(sg.isVisible());
1126         assertFalse(sc.isVisible()); // Now the group is visible again, we need to synchronize also
1127                                      // the Circle
1128     }
1129 
1130     @Test
1131     public void testSynchronizationOfInvisibleNodes_2_withClip() {
1132         final Group g = new Group();
1133         final Circle c = new CircleTest.StubCircle(50);
1134 
1135         Scene s = new Scene(g);
1136         Stage st = new Stage();
1137         st.show();
1138         st.setScene(s);
1139 
1140         final NGGroup sg = NodeHelper.getPeer(g);
1141         final CircleTest.StubNGCircle sc = NodeHelper.getPeer(c);
1142 
1143         g.setClip(c);
1144 
1145         SceneShim.scenePulseListener_pulse(s);
1146 
1147         g.setVisible(false);
1148 
1149         SceneShim.scenePulseListener_pulse(s);
1150 
1151         assertFalse(sg.isVisible());
1152         assertTrue(sc.isVisible());
1153 
1154         c.setCenterX(10);             // Make the circle dirty. It won't be synchronized as it is practically invisible (through the parent)
1155 
1156         SceneShim.scenePulseListener_pulse(s);
1157 
1158         c.setVisible(false);         // As circle is invisible and dirty, this won't trigger a synchronization
1159 
1160         SceneShim.scenePulseListener_pulse(s);
1161 
1162         assertFalse(sg.isVisible());
1163         assertTrue(sc.isVisible()); // This has not been synchronized, as it's not necessary
1164                                     // The rendering will stop at the Group, which is invisible
1165 
1166         g.setVisible(true);
1167 
1168         SceneShim.scenePulseListener_pulse(s);
1169 
1170         assertTrue(sg.isVisible());
1171         assertFalse(sc.isVisible()); // Now the group is visible again, we need to synchronize also
1172                                      // the Circle
1173     }
1174 
1175     @Test
1176     public void testLocalToScreen() {
1177         Rectangle rect = new Rectangle();
1178 
1179         rect.setTranslateX(10);
1180         rect.setTranslateY(20);
1181 
1182         TestScene scene = new TestScene(new Group(rect));
1183         final TestStage testStage = new TestStage("");
1184         testStage.setX(100);
1185         testStage.setY(200);
1186         scene.set_window(testStage);
1187         Point2D p = rect.localToScreen(new Point2D(1, 2));
1188         assertEquals(111.0, p.getX(), 0.0001);
1189         assertEquals(222.0, p.getY(), 0.0001);
1190         Bounds b = rect.localToScreen(new BoundingBox(1, 2, 3, 4));
1191         assertEquals(111.0, b.getMinX(), 0.0001);
1192         assertEquals(222.0, b.getMinY(), 0.0001);
1193         assertEquals(3.0, b.getWidth(), 0.0001);
1194         assertEquals(4.0, b.getHeight(), 0.0001);
1195     }
1196 
1197     @Test
1198     public void testLocalToScreen3D() {
1199         Box box = new Box(10, 10, 10);
1200 
1201         box.setTranslateX(10);
1202         box.setTranslateY(20);
1203 
1204         TestScene scene = new TestScene(new Group(box));
1205         scene.setCamera(new PerspectiveCamera());
1206         final TestStage testStage = new TestStage("");
1207         testStage.setX(100);
1208         testStage.setY(200);
1209         scene.set_window(testStage);
1210 
1211         Point2D p = box.localToScreen(new Point3D(1, 2, -5));
1212         assertEquals(111.42, p.getX(), 0.1);
1213         assertEquals(223.14, p.getY(), 0.1);
1214         Bounds b = box.localToScreen(new BoundingBox(1, 2, -5, 1, 2, 10));
1215         assertEquals(110.66, b.getMinX(), 0.1);
1216         assertEquals(221.08, b.getMinY(), 0.1);
1217         assertEquals(1.88, b.getWidth(), 0.1);
1218         assertEquals(4.3, b.getHeight(), 0.1);
1219         assertEquals(0.0, b.getDepth(), 0.0001);
1220     }
1221 
1222     @Test
1223     public void testScreenToLocal() {
1224         Rectangle rect = new Rectangle();
1225 
1226         rect.setTranslateX(10);
1227         rect.setTranslateY(20);
1228 
1229         TestScene scene = new TestScene(new Group(rect));
1230         final TestStage testStage = new TestStage("");
1231         testStage.setX(100);
1232         testStage.setY(200);
1233         scene.set_window(testStage);
1234 
1235         assertEquals(new Point2D(1, 2), rect.screenToLocal(new Point2D(111, 222)));
1236         assertEquals(new BoundingBox(1, 2, 3, 4), rect.screenToLocal(new BoundingBox(111, 222, 3, 4)));
1237     }
1238 
1239     @Test
1240     public void testLocalToScreenWithTranslatedCamera() {
1241         Rectangle rect = new Rectangle();
1242 
1243         rect.setTranslateX(10);
1244         rect.setTranslateY(20);
1245 
1246         ParallelCamera cam = new ParallelCamera();
1247         TestScene scene = new TestScene(new Group(rect, cam));
1248         scene.setCamera(cam);
1249         final TestStage testStage = new TestStage("");
1250         testStage.setX(100);
1251         testStage.setY(200);
1252         cam.setTranslateX(30);
1253         cam.setTranslateY(20);
1254         scene.set_window(testStage);
1255 
1256         Point2D p = rect.localToScreen(new Point2D(1, 2));
1257         assertEquals(81.0, p.getX(), 0.0001);
1258         assertEquals(202.0, p.getY(), 0.0001);
1259         Bounds b = rect.localToScreen(new BoundingBox(1, 2, 3, 4));
1260         assertEquals(81.0, b.getMinX(), 0.0001);
1261         assertEquals(202.0, b.getMinY(), 0.0001);
1262         assertEquals(3.0, b.getWidth(), 0.0001);
1263         assertEquals(4.0, b.getHeight(), 0.0001);
1264     }
1265 
1266     @Test
1267     public void testScreenToLocalWithTranslatedCamera() {
1268         Rectangle rect = new Rectangle();
1269 
1270         rect.setTranslateX(10);
1271         rect.setTranslateY(20);
1272 
1273         ParallelCamera cam = new ParallelCamera();
1274         TestScene scene = new TestScene(new Group(rect, cam));
1275         scene.setCamera(cam);
1276         final TestStage testStage = new TestStage("");
1277         testStage.setX(100);
1278         testStage.setY(200);
1279         cam.setTranslateX(30);
1280         cam.setTranslateY(20);
1281         scene.set_window(testStage);
1282 
1283         assertEquals(new Point2D(31, 22), rect.screenToLocal(new Point2D(111, 222)));
1284         assertEquals(new BoundingBox(31, 22, 3, 4), rect.screenToLocal(new BoundingBox(111, 222, 3, 4)));
1285     }
1286 
1287     @Test
1288     public void testLocalToScreenInsideSubScene() {
1289         Rectangle rect = new Rectangle();
1290         rect.setTranslateX(4);
1291         rect.setTranslateY(9);
1292         SubScene subScene = new SubScene(new Group(rect), 100, 100);
1293         subScene.setTranslateX(6);
1294         subScene.setTranslateY(11);
1295 
1296         TestScene scene = new TestScene(new Group(subScene));
1297         final TestStage testStage = new TestStage("");
1298         testStage.setX(100);
1299         testStage.setY(200);
1300         scene.set_window(testStage);
1301 
1302         Point2D p = rect.localToScreen(new Point2D(1, 2));
1303         assertEquals(111.0, p.getX(), 0.0001);
1304         assertEquals(222.0, p.getY(), 0.0001);
1305         Bounds b = rect.localToScreen(new BoundingBox(1, 2, 3, 4));
1306         assertEquals(111.0, b.getMinX(), 0.0001);
1307         assertEquals(222.0, b.getMinY(), 0.0001);
1308         assertEquals(3.0, b.getWidth(), 0.0001);
1309         assertEquals(4.0, b.getHeight(), 0.0001);
1310     }
1311 
1312     @Test
1313     public void testScreenToLocalInsideSubScene() {
1314         Rectangle rect = new Rectangle();
1315         rect.setTranslateX(4);
1316         rect.setTranslateY(9);
1317         SubScene subScene = new SubScene(new Group(rect), 100, 100);
1318         subScene.setTranslateX(6);
1319         subScene.setTranslateY(11);
1320 
1321         TestScene scene = new TestScene(new Group(subScene));
1322         final TestStage testStage = new TestStage("");
1323         testStage.setX(100);
1324         testStage.setY(200);
1325         scene.set_window(testStage);
1326 
1327         assertEquals(new Point2D(1, 2), rect.screenToLocal(new Point2D(111, 222)));
1328         assertEquals(new BoundingBox(1, 2, 3, 4), rect.screenToLocal(new BoundingBox(111, 222, 3, 4)));
1329     }
1330 
1331     @Test
1332     public void test2DLocalToScreenOn3DRotatedSubScene() {
1333         Rectangle rect = new Rectangle();
1334         rect.setTranslateX(5);
1335         rect.setTranslateY(10);
1336         SubScene subScene = new SubScene(new Group(rect), 100, 100);
1337         subScene.setTranslateX(5);
1338         subScene.setTranslateY(10);
1339         subScene.setRotationAxis(Rotate.Y_AXIS);
1340         subScene.setRotate(40);
1341 
1342         TestScene scene = new TestScene(new Group(subScene));
1343         scene.setCamera(new PerspectiveCamera());
1344         final TestStage testStage = new TestStage("");
1345         testStage.setX(100);
1346         testStage.setY(200);
1347         scene.set_window(testStage);
1348 
1349         Point2D p = rect.localToScreen(new Point2D(1, 2));
1350         assertEquals(124.36, p.getX(), 0.1);
1351         assertEquals(226.0, p.getY(), 0.1);
1352         Bounds b = rect.localToScreen(new BoundingBox(1, 2, 3, 4));
1353         assertEquals(124.36, b.getMinX(), 0.1);
1354         assertEquals(225.75, b.getMinY(), 0.1);
1355         assertEquals(1.85, b.getWidth(), 0.1);
1356         assertEquals(3.76, b.getHeight(), 0.1);
1357     }
1358 
1359     @Test
1360     public void test2DScreenToLocalTo3DRotatedSubScene() {
1361         Rectangle rect = new Rectangle();
1362         rect.setTranslateX(5);
1363         rect.setTranslateY(10);
1364         SubScene subScene = new SubScene(new Group(rect), 100, 100);
1365         subScene.setTranslateX(5);
1366         subScene.setTranslateY(10);
1367         subScene.setRotationAxis(Rotate.Y_AXIS);
1368         subScene.setRotate(40);
1369 
1370         TestScene scene = new TestScene(new Group(subScene));
1371         scene.setCamera(new PerspectiveCamera());
1372         final TestStage testStage = new TestStage("");
1373         testStage.setX(100);
1374         testStage.setY(200);
1375         scene.set_window(testStage);
1376 
1377         Point2D p = rect.screenToLocal(new Point2D(124.36, 226.0));
1378         assertEquals(1, p.getX(), 0.1);
1379         assertEquals(2, p.getY(), 0.1);
1380         Bounds b = rect.screenToLocal(new BoundingBox(124.36, 225.75, 1.85, 3.76));
1381         assertEquals(1, b.getMinX(), 0.1);
1382         assertEquals(1.72, b.getMinY(), 0.1);
1383         assertEquals(3, b.getWidth(), 0.1);
1384         assertEquals(4.52, b.getHeight(), 0.1);
1385     }
1386 
1387     @Test
1388     public void testScreenToLocalWithNonInvertibleTransform() {
1389         Rectangle rect = new Rectangle();
1390 
1391         rect.setScaleX(0.0);
1392 
1393         TestScene scene = new TestScene(new Group(rect));
1394         final TestStage testStage = new TestStage("");
1395         testStage.setX(100);
1396         testStage.setY(200);
1397         scene.set_window(testStage);
1398 
1399         assertNull(rect.screenToLocal(new Point2D(111, 222)));
1400         assertNull(rect.screenToLocal(new BoundingBox(111, 222, 3, 4)));
1401     }
1402 
1403     @Test
1404     public void testScreenToLocalInsideNonInvertibleSubScene() {
1405         Rectangle rect = new Rectangle();
1406         rect.setTranslateX(4);
1407         rect.setTranslateY(9);
1408         SubScene subScene = new SubScene(new Group(rect), 100, 100);
1409         subScene.setScaleX(0.0);
1410 
1411         TestScene scene = new TestScene(new Group(subScene));
1412         final TestStage testStage = new TestStage("");
1413         testStage.setX(100);
1414         testStage.setY(200);
1415         scene.set_window(testStage);
1416 
1417         assertNull(rect.screenToLocal(new Point2D(111, 222)));
1418         assertNull(rect.screenToLocal(new BoundingBox(111, 222, 3, 4)));
1419     }
1420 
1421     @Test
1422     public void testRootMirroringWithTranslate() {
1423         final Group rootGroup = new Group();
1424         rootGroup.setTranslateX(20);
1425         rootGroup.setNodeOrientation(NodeOrientation.RIGHT_TO_LEFT);
1426         final Scene scene = new Scene(rootGroup, 200, 200);
1427 
1428         final Point2D trPoint = scene.getRoot().localToScene(0, 0);
1429         assertEquals(180, trPoint.getX(), 0.1);
1430     }
1431 
1432 
1433     @Test
1434     public void testLayoutXYTriggersParentSizeChange() {
1435         final Group rootGroup = new Group();
1436         final Group subGroup = new Group();
1437         ParentShim.getChildren(rootGroup).add(subGroup);
1438 
1439         Rectangle r = new Rectangle(50,50);
1440         r.setManaged(false);
1441         Rectangle staticR = new Rectangle(1,1);
1442         ParentShim.getChildren(subGroup).addAll(r, staticR);
1443 
1444         assertEquals(50,subGroup.getLayoutBounds().getWidth(), 1e-10);
1445         assertEquals(50,subGroup.getLayoutBounds().getHeight(), 1e-10);
1446 
1447         r.setLayoutX(50);
1448 
1449         rootGroup.layout();
1450 
1451         assertEquals(100,subGroup.getLayoutBounds().getWidth(), 1e-10);
1452         assertEquals(50,subGroup.getLayoutBounds().getHeight(), 1e-10);
1453 
1454     }
1455 
1456     @Test
1457     public void testLayoutXYWontBreakLayout() {
1458         final Group rootGroup = new Group();
1459         final AnchorPane pane = new AnchorPane();
1460         ParentShim.getChildren(rootGroup).add(pane);
1461 
1462         Rectangle r = new Rectangle(50,50);
1463         ParentShim.getChildren(pane).add(r);
1464 
1465         AnchorPane.setLeftAnchor(r, 10d);
1466         AnchorPane.setTopAnchor(r, 10d);
1467 
1468         rootGroup.layout();
1469 
1470         assertEquals(10, r.getLayoutX(), 1e-10);
1471         assertEquals(10, r.getLayoutY(), 1e-10);
1472 
1473         r.setLayoutX(50);
1474 
1475         assertEquals(50, r.getLayoutX(), 1e-10);
1476         assertEquals(10, r.getLayoutY(), 1e-10);
1477 
1478         rootGroup.layout();
1479 
1480         assertEquals(10, r.getLayoutX(), 1e-10);
1481         assertEquals(10, r.getLayoutY(), 1e-10);
1482 
1483     }
1484 
1485     @Test
1486     public void clipShouldUpdateAfterParentVisibilityChange() {
1487 
1488         final Group root = new Group();
1489         Scene scene = new Scene(root, 300, 300);
1490 
1491         final Group parent = new Group();
1492         parent.setVisible(false);
1493 
1494         final Circle circle = new Circle(100, 100, 100);
1495         ParentShim.getChildren(parent).add(circle);
1496 
1497         final Rectangle clip = new StubRect(100, 100);
1498         circle.setClip(clip);
1499 
1500         ParentShim.getChildren(root).add(parent);
1501         parent.setVisible(true);
1502 
1503         Stage stage = new Stage();
1504         stage.setScene(scene);
1505         stage.show();
1506 
1507         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1508 
1509         clip.setWidth(300);
1510 
1511         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1512 
1513         assertEquals(300, ((MockNGRect) NodeHelper.getPeer(clip)).w, 1e-10);
1514     }
1515 
1516     @Test
1517     public void untransformedNodeShouldSyncIdentityTransform() {
1518         final Node node = createTestRect();
1519         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1520         assertSame(BaseTransform.IDENTITY_TRANSFORM,
1521                 ((MockNGRect) NodeHelper.getPeer(node)).t);
1522     }
1523 
1524     @Test
1525     public void nodeTransfomedByIdentitiesShouldSyncIdentityTransform() {
1526         final Node node = createTestRect();
1527         node.setRotationAxis(Rotate.X_AXIS);
1528         node.getTransforms().add(new Translate());
1529         node.getTransforms().add(new Scale());
1530         node.getTransforms().add(new Affine());
1531         node.getTransforms().add(new Rotate(0, Rotate.Y_AXIS));
1532         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1533         assertSame(BaseTransform.IDENTITY_TRANSFORM,
1534                 ((MockNGRect) NodeHelper.getPeer(node)).t);
1535     }
1536 
1537     @Test
1538     public void translatedNodeShouldSyncTranslateTransform1() {
1539         final Node node = createTestRect();
1540         node.setTranslateX(30);
1541         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1542         assertSame(Translate2D.class,
1543                 ((MockNGRect) NodeHelper.getPeer(node)).t.getClass());
1544     }
1545 
1546     @Test
1547     public void translatedNodeShouldSyncTranslateTransform2() {
1548         final Node node = createTestRect();
1549         node.getTransforms().add(new Translate(20, 10));
1550         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1551         assertSame(Translate2D.class,
1552                 ((MockNGRect) NodeHelper.getPeer(node)).t.getClass());
1553     }
1554 
1555     @Test
1556     public void multitranslatedNodeShouldSyncTranslateTransform() {
1557         final Node node = createTestRect();
1558         node.setTranslateX(30);
1559         node.getTransforms().add(new Translate(20, 10));
1560         node.getTransforms().add(new Translate(10, 20));
1561         node.getTransforms().add(new Translate(5, 5, 0));
1562         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1563         assertSame(Translate2D.class,
1564                 ((MockNGRect) NodeHelper.getPeer(node)).t.getClass());
1565     }
1566 
1567     @Test
1568     public void mirroringShouldSyncAffine2DTransform() {
1569         final Node node = createTestRect();
1570         node.setNodeOrientation(NodeOrientation.RIGHT_TO_LEFT);
1571         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1572         assertSame(Affine2D.class,
1573                 ((MockNGRect) NodeHelper.getPeer(node)).t.getClass());
1574     }
1575 
1576     @Test
1577     public void rotatedNodeShouldSyncAffine2DTransform1() {
1578         final Node node = createTestRect();
1579         node.setRotate(20);
1580         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1581         assertSame(Affine2D.class,
1582                 ((MockNGRect) NodeHelper.getPeer(node)).t.getClass());
1583     }
1584 
1585     @Test
1586     public void rotatedNodeShouldSyncAffine2DTransform2() {
1587         final Node node = createTestRect();
1588         node.getTransforms().add(new Rotate(20));
1589         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1590         assertSame(Affine2D.class,
1591                 ((MockNGRect) NodeHelper.getPeer(node)).t.getClass());
1592     }
1593 
1594     @Test
1595     public void multiRotatedNodeShouldSyncAffine2DTransform() {
1596         final Node node = createTestRect();
1597         node.setRotate(20);
1598         node.getTransforms().add(new Rotate(20));
1599         node.getTransforms().add(new Rotate(0, Rotate.X_AXIS));
1600         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1601         assertSame(Affine2D.class,
1602                 ((MockNGRect) NodeHelper.getPeer(node)).t.getClass());
1603     }
1604 
1605     @Test
1606     public void scaledNodeShouldSyncAffine2DTransform1() {
1607         final Node node = createTestRect();
1608         node.setScaleX(2);
1609         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1610         assertSame(Affine2D.class,
1611                 ((MockNGRect) NodeHelper.getPeer(node)).t.getClass());
1612     }
1613 
1614     @Test
1615     public void scaledNodeShouldSyncAffine2DTransform2() {
1616         final Node node = createTestRect();
1617         node.getTransforms().add(new Scale(2, 1));
1618         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1619         assertSame(Affine2D.class,
1620                 ((MockNGRect) NodeHelper.getPeer(node)).t.getClass());
1621     }
1622 
1623     @Test
1624     public void multiScaledNodeShouldSyncAffine2DTransform() {
1625         final Node node = createTestRect();
1626         node.setScaleX(20);
1627         node.getTransforms().add(new Scale(2, 1));
1628         node.getTransforms().add(new Scale(0.5, 2, 1));
1629         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1630         assertSame(Affine2D.class,
1631                 ((MockNGRect) NodeHelper.getPeer(node)).t.getClass());
1632     }
1633 
1634     @Test
1635     public void shearedNodeShouldSyncAffine2DTransform() {
1636         final Node node = createTestRect();
1637         node.getTransforms().add(new Shear(2, 1));
1638         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1639         assertSame(Affine2D.class,
1640                 ((MockNGRect) NodeHelper.getPeer(node)).t.getClass());
1641     }
1642 
1643     @Test
1644     public void ztranslatedNodeShouldSyncAffine3DTransform1() {
1645         final Node node = createTestRect();
1646         node.setTranslateZ(30);
1647         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1648         assertSame(Affine3D.class,
1649                 ((MockNGRect) NodeHelper.getPeer(node)).t.getClass());
1650     }
1651 
1652     @Test
1653     public void ztranslatedNodeShouldSyncAffine3DTransform2() {
1654         final Node node = createTestRect();
1655         node.getTransforms().add(new Translate(0, 0, 10));
1656         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1657         assertSame(Affine3D.class,
1658                 ((MockNGRect) NodeHelper.getPeer(node)).t.getClass());
1659     }
1660 
1661     @Test
1662     public void zscaledNodeShouldSyncAffine3DTransform1() {
1663         final Node node = createTestRect();
1664         node.setScaleZ(0.5);
1665         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1666         assertSame(Affine3D.class,
1667                 ((MockNGRect) NodeHelper.getPeer(node)).t.getClass());
1668     }
1669 
1670     @Test
1671     public void zscaledNodeShouldSyncAffine3DTransform2() {
1672         final Node node = createTestRect();
1673         node.getTransforms().add(new Scale(1, 1, 2));
1674         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1675         assertSame(Affine3D.class,
1676                 ((MockNGRect) NodeHelper.getPeer(node)).t.getClass());
1677     }
1678 
1679     @Test
1680     public void nonZRotatedNodeShouldSyncAffine3DTransform1() {
1681         final Node node = createTestRect();
1682         node.setRotationAxis(Rotate.Y_AXIS);
1683         node.setRotate(10);
1684         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1685         assertSame(Affine3D.class,
1686                 ((MockNGRect) NodeHelper.getPeer(node)).t.getClass());
1687     }
1688 
1689     @Test
1690     public void nonZRotatedNodeShouldSyncAffine3DTransform2() {
1691         final Node node = createTestRect();
1692         node.getTransforms().add(new Rotate(10, Rotate.X_AXIS));
1693         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1694         assertSame(Affine3D.class,
1695                 ((MockNGRect) NodeHelper.getPeer(node)).t.getClass());
1696     }
1697 
1698     @Test
1699     public void translateTransformShouldBeReusedWhenPossible() {
1700         final Node node = createTestRect();
1701         node.setTranslateX(10);
1702         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1703 
1704         BaseTransform t = ((MockNGRect) NodeHelper.getPeer(node)).t;
1705 
1706         ((MockNGRect) NodeHelper.getPeer(node)).t = null;
1707         node.setTranslateX(20);
1708         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1709 
1710         assertSame(t, ((MockNGRect) NodeHelper.getPeer(node)).t);
1711     }
1712 
1713     @Test
1714     public void affine2DTransformShouldBeReusedWhenPossible() {
1715         final Node node = createTestRect();
1716         node.setScaleX(10);
1717         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1718 
1719         BaseTransform t = ((MockNGRect) NodeHelper.getPeer(node)).t;
1720 
1721         ((MockNGRect) NodeHelper.getPeer(node)).t = null;
1722         node.setRotate(20);
1723         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1724 
1725         assertSame(t, ((MockNGRect) NodeHelper.getPeer(node)).t);
1726     }
1727 
1728     @Test
1729     public void affine3DTransformShouldBeReusedWhenPossible() {
1730         final Node node = createTestRect();
1731         node.setScaleZ(10);
1732         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1733 
1734         BaseTransform t = ((MockNGRect) NodeHelper.getPeer(node)).t;
1735 
1736         ((MockNGRect) NodeHelper.getPeer(node)).t = null;
1737         node.setRotate(20);
1738         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1739 
1740         assertSame(t, ((MockNGRect) NodeHelper.getPeer(node)).t);
1741     }
1742 
1743     @Test
1744     public void rtlSceneSizeShouldBeComputedCorrectly() {
1745         Scene scene = new Scene(new Group(new Rectangle(100, 100)));
1746         scene.setNodeOrientation(NodeOrientation.RIGHT_TO_LEFT);
1747         Stage stage = new Stage();
1748         stage.setScene(scene);
1749         stage.show();
1750         assertEquals(100.0, scene.getWidth(), 0.00001);
1751     }
1752 
1753     private Node createTestRect() {
1754         final Rectangle rect = new StubRect();
1755         Scene scene = new Scene(new Group(rect));
1756         Stage stage = new Stage();
1757         stage.setScene(scene);
1758         stage.show();
1759         return rect;
1760     }
1761 
1762     private static class MockNGRect extends NGRectangle {
1763         double w = 0;
1764         BaseTransform t = null;
1765 
1766         @Override public void updateRectangle(float x, float y, float width,
1767                 float height, float arcWidth, float arcHeight) {
1768             w = width;
1769         }
1770 
1771         @Override
1772         public void setTransformMatrix(BaseTransform tx) {
1773             t = tx;
1774         }
1775     }
1776 
1777     static class StubRect extends Rectangle {
1778         static {
1779             StubRectHelper.setStubRectAccessor(new StubRectHelper.StubRectAccessor() {
1780                 @Override
1781                 public NGNode doCreatePeer(Node node) {
1782                     return ((StubRect) node).doCreatePeer();
1783                 }
1784             });
1785         }
1786 
1787         StubRect() {
1788             super();
1789             StubRectHelper.initHelper(this);
1790         }
1791 
1792         StubRect(double width, double height) {
1793             super(width, height);
1794             StubRectHelper.initHelper(this);
1795         }
1796 
1797         private NGNode doCreatePeer() {
1798             return new MockNGRect();
1799         }
1800     }
1801 
1802     public static class StubRectHelper extends RectangleHelper {
1803 
1804         private static final StubRectHelper theInstance;
1805         private static StubRectAccessor stubRectAccessor;
1806 
1807         static {
1808             theInstance = new StubRectHelper();
1809             Utils.forceInit(StubRect.class);
1810         }
1811 
1812         private static StubRectHelper getInstance() {
1813             return theInstance;
1814         }
1815 
1816         public static void initHelper(StubRect stubRect) {
1817             setHelper(stubRect, getInstance());
1818         }
1819 
1820         public static void setStubRectAccessor(final StubRectAccessor newAccessor) {
1821             if (stubRectAccessor != null) {
1822                 throw new IllegalStateException();
1823             }
1824 
1825             stubRectAccessor = newAccessor;
1826         }
1827 
1828         @Override
1829         protected NGNode createPeerImpl(Node node) {
1830             return stubRectAccessor.doCreatePeer(node);
1831         }
1832 
1833         public interface StubRectAccessor {
1834             NGNode doCreatePeer(Node node);
1835         }
1836 
1837     }
1838 }