1 /* 2 * Copyright (c) 2010, 2016, 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.animation; 27 28 import javafx.beans.property.DoubleProperty; 29 import javafx.beans.property.ObjectProperty; 30 import javafx.beans.property.ObjectPropertyBase; 31 import javafx.beans.property.SimpleDoubleProperty; 32 import javafx.beans.property.SimpleObjectProperty; 33 import javafx.scene.Node; 34 import javafx.util.Duration; 35 36 /** 37 * This {@code Transition} creates a scale animation that spans its 38 * {@link #duration}. This is done by updating the {@code scaleX}, 39 * {@code scaleY} and {@code scaleZ} variables of the {@code node} at regular 40 * interval. 41 * <p> 42 * It starts from the ({@code fromX}, {@code fromY}, {@code fromZ}) value if 43 * provided else uses the {@code node}'s ({@code scaleX}, {@code scaleY}, 44 * {@code scaleZ}) value. 45 * <p> 46 * It stops at the ({@code toX}, {@code toY}, {@code toZ}) value if provided 47 * else it will use start value plus ({@code byX}, {@code byY}, {@code byZ}) 48 * value. 49 * <p> 50 * The ({@code toX}, {@code toY}, {@code toZ}) value takes precedence if both ( 51 * {@code toX}, {@code toY}, {@code toZ}) and ({@code byX}, {@code byY}, 52 * {@code byZ}) values are specified. 53 * 54 * <p> 55 * Code Segment Example: 56 * </p> 57 * 58 * <pre> 59 * <code> 60 * import javafx.scene.shape.*; 61 * import javafx.animation.*; 62 * 63 * ... 64 * 65 * Rectangle rect = new Rectangle (100, 40, 100, 100); 66 * rect.setArcHeight(50); 67 * rect.setArcWidth(50); 68 * rect.setFill(Color.VIOLET); 69 * 70 * ScaleTransition st = new ScaleTransition(Duration.millis(2000), rect); 71 * st.setByX(1.5f); 72 * st.setByY(1.5f); 73 * st.setCycleCount(4f); 74 * st.setAutoReverse(true); 75 * 76 * st.play(); 77 * 78 * ... 79 * 80 * </code> 81 * </pre> 82 * 83 * @see Transition 84 * @see Animation 85 * 86 * @since JavaFX 2.0 87 */ 88 public final class ScaleTransition extends Transition { 89 90 private static final double EPSILON = 1e-12; 91 private double startX; 92 private double startY; 93 private double startZ; 94 private double deltaX; 95 private double deltaY; 96 private double deltaZ; 97 98 /** 99 * The target node of this {@code ScaleTransition}. 100 * <p> 101 * It is not possible to change the target {@code node} of a running 102 * {@code ScaleTransition}. If the value of {@code node} is changed for a 103 * running {@code ScaleTransition}, the animation has to be stopped and 104 * started again to pick up the new value. 105 */ 106 private ObjectProperty<Node> node; 107 private static final Node DEFAULT_NODE = null; 108 109 public final void setNode(Node value) { 110 if ((node != null) || (value != null /* DEFAULT_NODE */)) { 111 nodeProperty().set(value); 112 } 113 } 114 115 public final Node getNode() { 116 return (node == null)? DEFAULT_NODE : node.get(); 117 } 118 119 public final ObjectProperty<Node> nodeProperty() { 120 if (node == null) { 121 node = new SimpleObjectProperty<Node>(this, "node", DEFAULT_NODE); 122 } 123 return node; 124 } 125 126 private Node cachedNode; 127 128 /** 129 * The duration of this {@code ScaleTransition}. 130 * <p> 131 * It is not possible to change the {@code duration} of a running 132 * {@code ScaleTransition}. If the value of {@code duration} is changed for 133 * a running {@code ScaleTransition}, the animation has to be stopped and 134 * started again to pick up the new value. 135 * <p> 136 * Note: While the unit of {@code duration} is a millisecond, the 137 * granularity depends on the underlying operating system and will in 138 * general be larger. For example animations on desktop systems usually run 139 * with a maximum of 60fps which gives a granularity of ~17 ms. 140 * 141 * Setting duration to value lower than {@link Duration#ZERO} will result 142 * in {@link IllegalArgumentException}. 143 * 144 * @defaultValue 400ms 145 */ 146 private ObjectProperty<Duration> duration; 147 private static final Duration DEFAULT_DURATION = Duration.millis(400); 148 149 public final void setDuration(Duration value) { 150 if ((duration != null) || (!DEFAULT_DURATION.equals(value))) { 151 durationProperty().set(value); 152 } 153 } 154 155 public final Duration getDuration() { 156 return (duration == null)? DEFAULT_DURATION : duration.get(); 157 } 158 159 public final ObjectProperty<Duration> durationProperty() { 160 if (duration == null) { 161 duration = new ObjectPropertyBase<Duration>(DEFAULT_DURATION) { 162 163 @Override 164 public void invalidated() { 165 try { 166 setCycleDuration(getDuration()); 167 } catch (IllegalArgumentException e) { 168 if (isBound()) { 169 unbind(); 170 } 171 set(getCycleDuration()); 172 throw e; 173 } 174 } 175 176 @Override 177 public Object getBean() { 178 return ScaleTransition.this; 179 } 180 181 @Override 182 public String getName() { 183 return "duration"; 184 } 185 }; 186 } 187 return duration; 188 } 189 190 /** 191 * Specifies the start X scale value of this {@code ScaleTransition}. 192 * <p> 193 * It is not possible to change {@code fromX} of a running 194 * {@code ScaleTransition}. If the value of {@code fromX} is changed for a 195 * running {@code ScaleTransition}, the animation has to be stopped and 196 * started again to pick up the new value. 197 * 198 * @defaultValue {@code Double.NaN} 199 */ 200 private DoubleProperty fromX; 201 private static final double DEFAULT_FROM_X = Double.NaN; 202 203 public final void setFromX(double value) { 204 if ((fromX != null) || (!Double.isNaN(value))) { 205 fromXProperty().set(value); 206 } 207 } 208 209 public final double getFromX() { 210 return (fromX == null) ? DEFAULT_FROM_X : fromX.get(); 211 } 212 213 public final DoubleProperty fromXProperty() { 214 if (fromX == null) { 215 fromX = new SimpleDoubleProperty(this, "fromX", DEFAULT_FROM_X); 216 } 217 return fromX; 218 } 219 220 /** 221 * Specifies the start Y scale value of this {@code ScaleTransition}. 222 * <p> 223 * It is not possible to change {@code fromY} of a running 224 * {@code ScaleTransition}. If the value of {@code fromY} is changed for a 225 * running {@code ScaleTransition}, the animation has to be stopped and 226 * started again to pick up the new value. 227 * 228 * @defaultValue {@code Double.NaN} 229 */ 230 private DoubleProperty fromY; 231 private static final double DEFAULT_FROM_Y = Double.NaN; 232 233 public final void setFromY(double value) { 234 if ((fromY != null) || (!Double.isNaN(value))) { 235 fromYProperty().set(value); 236 } 237 } 238 239 public final double getFromY() { 240 return (fromY == null)? DEFAULT_FROM_Y : fromY.get(); 241 } 242 243 public final DoubleProperty fromYProperty() { 244 if (fromY == null) { 245 fromY = new SimpleDoubleProperty(this, "fromY", DEFAULT_FROM_Y); 246 } 247 return fromY; 248 } 249 250 /** 251 * Specifies the start Z scale value of this {@code ScaleTransition}. 252 * <p> 253 * It is not possible to change {@code fromZ} of a running 254 * {@code ScaleTransition}. If the value of {@code fromZ} is changed for a 255 * running {@code ScaleTransition}, the animation has to be stopped and 256 * started again to pick up the new value. 257 * 258 * @defaultValue {@code Double.NaN} 259 */ 260 private DoubleProperty fromZ; 261 private static final double DEFAULT_FROM_Z = Double.NaN; 262 263 public final void setFromZ(double value) { 264 if ((fromZ != null) || (!Double.isNaN(value))) { 265 fromZProperty().set(value); 266 } 267 } 268 269 public final double getFromZ() { 270 return (fromZ == null)? DEFAULT_FROM_Z : fromZ.get(); 271 } 272 273 public final DoubleProperty fromZProperty() { 274 if (fromZ == null) { 275 fromZ = new SimpleDoubleProperty(this, "fromZ", DEFAULT_FROM_Z); 276 } 277 return fromZ; 278 } 279 280 /** 281 * Specifies the stop X scale value of this {@code ScaleTransition}. 282 * <p> 283 * It is not possible to change {@code toX} of a running 284 * {@code ScaleTransition}. If the value of {@code toX} is changed for a 285 * running {@code ScaleTransition}, the animation has to be stopped and 286 * started again to pick up the new value. 287 * 288 * @defaultValue {@code Double.NaN} 289 */ 290 private DoubleProperty toX; 291 private static final double DEFAULT_TO_X = Double.NaN; 292 293 public final void setToX(double value) { 294 if ((toX != null) || (!Double.isNaN(value))) { 295 toXProperty().set(value); 296 } 297 } 298 299 public final double getToX() { 300 return (toX == null)? DEFAULT_TO_X : toX.get(); 301 } 302 303 public final DoubleProperty toXProperty() { 304 if (toX == null) { 305 toX = new SimpleDoubleProperty(this, "toX", DEFAULT_TO_X); 306 } 307 return toX; 308 } 309 310 /** 311 * The stop Y scale value of this {@code ScaleTransition}. 312 * <p> 313 * It is not possible to change {@code toY} of a running 314 * {@code ScaleTransition}. If the value of {@code toY} is changed for a 315 * running {@code ScaleTransition}, the animation has to be stopped and 316 * started again to pick up the new value. 317 * 318 * @defaultValue {@code Double.NaN} 319 */ 320 private DoubleProperty toY; 321 private static final double DEFAULT_TO_Y = Double.NaN; 322 323 public final void setToY(double value) { 324 if ((toY != null) || (!Double.isNaN(value))) { 325 toYProperty().set(value); 326 } 327 } 328 329 public final double getToY() { 330 return (toY == null)? DEFAULT_TO_Y : toY.get(); 331 } 332 333 public final DoubleProperty toYProperty() { 334 if (toY == null) { 335 toY = new SimpleDoubleProperty(this, "toY", DEFAULT_TO_Y); 336 } 337 return toY; 338 } 339 340 /** 341 * The stop Z scale value of this {@code ScaleTransition}. 342 * <p> 343 * It is not possible to change {@code toZ} of a running 344 * {@code ScaleTransition}. If the value of {@code toZ} is changed for a 345 * running {@code ScaleTransition}, the animation has to be stopped and 346 * started again to pick up the new value. 347 * 348 * @defaultValue {@code Double.NaN} 349 */ 350 private DoubleProperty toZ; 351 private static final double DEFAULT_TO_Z = Double.NaN; 352 353 public final void setToZ(double value) { 354 if ((toZ != null) || (!Double.isNaN(value))) { 355 toZProperty().set(value); 356 } 357 } 358 359 public final double getToZ() { 360 return (toZ == null)? DEFAULT_TO_Z : toZ.get(); 361 } 362 363 public final DoubleProperty toZProperty() { 364 if (toZ == null) { 365 toZ = new SimpleDoubleProperty(this, "toZ", DEFAULT_TO_Z); 366 } 367 return toZ; 368 } 369 370 /** 371 * Specifies the incremented stop X scale value, from the start, of this 372 * {@code ScaleTransition}. 373 * <p> 374 * It is not possible to change {@code byX} of a running 375 * {@code ScaleTransition}. If the value of {@code byX} is changed for a 376 * running {@code ScaleTransition}, the animation has to be stopped and 377 * started again to pick up the new value. 378 */ 379 private DoubleProperty byX; 380 private static final double DEFAULT_BY_X = 0.0; 381 382 public final void setByX(double value) { 383 if ((byX != null) || (Math.abs(value - DEFAULT_BY_X) > EPSILON)) { 384 byXProperty().set(value); 385 } 386 } 387 388 public final double getByX() { 389 return (byX == null)? DEFAULT_BY_X : byX.get(); 390 } 391 392 public final DoubleProperty byXProperty() { 393 if (byX == null) { 394 byX = new SimpleDoubleProperty(this, "byX", DEFAULT_BY_X); 395 } 396 return byX; 397 } 398 399 /** 400 * Specifies the incremented stop Y scale value, from the start, of this 401 * {@code ScaleTransition}. 402 * <p> 403 * It is not possible to change {@code byY} of a running 404 * {@code ScaleTransition}. If the value of {@code byY} is changed for a 405 * running {@code ScaleTransition}, the animation has to be stopped and 406 * started again to pick up the new value. 407 */ 408 private DoubleProperty byY; 409 private static final double DEFAULT_BY_Y = 0.0; 410 411 public final void setByY(double value) { 412 if ((byY != null) || (Math.abs(value - DEFAULT_BY_Y) > EPSILON)) { 413 byYProperty().set(value); 414 } 415 } 416 417 public final double getByY() { 418 return (byY == null)? DEFAULT_BY_Y : byY.get(); 419 } 420 421 public final DoubleProperty byYProperty() { 422 if (byY == null) { 423 byY = new SimpleDoubleProperty(this, "byY", DEFAULT_BY_Y); 424 } 425 return byY; 426 } 427 428 /** 429 * Specifies the incremented stop Z scale value, from the start, of this 430 * {@code ScaleTransition}. 431 * <p> 432 * It is not possible to change {@code byZ} of a running 433 * {@code ScaleTransition}. If the value of {@code byZ} is changed for a 434 * running {@code ScaleTransition}, the animation has to be stopped and 435 * started again to pick up the new value. 436 */ 437 private DoubleProperty byZ; 438 private static final double DEFAULT_BY_Z = 0.0; 439 440 public final void setByZ(double value) { 441 if ((byZ != null) || (Math.abs(value - DEFAULT_BY_Z) > EPSILON)) { 442 byZProperty().set(value); 443 } 444 } 445 446 public final double getByZ() { 447 return (byZ == null)? DEFAULT_BY_Z : byZ.get(); 448 } 449 450 public final DoubleProperty byZProperty() { 451 if (byZ == null) { 452 byZ = new SimpleDoubleProperty(this, "byZ", DEFAULT_BY_Z); 453 } 454 return byZ; 455 } 456 457 /** 458 * The constructor of {@code ScaleTransition} 459 * 460 * @param duration 461 * The duration of the {@code ScaleTransition} 462 * @param node 463 * The {@code node} which will be scaled 464 */ 465 public ScaleTransition(Duration duration, Node node) { 466 setDuration(duration); 467 setNode(node); 468 setCycleDuration(duration); 469 } 470 471 /** 472 * The constructor of {@code ScaleTransition} 473 * 474 * @param duration 475 * The duration of the {@code ScaleTransition} 476 */ 477 public ScaleTransition(Duration duration) { 478 this(duration, null); 479 } 480 481 /** 482 * The constructor of {@code ScaleTransition} 483 */ 484 public ScaleTransition() { 485 this(DEFAULT_DURATION, null); 486 } 487 488 /** 489 * {@inheritDoc} 490 */ 491 @Override 492 public void interpolate(double frac) { 493 if (!Double.isNaN(startX)) { 494 cachedNode.setScaleX(startX + frac * deltaX); 495 } 496 if (!Double.isNaN(startY)) { 497 cachedNode.setScaleY(startY + frac * deltaY); 498 } 499 if (!Double.isNaN(startZ)) { 500 cachedNode.setScaleZ(startZ + frac * deltaZ); 501 } 502 } 503 504 private Node getTargetNode() { 505 final Node node = getNode(); 506 return (node != null) ? node : getParentTargetNode(); 507 } 508 509 @Override 510 boolean startable(boolean forceSync) { 511 return super.startable(forceSync) 512 && ((getTargetNode() != null) || (!forceSync && (cachedNode != null))); 513 } 514 515 @Override 516 void sync(boolean forceSync) { 517 super.sync(forceSync); 518 if (forceSync || (cachedNode == null)) { 519 cachedNode = getTargetNode(); 520 521 final double _fromX = getFromX(); 522 final double _fromY = getFromY(); 523 final double _fromZ = getFromZ(); 524 525 final double _toX = getToX(); 526 final double _toY = getToY(); 527 final double _toZ = getToZ(); 528 529 final double _byX = getByX(); 530 final double _byY = getByY(); 531 final double _byZ = getByZ(); 532 533 if (Double.isNaN(_fromX) && Double.isNaN(_toX) && (Math.abs(_byX) < EPSILON)) { 534 startX = Double.NaN; 535 } else { 536 startX = (!Double.isNaN(_fromX)) ? _fromX : cachedNode.getScaleX(); 537 deltaX = (!Double.isNaN(_toX)) ? _toX - startX : getByX(); 538 } 539 540 if (Double.isNaN(_fromY) && Double.isNaN(_toY) && (Math.abs(_byY) < EPSILON)) { 541 startY = Double.NaN; 542 } else { 543 startY = (!Double.isNaN(_fromY)) ? _fromY : cachedNode.getScaleY(); 544 deltaY = (!Double.isNaN(_toY)) ? _toY - startY : getByY(); 545 } 546 547 if (Double.isNaN(_fromZ) && Double.isNaN(_toZ) && (Math.abs(_byZ) < EPSILON)) { 548 startZ = Double.NaN; 549 } else { 550 startZ = (!Double.isNaN(_fromZ)) ? _fromZ : cachedNode.getScaleZ(); 551 deltaZ = (!Double.isNaN(_toZ)) ? _toZ - startZ : getByZ(); 552 } 553 } 554 } 555 556 }