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 package com.oracle.javafx.scenebuilder.kit.editor.panel.content.driver.handles; 33 34 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.ContentPanelController; 35 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.driver.AbstractDriver; 36 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.gesture.AbstractGesture; 37 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.gesture.mouse.DiscardGesture; 38 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.gesture.mouse.ResizeGesture; 39 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.util.CardinalPoint; 40 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMInstance; 41 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMObject; 42 import com.oracle.javafx.scenebuilder.kit.util.MathUtils; 43 import java.util.List; 44 import javafx.geometry.BoundingBox; 45 import javafx.geometry.Bounds; 46 import javafx.geometry.Point2D; 47 import javafx.scene.Cursor; 48 import javafx.scene.Node; 49 import javafx.scene.image.Image; 50 import javafx.scene.image.ImageView; 51 import javafx.scene.shape.ClosePath; 52 import javafx.scene.shape.LineTo; 53 import javafx.scene.shape.MoveTo; 54 import javafx.scene.shape.Path; 55 import javafx.scene.shape.PathElement; 56 57 /** 58 * 59 * 60 */ 61 public abstract class AbstractGenericHandles<T> extends AbstractHandles<T> { 62 63 /* 64 * 65 * handleNN 66 * handleNW o----------o----------o handleNE 67 * | | 68 * | | 69 * handleWW o o handleEE 70 * | | 71 * | | 72 * handleSW o----------o----------o handleSE 73 * handleSS 74 * 75 */ 76 77 private final ImageView handleNW = new ImageView(); 78 private final ImageView handleNE = new ImageView(); 79 private final ImageView handleSE = new ImageView(); 80 private final ImageView handleSW = new ImageView(); 81 private final ImageView handleNN = new ImageView(); 82 private final ImageView handleEE = new ImageView(); 83 private final ImageView handleSS = new ImageView(); 84 private final ImageView handleWW = new ImageView(); 85 private final MoveTo moveTo0 = new MoveTo(); 86 private final LineTo lineTo1 = new LineTo(); 87 private final LineTo lineTo2 = new LineTo(); 88 private final LineTo lineTo3 = new LineTo(); 89 90 public AbstractGenericHandles(ContentPanelController contentPanelController, 91 FXOMObject fxomObject, Class<T> sceneGraphObjectClass) { 92 super(contentPanelController, fxomObject, sceneGraphObjectClass); 93 94 final Path shadow = new Path(); 95 final List<PathElement> shadowElements = shadow.getElements(); 96 shadowElements.add(moveTo0); 97 shadowElements.add(lineTo1); 98 shadowElements.add(lineTo2); 99 shadowElements.add(lineTo3); 100 shadowElements.add(new ClosePath()); 101 shadow.getStyleClass().add("selection-rect"); 102 shadow.setMouseTransparent(true); 103 104 setupHandleImages(); 105 106 handleNW.setPickOnBounds(true); 107 handleNE.setPickOnBounds(true); 108 handleSE.setPickOnBounds(true); 109 handleSW.setPickOnBounds(true); 110 111 handleNN.setPickOnBounds(true); 112 handleEE.setPickOnBounds(true); 113 handleSS.setPickOnBounds(true); 114 handleWW.setPickOnBounds(true); 115 116 attachHandles(handleNW); 117 attachHandles(handleNE); 118 attachHandles(handleSE); 119 attachHandles(handleSW); 120 121 attachHandles(handleNN); 122 attachHandles(handleEE); 123 attachHandles(handleSS); 124 attachHandles(handleWW); 125 126 final List<Node> rootNodeChildren = getRootNode().getChildren(); 127 rootNodeChildren.add(shadow); 128 rootNodeChildren.add(handleNW); 129 rootNodeChildren.add(handleNE); 130 rootNodeChildren.add(handleSE); 131 rootNodeChildren.add(handleSW); 132 133 rootNodeChildren.add(handleNN); 134 rootNodeChildren.add(handleEE); 135 rootNodeChildren.add(handleSS); 136 rootNodeChildren.add(handleWW); 137 } 138 139 public Node getHandleNode(CardinalPoint cp) { 140 final Node result; 141 142 switch(cp) { 143 case N: 144 result = handleNN; 145 break; 146 case S: 147 result = handleSS; 148 break; 149 case E: 150 result = handleEE; 151 break; 152 case W: 153 result = handleWW; 154 break; 155 case NW: 156 result = handleNW; 157 break; 158 case NE: 159 result = handleNE; 160 break; 161 case SW: 162 result = handleSW; 163 break; 164 case SE: 165 result = handleSE; 166 break; 167 default: 168 assert false; 169 result = null; 170 break; 171 } 172 173 return result; 174 } 175 176 /* 177 * AbstractHandles 178 */ 179 180 @Override 181 protected void layoutDecoration() { 182 final Bounds b = getSceneGraphObjectBounds(); 183 184 final double minX = b.getMinX(); 185 final double minY = b.getMinY(); 186 final double maxX = b.getMaxX(); 187 final double maxY = b.getMaxY(); 188 final double midX = (minX + maxX) / 2.0; 189 final double midY = (minY + maxY) / 2.0; 190 191 final boolean zeroWidth = MathUtils.equals(minX, maxX); 192 final boolean zeroHeight = MathUtils.equals(minY, maxY); 193 194 final boolean snapToPixel = true; 195 final Point2D pNW, pNE, pSE, pSW; 196 final Point2D pNN, pEE, pSS, pWW; 197 198 if (zeroWidth && zeroHeight) { 199 pNW = pNE = pSE = pSW = 200 pNN = pEE = pSS = pWW = 201 sceneGraphObjectToDecoration(minX, minY, snapToPixel); 202 } else if (zeroWidth) { 203 pNW = pNN = pNE = 204 sceneGraphObjectToDecoration(minX, minY, snapToPixel); 205 pSW = pSS = pSE = 206 sceneGraphObjectToDecoration(minX, maxY, snapToPixel); 207 pEE = pWW = 208 sceneGraphObjectToDecoration(minX, midY, snapToPixel); 209 } else if (b.getHeight() == 0) { 210 pNW = pWW = pSW = 211 sceneGraphObjectToDecoration(minX, minY, snapToPixel); 212 pNE = pEE = pSE = 213 sceneGraphObjectToDecoration(maxX, minY, snapToPixel); 214 pNN = pSS = 215 sceneGraphObjectToDecoration(midX, minY, snapToPixel); 216 } else { 217 pNW = sceneGraphObjectToDecoration(minX, minY, snapToPixel); 218 pNE = sceneGraphObjectToDecoration(maxX, minY, snapToPixel); 219 pSE = sceneGraphObjectToDecoration(maxX, maxY, snapToPixel); 220 pSW = sceneGraphObjectToDecoration(minX, maxY, snapToPixel); 221 222 pNN = sceneGraphObjectToDecoration(midX, minY, snapToPixel); 223 pEE = sceneGraphObjectToDecoration(maxX, midY, snapToPixel); 224 pSS = sceneGraphObjectToDecoration(midX, maxY, snapToPixel); 225 pWW = sceneGraphObjectToDecoration(minX, midY, snapToPixel); 226 } 227 228 moveTo0.setX(pNW.getX()); 229 moveTo0.setY(pNW.getY()); 230 lineTo1.setX(pNE.getX()); 231 lineTo1.setY(pNE.getY()); 232 lineTo2.setX(pSE.getX()); 233 lineTo2.setY(pSE.getY()); 234 lineTo3.setX(pSW.getX()); 235 lineTo3.setY(pSW.getY()); 236 237 handleNW.setLayoutX(pNW.getX()); 238 handleNW.setLayoutY(pNW.getY()); 239 handleNE.setLayoutX(pNE.getX()); 240 handleNE.setLayoutY(pNE.getY()); 241 handleSE.setLayoutX(pSE.getX()); 242 handleSE.setLayoutY(pSE.getY()); 243 handleSW.setLayoutX(pSW.getX()); 244 handleSW.setLayoutY(pSW.getY()); 245 246 handleNN.setLayoutX(pNN.getX()); 247 handleNN.setLayoutY(pNN.getY()); 248 handleEE.setLayoutX(pEE.getX()); 249 handleEE.setLayoutY(pEE.getY()); 250 handleSS.setLayoutX(pSS.getX()); 251 handleSS.setLayoutY(pSS.getY()); 252 handleWW.setLayoutX(pWW.getX()); 253 handleWW.setLayoutY(pWW.getY()); 254 255 final Bounds handlesBounds = computeBounds(pNW, pNE, pSE, pSW); 256 final int rotation = computeNWHandleRotation(pNW, handlesBounds); 257 258 setupCornerHandle(handleNW, rotation + 0); 259 setupCornerHandle(handleNE, rotation + 90); 260 setupCornerHandle(handleSE, rotation + 180); 261 setupCornerHandle(handleSW, rotation + 270); 262 263 setupSideHandle(handleNN, rotation + 0); 264 setupSideHandle(handleEE, rotation + 90); 265 setupSideHandle(handleSS, rotation + 180); 266 setupSideHandle(handleWW, rotation + 270); 267 268 showHideSideHandle(handleNN, pNW, pNE); 269 showHideSideHandle(handleEE, pNE, pSE); 270 showHideSideHandle(handleSS, pSW, pSE); 271 showHideSideHandle(handleWW, pNW, pSW); 272 } 273 274 275 @Override 276 public AbstractGesture findGesture(Node node) { 277 final AbstractGesture result; 278 279 if (isResizable() == false) { 280 result = new DiscardGesture(getContentPanelController()); 281 } else { 282 assert getFxomObject() instanceof FXOMInstance; 283 284 final FXOMInstance fxomInstance = (FXOMInstance) getFxomObject(); 285 286 if (node == handleNW) { 287 result = new ResizeGesture(getContentPanelController(), 288 fxomInstance, CardinalPoint.NW); 289 } else if (node == handleNE) { 290 result = new ResizeGesture(getContentPanelController(), 291 fxomInstance, CardinalPoint.NE); 292 } else if (node == handleSE) { 293 result = new ResizeGesture(getContentPanelController(), 294 fxomInstance, CardinalPoint.SE); 295 } else if (node == handleSW) { 296 result = new ResizeGesture(getContentPanelController(), 297 fxomInstance, CardinalPoint.SW); 298 } else if (node == handleNN) { 299 result = new ResizeGesture(getContentPanelController(), 300 fxomInstance, CardinalPoint.N); 301 } else if (node == handleEE) { 302 result = new ResizeGesture(getContentPanelController(), 303 fxomInstance, CardinalPoint.E); 304 } else if (node == handleSS) { 305 result = new ResizeGesture(getContentPanelController(), 306 fxomInstance, CardinalPoint.S); 307 } else if (node == handleWW) { 308 result = new ResizeGesture(getContentPanelController(), 309 fxomInstance, CardinalPoint.W); 310 } else { 311 result = null; 312 } 313 } 314 315 return result; 316 } 317 318 @Override 319 public void enabledDidChange() { 320 setupHandleImages(); 321 } 322 323 /* 324 * Private 325 */ 326 327 private Bounds computeBounds(Point2D p0, Point2D p1, Point2D p2, Point2D p3) { 328 final double minX, minY, maxX, maxY; 329 330 minX = Math.min(Math.min(p0.getX(), p1.getX()), Math.min(p2.getX(), p3.getX())); 331 minY = Math.min(Math.min(p0.getY(), p1.getY()), Math.min(p2.getY(), p3.getY())); 332 maxX = Math.max(Math.max(p0.getX(), p1.getX()), Math.max(p2.getX(), p3.getX())); 333 maxY = Math.max(Math.max(p0.getY(), p1.getY()), Math.max(p2.getY(), p3.getY())); 334 335 return new BoundingBox(minX, minY, maxX - minX, maxY - minY); 336 } 337 338 private void setupCornerHandle(ImageView handle, int rotation) { 339 340 rotation = ((rotation % 360) + 360) % 360; // Clamp between 0 and 360 341 342 final double dx, dy; 343 final double handleWidth = handle.getLayoutBounds().getWidth(); 344 if (rotation == 0) { 345 dx = +0.0; 346 dy = +0.0; 347 } else if (rotation == 90) { 348 dx = -handleWidth; 349 dy = +0.0; 350 } else if (rotation == 180) { 351 dx = -handleWidth; 352 dy = -handleWidth; 353 } else if (rotation == 270) { 354 dx = +0.0; 355 dy = -handleWidth; 356 } else { 357 assert false : "rotation=" + rotation; 358 dx = +0.0; 359 dy = +0.0; 360 } 361 362 handle.setRotate(rotation); 363 handle.setTranslateX(dx); 364 handle.setTranslateY(dy); 365 } 366 367 private void setupSideHandle(ImageView handle, int rotation) { 368 369 rotation = ((rotation % 360) + 360) % 360; // Clamp between 0 and 360 370 371 final double dx, dy; 372 final double w = handle.getLayoutBounds().getWidth() / 2.0; 373 final double h = handle.getLayoutBounds().getHeight() / 2.0; 374 final double k0 = 1.0; // Hugly trick to force pixel alignment :( 375 376 if (rotation == 0) { 377 dx = -w; 378 dy = +0.0; 379 } else if (rotation == 90) { 380 dx = -w -h; 381 dy = -h - k0; 382 } else if (rotation == 180) { 383 dx = -w + k0; 384 dy = -h * 2; 385 } else if (rotation == 270) { 386 dx = -w + h; 387 dy = -h; 388 } else { 389 assert false : "rotation=" + rotation; 390 dx = +0.0; 391 dy = +0.0; 392 } 393 394 handle.setRotate(rotation); 395 handle.setTranslateX(dx); 396 handle.setTranslateY(dy); 397 } 398 399 private int computeNWHandleRotation(Point2D handlePos, Bounds handlesBounds) { 400 final int result; 401 402 assert handlePos != null; 403 assert handlesBounds != null; 404 assert handlesBounds.contains(handlePos); 405 406 407 if ((handlesBounds.getWidth() == 0) || (handlesBounds.getHeight() == 0)) { 408 // scene graph object is zero sized 409 result = +180; 410 } else { 411 /* 412 * x0 xm x1 413 * y0 *---------*---------* 414 * | tl | tr | 415 * | | | 416 * | +180° | +270° | 417 * | | | 418 * ym *---------*---------* 419 * | bl | br | 420 * | | | 421 * | +90° | 0° | 422 * | | | 423 * y1 *---------*---------* 424 */ 425 426 final double x0 = handlesBounds.getMinX(); 427 final double x1 = handlesBounds.getMaxX(); 428 final double xm = (x0 + x1) / 2.0; 429 final double y0 = handlesBounds.getMinY(); 430 final double y1 = handlesBounds.getMaxY(); 431 final double ym = (y0 + y1) / 2.0; 432 433 final double x = handlePos.getX(); 434 final double y = handlePos.getY(); 435 436 if (x <= xm) { 437 if (y <= ym) { 438 // (x, y) is in the top left quadrant 439 result = +180; 440 } else { 441 // (x, y) is in the bottom left quadrant 442 result = +90; 443 } 444 } else { 445 if (y <= ym) { 446 // (x, y) is in the top right quadrant 447 result = +270; 448 } else { 449 // (x, y) is in the bottom right quadrant 450 result = +0; 451 } 452 } 453 } 454 455 return result; 456 } 457 458 459 private void showHideSideHandle(ImageView handle, Point2D p0, Point2D p1) { 460 461 final double dx = p1.getX() - p0.getX(); 462 final double dy = p1.getY() - p0.getY(); 463 final double d01 = Math.sqrt(dx * dx + dy * dy); 464 465 final double sideHandleWidth = getSideHandleImage().getWidth(); 466 final double sideHandleHeight = getSideHandleImage().getHeight(); 467 final double sideHandleSize = Math.max(sideHandleWidth, sideHandleHeight); 468 469 final boolean handleVisible = sideHandleSize < d01; 470 handle.setVisible(handleVisible); 471 handle.setMouseTransparent(! handleVisible); 472 } 473 474 475 private void setupHandleImages() { 476 final Image handleImage, sideHandleImage; 477 if (isEnabled() && isResizable()) { 478 479 handleNW.setCursor(Cursor.NW_RESIZE); 480 handleNE.setCursor(Cursor.NE_RESIZE); 481 handleSE.setCursor(Cursor.SE_RESIZE); 482 handleSW.setCursor(Cursor.SW_RESIZE); 483 484 handleNN.setCursor(Cursor.N_RESIZE); 485 handleEE.setCursor(Cursor.E_RESIZE); 486 handleSS.setCursor(Cursor.S_RESIZE); 487 handleWW.setCursor(Cursor.W_RESIZE); 488 489 handleImage = getCornerHandleImage(); 490 sideHandleImage = getSideHandleImage(); 491 492 } else { 493 494 handleNW.setCursor(Cursor.DEFAULT); 495 handleNE.setCursor(Cursor.DEFAULT); 496 handleSE.setCursor(Cursor.DEFAULT); 497 handleSW.setCursor(Cursor.DEFAULT); 498 499 handleNN.setCursor(Cursor.DEFAULT); 500 handleEE.setCursor(Cursor.DEFAULT); 501 handleSS.setCursor(Cursor.DEFAULT); 502 handleWW.setCursor(Cursor.DEFAULT); 503 504 handleImage = getCornerHandleDimImage(); 505 sideHandleImage = getSideHandleDimImage(); 506 } 507 508 handleNW.setImage(handleImage); 509 handleNE.setImage(handleImage); 510 handleSE.setImage(handleImage); 511 handleSW.setImage(handleImage); 512 513 handleNN.setImage(sideHandleImage); 514 handleEE.setImage(sideHandleImage); 515 handleSS.setImage(sideHandleImage); 516 handleWW.setImage(sideHandleImage); 517 518 } 519 520 521 private boolean isResizable() { 522 final AbstractDriver driver 523 = getContentPanelController().lookupDriver(getFxomObject()); 524 return driver.makeResizer(getFxomObject()) != null; 525 } 526 527 /* 528 * Wraper to avoid the 'leaking this in constructor' warning emitted by NB. 529 */ 530 private void attachHandles(Node node) { 531 attachHandles(node, this); 532 } 533 }