1 /*
   2  * Copyright (c) 2011, 2014, 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 com.sun.javafx.sg.prism;
  27 
  28 import java.util.ArrayList;
  29 import java.util.List;
  30 import com.sun.javafx.geom.BaseBounds;
  31 import com.sun.javafx.geom.RectBounds;
  32 import com.sun.javafx.geom.transform.BaseTransform;
  33 import com.sun.scenario.effect.DropShadow;
  34 import org.junit.Before;
  35 import org.junit.Test;
  36 import org.junit.runner.RunWith;
  37 import org.junit.runners.Parameterized;
  38 import static org.junit.Assert.assertEquals;
  39 import static org.junit.Assert.assertFalse;
  40 
  41 /**
  42  * A series of tests where we are checking for the dirty region on a grid
  43  * of nodes. This is a parameterized test (based on its base class).
  44  * Each time the test is run, it will be given a way to create nodes,
  45  * and a way to make them dirty. Each test then only has to invoke the
  46  * creator to make nodes and the polluter to make them dirty, and then
  47  * check that the right methods were called and the right dirty regions
  48  * computed. There is some magic here -- every node created by the creator
  49  * must implement TestNGNode; the base class must set the "root" in the
  50  * parent class.
  51  */
  52 @RunWith(Parameterized.class)
  53 public class GridDirtyRegionTest extends DirtyRegionTestBase {
  54     /**
  55      * Specified to avoid magic numbers. There are several places where we
  56      * translate the root in order to make it dirty.
  57      */
  58     private static final float TRANSLATE_DELTA = 50;
  59 
  60     /**
  61      * Creates a new instance of the GridDirtyRegionTest. Since this is a
  62      * parameterized test, we are passed the node creator and polluter.
  63      */
  64     public GridDirtyRegionTest(Creator creator, Polluter polluter) {
  65         super(creator, polluter);
  66     }
  67 
  68     /**
  69      * Constructs a non-overlapping grid of nodes. Each node is a direct child
  70      * of the root node. They may end up overlapping when they become dirty,
  71      * but they don't start out that way! Each node is placed where it belongs
  72      * in the grid by translating them into place.
  73      */
  74     @Before public void setUp() {
  75         // create the grid
  76         NGNode[] content = new NGNode[9];
  77         for (int row=0; row<3; row++) {
  78             for (int col=0; col<3; col++) {
  79                 NGNode node = creator.create();
  80                 BaseTransform tx = BaseTransform.IDENTITY_TRANSFORM;
  81                 tx = tx.deriveWithTranslation((col * 110), (row * 110));
  82                 transform(node, tx);
  83                 content[(row * 3) + col] = node;
  84             }
  85         }
  86         root = createGroup(content);
  87 
  88         // The grid is created & populated. We'll now go through and manually
  89         // clean them all up so that when we perform the test, it is from the
  90         // starting point of a completely cleaned tree
  91         root.render(TestGraphics.TEST_GRAPHICS);
  92     }
  93 
  94     @Test public void sanityCheck() {
  95         NGNode node = root.getChildren().get(0);
  96         assertEquals(new RectBounds(0, 0, 100, 100), node.getContentBounds(new RectBounds(), BaseTransform.IDENTITY_TRANSFORM));
  97         assertEquals(new RectBounds(0, 0, 100, 100), node.getCompleteBounds(new RectBounds(), BaseTransform.IDENTITY_TRANSFORM));
  98 
  99         node = root.getChildren().get(1);
 100         assertEquals(new RectBounds(0, 0, 100, 100), node.getContentBounds(new RectBounds(), BaseTransform.IDENTITY_TRANSFORM));
 101         assertEquals(new RectBounds(110, 0, 210, 100), node.getCompleteBounds(new RectBounds(), BaseTransform.IDENTITY_TRANSFORM));
 102 
 103         node = root.getChildren().get(3);
 104         assertEquals(new RectBounds(0, 0, 100, 100), node.getContentBounds(new RectBounds(), BaseTransform.IDENTITY_TRANSFORM));
 105         assertEquals(new RectBounds(0, 110, 100, 210), node.getCompleteBounds(new RectBounds(), BaseTransform.IDENTITY_TRANSFORM));
 106     }
 107 
 108     @Test public void cleanNodesShouldNotContributeToDirtyRegion() {
 109         // By default the scene should be clean
 110         assertDirtyRegionEquals(root, new RectBounds());
 111     }
 112 
 113     @Test public void cleanChildNodesOnADirtyParentShouldNotContributeToDirtyRegion() {
 114         // Now if I translate the root, none of the child nodes
 115         // should contribute to the dirty region
 116         translate(root, TRANSLATE_DELTA, TRANSLATE_DELTA);
 117         for (NGNode child : root.getChildren()) {
 118             assertDirtyRegionEquals(child, new RectBounds());
 119         }
 120     }
 121 
 122     @Test public void whenOnlyTheRootIsDirtyOnlyTheRootShouldBeAskedToAccumulateDirtyRegions() {
 123         translate(root, TRANSLATE_DELTA, TRANSLATE_DELTA);
 124         assertOnlyTheseNodesAreAskedToAccumulateDirtyRegions(root);
 125     }
 126 
 127     @Test public void cleanChildNodesOnACleanParentShouldNotContributeToDirtyRegion() {
 128         // If I make one of the children dirty, then the child should contribute
 129         // to the dirty region, but none of the other nodes should. This test just
 130         // checks this second part -- that none of the other child nodes contribute.
 131         NGNode middleChild = root.getChildren().get(root.getChildren().size()/2);
 132         polluter.pollute(middleChild);
 133         for (NGNode child : root.getChildren()) {
 134             if (child != middleChild) { // skip the dirty node
 135                 assertDirtyRegionEquals(child, new RectBounds());
 136             }
 137         }
 138     }
 139 
 140     @Test public void whenOnlyASingleChildIsDirtyThenParentAndAllChildrenAreAskedToAccumulateDirtyRegions() {
 141         NGNode middleChild = root.getChildren().get(root.getChildren().size()/2);
 142         polluter.pollute(middleChild);
 143         List<NGNode> nodes = new ArrayList<>(root.getChildren());
 144         nodes.add(root);
 145         NGNode[] arr = new NGNode[nodes.size()];
 146         for (int i=0; i<nodes.size(); i++) arr[i] = nodes.get(i);
 147         // The middle child should be changed
 148         assertOnlyTheseNodesAreAskedToAccumulateDirtyRegions(arr);
 149     }
 150 
 151     @Test public void whenOnlyASingleChildIsDirtyThenOnlyParentAndThatChildShouldComputeDirtyRegions() {
 152         NGNode middleChild = root.getChildren().get(root.getChildren().size()/2);
 153         polluter.pollute(middleChild);
 154         /*
 155             Testing discovered a really great thing that happens -- if the dirty node is
 156             a group, but it has no dirty child nodes, then the group itself will not end
 157             up with accumulateGroupDirtyRegion getting called -- only the node variant.
 158             Which makes perfect sense and is something of an optimization.
 159          */
 160         assertOnlyTheseNodesAreAskedToComputeDirtyRegions(root, middleChild);
 161     }
 162 
 163     @Test public void aDirtyChildNodeShouldFormTheDirtyRegionWhenItIsTheOnlyDirtyNode() {
 164         NGNode middleChild = root.getChildren().get(root.getChildren().size()/2);
 165         assertDirtyRegionEquals(root, polluter.polluteAndGetExpectedBounds(middleChild));
 166     }
 167 
 168     @Test public void theUnionOfTwoDirtyChildNodesDirtyRegionsShouldFormTheDirtyRegionWhenTheyAreTheOnlyDirtyNodes() {
 169         NGNode firstChild = root.getChildren().get(0);
 170         NGNode middleChild = root.getChildren().get(root.getChildren().size()/2);
 171         RectBounds firstChildArea = polluter.polluteAndGetExpectedBounds(firstChild);
 172         RectBounds middleChildArea = polluter.polluteAndGetExpectedBounds(middleChild);
 173         RectBounds expected = (RectBounds)firstChildArea.deriveWithUnion(middleChildArea);
 174         assertDirtyRegionEquals(root, expected);
 175     }
 176 
 177     @Test public void whenTheParentIsDirtyAndSomeChildrenAreDirtyTheParentBoundsShouldFormTheDirtyRegion() {
 178         BaseBounds original = root.getCompleteBounds(new RectBounds(), BaseTransform.IDENTITY_TRANSFORM);
 179         translate(root, TRANSLATE_DELTA, TRANSLATE_DELTA);
 180         BaseBounds transformed = root.getCompleteBounds(new RectBounds(), BaseTransform.IDENTITY_TRANSFORM);
 181         polluter.pollute(root.getChildren().get(0));
 182         polluter.pollute(root.getChildren().get(root.getChildren().size()/2));
 183         polluter.pollute(root.getChildren().get(root.getChildren().size()-1));
 184         RectBounds expected = (RectBounds)original.deriveWithUnion(transformed);
 185         assertDirtyRegionEquals(root, expected);
 186     }
 187 
 188     @Test public void anEffectShouldChangeTheTransformedBoundsOfAChild() {
 189         NGNode middleChild = root.getChildren().get(root.getChildren().size()/2);
 190         BaseBounds oldTransformedBounds = middleChild.getCompleteBounds(new RectBounds(), BaseTransform.IDENTITY_TRANSFORM);
 191         DropShadow shadow = new DropShadow();
 192         shadow.setGaussianWidth(21);
 193         shadow.setGaussianHeight(21);
 194         shadow.setOffsetX(2);
 195         shadow.setOffsetY(2);
 196         setEffect(middleChild, shadow);
 197         BaseBounds newTransformedBounds = middleChild.getCompleteBounds(new RectBounds(), BaseTransform.IDENTITY_TRANSFORM);
 198         assertFalse(newTransformedBounds.equals(oldTransformedBounds));
 199     }
 200 
 201     @Test public void whenAnEffectIsSetTheChildBecomesDirtyAndTheDirtyRegionIncludesTheEffectBounds() {
 202         NGNode middleChild = root.getChildren().get(root.getChildren().size()/2);
 203         DropShadow shadow = new DropShadow();
 204         shadow.setGaussianWidth(21);
 205         shadow.setGaussianHeight(21);
 206         shadow.setOffsetX(2);
 207         shadow.setOffsetY(2);
 208         BaseBounds oldTransformedBounds = middleChild.getCompleteBounds(new RectBounds(), BaseTransform.IDENTITY_TRANSFORM);
 209         setEffect(middleChild, shadow);
 210         BaseBounds newTransformedBounds = middleChild.getCompleteBounds(new RectBounds(), BaseTransform.IDENTITY_TRANSFORM);
 211         RectBounds expected = (RectBounds)oldTransformedBounds.deriveWithUnion(newTransformedBounds);
 212         assertDirtyRegionEquals(root, expected);
 213     }
 214 
 215     @Test public void whenAnEffectIsChangedOnTheChildTheDirtyRegionIncludesTheOldAndNewEffectBounds() {
 216         NGNode middleChild = root.getChildren().get(root.getChildren().size()/2);
 217         DropShadow shadow = new DropShadow();
 218         shadow.setGaussianWidth(21);
 219         shadow.setGaussianHeight(21);
 220         shadow.setOffsetX(2);
 221         shadow.setOffsetY(2);
 222         BaseBounds oldTransformedBounds = middleChild.getCompleteBounds(new RectBounds(), BaseTransform.IDENTITY_TRANSFORM);
 223         setEffect(middleChild, shadow);
 224         BaseBounds newTransformedBounds = middleChild.getCompleteBounds(new RectBounds(), BaseTransform.IDENTITY_TRANSFORM);
 225         shadow.setOffsetX(20);
 226         shadow.setOffsetY(20);
 227         BaseBounds evenNewerTransformedBounds = middleChild.getCompleteBounds(new RectBounds(), BaseTransform.IDENTITY_TRANSFORM);
 228         RectBounds expected = (RectBounds)oldTransformedBounds.deriveWithUnion(newTransformedBounds).deriveWithUnion(evenNewerTransformedBounds);
 229         assertDirtyRegionEquals(root, expected);
 230     }
 231 
 232     // TODO be sure to test changing properties on the node clip. For example, use a rect clip
 233     // and change its geometry (RT-26928)
 234 
 235 
 236     // TODO be sure to write a number of tests regarding the screen clip, and make sure that
 237     // I test that accumulating dirty regions is correct in the presence of a clip. (RT-26928)
 238 }