--- old/modules/graphics/src/test/java/javafx/scene/Parent_recomputeBounds_Test.java 2015-09-11 21:26:24.977285635 -0400 +++ /dev/null 2015-09-11 11:06:08.592686920 -0400 @@ -1,610 +0,0 @@ -/* - * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package javafx.scene; - -import com.sun.javafx.test.TestHelper; -import javafx.geometry.BoundingBox; -import javafx.scene.transform.Rotate; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import javafx.geometry.Bounds; -import javafx.geometry.Point2D; -import javafx.scene.shape.Rectangle; -import javafx.scene.shape.Circle; -import javafx.scene.transform.Translate; - -import org.junit.Test; - -public class Parent_recomputeBounds_Test { - private static final Rectangle r1 = new Rectangle(100, 100, 100, 100); - private static final Rectangle r2 = new Rectangle(250, 250, 50, 50); - private static final Rectangle r3 = new Rectangle(50, 50, 10, 10); - - @Test - public void shouldRecomputeBoundsWhenNodeAdded() { - final Group g = new Group(); - Bounds b; - - b = g.getBoundsInParent(); - assertTrue(b.isEmpty()); - - g.getChildren().add(r1); - - b = g.getBoundsInParent(); - assertEquals(100, b.getMinX(), 0.0001); - assertEquals(100, b.getMinY(), 0.0001); - assertEquals(100, b.getWidth(), 0.0001); - assertEquals(100, b.getHeight(), 0.0001); - - g.getChildren().add(r2); - - b = g.getBoundsInParent(); - assertEquals(100, b.getMinX(), 0.0001); - assertEquals(100, b.getMinY(), 0.0001); - assertEquals(200, b.getWidth(), 0.0001); - assertEquals(200, b.getHeight(), 0.0001); - - g.getChildren().add(r3); - - b = g.getBoundsInParent(); - assertEquals(50, b.getMinX(), 0.0001); - assertEquals(50, b.getMinY(), 0.0001); - assertEquals(250, b.getWidth(), 0.0001); - assertEquals(250, b.getHeight(), 0.0001); - } - - @Test - public void shouldRecomputeBoundsWhenNodeRemoved() { - final Group g = new Group(); - g.getChildren().add(r1); - g.getChildren().add(r2); - g.getChildren().add(r3); - Bounds b; - - b = g.getBoundsInParent(); - assertEquals(50, b.getMinX(), 0.0001); - assertEquals(50, b.getMinY(), 0.0001); - assertEquals(250, b.getWidth(), 0.0001); - assertEquals(250, b.getHeight(), 0.0001); - - g.getChildren().remove(r3); - - b = g.getBoundsInParent(); - assertEquals(100, b.getMinX(), 0.0001); - assertEquals(100, b.getMinY(), 0.0001); - assertEquals(200, b.getWidth(), 0.0001); - assertEquals(200, b.getHeight(), 0.0001); - - g.getChildren().remove(r2); - - b = g.getBoundsInParent(); - assertEquals(100, b.getMinX(), 0.0001); - assertEquals(100, b.getMinY(), 0.0001); - assertEquals(100, b.getWidth(), 0.0001); - assertEquals(100, b.getHeight(), 0.0001); - - g.getChildren().remove(r1); - - b = g.getBoundsInParent(); - assertTrue(b.isEmpty()); - } - - @Test - public void shouldRecomputeBoundsWhenNodeHiddenOrShown() { - final Group g = new Group(); - g.getChildren().add(r1); - g.getChildren().add(r2); - g.getChildren().add(r3); - Bounds b; - - b = g.getBoundsInParent(); - assertEquals(50, b.getMinX(), 0.0001); - assertEquals(50, b.getMinY(), 0.0001); - assertEquals(250, b.getWidth(), 0.0001); - assertEquals(250, b.getHeight(), 0.0001); - - r3.setVisible(false); - - b = g.getBoundsInParent(); - assertEquals(100, b.getMinX(), 0.0001); - assertEquals(100, b.getMinY(), 0.0001); - assertEquals(200, b.getWidth(), 0.0001); - assertEquals(200, b.getHeight(), 0.0001); - - r3.setVisible(true); - - b = g.getBoundsInParent(); - assertEquals(50, b.getMinX(), 0.0001); - assertEquals(50, b.getMinY(), 0.0001); - assertEquals(250, b.getWidth(), 0.0001); - assertEquals(250, b.getHeight(), 0.0001); - } - - @Test - public void shouldNotRecomputeBoundsWhenHiddenNodeChanges() { - final Group g = new Group(); - final Rectangle r = new Rectangle(200,200,1,1); - g.getChildren().add(r2); - g.getChildren().add(r); - g.getChildren().add(r3); - Bounds b; - - r.setVisible(false); - - b = g.getBoundsInParent(); - assertEquals(50, b.getMinX(), 0.0001); - assertEquals(50, b.getMinY(), 0.0001); - assertEquals(250, b.getWidth(), 0.0001); - assertEquals(250, b.getHeight(), 0.0001); - - r.setX(10); - - b = g.getBoundsInParent(); - assertEquals(50, b.getMinX(), 0.0001); - assertEquals(50, b.getMinY(), 0.0001); - assertEquals(250, b.getWidth(), 0.0001); - assertEquals(250, b.getHeight(), 0.0001); - - r.setWidth(500); - - b = g.getBoundsInParent(); - assertEquals(50, b.getMinX(), 0.0001); - assertEquals(50, b.getMinY(), 0.0001); - assertEquals(250, b.getWidth(), 0.0001); - assertEquals(250, b.getHeight(), 0.0001); - } - - @Test - public void shouldNotRecomputeBoundsWhenNodeChangesInsideEdges() { - final Group g = new Group(); - final Rectangle r = new Rectangle(200,200,1,1); - g.getChildren().add(r2); - g.getChildren().add(r); - g.getChildren().add(r3); - Bounds b; - - b = g.getBoundsInParent(); - assertEquals(50, b.getMinX(), 0.0001); - assertEquals(50, b.getMinY(), 0.0001); - assertEquals(250, b.getWidth(), 0.0001); - assertEquals(250, b.getHeight(), 0.0001); - - r.setX(190); - r.setY(190); - r.setWidth(48); - r.setHeight(48); - - b = g.getBoundsInParent(); - assertEquals(50, b.getMinX(), 0.0001); - assertEquals(50, b.getMinY(), 0.0001); - assertEquals(250, b.getWidth(), 0.0001); - assertEquals(250, b.getHeight(), 0.0001); - } - - @Test - public void shouldNotRecomputeBoundsWhenEdgeNodeEnlargesInwards() { - final Group g = new Group(); - final Rectangle r = new Rectangle(200,200,50,50); - g.getChildren().add(r); - g.getChildren().add(r3); - Bounds b; - - b = g.getBoundsInParent(); - assertEquals(50, b.getMinX(), 0.0001); - assertEquals(50, b.getMinY(), 0.0001); - assertEquals(200, b.getWidth(), 0.0001); - assertEquals(200, b.getHeight(), 0.0001); - - r.setX(100); - r.setY(100); - r.setWidth(150); - r.setHeight(150); - - b = g.getBoundsInParent(); - assertEquals(50, b.getMinX(), 0.0001); - assertEquals(50, b.getMinY(), 0.0001); - assertEquals(200, b.getWidth(), 0.0001); - assertEquals(200, b.getHeight(), 0.0001); - } - - @Test - public void shouldRecomputeBoundsWhenNodeMoved() { - final Group g = new Group(); - final Rectangle r = new Rectangle(200,200,1,1); - - g.getChildren().add(r2); - g.getChildren().add(r); - g.getChildren().add(r3); - Bounds b; - - b = g.getBoundsInParent(); - assertEquals(50, b.getMinX(), 0.0001); - assertEquals(50, b.getMinY(), 0.0001); - assertEquals(250, b.getWidth(), 0.0001); - assertEquals(250, b.getHeight(), 0.0001); - - r.setX(40); - - b = g.getBoundsInParent(); - assertEquals(40, b.getMinX(), 0.0001); - assertEquals(50, b.getMinY(), 0.0001); - assertEquals(260, b.getWidth(), 0.0001); - assertEquals(250, b.getHeight(), 0.0001); - - r.setX(200); - - b = g.getBoundsInParent(); - assertEquals(50, b.getMinX(), 0.0001); - assertEquals(50, b.getMinY(), 0.0001); - assertEquals(250, b.getWidth(), 0.0001); - assertEquals(250, b.getHeight(), 0.0001); - } - - @Test - public void shouldRecomputeBoundsWhenNodeResized() { - final Group g = new Group(); - final Rectangle r = new Rectangle(200,200,1,1); - - g.getChildren().add(r2); - g.getChildren().add(r); - g.getChildren().add(r3); - Bounds b; - - b = g.getBoundsInParent(); - assertEquals(50, b.getMinX(), 0.0001); - assertEquals(50, b.getMinY(), 0.0001); - assertEquals(250, b.getWidth(), 0.0001); - assertEquals(250, b.getHeight(), 0.0001); - - r.setWidth(200); - - b = g.getBoundsInParent(); - assertEquals(50, b.getMinX(), 0.0001); - assertEquals(50, b.getMinY(), 0.0001); - assertEquals(350, b.getWidth(), 0.0001); - assertEquals(250, b.getHeight(), 0.0001); - - r.setWidth(5); - - b = g.getBoundsInParent(); - assertEquals(50, b.getMinX(), 0.0001); - assertEquals(50, b.getMinY(), 0.0001); - assertEquals(250, b.getWidth(), 0.0001); - assertEquals(250, b.getHeight(), 0.0001); - } - - @Test - public void shouldNotRecomputeBoundsWhenManyNodesChange() { - final Group g = new Group(); - final Rectangle lt = new Rectangle(100, 100, 100, 100); - final Rectangle rt = new Rectangle(200, 100, 100, 100); - final Rectangle lb = new Rectangle(100, 200, 100, 100); - final Rectangle rb = new Rectangle(200, 200, 100, 100); - final Rectangle rm1 = new Rectangle(150, 150, 50, 50); - final Rectangle rm2 = new Rectangle(150, 150, 50, 50); - Bounds b; - - g.getChildren().add(lt); - g.getChildren().add(rt); - g.getChildren().add(lb); - g.getChildren().add(rb); - g.getChildren().add(rm1); - g.getChildren().add(rm2); - for (int i = 0; i < 20; i++) { - g.getChildren().add(new Rectangle(150 + i, 150 + i, 50 + i, 50 + i)); - } - - b = g.getBoundsInParent(); - assertEquals(100, b.getMinX(), 0.0001); - assertEquals(100, b.getMinY(), 0.0001); - assertEquals(200, b.getWidth(), 0.0001); - assertEquals(200, b.getHeight(), 0.0001); - - lt.setX(50); - rt.setX(250); - lb.setY(250); - rb.setX(220); - rm1.setY(50); - rm2.setX(151); - rm2.setY(151); - - b = g.getBoundsInParent(); - assertEquals(50, b.getMinX(), 0.0001); - assertEquals(50, b.getMinY(), 0.0001); - assertEquals(300, b.getWidth(), 0.0001); - assertEquals(300, b.getHeight(), 0.0001); - } - - @Test - public void transformedBoundsCalculationShouldNotInfluenceUntransformed() { - final Group g = new Group(); - final Rectangle lt = new Rectangle(100, 100, 100, 100); - final Rectangle rt = new Rectangle(200, 100, 100, 100); - final Rectangle lb = new Rectangle(100, 200, 100, 100); - final Rectangle rb = new Rectangle(200, 200, 100, 100); - final Rectangle rm1 = new Rectangle(150, 150, 50, 50); - final Rectangle rm2 = new Rectangle(150, 150, 50, 50); - Bounds b; - - g.getChildren().addAll(lt, rt, lb, rb, rm1, rm2); - for (int i = 0; i < 20; i++) { - g.getChildren().add(new Rectangle(150 + i, 150 + i, 50 + i, 50 + i)); - } - - b = g.getBoundsInLocal(); - assertEquals(100, b.getMinX(), 0.0001); - assertEquals(100, b.getMinY(), 0.0001); - assertEquals(200, b.getWidth(), 0.0001); - assertEquals(200, b.getHeight(), 0.0001); - - g.getTransforms().add(new Rotate(45)); - - lt.setX(50); - rt.setX(250); - lb.setY(250); - rb.setX(220); - rm1.setY(50); - rm2.setX(151); - rm2.setY(151); - - // this call gets transformed Parent bounds and shouldn't clear the - // Parent's dirty children list - g.getBoundsInParent(); - - b = g.getBoundsInLocal(); - assertEquals(50, b.getMinX(), 0.0001); - assertEquals(50, b.getMinY(), 0.0001); - assertEquals(300, b.getWidth(), 0.0001); - assertEquals(300, b.getHeight(), 0.0001); - } - - @Test - public void shouldNotStartBoundsCalculationFromEmptyBounds() { - final Group g = new Group(); - final Rectangle lt = new Rectangle(50, 50, 50, 50); - final Rectangle rb = new Rectangle(100, 100, 50, 50); - Bounds b; - - g.getChildren().addAll(lt, rb); - - b = g.getBoundsInLocal(); - assertEquals(50, b.getMinX(), 0.0001); - assertEquals(50, b.getMinY(), 0.0001); - assertEquals(100, b.getWidth(), 0.0001); - assertEquals(100, b.getHeight(), 0.0001); - - // invalidate (empty) bounds - rb.setVisible(false); - - // add new rectangle, bounds should not be recalculated by using - // empty bounds - g.getChildren().add(new Rectangle(150, 150, 50, 50)); - - b = g.getBoundsInLocal(); - assertEquals(50, b.getMinX(), 0.0001); - assertEquals(50, b.getMinY(), 0.0001); - assertEquals(150, b.getWidth(), 0.0001); - assertEquals(150, b.getHeight(), 0.0001); - } - - @Test - public void shouldNotIgnoreMultipleAddedNodes() { - final Group g = new Group(); - final Rectangle lt = new Rectangle(100, 100, 100, 100); - final Rectangle mt = new Rectangle(200, 100, 100, 100); - final Rectangle rt = new Rectangle(300, 100, 100, 100); - final Rectangle rb = new Rectangle(300, 200, 100, 100); - Bounds b; - - g.getChildren().addAll(lt, mt); - - b = g.getBoundsInLocal(); - assertEquals(100, b.getMinX(), 0.0001); - assertEquals(100, b.getMinY(), 0.0001); - assertEquals(200, b.getWidth(), 0.0001); - assertEquals(100, b.getHeight(), 0.0001); - - ((Node) rb).boundsChanged = false; - // rt, rb should be either incorporated into parent bounds directly - // or marked as dirty for parent bounds calculation - g.getChildren().addAll(rt, rb); - - b = g.getBoundsInLocal(); - assertEquals(100, b.getMinX(), 0.0001); - assertEquals(100, b.getMinY(), 0.0001); - assertEquals(300, b.getWidth(), 0.0001); - assertEquals(200, b.getHeight(), 0.0001); - } - - @Test - public void shouldNotCreateEmptyDirtyChildrenList() { - final Group g = new Group(); - final Rectangle lt = new Rectangle(100, 100, 100, 100); - final Rectangle rt = new Rectangle(200, 100, 100, 100); - final Rectangle lb = new Rectangle(100, 200, 100, 100); - final Rectangle rb = new Rectangle(200, 200, 100, 100); - Bounds b; - - g.getChildren().addAll(lt, rt, lb, rb); - final int toAdd = Parent.DIRTY_CHILDREN_THRESHOLD - 4; - for (int i = 0; i < toAdd; ++i) { - g.getChildren().add( - new Rectangle(150 + i * 80 / (toAdd - 1), 190, 20, 20)); - } - - b = g.getBoundsInLocal(); - assertEquals(100, b.getMinX(), 0.0001); - assertEquals(100, b.getMinY(), 0.0001); - assertEquals(200, b.getWidth(), 0.0001); - assertEquals(200, b.getHeight(), 0.0001); - - lt.setX(50); - // this should create a dirty children list on Parent, even though - // the added node doesn't change the group gemetry, the created - // dirty children list should still contain the previously modified - // corner node (lt) - g.getChildren().add(new Rectangle(150, 150, 100, 100)); - - b = g.getBoundsInLocal(); - assertEquals(50, b.getMinX(), 0.0001); - assertEquals(100, b.getMinY(), 0.0001); - assertEquals(250, b.getWidth(), 0.0001); - assertEquals(200, b.getHeight(), 0.0001); - } - - @Test - public void - shouldNotSkipGeomChangedForChildAdditionInsideUntransformedBounds() - { - final Group g = new Group(new Circle(0, -100, 0.001), - new Circle(0, 100, 0.001), - new Circle(-100, 0, 0.001), - new Circle(100, 0, 0.001)); - g.getTransforms().add(new Rotate(-45)); - - Bounds b; - - b = g.getBoundsInParent(); - assertEquals(-100 * Math.sqrt(2) / 2, b.getMinX(), 0.1); - assertEquals(-100 * Math.sqrt(2) / 2, b.getMinY(), 0.1); - assertEquals(100 * Math.sqrt(2), b.getWidth(), 0.1); - assertEquals(100 * Math.sqrt(2), b.getHeight(), 0.1); - - g.getChildren().add(new Circle(95, -95, 0.001)); - - b = g.getBoundsInParent(); - assertEquals(-100 * Math.sqrt(2) / 2, b.getMinX(), 0.1); - assertEquals(-95 * Math.sqrt(2), b.getMinY(), 0.1); - assertEquals(100 * Math.sqrt(2), b.getWidth(), 0.1); - assertEquals((50 + 95) * Math.sqrt(2), b.getHeight(), 0.1); - } - - @Test - public void - shouldNotSkipGeomChangedForChildRemovalInsideUntransformedBounds() - { - final Circle toRemove = new Circle(95, -95, 0.001); - final Group g = new Group(toRemove, - new Circle(0, -100, 0.001), - new Circle(0, 100, 0.001), - new Circle(-100, 0, 0.001), - new Circle(100, 0, 0.001)); - g.getTransforms().add(new Rotate(-45)); - - Bounds b; - - b = g.getBoundsInParent(); - assertEquals(-100 * Math.sqrt(2) / 2, b.getMinX(), 0.1); - assertEquals(-95 * Math.sqrt(2), b.getMinY(), 0.1); - assertEquals(100 * Math.sqrt(2), b.getWidth(), 0.1); - assertEquals((50 + 95) * Math.sqrt(2), b.getHeight(), 0.1); - - g.getChildren().remove(toRemove); - - b = g.getBoundsInParent(); - assertEquals(-100 * Math.sqrt(2) / 2, b.getMinX(), 0.1); - assertEquals(-100 * Math.sqrt(2) / 2, b.getMinY(), 0.1); - assertEquals(100 * Math.sqrt(2), b.getWidth(), 0.1); - assertEquals(100 * Math.sqrt(2), b.getHeight(), 0.1); - } - - @Test - public void nodeShouldNotifyParentEvenIfItsTransformedBoundsAreDirty() { - final Rectangle child = new Rectangle(0, 0, 100, 100); - final Group parent = new Group(child); - - // ensures that child's getTransformedBounds will be called with - // a non-identity transform argument during parent's bounds calculations - parent.getTransforms().add(new Rotate(-45)); - - // make the cached child's transformed bounds dirty - child.getTransforms().add(new Translate(50, 0)); - - // the cached child's transformed bounds will remain dirty because - // of a non-trivial parent transformation - TestHelper.assertSimilar( - boundsOfRotatedRect(50, 0, 100, 100, -45), - parent.getBoundsInParent()); - - // during the following call the child bounds changed notification - // should still be generated even though the child's cached transformed - // bounds are already dirty - child.getTransforms().add(new Translate(50, 0)); - - TestHelper.assertSimilar( - boundsOfRotatedRect(100, 0, 100, 100, -45), - parent.getBoundsInParent()); - } - - private static Bounds boundsOfRotatedRect( - final double x, final double y, - final double width, final double height, - final double angle) { - final Point2D p1 = rotatePoint(x, y, angle); - final Point2D p2 = rotatePoint(x + width, y, angle); - final Point2D p3 = rotatePoint(x, y + height, angle); - final Point2D p4 = rotatePoint(x + width, y + height, angle); - - final double minx = min(p1.getX(), p2.getX(), p3.getX(), p4.getX()); - final double miny = min(p1.getY(), p2.getY(), p3.getY(), p4.getY()); - final double maxx = max(p1.getX(), p2.getX(), p3.getX(), p4.getX()); - final double maxy = max(p1.getY(), p2.getY(), p3.getY(), p4.getY()); - - return new BoundingBox(minx, miny, maxx - minx, maxy - miny); - } - - private static Point2D rotatePoint(final double x, final double y, - final double angle) { - final double rada = Math.toRadians(angle); - final double sina = Math.sin(rada); - final double cosa = Math.cos(rada); - - return new Point2D(x * cosa - y * sina, - x * sina + y * cosa); - } - - private static double min(final double... values) { - double result = values[0]; - for (int i = 1; i < values.length; ++i) { - if (result > values[i]) { - result = values[i]; - } - } - - return result; - } - - private static double max(final double... values) { - double result = values[0]; - for (int i = 1; i < values.length; ++i) { - if (result < values[i]) { - result = values[i]; - } - } - - return result; - } -} --- /dev/null 2015-09-11 11:06:08.592686920 -0400 +++ new/modules/graphics/src/test/java/test/javafx/scene/Parent_recomputeBounds_Test.java 2015-09-11 21:26:24.841285636 -0400 @@ -0,0 +1,614 @@ +/* + * Copyright (c) 2010, 2013, 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 test.com.sun.javafx.test.TestHelper; +import javafx.geometry.BoundingBox; +import javafx.scene.transform.Rotate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import javafx.geometry.Bounds; +import javafx.geometry.Point2D; +import javafx.scene.Group; +import javafx.scene.NodeShim; +import javafx.scene.ParentShim; +import javafx.scene.shape.Rectangle; +import javafx.scene.shape.Circle; +import javafx.scene.transform.Translate; + +import org.junit.Test; + +public class Parent_recomputeBounds_Test { + private static final Rectangle r1 = new Rectangle(100, 100, 100, 100); + private static final Rectangle r2 = new Rectangle(250, 250, 50, 50); + private static final Rectangle r3 = new Rectangle(50, 50, 10, 10); + + @Test + public void shouldRecomputeBoundsWhenNodeAdded() { + final Group g = new Group(); + Bounds b; + + b = g.getBoundsInParent(); + assertTrue(b.isEmpty()); + + g.getChildren().add(r1); + + b = g.getBoundsInParent(); + assertEquals(100, b.getMinX(), 0.0001); + assertEquals(100, b.getMinY(), 0.0001); + assertEquals(100, b.getWidth(), 0.0001); + assertEquals(100, b.getHeight(), 0.0001); + + g.getChildren().add(r2); + + b = g.getBoundsInParent(); + assertEquals(100, b.getMinX(), 0.0001); + assertEquals(100, b.getMinY(), 0.0001); + assertEquals(200, b.getWidth(), 0.0001); + assertEquals(200, b.getHeight(), 0.0001); + + g.getChildren().add(r3); + + b = g.getBoundsInParent(); + assertEquals(50, b.getMinX(), 0.0001); + assertEquals(50, b.getMinY(), 0.0001); + assertEquals(250, b.getWidth(), 0.0001); + assertEquals(250, b.getHeight(), 0.0001); + } + + @Test + public void shouldRecomputeBoundsWhenNodeRemoved() { + final Group g = new Group(); + g.getChildren().add(r1); + g.getChildren().add(r2); + g.getChildren().add(r3); + Bounds b; + + b = g.getBoundsInParent(); + assertEquals(50, b.getMinX(), 0.0001); + assertEquals(50, b.getMinY(), 0.0001); + assertEquals(250, b.getWidth(), 0.0001); + assertEquals(250, b.getHeight(), 0.0001); + + g.getChildren().remove(r3); + + b = g.getBoundsInParent(); + assertEquals(100, b.getMinX(), 0.0001); + assertEquals(100, b.getMinY(), 0.0001); + assertEquals(200, b.getWidth(), 0.0001); + assertEquals(200, b.getHeight(), 0.0001); + + g.getChildren().remove(r2); + + b = g.getBoundsInParent(); + assertEquals(100, b.getMinX(), 0.0001); + assertEquals(100, b.getMinY(), 0.0001); + assertEquals(100, b.getWidth(), 0.0001); + assertEquals(100, b.getHeight(), 0.0001); + + g.getChildren().remove(r1); + + b = g.getBoundsInParent(); + assertTrue(b.isEmpty()); + } + + @Test + public void shouldRecomputeBoundsWhenNodeHiddenOrShown() { + final Group g = new Group(); + g.getChildren().add(r1); + g.getChildren().add(r2); + g.getChildren().add(r3); + Bounds b; + + b = g.getBoundsInParent(); + assertEquals(50, b.getMinX(), 0.0001); + assertEquals(50, b.getMinY(), 0.0001); + assertEquals(250, b.getWidth(), 0.0001); + assertEquals(250, b.getHeight(), 0.0001); + + r3.setVisible(false); + + b = g.getBoundsInParent(); + assertEquals(100, b.getMinX(), 0.0001); + assertEquals(100, b.getMinY(), 0.0001); + assertEquals(200, b.getWidth(), 0.0001); + assertEquals(200, b.getHeight(), 0.0001); + + r3.setVisible(true); + + b = g.getBoundsInParent(); + assertEquals(50, b.getMinX(), 0.0001); + assertEquals(50, b.getMinY(), 0.0001); + assertEquals(250, b.getWidth(), 0.0001); + assertEquals(250, b.getHeight(), 0.0001); + } + + @Test + public void shouldNotRecomputeBoundsWhenHiddenNodeChanges() { + final Group g = new Group(); + final Rectangle r = new Rectangle(200,200,1,1); + g.getChildren().add(r2); + g.getChildren().add(r); + g.getChildren().add(r3); + Bounds b; + + r.setVisible(false); + + b = g.getBoundsInParent(); + assertEquals(50, b.getMinX(), 0.0001); + assertEquals(50, b.getMinY(), 0.0001); + assertEquals(250, b.getWidth(), 0.0001); + assertEquals(250, b.getHeight(), 0.0001); + + r.setX(10); + + b = g.getBoundsInParent(); + assertEquals(50, b.getMinX(), 0.0001); + assertEquals(50, b.getMinY(), 0.0001); + assertEquals(250, b.getWidth(), 0.0001); + assertEquals(250, b.getHeight(), 0.0001); + + r.setWidth(500); + + b = g.getBoundsInParent(); + assertEquals(50, b.getMinX(), 0.0001); + assertEquals(50, b.getMinY(), 0.0001); + assertEquals(250, b.getWidth(), 0.0001); + assertEquals(250, b.getHeight(), 0.0001); + } + + @Test + public void shouldNotRecomputeBoundsWhenNodeChangesInsideEdges() { + final Group g = new Group(); + final Rectangle r = new Rectangle(200,200,1,1); + g.getChildren().add(r2); + g.getChildren().add(r); + g.getChildren().add(r3); + Bounds b; + + b = g.getBoundsInParent(); + assertEquals(50, b.getMinX(), 0.0001); + assertEquals(50, b.getMinY(), 0.0001); + assertEquals(250, b.getWidth(), 0.0001); + assertEquals(250, b.getHeight(), 0.0001); + + r.setX(190); + r.setY(190); + r.setWidth(48); + r.setHeight(48); + + b = g.getBoundsInParent(); + assertEquals(50, b.getMinX(), 0.0001); + assertEquals(50, b.getMinY(), 0.0001); + assertEquals(250, b.getWidth(), 0.0001); + assertEquals(250, b.getHeight(), 0.0001); + } + + @Test + public void shouldNotRecomputeBoundsWhenEdgeNodeEnlargesInwards() { + final Group g = new Group(); + final Rectangle r = new Rectangle(200,200,50,50); + g.getChildren().add(r); + g.getChildren().add(r3); + Bounds b; + + b = g.getBoundsInParent(); + assertEquals(50, b.getMinX(), 0.0001); + assertEquals(50, b.getMinY(), 0.0001); + assertEquals(200, b.getWidth(), 0.0001); + assertEquals(200, b.getHeight(), 0.0001); + + r.setX(100); + r.setY(100); + r.setWidth(150); + r.setHeight(150); + + b = g.getBoundsInParent(); + assertEquals(50, b.getMinX(), 0.0001); + assertEquals(50, b.getMinY(), 0.0001); + assertEquals(200, b.getWidth(), 0.0001); + assertEquals(200, b.getHeight(), 0.0001); + } + + @Test + public void shouldRecomputeBoundsWhenNodeMoved() { + final Group g = new Group(); + final Rectangle r = new Rectangle(200,200,1,1); + + g.getChildren().add(r2); + g.getChildren().add(r); + g.getChildren().add(r3); + Bounds b; + + b = g.getBoundsInParent(); + assertEquals(50, b.getMinX(), 0.0001); + assertEquals(50, b.getMinY(), 0.0001); + assertEquals(250, b.getWidth(), 0.0001); + assertEquals(250, b.getHeight(), 0.0001); + + r.setX(40); + + b = g.getBoundsInParent(); + assertEquals(40, b.getMinX(), 0.0001); + assertEquals(50, b.getMinY(), 0.0001); + assertEquals(260, b.getWidth(), 0.0001); + assertEquals(250, b.getHeight(), 0.0001); + + r.setX(200); + + b = g.getBoundsInParent(); + assertEquals(50, b.getMinX(), 0.0001); + assertEquals(50, b.getMinY(), 0.0001); + assertEquals(250, b.getWidth(), 0.0001); + assertEquals(250, b.getHeight(), 0.0001); + } + + @Test + public void shouldRecomputeBoundsWhenNodeResized() { + final Group g = new Group(); + final Rectangle r = new Rectangle(200,200,1,1); + + g.getChildren().add(r2); + g.getChildren().add(r); + g.getChildren().add(r3); + Bounds b; + + b = g.getBoundsInParent(); + assertEquals(50, b.getMinX(), 0.0001); + assertEquals(50, b.getMinY(), 0.0001); + assertEquals(250, b.getWidth(), 0.0001); + assertEquals(250, b.getHeight(), 0.0001); + + r.setWidth(200); + + b = g.getBoundsInParent(); + assertEquals(50, b.getMinX(), 0.0001); + assertEquals(50, b.getMinY(), 0.0001); + assertEquals(350, b.getWidth(), 0.0001); + assertEquals(250, b.getHeight(), 0.0001); + + r.setWidth(5); + + b = g.getBoundsInParent(); + assertEquals(50, b.getMinX(), 0.0001); + assertEquals(50, b.getMinY(), 0.0001); + assertEquals(250, b.getWidth(), 0.0001); + assertEquals(250, b.getHeight(), 0.0001); + } + + @Test + public void shouldNotRecomputeBoundsWhenManyNodesChange() { + final Group g = new Group(); + final Rectangle lt = new Rectangle(100, 100, 100, 100); + final Rectangle rt = new Rectangle(200, 100, 100, 100); + final Rectangle lb = new Rectangle(100, 200, 100, 100); + final Rectangle rb = new Rectangle(200, 200, 100, 100); + final Rectangle rm1 = new Rectangle(150, 150, 50, 50); + final Rectangle rm2 = new Rectangle(150, 150, 50, 50); + Bounds b; + + g.getChildren().add(lt); + g.getChildren().add(rt); + g.getChildren().add(lb); + g.getChildren().add(rb); + g.getChildren().add(rm1); + g.getChildren().add(rm2); + for (int i = 0; i < 20; i++) { + g.getChildren().add(new Rectangle(150 + i, 150 + i, 50 + i, 50 + i)); + } + + b = g.getBoundsInParent(); + assertEquals(100, b.getMinX(), 0.0001); + assertEquals(100, b.getMinY(), 0.0001); + assertEquals(200, b.getWidth(), 0.0001); + assertEquals(200, b.getHeight(), 0.0001); + + lt.setX(50); + rt.setX(250); + lb.setY(250); + rb.setX(220); + rm1.setY(50); + rm2.setX(151); + rm2.setY(151); + + b = g.getBoundsInParent(); + assertEquals(50, b.getMinX(), 0.0001); + assertEquals(50, b.getMinY(), 0.0001); + assertEquals(300, b.getWidth(), 0.0001); + assertEquals(300, b.getHeight(), 0.0001); + } + + @Test + public void transformedBoundsCalculationShouldNotInfluenceUntransformed() { + final Group g = new Group(); + final Rectangle lt = new Rectangle(100, 100, 100, 100); + final Rectangle rt = new Rectangle(200, 100, 100, 100); + final Rectangle lb = new Rectangle(100, 200, 100, 100); + final Rectangle rb = new Rectangle(200, 200, 100, 100); + final Rectangle rm1 = new Rectangle(150, 150, 50, 50); + final Rectangle rm2 = new Rectangle(150, 150, 50, 50); + Bounds b; + + g.getChildren().addAll(lt, rt, lb, rb, rm1, rm2); + for (int i = 0; i < 20; i++) { + g.getChildren().add(new Rectangle(150 + i, 150 + i, 50 + i, 50 + i)); + } + + b = g.getBoundsInLocal(); + assertEquals(100, b.getMinX(), 0.0001); + assertEquals(100, b.getMinY(), 0.0001); + assertEquals(200, b.getWidth(), 0.0001); + assertEquals(200, b.getHeight(), 0.0001); + + g.getTransforms().add(new Rotate(45)); + + lt.setX(50); + rt.setX(250); + lb.setY(250); + rb.setX(220); + rm1.setY(50); + rm2.setX(151); + rm2.setY(151); + + // this call gets transformed Parent bounds and shouldn't clear the + // Parent's dirty children list + g.getBoundsInParent(); + + b = g.getBoundsInLocal(); + assertEquals(50, b.getMinX(), 0.0001); + assertEquals(50, b.getMinY(), 0.0001); + assertEquals(300, b.getWidth(), 0.0001); + assertEquals(300, b.getHeight(), 0.0001); + } + + @Test + public void shouldNotStartBoundsCalculationFromEmptyBounds() { + final Group g = new Group(); + final Rectangle lt = new Rectangle(50, 50, 50, 50); + final Rectangle rb = new Rectangle(100, 100, 50, 50); + Bounds b; + + g.getChildren().addAll(lt, rb); + + b = g.getBoundsInLocal(); + assertEquals(50, b.getMinX(), 0.0001); + assertEquals(50, b.getMinY(), 0.0001); + assertEquals(100, b.getWidth(), 0.0001); + assertEquals(100, b.getHeight(), 0.0001); + + // invalidate (empty) bounds + rb.setVisible(false); + + // add new rectangle, bounds should not be recalculated by using + // empty bounds + g.getChildren().add(new Rectangle(150, 150, 50, 50)); + + b = g.getBoundsInLocal(); + assertEquals(50, b.getMinX(), 0.0001); + assertEquals(50, b.getMinY(), 0.0001); + assertEquals(150, b.getWidth(), 0.0001); + assertEquals(150, b.getHeight(), 0.0001); + } + + @Test + public void shouldNotIgnoreMultipleAddedNodes() { + final Group g = new Group(); + final Rectangle lt = new Rectangle(100, 100, 100, 100); + final Rectangle mt = new Rectangle(200, 100, 100, 100); + final Rectangle rt = new Rectangle(300, 100, 100, 100); + final Rectangle rb = new Rectangle(300, 200, 100, 100); + Bounds b; + + g.getChildren().addAll(lt, mt); + + b = g.getBoundsInLocal(); + assertEquals(100, b.getMinX(), 0.0001); + assertEquals(100, b.getMinY(), 0.0001); + assertEquals(200, b.getWidth(), 0.0001); + assertEquals(100, b.getHeight(), 0.0001); + + NodeShim.set_boundsChanged(rb, false); + //((Node) rb).NodeShim.boundsChanged(rb) = false; + // rt, rb should be either incorporated into parent bounds directly + // or marked as dirty for parent bounds calculation + g.getChildren().addAll(rt, rb); + + b = g.getBoundsInLocal(); + assertEquals(100, b.getMinX(), 0.0001); + assertEquals(100, b.getMinY(), 0.0001); + assertEquals(300, b.getWidth(), 0.0001); + assertEquals(200, b.getHeight(), 0.0001); + } + + @Test + public void shouldNotCreateEmptyDirtyChildrenList() { + final Group g = new Group(); + final Rectangle lt = new Rectangle(100, 100, 100, 100); + final Rectangle rt = new Rectangle(200, 100, 100, 100); + final Rectangle lb = new Rectangle(100, 200, 100, 100); + final Rectangle rb = new Rectangle(200, 200, 100, 100); + Bounds b; + + g.getChildren().addAll(lt, rt, lb, rb); + final int toAdd = ParentShim.DIRTY_CHILDREN_THRESHOLD - 4; + for (int i = 0; i < toAdd; ++i) { + g.getChildren().add( + new Rectangle(150 + i * 80 / (toAdd - 1), 190, 20, 20)); + } + + b = g.getBoundsInLocal(); + assertEquals(100, b.getMinX(), 0.0001); + assertEquals(100, b.getMinY(), 0.0001); + assertEquals(200, b.getWidth(), 0.0001); + assertEquals(200, b.getHeight(), 0.0001); + + lt.setX(50); + // this should create a dirty children list on Parent, even though + // the added node doesn't change the group gemetry, the created + // dirty children list should still contain the previously modified + // corner node (lt) + g.getChildren().add(new Rectangle(150, 150, 100, 100)); + + b = g.getBoundsInLocal(); + assertEquals(50, b.getMinX(), 0.0001); + assertEquals(100, b.getMinY(), 0.0001); + assertEquals(250, b.getWidth(), 0.0001); + assertEquals(200, b.getHeight(), 0.0001); + } + + @Test + public void + shouldNotSkipGeomChangedForChildAdditionInsideUntransformedBounds() + { + final Group g = new Group(new Circle(0, -100, 0.001), + new Circle(0, 100, 0.001), + new Circle(-100, 0, 0.001), + new Circle(100, 0, 0.001)); + g.getTransforms().add(new Rotate(-45)); + + Bounds b; + + b = g.getBoundsInParent(); + assertEquals(-100 * Math.sqrt(2) / 2, b.getMinX(), 0.1); + assertEquals(-100 * Math.sqrt(2) / 2, b.getMinY(), 0.1); + assertEquals(100 * Math.sqrt(2), b.getWidth(), 0.1); + assertEquals(100 * Math.sqrt(2), b.getHeight(), 0.1); + + g.getChildren().add(new Circle(95, -95, 0.001)); + + b = g.getBoundsInParent(); + assertEquals(-100 * Math.sqrt(2) / 2, b.getMinX(), 0.1); + assertEquals(-95 * Math.sqrt(2), b.getMinY(), 0.1); + assertEquals(100 * Math.sqrt(2), b.getWidth(), 0.1); + assertEquals((50 + 95) * Math.sqrt(2), b.getHeight(), 0.1); + } + + @Test + public void + shouldNotSkipGeomChangedForChildRemovalInsideUntransformedBounds() + { + final Circle toRemove = new Circle(95, -95, 0.001); + final Group g = new Group(toRemove, + new Circle(0, -100, 0.001), + new Circle(0, 100, 0.001), + new Circle(-100, 0, 0.001), + new Circle(100, 0, 0.001)); + g.getTransforms().add(new Rotate(-45)); + + Bounds b; + + b = g.getBoundsInParent(); + assertEquals(-100 * Math.sqrt(2) / 2, b.getMinX(), 0.1); + assertEquals(-95 * Math.sqrt(2), b.getMinY(), 0.1); + assertEquals(100 * Math.sqrt(2), b.getWidth(), 0.1); + assertEquals((50 + 95) * Math.sqrt(2), b.getHeight(), 0.1); + + g.getChildren().remove(toRemove); + + b = g.getBoundsInParent(); + assertEquals(-100 * Math.sqrt(2) / 2, b.getMinX(), 0.1); + assertEquals(-100 * Math.sqrt(2) / 2, b.getMinY(), 0.1); + assertEquals(100 * Math.sqrt(2), b.getWidth(), 0.1); + assertEquals(100 * Math.sqrt(2), b.getHeight(), 0.1); + } + + @Test + public void nodeShouldNotifyParentEvenIfItsTransformedBoundsAreDirty() { + final Rectangle child = new Rectangle(0, 0, 100, 100); + final Group parent = new Group(child); + + // ensures that child's getTransformedBounds will be called with + // a non-identity transform argument during parent's bounds calculations + parent.getTransforms().add(new Rotate(-45)); + + // make the cached child's transformed bounds dirty + child.getTransforms().add(new Translate(50, 0)); + + // the cached child's transformed bounds will remain dirty because + // of a non-trivial parent transformation + TestHelper.assertSimilar( + boundsOfRotatedRect(50, 0, 100, 100, -45), + parent.getBoundsInParent()); + + // during the following call the child bounds changed notification + // should still be generated even though the child's cached transformed + // bounds are already dirty + child.getTransforms().add(new Translate(50, 0)); + + TestHelper.assertSimilar( + boundsOfRotatedRect(100, 0, 100, 100, -45), + parent.getBoundsInParent()); + } + + private static Bounds boundsOfRotatedRect( + final double x, final double y, + final double width, final double height, + final double angle) { + final Point2D p1 = rotatePoint(x, y, angle); + final Point2D p2 = rotatePoint(x + width, y, angle); + final Point2D p3 = rotatePoint(x, y + height, angle); + final Point2D p4 = rotatePoint(x + width, y + height, angle); + + final double minx = min(p1.getX(), p2.getX(), p3.getX(), p4.getX()); + final double miny = min(p1.getY(), p2.getY(), p3.getY(), p4.getY()); + final double maxx = max(p1.getX(), p2.getX(), p3.getX(), p4.getX()); + final double maxy = max(p1.getY(), p2.getY(), p3.getY(), p4.getY()); + + return new BoundingBox(minx, miny, maxx - minx, maxy - miny); + } + + private static Point2D rotatePoint(final double x, final double y, + final double angle) { + final double rada = Math.toRadians(angle); + final double sina = Math.sin(rada); + final double cosa = Math.cos(rada); + + return new Point2D(x * cosa - y * sina, + x * sina + y * cosa); + } + + private static double min(final double... values) { + double result = values[0]; + for (int i = 1; i < values.length; ++i) { + if (result > values[i]) { + result = values[i]; + } + } + + return result; + } + + private static double max(final double... values) { + double result = values[0]; + for (int i = 1; i < values.length; ++i) { + if (result < values[i]) { + result = values[i]; + } + } + + return result; + } +}