1 /* 2 * Copyright (c) 2010, 2013, 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 javafx.scene; 27 28 import com.sun.javafx.test.TestHelper; 29 import javafx.geometry.BoundingBox; 30 import javafx.scene.transform.Rotate; 31 import static org.junit.Assert.assertEquals; 32 import static org.junit.Assert.assertTrue; 33 import javafx.geometry.Bounds; 34 import javafx.geometry.Point2D; 35 import javafx.scene.shape.Rectangle; 36 import javafx.scene.shape.Circle; 37 import javafx.scene.transform.Translate; 38 39 import org.junit.Test; 40 41 public class Parent_recomputeBounds_Test { 42 private static final Rectangle r1 = new Rectangle(100, 100, 100, 100); 43 private static final Rectangle r2 = new Rectangle(250, 250, 50, 50); 44 private static final Rectangle r3 = new Rectangle(50, 50, 10, 10); 45 46 @Test 47 public void shouldRecomputeBoundsWhenNodeAdded() { 48 final Group g = new Group(); 49 Bounds b; 50 51 b = g.getBoundsInParent(); 52 assertTrue(b.isEmpty()); 53 54 g.getChildren().add(r1); 55 56 b = g.getBoundsInParent(); 57 assertEquals(100, b.getMinX(), 0.0001); 58 assertEquals(100, b.getMinY(), 0.0001); 59 assertEquals(100, b.getWidth(), 0.0001); 60 assertEquals(100, b.getHeight(), 0.0001); 61 62 g.getChildren().add(r2); 63 64 b = g.getBoundsInParent(); 65 assertEquals(100, b.getMinX(), 0.0001); 66 assertEquals(100, b.getMinY(), 0.0001); 67 assertEquals(200, b.getWidth(), 0.0001); 68 assertEquals(200, b.getHeight(), 0.0001); 69 70 g.getChildren().add(r3); 71 72 b = g.getBoundsInParent(); 73 assertEquals(50, b.getMinX(), 0.0001); 74 assertEquals(50, b.getMinY(), 0.0001); 75 assertEquals(250, b.getWidth(), 0.0001); 76 assertEquals(250, b.getHeight(), 0.0001); 77 } 78 79 @Test 80 public void shouldRecomputeBoundsWhenNodeRemoved() { 81 final Group g = new Group(); 82 g.getChildren().add(r1); 83 g.getChildren().add(r2); 84 g.getChildren().add(r3); 85 Bounds b; 86 87 b = g.getBoundsInParent(); 88 assertEquals(50, b.getMinX(), 0.0001); 89 assertEquals(50, b.getMinY(), 0.0001); 90 assertEquals(250, b.getWidth(), 0.0001); 91 assertEquals(250, b.getHeight(), 0.0001); 92 93 g.getChildren().remove(r3); 94 95 b = g.getBoundsInParent(); 96 assertEquals(100, b.getMinX(), 0.0001); 97 assertEquals(100, b.getMinY(), 0.0001); 98 assertEquals(200, b.getWidth(), 0.0001); 99 assertEquals(200, b.getHeight(), 0.0001); 100 101 g.getChildren().remove(r2); 102 103 b = g.getBoundsInParent(); 104 assertEquals(100, b.getMinX(), 0.0001); 105 assertEquals(100, b.getMinY(), 0.0001); 106 assertEquals(100, b.getWidth(), 0.0001); 107 assertEquals(100, b.getHeight(), 0.0001); 108 109 g.getChildren().remove(r1); 110 111 b = g.getBoundsInParent(); 112 assertTrue(b.isEmpty()); 113 } 114 115 @Test 116 public void shouldRecomputeBoundsWhenNodeHiddenOrShown() { 117 final Group g = new Group(); 118 g.getChildren().add(r1); 119 g.getChildren().add(r2); 120 g.getChildren().add(r3); 121 Bounds b; 122 123 b = g.getBoundsInParent(); 124 assertEquals(50, b.getMinX(), 0.0001); 125 assertEquals(50, b.getMinY(), 0.0001); 126 assertEquals(250, b.getWidth(), 0.0001); 127 assertEquals(250, b.getHeight(), 0.0001); 128 129 r3.setVisible(false); 130 131 b = g.getBoundsInParent(); 132 assertEquals(100, b.getMinX(), 0.0001); 133 assertEquals(100, b.getMinY(), 0.0001); 134 assertEquals(200, b.getWidth(), 0.0001); 135 assertEquals(200, b.getHeight(), 0.0001); 136 137 r3.setVisible(true); 138 139 b = g.getBoundsInParent(); 140 assertEquals(50, b.getMinX(), 0.0001); 141 assertEquals(50, b.getMinY(), 0.0001); 142 assertEquals(250, b.getWidth(), 0.0001); 143 assertEquals(250, b.getHeight(), 0.0001); 144 } 145 146 @Test 147 public void shouldNotRecomputeBoundsWhenHiddenNodeChanges() { 148 final Group g = new Group(); 149 final Rectangle r = new Rectangle(200,200,1,1); 150 g.getChildren().add(r2); 151 g.getChildren().add(r); 152 g.getChildren().add(r3); 153 Bounds b; 154 155 r.setVisible(false); 156 157 b = g.getBoundsInParent(); 158 assertEquals(50, b.getMinX(), 0.0001); 159 assertEquals(50, b.getMinY(), 0.0001); 160 assertEquals(250, b.getWidth(), 0.0001); 161 assertEquals(250, b.getHeight(), 0.0001); 162 163 r.setX(10); 164 165 b = g.getBoundsInParent(); 166 assertEquals(50, b.getMinX(), 0.0001); 167 assertEquals(50, b.getMinY(), 0.0001); 168 assertEquals(250, b.getWidth(), 0.0001); 169 assertEquals(250, b.getHeight(), 0.0001); 170 171 r.setWidth(500); 172 173 b = g.getBoundsInParent(); 174 assertEquals(50, b.getMinX(), 0.0001); 175 assertEquals(50, b.getMinY(), 0.0001); 176 assertEquals(250, b.getWidth(), 0.0001); 177 assertEquals(250, b.getHeight(), 0.0001); 178 } 179 180 @Test 181 public void shouldNotRecomputeBoundsWhenNodeChangesInsideEdges() { 182 final Group g = new Group(); 183 final Rectangle r = new Rectangle(200,200,1,1); 184 g.getChildren().add(r2); 185 g.getChildren().add(r); 186 g.getChildren().add(r3); 187 Bounds b; 188 189 b = g.getBoundsInParent(); 190 assertEquals(50, b.getMinX(), 0.0001); 191 assertEquals(50, b.getMinY(), 0.0001); 192 assertEquals(250, b.getWidth(), 0.0001); 193 assertEquals(250, b.getHeight(), 0.0001); 194 195 r.setX(190); 196 r.setY(190); 197 r.setWidth(48); 198 r.setHeight(48); 199 200 b = g.getBoundsInParent(); 201 assertEquals(50, b.getMinX(), 0.0001); 202 assertEquals(50, b.getMinY(), 0.0001); 203 assertEquals(250, b.getWidth(), 0.0001); 204 assertEquals(250, b.getHeight(), 0.0001); 205 } 206 207 @Test 208 public void shouldNotRecomputeBoundsWhenEdgeNodeEnlargesInwards() { 209 final Group g = new Group(); 210 final Rectangle r = new Rectangle(200,200,50,50); 211 g.getChildren().add(r); 212 g.getChildren().add(r3); 213 Bounds b; 214 215 b = g.getBoundsInParent(); 216 assertEquals(50, b.getMinX(), 0.0001); 217 assertEquals(50, b.getMinY(), 0.0001); 218 assertEquals(200, b.getWidth(), 0.0001); 219 assertEquals(200, b.getHeight(), 0.0001); 220 221 r.setX(100); 222 r.setY(100); 223 r.setWidth(150); 224 r.setHeight(150); 225 226 b = g.getBoundsInParent(); 227 assertEquals(50, b.getMinX(), 0.0001); 228 assertEquals(50, b.getMinY(), 0.0001); 229 assertEquals(200, b.getWidth(), 0.0001); 230 assertEquals(200, b.getHeight(), 0.0001); 231 } 232 233 @Test 234 public void shouldRecomputeBoundsWhenNodeMoved() { 235 final Group g = new Group(); 236 final Rectangle r = new Rectangle(200,200,1,1); 237 238 g.getChildren().add(r2); 239 g.getChildren().add(r); 240 g.getChildren().add(r3); 241 Bounds b; 242 243 b = g.getBoundsInParent(); 244 assertEquals(50, b.getMinX(), 0.0001); 245 assertEquals(50, b.getMinY(), 0.0001); 246 assertEquals(250, b.getWidth(), 0.0001); 247 assertEquals(250, b.getHeight(), 0.0001); 248 249 r.setX(40); 250 251 b = g.getBoundsInParent(); 252 assertEquals(40, b.getMinX(), 0.0001); 253 assertEquals(50, b.getMinY(), 0.0001); 254 assertEquals(260, b.getWidth(), 0.0001); 255 assertEquals(250, b.getHeight(), 0.0001); 256 257 r.setX(200); 258 259 b = g.getBoundsInParent(); 260 assertEquals(50, b.getMinX(), 0.0001); 261 assertEquals(50, b.getMinY(), 0.0001); 262 assertEquals(250, b.getWidth(), 0.0001); 263 assertEquals(250, b.getHeight(), 0.0001); 264 } 265 266 @Test 267 public void shouldRecomputeBoundsWhenNodeResized() { 268 final Group g = new Group(); 269 final Rectangle r = new Rectangle(200,200,1,1); 270 271 g.getChildren().add(r2); 272 g.getChildren().add(r); 273 g.getChildren().add(r3); 274 Bounds b; 275 276 b = g.getBoundsInParent(); 277 assertEquals(50, b.getMinX(), 0.0001); 278 assertEquals(50, b.getMinY(), 0.0001); 279 assertEquals(250, b.getWidth(), 0.0001); 280 assertEquals(250, b.getHeight(), 0.0001); 281 282 r.setWidth(200); 283 284 b = g.getBoundsInParent(); 285 assertEquals(50, b.getMinX(), 0.0001); 286 assertEquals(50, b.getMinY(), 0.0001); 287 assertEquals(350, b.getWidth(), 0.0001); 288 assertEquals(250, b.getHeight(), 0.0001); 289 290 r.setWidth(5); 291 292 b = g.getBoundsInParent(); 293 assertEquals(50, b.getMinX(), 0.0001); 294 assertEquals(50, b.getMinY(), 0.0001); 295 assertEquals(250, b.getWidth(), 0.0001); 296 assertEquals(250, b.getHeight(), 0.0001); 297 } 298 299 @Test 300 public void shouldNotRecomputeBoundsWhenManyNodesChange() { 301 final Group g = new Group(); 302 final Rectangle lt = new Rectangle(100, 100, 100, 100); 303 final Rectangle rt = new Rectangle(200, 100, 100, 100); 304 final Rectangle lb = new Rectangle(100, 200, 100, 100); 305 final Rectangle rb = new Rectangle(200, 200, 100, 100); 306 final Rectangle rm1 = new Rectangle(150, 150, 50, 50); 307 final Rectangle rm2 = new Rectangle(150, 150, 50, 50); 308 Bounds b; 309 310 g.getChildren().add(lt); 311 g.getChildren().add(rt); 312 g.getChildren().add(lb); 313 g.getChildren().add(rb); 314 g.getChildren().add(rm1); 315 g.getChildren().add(rm2); 316 for (int i = 0; i < 20; i++) { 317 g.getChildren().add(new Rectangle(150 + i, 150 + i, 50 + i, 50 + i)); 318 } 319 320 b = g.getBoundsInParent(); 321 assertEquals(100, b.getMinX(), 0.0001); 322 assertEquals(100, b.getMinY(), 0.0001); 323 assertEquals(200, b.getWidth(), 0.0001); 324 assertEquals(200, b.getHeight(), 0.0001); 325 326 lt.setX(50); 327 rt.setX(250); 328 lb.setY(250); 329 rb.setX(220); 330 rm1.setY(50); 331 rm2.setX(151); 332 rm2.setY(151); 333 334 b = g.getBoundsInParent(); 335 assertEquals(50, b.getMinX(), 0.0001); 336 assertEquals(50, b.getMinY(), 0.0001); 337 assertEquals(300, b.getWidth(), 0.0001); 338 assertEquals(300, b.getHeight(), 0.0001); 339 } 340 341 @Test 342 public void transformedBoundsCalculationShouldNotInfluenceUntransformed() { 343 final Group g = new Group(); 344 final Rectangle lt = new Rectangle(100, 100, 100, 100); 345 final Rectangle rt = new Rectangle(200, 100, 100, 100); 346 final Rectangle lb = new Rectangle(100, 200, 100, 100); 347 final Rectangle rb = new Rectangle(200, 200, 100, 100); 348 final Rectangle rm1 = new Rectangle(150, 150, 50, 50); 349 final Rectangle rm2 = new Rectangle(150, 150, 50, 50); 350 Bounds b; 351 352 g.getChildren().addAll(lt, rt, lb, rb, rm1, rm2); 353 for (int i = 0; i < 20; i++) { 354 g.getChildren().add(new Rectangle(150 + i, 150 + i, 50 + i, 50 + i)); 355 } 356 357 b = g.getBoundsInLocal(); 358 assertEquals(100, b.getMinX(), 0.0001); 359 assertEquals(100, b.getMinY(), 0.0001); 360 assertEquals(200, b.getWidth(), 0.0001); 361 assertEquals(200, b.getHeight(), 0.0001); 362 363 g.getTransforms().add(new Rotate(45)); 364 365 lt.setX(50); 366 rt.setX(250); 367 lb.setY(250); 368 rb.setX(220); 369 rm1.setY(50); 370 rm2.setX(151); 371 rm2.setY(151); 372 373 // this call gets transformed Parent bounds and shouldn't clear the 374 // Parent's dirty children list 375 g.getBoundsInParent(); 376 377 b = g.getBoundsInLocal(); 378 assertEquals(50, b.getMinX(), 0.0001); 379 assertEquals(50, b.getMinY(), 0.0001); 380 assertEquals(300, b.getWidth(), 0.0001); 381 assertEquals(300, b.getHeight(), 0.0001); 382 } 383 384 @Test 385 public void shouldNotStartBoundsCalculationFromEmptyBounds() { 386 final Group g = new Group(); 387 final Rectangle lt = new Rectangle(50, 50, 50, 50); 388 final Rectangle rb = new Rectangle(100, 100, 50, 50); 389 Bounds b; 390 391 g.getChildren().addAll(lt, rb); 392 393 b = g.getBoundsInLocal(); 394 assertEquals(50, b.getMinX(), 0.0001); 395 assertEquals(50, b.getMinY(), 0.0001); 396 assertEquals(100, b.getWidth(), 0.0001); 397 assertEquals(100, b.getHeight(), 0.0001); 398 399 // invalidate (empty) bounds 400 rb.setVisible(false); 401 402 // add new rectangle, bounds should not be recalculated by using 403 // empty bounds 404 g.getChildren().add(new Rectangle(150, 150, 50, 50)); 405 406 b = g.getBoundsInLocal(); 407 assertEquals(50, b.getMinX(), 0.0001); 408 assertEquals(50, b.getMinY(), 0.0001); 409 assertEquals(150, b.getWidth(), 0.0001); 410 assertEquals(150, b.getHeight(), 0.0001); 411 } 412 413 @Test 414 public void shouldNotIgnoreMultipleAddedNodes() { 415 final Group g = new Group(); 416 final Rectangle lt = new Rectangle(100, 100, 100, 100); 417 final Rectangle mt = new Rectangle(200, 100, 100, 100); 418 final Rectangle rt = new Rectangle(300, 100, 100, 100); 419 final Rectangle rb = new Rectangle(300, 200, 100, 100); 420 Bounds b; 421 422 g.getChildren().addAll(lt, mt); 423 424 b = g.getBoundsInLocal(); 425 assertEquals(100, b.getMinX(), 0.0001); 426 assertEquals(100, b.getMinY(), 0.0001); 427 assertEquals(200, b.getWidth(), 0.0001); 428 assertEquals(100, b.getHeight(), 0.0001); 429 430 ((Node) rb).boundsChanged = false; 431 // rt, rb should be either incorporated into parent bounds directly 432 // or marked as dirty for parent bounds calculation 433 g.getChildren().addAll(rt, rb); 434 435 b = g.getBoundsInLocal(); 436 assertEquals(100, b.getMinX(), 0.0001); 437 assertEquals(100, b.getMinY(), 0.0001); 438 assertEquals(300, b.getWidth(), 0.0001); 439 assertEquals(200, b.getHeight(), 0.0001); 440 } 441 442 @Test 443 public void shouldNotCreateEmptyDirtyChildrenList() { 444 final Group g = new Group(); 445 final Rectangle lt = new Rectangle(100, 100, 100, 100); 446 final Rectangle rt = new Rectangle(200, 100, 100, 100); 447 final Rectangle lb = new Rectangle(100, 200, 100, 100); 448 final Rectangle rb = new Rectangle(200, 200, 100, 100); 449 Bounds b; 450 451 g.getChildren().addAll(lt, rt, lb, rb); 452 final int toAdd = Parent.DIRTY_CHILDREN_THRESHOLD - 4; 453 for (int i = 0; i < toAdd; ++i) { 454 g.getChildren().add( 455 new Rectangle(150 + i * 80 / (toAdd - 1), 190, 20, 20)); 456 } 457 458 b = g.getBoundsInLocal(); 459 assertEquals(100, b.getMinX(), 0.0001); 460 assertEquals(100, b.getMinY(), 0.0001); 461 assertEquals(200, b.getWidth(), 0.0001); 462 assertEquals(200, b.getHeight(), 0.0001); 463 464 lt.setX(50); 465 // this should create a dirty children list on Parent, even though 466 // the added node doesn't change the group gemetry, the created 467 // dirty children list should still contain the previously modified 468 // corner node (lt) 469 g.getChildren().add(new Rectangle(150, 150, 100, 100)); 470 471 b = g.getBoundsInLocal(); 472 assertEquals(50, b.getMinX(), 0.0001); 473 assertEquals(100, b.getMinY(), 0.0001); 474 assertEquals(250, b.getWidth(), 0.0001); 475 assertEquals(200, b.getHeight(), 0.0001); 476 } 477 478 @Test 479 public void 480 shouldNotSkipGeomChangedForChildAdditionInsideUntransformedBounds() 481 { 482 final Group g = new Group(new Circle(0, -100, 0.001), 483 new Circle(0, 100, 0.001), 484 new Circle(-100, 0, 0.001), 485 new Circle(100, 0, 0.001)); 486 g.getTransforms().add(new Rotate(-45)); 487 488 Bounds b; 489 490 b = g.getBoundsInParent(); 491 assertEquals(-100 * Math.sqrt(2) / 2, b.getMinX(), 0.1); 492 assertEquals(-100 * Math.sqrt(2) / 2, b.getMinY(), 0.1); 493 assertEquals(100 * Math.sqrt(2), b.getWidth(), 0.1); 494 assertEquals(100 * Math.sqrt(2), b.getHeight(), 0.1); 495 496 g.getChildren().add(new Circle(95, -95, 0.001)); 497 498 b = g.getBoundsInParent(); 499 assertEquals(-100 * Math.sqrt(2) / 2, b.getMinX(), 0.1); 500 assertEquals(-95 * Math.sqrt(2), b.getMinY(), 0.1); 501 assertEquals(100 * Math.sqrt(2), b.getWidth(), 0.1); 502 assertEquals((50 + 95) * Math.sqrt(2), b.getHeight(), 0.1); 503 } 504 505 @Test 506 public void 507 shouldNotSkipGeomChangedForChildRemovalInsideUntransformedBounds() 508 { 509 final Circle toRemove = new Circle(95, -95, 0.001); 510 final Group g = new Group(toRemove, 511 new Circle(0, -100, 0.001), 512 new Circle(0, 100, 0.001), 513 new Circle(-100, 0, 0.001), 514 new Circle(100, 0, 0.001)); 515 g.getTransforms().add(new Rotate(-45)); 516 517 Bounds b; 518 519 b = g.getBoundsInParent(); 520 assertEquals(-100 * Math.sqrt(2) / 2, b.getMinX(), 0.1); 521 assertEquals(-95 * Math.sqrt(2), b.getMinY(), 0.1); 522 assertEquals(100 * Math.sqrt(2), b.getWidth(), 0.1); 523 assertEquals((50 + 95) * Math.sqrt(2), b.getHeight(), 0.1); 524 525 g.getChildren().remove(toRemove); 526 527 b = g.getBoundsInParent(); 528 assertEquals(-100 * Math.sqrt(2) / 2, b.getMinX(), 0.1); 529 assertEquals(-100 * Math.sqrt(2) / 2, b.getMinY(), 0.1); 530 assertEquals(100 * Math.sqrt(2), b.getWidth(), 0.1); 531 assertEquals(100 * Math.sqrt(2), b.getHeight(), 0.1); 532 } 533 534 @Test 535 public void nodeShouldNotifyParentEvenIfItsTransformedBoundsAreDirty() { 536 final Rectangle child = new Rectangle(0, 0, 100, 100); 537 final Group parent = new Group(child); 538 539 // ensures that child's getTransformedBounds will be called with 540 // a non-identity transform argument during parent's bounds calculations 541 parent.getTransforms().add(new Rotate(-45)); 542 543 // make the cached child's transformed bounds dirty 544 child.getTransforms().add(new Translate(50, 0)); 545 546 // the cached child's transformed bounds will remain dirty because 547 // of a non-trivial parent transformation 548 TestHelper.assertSimilar( 549 boundsOfRotatedRect(50, 0, 100, 100, -45), 550 parent.getBoundsInParent()); 551 552 // during the following call the child bounds changed notification 553 // should still be generated even though the child's cached transformed 554 // bounds are already dirty 555 child.getTransforms().add(new Translate(50, 0)); 556 557 TestHelper.assertSimilar( 558 boundsOfRotatedRect(100, 0, 100, 100, -45), 559 parent.getBoundsInParent()); 560 } 561 562 private static Bounds boundsOfRotatedRect( 563 final double x, final double y, 564 final double width, final double height, 565 final double angle) { 566 final Point2D p1 = rotatePoint(x, y, angle); 567 final Point2D p2 = rotatePoint(x + width, y, angle); 568 final Point2D p3 = rotatePoint(x, y + height, angle); 569 final Point2D p4 = rotatePoint(x + width, y + height, angle); 570 571 final double minx = min(p1.getX(), p2.getX(), p3.getX(), p4.getX()); 572 final double miny = min(p1.getY(), p2.getY(), p3.getY(), p4.getY()); 573 final double maxx = max(p1.getX(), p2.getX(), p3.getX(), p4.getX()); 574 final double maxy = max(p1.getY(), p2.getY(), p3.getY(), p4.getY()); 575 576 return new BoundingBox(minx, miny, maxx - minx, maxy - miny); 577 } 578 579 private static Point2D rotatePoint(final double x, final double y, 580 final double angle) { 581 final double rada = Math.toRadians(angle); 582 final double sina = Math.sin(rada); 583 final double cosa = Math.cos(rada); 584 585 return new Point2D(x * cosa - y * sina, 586 x * sina + y * cosa); 587 } 588 589 private static double min(final double... values) { 590 double result = values[0]; 591 for (int i = 1; i < values.length; ++i) { 592 if (result > values[i]) { 593 result = values[i]; 594 } 595 } 596 597 return result; 598 } 599 600 private static double max(final double... values) { 601 double result = values[0]; 602 for (int i = 1; i < values.length; ++i) { 603 if (result < values[i]) { 604 result = values[i]; 605 } 606 } 607 608 return result; 609 } 610 }