1 /*
   2  * Copyright (c) 2012, 2014, Oracle and/or its affiliates.
   3  * All rights reserved. Use is subject to license terms.
   4  *
   5  * This file is available and licensed under the following license:
   6  *
   7  * Redistribution and use in source and binary forms, with or without
   8  * modification, are permitted provided that the following conditions
   9  * are met:
  10  *
  11  *  - Redistributions of source code must retain the above copyright
  12  *    notice, this list of conditions and the following disclaimer.
  13  *  - Redistributions in binary form must reproduce the above copyright
  14  *    notice, this list of conditions and the following disclaimer in
  15  *    the documentation and/or other materials provided with the distribution.
  16  *  - Neither the name of Oracle Corporation nor the names of its
  17  *    contributors may be used to endorse or promote products derived
  18  *    from this software without specific prior written permission.
  19  *
  20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31  */
  32 
  33 package com.oracle.javafx.scenebuilder.kit.editor.panel.content.guides;
  34 
  35 import com.oracle.javafx.scenebuilder.kit.util.MathUtils;
  36 import java.util.Collections;
  37 import java.util.List;
  38 import javafx.geometry.Bounds;
  39 import javafx.scene.Group;
  40 import javafx.scene.Node;
  41 import javafx.scene.paint.Paint;
  42 
  43 /**
  44  *
  45  */
  46 public class MovingGuideController {
  47 
  48     private final double MATCH_DISTANCE = 6.0;
  49 
  50     private final HorizontalLineIndex horizontalLineIndex = new HorizontalLineIndex();
  51     private final VerticalLineIndex verticalLineIndex = new VerticalLineIndex();
  52     private final MovingGuideRenderer renderer;
  53     private double suggestedDX;
  54     private double suggestedDY;
  55 
  56     public MovingGuideController(Paint chromeColor, Bounds scopeInScene) {
  57         this.renderer = new MovingGuideRenderer(chromeColor, scopeInScene);
  58     }
  59 
  60     public void addSampleBounds(Node node) {
  61         assert node != null;
  62         assert node.getScene() != null;
  63 
  64         final Bounds layoutBounds = node.getLayoutBounds();
  65         final Bounds boundsInScene = node.localToScene(layoutBounds, true /* rootScene */);
  66         addSampleBounds(boundsInScene, true /* addMiddle */);
  67     }
  68 
  69     public void addSampleBounds(Bounds boundsInScene, boolean addMiddle) {
  70         final double minX = boundsInScene.getMinX();
  71         final double minY = boundsInScene.getMinY();
  72         final double maxX = boundsInScene.getMaxX();
  73         final double maxY = boundsInScene.getMaxY();
  74         final double midX = (minX + maxX) / 2.0;
  75         final double midY = (minY + maxY) / 2.0;
  76 
  77         horizontalLineIndex.addLine(new HorizontalSegment(minX, maxX, minY));
  78         horizontalLineIndex.addLine(new HorizontalSegment(minX, maxX, maxY));
  79         verticalLineIndex.addLine(new VerticalSegment(minX, minY, maxY));
  80         verticalLineIndex.addLine(new VerticalSegment(maxX, minY, maxY));
  81 
  82         if (addMiddle) {
  83             horizontalLineIndex.addLine(new HorizontalSegment(minX, maxX, midY));
  84             verticalLineIndex.addLine(new VerticalSegment(midX, minY, maxY));
  85         }
  86     }
  87 
  88     public void clearSampleBounds() {
  89         horizontalLineIndex.clear();
  90         verticalLineIndex.clear();
  91         clear();
  92     }
  93 
  94     public boolean hasSampleBounds() {
  95         return (horizontalLineIndex.isEmpty() == false) || (verticalLineIndex.isEmpty() == false);
  96     }
  97 
  98     public void clear() {
  99         renderer.setLines(Collections.emptyList(), Collections.emptyList());
 100     }
 101 
 102     public void match(Bounds targetBounds) {
 103         List<HorizontalSegment> horizontalMatchingLines;
 104         List<VerticalSegment> verticalMatchingLines;
 105         boolean matchedHorizontally = false;
 106         boolean matchedVertically = false;
 107 
 108         // Match horizontal center line of targetBounds
 109         horizontalMatchingLines
 110                 = horizontalLineIndex.matchCenter(targetBounds, MATCH_DISTANCE);
 111         if (horizontalMatchingLines.isEmpty() == false) {
 112             matchedHorizontally = true;
 113             final HorizontalSegment line = horizontalMatchingLines.get(0);
 114             assert MathUtils.equals(line.getY1(), line.getY2());
 115             final double targetMinY = targetBounds.getMinY();
 116             final double targetMaxY = targetBounds.getMaxY();
 117             final double targetMidY = (targetMinY + targetMaxY) / 2.0;
 118             suggestedDY = line.getY1() - targetMidY;
 119         }
 120 
 121         // Match north boundary of targetBounds
 122         if (matchedHorizontally == false) {
 123             horizontalMatchingLines
 124                     = horizontalLineIndex.matchNorth(targetBounds, MATCH_DISTANCE);
 125             if (horizontalMatchingLines.isEmpty() == false) {
 126                 matchedHorizontally = true;
 127                 final HorizontalSegment line = horizontalMatchingLines.get(0);
 128                 assert MathUtils.equals(line.getY1(), line.getY2());
 129                 suggestedDY = line.getY1() - targetBounds.getMinY();
 130             }
 131         }
 132 
 133         // Match south boundary of targetBounds
 134         if (matchedHorizontally == false) {
 135             horizontalMatchingLines
 136                     = horizontalLineIndex.matchSouth(targetBounds, MATCH_DISTANCE);
 137             if (horizontalMatchingLines.isEmpty() == false) {
 138                 matchedHorizontally = true;
 139                 final HorizontalSegment line = horizontalMatchingLines.get(0);
 140                 assert MathUtils.equals(line.getY1(), line.getY2());
 141                 suggestedDY = line.getY1() - targetBounds.getMaxY();
 142             }
 143         }
 144 
 145         if (matchedHorizontally == false) {
 146             suggestedDY = 0.0;
 147         }
 148 
 149         // Match vertical center line of targetBounds
 150         verticalMatchingLines
 151                 = verticalLineIndex.matchCenter(targetBounds, MATCH_DISTANCE);
 152         if (verticalMatchingLines.isEmpty() == false) {
 153             matchedVertically = true;
 154             final VerticalSegment line = verticalMatchingLines.get(0);
 155             assert MathUtils.equals(line.getX1(), line.getX2());
 156             final double targetMinX = targetBounds.getMinX();
 157             final double targetMaxX = targetBounds.getMaxX();
 158             final double targetMidX = (targetMinX + targetMaxX) / 2.0;
 159             suggestedDX = line.getX1() - targetMidX;
 160         }
 161 
 162         // Match west boundary of targetBounds
 163         if (matchedVertically == false) {
 164             verticalMatchingLines
 165                     = verticalLineIndex.matchWest(targetBounds, MATCH_DISTANCE);
 166             if (verticalMatchingLines.isEmpty() == false) {
 167                 matchedVertically = true;
 168                 final VerticalSegment line = verticalMatchingLines.get(0);
 169                 assert MathUtils.equals(line.getX1(), line.getX2());
 170                 suggestedDX = line.getX1() - targetBounds.getMinX();
 171             }
 172         }
 173 
 174         // Match east boundary of targetBounds
 175         if (matchedVertically == false) {
 176             verticalMatchingLines
 177                     = verticalLineIndex.matchEast(targetBounds, MATCH_DISTANCE);
 178             if (verticalMatchingLines.isEmpty() == false) {
 179                 matchedVertically = true;
 180                 final VerticalSegment line = verticalMatchingLines.get(0);
 181                 assert MathUtils.equals(line.getX1(), line.getX2());
 182                 suggestedDX = line.getX1() - targetBounds.getMaxX();
 183             }
 184         }
 185 
 186         if (matchedVertically == false) {
 187             suggestedDX = 0.0;
 188         }
 189 
 190         renderer.setLines(horizontalMatchingLines, verticalMatchingLines);
 191     }
 192 
 193 
 194     public double getSuggestedDX() {
 195         return suggestedDX;
 196     }
 197 
 198 
 199     public double getSuggestedDY() {
 200         return suggestedDY;
 201     }
 202 
 203     public Group getGuideGroup() {
 204         return renderer.getGuideGroup();
 205     }
 206 }