Spec-Zone .ru
спецификации, руководства, описания, API
|
001/* 002 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package javafx.animation; 027 028import java.util.HashMap; 029import javafx.beans.property.BooleanProperty; 030import javafx.beans.property.DoubleProperty; 031import javafx.beans.property.DoublePropertyBase; 032import javafx.beans.property.IntegerProperty; 033import javafx.beans.property.IntegerPropertyBase; 034import javafx.beans.property.ObjectProperty; 035import javafx.beans.property.ObjectPropertyBase; 036import javafx.beans.property.ReadOnlyDoubleProperty; 037import javafx.beans.property.ReadOnlyDoublePropertyBase; 038import javafx.beans.property.ReadOnlyObjectProperty; 039import javafx.beans.property.ReadOnlyObjectPropertyBase; 040import javafx.beans.property.SimpleBooleanProperty; 041import javafx.beans.property.SimpleObjectProperty; 042import javafx.collections.FXCollections; 043import javafx.collections.ObservableMap; 044import javafx.event.ActionEvent; 045import javafx.event.EventHandler; 046import javafx.util.Duration; 047import com.sun.javafx.animation.TickCalculation; 048import com.sun.scenario.ToolkitAccessor; 049import com.sun.scenario.animation.AbstractMasterTimer; 050import com.sun.scenario.animation.shared.ClipEnvelope; 051import com.sun.scenario.animation.shared.PulseReceiver; 052 053import static com.sun.javafx.animation.TickCalculation.*; 054 055/** 056 * The class {@code Animation} provides the core functionality of all animations 057 * used in the JavaFX runtime. 058 * <p> 059 * An animation can run in a loop by setting {@link #cycleCount}. To make an 060 * animation run back and forth while looping, set the {@link #autoReverse} 061 * -flag. 062 * <p> 063 * Call {@link #play()} or {@link #playFromStart()} to play an {@code Animation} 064 * . The {@code Animation} progresses in the direction and speed specified by 065 * {@link #rate}, and stops when its duration is elapsed. An {@code Animation} 066 * with indefinite duration (a {@link #cycleCount} of {@link #INDEFINITE}) runs 067 * repeatedly until the {@link #stop()} method is explicitly called, which will 068 * stop the running {@code Animation} and reset its play head to the initial 069 * position. 070 * <p> 071 * An {@code Animation} can be paused by calling {@link #pause()}, and the next 072 * {@link #play()} call will resume the {@code Animation} from where it was 073 * paused. 074 * <p> 075 * An {@code Animation}'s play head can be randomly positioned, whether it is 076 * running or not. If the {@code Animation} is running, the play head jumps to 077 * the specified position immediately and continues playing from new position. 078 * If the {@code Animation} is not running, the next {@link #play()} will start 079 * the {@code Animation} from the specified position. 080 * <p> 081 * Inverting the value of {@link #rate} toggles the play direction. 082 * 083 * @see Timeline 084 * @see Transition 085 * 086 */ 087public abstract class Animation { 088 089 static { 090 AnimationAccessorImpl.DEFAULT = new AnimationAccessorImpl(); 091 } 092 093 /** 094 * Used to specify an animation that repeats indefinitely, until the 095 * {@code stop()} method is called. 096 */ 097 public static final int INDEFINITE = -1; 098 099 /** 100 * The possible states for {@link Animation#statusProperty status}. 101 */ 102 public static enum Status { 103 /** 104 * The paused state. 105 */ 106 PAUSED, 107 /** 108 * The running state. 109 */ 110 RUNNING, 111 /** 112 * The stopped state. 113 */ 114 STOPPED 115 } 116 117 private static final double EPSILON = 1e-12; 118 119 /* 120 These four fields and associated methods were moved here from AnimationPulseReceiver 121 when that class was removed. They could probably be integrated much cleaner into Animation, 122 but to make sure the change was made without introducing regressions, this code was 123 moved pretty much verbatim. 124 */ 125 private long startTime; 126 private long pauseTime; 127 private boolean paused = false; 128 private final AbstractMasterTimer timer; 129 130 private long now() { 131 return TickCalculation.fromNano(timer.nanos()); 132 } 133 134 void startReceiver(long delay) { 135 paused = false; 136 startTime = now() + delay; 137 timer.addPulseReceiver(pulseReceiver); 138 } 139 140 void pauseReceiver() { 141 if (!paused) { 142 pauseTime = now(); 143 paused = true; 144 timer.removePulseReceiver(pulseReceiver); 145 } 146 } 147 148 void resumeReceiver() { 149 if (paused) { 150 final long deltaTime = now() - pauseTime; 151 startTime += deltaTime; 152 paused = false; 153 timer.addPulseReceiver(pulseReceiver); 154 } 155 } 156 157 // package private only for the sake of testing 158 final PulseReceiver pulseReceiver = new PulseReceiver() { 159 @Override public void timePulse(long now) { 160 final long elapsedTime = now - startTime; 161 if (elapsedTime < 0) { 162 return; 163 } 164 165 impl_timePulse(elapsedTime); 166 } 167 }; 168 169 private class CurrentRateProperty extends ReadOnlyDoublePropertyBase { 170 private double value; 171 172 @Override 173 public Object getBean() { 174 return Animation.this; 175 } 176 177 @Override 178 public String getName() { 179 return "currentRate"; 180 } 181 182 @Override 183 public double get() { 184 return value; 185 } 186 187 private void set(double value) { 188 this.value = value; 189 fireValueChangedEvent(); 190 } 191 } 192 193 private class AnimationReadOnlyProperty<T> extends ReadOnlyObjectPropertyBase<T> { 194 195 private final String name; 196 private T value; 197 198 private AnimationReadOnlyProperty(String name, T value) { 199 this.name = name; 200 this.value = value; 201 } 202 203 @Override 204 public Object getBean() { 205 return Animation.this; 206 } 207 208 @Override 209 public String getName() { 210 return name; 211 } 212 213 @Override 214 public T get() { 215 return value; 216 } 217 218 private void set(T value) { 219 this.value = value; 220 fireValueChangedEvent(); 221 } 222 } 223 224 /** 225 * The parent of this {@code Animation}. If this animation has not been 226 * added to another animation, such as {@link ParallelTransition} and 227 * {@link SequentialTransition}, then parent will be null. 228 * 229 * @defaultValue null 230 */ 231 Animation parent = null; 232 233 /* Package-private for testing purposes */ 234 ClipEnvelope clipEnvelope; 235 236 private boolean lastPlayedFinished = false; 237 238 private boolean lastPlayedForward = true; 239 /** 240 * Defines the direction/speed at which the {@code Animation} is expected to 241 * be played. 242 * <p> 243 * The absolute value of {@code rate} indicates the speed which the 244 * {@code Animation} is to be played, while the sign of {@code rate} 245 * indicates the direction. A positive value of {@code rate} indicates 246 * forward play, a negative value indicates backward play and {@code 0.0} to 247 * stop a running {@code Animation}. 248 * <p> 249 * Rate {@code 1.0} is normal play, {@code 2.0} is 2 time normal, 250 * {@code -1.0} is backwards, etc... 251 * 252 * <p> 253 * Inverting the rate of a running {@code Animation} will cause the 254 * {@code Animation} to reverse direction in place and play back over the 255 * portion of the {@code Animation} that has already elapsed. 256 * 257 * @defaultValue 1.0 258 */ 259 private DoubleProperty rate; 260 private static final double DEFAULT_RATE = 1.0; 261 262 public final void setRate(double value) { 263 if ((rate != null) || (Math.abs(value - DEFAULT_RATE) > EPSILON)) { 264 rateProperty().set(value); 265 } 266 } 267 268 public final double getRate() { 269 return (rate == null)? DEFAULT_RATE : rate.get(); 270 } 271 272 public final DoubleProperty rateProperty() { 273 if (rate == null) { 274 rate = new DoublePropertyBase(DEFAULT_RATE) { 275 276 @Override 277 public void invalidated() { 278 final double newRate = getRate(); 279 if (isRunningEmbedded()) { 280 if (isBound()) { 281 unbind(); 282 } 283 set(oldRate); 284 throw new IllegalArgumentException("Cannot set rate of embedded animation while running."); 285 } else { 286 if (Math.abs(newRate) < EPSILON) { 287 if (getStatus() == Status.RUNNING) { 288 lastPlayedForward = (Math.abs(getCurrentRate() 289 - oldRate) < EPSILON); 290 } 291 setCurrentRate(0.0); 292 pauseReceiver(); 293 } else { 294 if (getStatus() == Status.RUNNING) { 295 final double currentRate = getCurrentRate(); 296 if (Math.abs(currentRate) < EPSILON) { 297 setCurrentRate(lastPlayedForward ? newRate : -newRate); 298 resumeReceiver(); 299 } else { 300 final boolean playingForward = Math.abs(currentRate - oldRate) < EPSILON; 301 setCurrentRate(playingForward ? newRate : -newRate); 302 } 303 } 304 oldRate = newRate; 305 } 306 clipEnvelope.setRate(newRate); 307 } 308 } 309 310 @Override 311 public Object getBean() { 312 return Animation.this; 313 } 314 315 @Override 316 public String getName() { 317 return "rate"; 318 } 319 }; 320 } 321 return rate; 322 } 323 324 private boolean isRunningEmbedded() { 325 if (parent == null) { 326 return false; 327 } 328 return parent.getStatus() != Status.STOPPED || parent.isRunningEmbedded(); 329 } 330 331 private double oldRate = 1.0; 332 /** 333 * Read-only variable to indicate current direction/speed at which the 334 * {@code Animation} is being played. 335 * <p> 336 * {@code currentRate} is not necessary equal to {@code rate}. 337 * {@code currentRate} is set to {@code 0.0} when animation is paused or 338 * stopped. {@code currentRate} may also point to different direction during 339 * reverse cycles when {@code autoReverse} is {@code true} 340 * 341 * @defaultValue 0.0 342 */ 343 private ReadOnlyDoubleProperty currentRate; 344 private static final double DEFAULT_CURRENT_RATE = 0.0; 345 346 private void setCurrentRate(double value) { 347 if ((currentRate != null) || (Math.abs(value - DEFAULT_CURRENT_RATE) > EPSILON)) { 348 ((CurrentRateProperty)currentRateProperty()).set(value); 349 } 350 } 351 352 public final double getCurrentRate() { 353 return (currentRate == null)? DEFAULT_CURRENT_RATE : currentRate.get(); 354 } 355 356 public final ReadOnlyDoubleProperty currentRateProperty() { 357 if (currentRate == null) { 358 currentRate = new CurrentRateProperty(); 359 } 360 return currentRate; 361 } 362 363 /** 364 * Read-only variable to indicate the duration of one cycle of this 365 * {@code Animation}: the time it takes to play from time 0 to the 366 * end of the Animation (at the default {@code rate} of 367 * 1.0). 368 * 369 * @defaultValue 0ms 370 */ 371 private ReadOnlyObjectProperty<Duration> cycleDuration; 372 private static final Duration DEFAULT_CYCLE_DURATION = Duration.ZERO; 373 374 protected final void setCycleDuration(Duration value) { 375 if ((cycleDuration != null) || (!DEFAULT_CYCLE_DURATION.equals(value))) { 376 if (value.lessThan(Duration.ZERO)) { 377 throw new IllegalArgumentException("Cycle duration cannot be negative"); 378 } 379 ((AnimationReadOnlyProperty<Duration>)cycleDurationProperty()).set(value); 380 updateTotalDuration(); 381 } 382 } 383 384 public final Duration getCycleDuration() { 385 return (cycleDuration == null)? DEFAULT_CYCLE_DURATION : cycleDuration.get(); 386 } 387 388 public final ReadOnlyObjectProperty<Duration> cycleDurationProperty() { 389 if (cycleDuration == null) { 390 cycleDuration = new AnimationReadOnlyProperty<Duration>("cycleDuration", DEFAULT_CYCLE_DURATION); 391 } 392 return cycleDuration; 393 } 394 395 /** 396 * Read-only variable to indicate the total duration of this 397 * {@code Animation}, including repeats. A {@code Animation} with a {@code cycleCount} 398 * of {@code Animation.INDEFINITE} will have a {@code totalDuration} of 399 * {@code Duration.INDEFINITE}. 400 * 401 * <p> 402 * This is set to cycleDuration * cycleCount. 403 * 404 * @defaultValue 0ms 405 */ 406 private ReadOnlyObjectProperty<Duration> totalDuration; 407 private static final Duration DEFAULT_TOTAL_DURATION = Duration.ZERO; 408 409 public final Duration getTotalDuration() { 410 return (totalDuration == null)? DEFAULT_TOTAL_DURATION : totalDuration.get(); 411 } 412 413 public final ReadOnlyObjectProperty<Duration> totalDurationProperty() { 414 if (totalDuration == null) { 415 totalDuration = new AnimationReadOnlyProperty<Duration>("totalDuration", DEFAULT_TOTAL_DURATION); 416 } 417 return totalDuration; 418 } 419 420 private void updateTotalDuration() { 421 // Implementing the bind eagerly, because cycleCount and 422 // cycleDuration should not change that often 423 final int cycleCount = getCycleCount(); 424 final Duration cycleDuration = getCycleDuration(); 425 final Duration newTotalDuration = Duration.ZERO.equals(cycleDuration) ? Duration.ZERO 426 : (cycleCount == Animation.INDEFINITE) ? Duration.INDEFINITE 427 : (cycleCount <= 1) ? cycleDuration : cycleDuration 428 .multiply(cycleCount); 429 if ((totalDuration != null) || (!DEFAULT_TOTAL_DURATION.equals(newTotalDuration))) { 430 ((AnimationReadOnlyProperty<Duration>)totalDurationProperty()).set(newTotalDuration); 431 } 432 if (newTotalDuration.lessThan(getCurrentTime())) { 433 jumpTo(newTotalDuration); 434 } 435 } 436 437 /** 438 * Defines the {@code Animation}'s play head position. 439 * 440 * @defaultValue 0ms 441 */ 442 private CurrentTimeProperty currentTime; 443 private long currentTicks; 444 private class CurrentTimeProperty extends ReadOnlyObjectPropertyBase<Duration> { 445 446 @Override 447 public Object getBean() { 448 return Animation.this; 449 } 450 451 @Override 452 public String getName() { 453 return "currentTime"; 454 } 455 456 @Override 457 public Duration get() { 458 return getCurrentTime(); 459 } 460 461 @Override 462 public void fireValueChangedEvent() { 463 super.fireValueChangedEvent(); 464 } 465 466 } 467 468 public final Duration getCurrentTime() { 469 return TickCalculation.toDuration(currentTicks); 470 } 471 472 public final ReadOnlyObjectProperty<Duration> currentTimeProperty() { 473 if (currentTime == null) { 474 currentTime = new CurrentTimeProperty(); 475 } 476 return currentTime; 477 } 478 479 /** 480 * Delays the start of an animation. 481 * 482 * Cannot be negative. Setting to a negative number will result in {@link IllegalArgumentException}. 483 * 484 * @defaultValue 0ms 485 */ 486 private ObjectProperty<Duration> delay; 487 private static final Duration DEFAULT_DELAY = Duration.ZERO; 488 489 public final void setDelay(Duration value) { 490 if ((delay != null) || (!DEFAULT_DELAY.equals(value))) { 491 delayProperty().set(value); 492 } 493 } 494 495 public final Duration getDelay() { 496 return (delay == null)? DEFAULT_DELAY : delay.get(); 497 } 498 499 public final ObjectProperty<Duration> delayProperty() { 500 if (delay == null) { 501 delay = new ObjectPropertyBase<Duration>(DEFAULT_DELAY) { 502 503 @Override 504 public Object getBean() { 505 return Animation.this; 506 } 507 508 @Override 509 public String getName() { 510 return "delay"; 511 } 512 513 @Override 514 protected void invalidated() { 515 final Duration newDuration = get(); 516 if (newDuration.lessThan(Duration.ZERO)) { 517 if (isBound()) { 518 unbind(); 519 } 520 set(Duration.ZERO); 521 throw new IllegalArgumentException("Cannot set delay to negative value. Setting to Duration.ZERO"); 522 } 523 } 524 525 }; 526 } 527 return delay; 528 } 529 530 /** 531 * Defines the number of cycles in this animation. The {@code cycleCount} 532 * may be {@code INDEFINITE} for animations that repeat indefinitely, but 533 * must otherwise be > 0. 534 * <p> 535 * It is not possible to change the {@code cycleCount} of a running 536 * {@code Animation}. If the value of {@code cycleCount} is changed for a 537 * running {@code Animation}, the animation has to be stopped and started again to pick 538 * up the new value. 539 * 540 * @defaultValue 1.0 541 * 542 */ 543 private IntegerProperty cycleCount; 544 private static final int DEFAULT_CYCLE_COUNT = 1; 545 546 public final void setCycleCount(int value) { 547 if ((cycleCount != null) || (value != DEFAULT_CYCLE_COUNT)) { 548 cycleCountProperty().set(value); 549 } 550 } 551 552 public final int getCycleCount() { 553 return (cycleCount == null)? DEFAULT_CYCLE_COUNT : cycleCount.get(); 554 } 555 556 public final IntegerProperty cycleCountProperty() { 557 if (cycleCount == null) { 558 cycleCount = new IntegerPropertyBase(DEFAULT_CYCLE_COUNT) { 559 560 @Override 561 public void invalidated() { 562 updateTotalDuration(); 563 } 564 565 @Override 566 public Object getBean() { 567 return Animation.this; 568 } 569 570 @Override 571 public String getName() { 572 return "cycleCount"; 573 } 574 }; 575 } 576 return cycleCount; 577 } 578 579 /** 580 * Defines whether this 581 * {@code Animation} reverses direction on alternating cycles. If 582 * {@code true}, the 583 * {@code Animation} will proceed forward on the first cycle, 584 * then reverses on the second cycle, and so on. Otherwise, animation will 585 * loop such that each cycle proceeds forward from the start. 586 * 587 * It is not possible to change the {@code autoReverse} flag of a running 588 * {@code Animation}. If the value of {@code autoReverse} is changed for a 589 * running {@code Animation}, the animation has to be stopped and started again to pick 590 * up the new value. 591 * 592 * @defaultValue false 593 */ 594 private BooleanProperty autoReverse; 595 private static final boolean DEFAULT_AUTO_REVERSE = false; 596 597 public final void setAutoReverse(boolean value) { 598 if ((autoReverse != null) || (value != DEFAULT_AUTO_REVERSE)) { 599 autoReverseProperty().set(value); 600 } 601 } 602 603 public final boolean isAutoReverse() { 604 return (autoReverse == null)? DEFAULT_AUTO_REVERSE : autoReverse.get(); 605 } 606 607 public final BooleanProperty autoReverseProperty() { 608 if (autoReverse == null) { 609 autoReverse = new SimpleBooleanProperty(this, "autoReverse", DEFAULT_AUTO_REVERSE); 610 } 611 return autoReverse; 612 } 613 614 /** 615 * The status of the {@code Animation}. 616 * 617 * In {@code Animation} can be in one of three states: 618 * {@link Status#STOPPED}, {@link Status#PAUSED} or {@link Status#RUNNING}. 619 */ 620 private ReadOnlyObjectProperty<Status> status; 621 private static final Status DEFAULT_STATUS = Status.STOPPED; 622 623 protected final void setStatus(Status value) { 624 if ((status != null) || (!DEFAULT_STATUS.equals(value))) { 625 ((AnimationReadOnlyProperty<Status>)statusProperty()).set(value); 626 } 627 } 628 629 public final Status getStatus() { 630 return (status == null)? DEFAULT_STATUS : status.get(); 631 } 632 633 public final ReadOnlyObjectProperty<Status> statusProperty() { 634 if (status == null) { 635 status = new AnimationReadOnlyProperty<Status>("status", Status.STOPPED); 636 } 637 return status; 638 } 639 640 private final double targetFramerate; 641 private final int resolution; 642 private long lastPulse; 643 644 /** 645 * The target framerate is the maximum framerate at which this {@code Animation} 646 * will run, in frames per second. This can be used, for example, to keep 647 * particularly complex {@code Animations} from over-consuming system resources. 648 * By default, an {@code Animation}'s framerate is not explicitly limited, meaning 649 * the {@code Animation} will run at an optimal framerate for the underlying platform. 650 * 651 * @return the target framerate 652 */ 653 public final double getTargetFramerate() { 654 return targetFramerate; 655 } 656 657 /** 658 * The action to be executed at the conclusion of this {@code Animation}. 659 * 660 * @defaultValue null 661 */ 662 private ObjectProperty<EventHandler<ActionEvent>> onFinished; 663 private static final EventHandler<ActionEvent> DEFAULT_ON_FINISHED = null; 664 665 public final void setOnFinished(EventHandler<ActionEvent> value) { 666 if ((onFinished != null) || (value != null /* DEFAULT_ON_FINISHED */)) { 667 onFinishedProperty().set(value); 668 } 669 } 670 671 public final EventHandler<ActionEvent> getOnFinished() { 672 return (onFinished == null)? DEFAULT_ON_FINISHED : onFinished.get(); 673 } 674 675 public final ObjectProperty<EventHandler<ActionEvent>> onFinishedProperty() { 676 if (onFinished == null) { 677 onFinished = new SimpleObjectProperty<EventHandler<ActionEvent>>(this, "onFinished", DEFAULT_ON_FINISHED); 678 } 679 return onFinished; 680 } 681 682 private final ObservableMap<String, Duration> cuePoints = FXCollections 683 .observableMap(new HashMap<String, Duration>(0)); 684 685 /** 686 * The cue points can be 687 * used to mark important positions of the {@code Animation}. Once a cue 688 * point was defined, it can be used as an argument of 689 * {@link #jumpTo(String) jumpTo()} and {@link #playFrom(String) playFrom()} 690 * to move to the associated position quickly. 691 * <p> 692 * Every {@code Animation} has two predefined cue points {@code "start"} and 693 * {@code "end"}, which are set at the start respectively the end of the 694 * {@code Animation}. The predefined cuepoints do not appear in the map, 695 * attempts to override them have no effect. 696 * <p> 697 * Another option to define a cue point in a {@code Animation} is to set the 698 * {@link KeyFrame#name} property of a {@link KeyFrame}. 699 * 700 * @return {@link javafx.collections.ObservableMap} of cue points 701 */ 702 public final ObservableMap<String, Duration> getCuePoints() { 703 return cuePoints; 704 } 705 706 /** 707 * Jumps to a given position in this {@code Animation}. 708 * 709 * If the given time is less than {@link Duration#ZERO}, this method will 710 * jump to the start of the animation. If the given time is larger than the 711 * duration of this {@code Animation}, this method will jump to the end. 712 * 713 * @param time 714 * the new position 715 * @throws NullPointerException 716 * if {@code time} is {@code null} 717 * @throws IllegalArgumentException 718 * if {@code time} is {@link Duration#UNKNOWN} 719 * @throws IllegalStateException 720 * if embedded in another animation, 721 * such as {@link SequentialTransition} or {@link ParallelTransition} 722 */ 723 public void jumpTo(Duration time) { 724 if (time == null) { 725 throw new NullPointerException("Time needs to be specified."); 726 } 727 if (time.isUnknown()) { 728 throw new IllegalArgumentException("The time is invalid"); 729 } 730 if (parent != null) { 731 throw new IllegalStateException("Cannot jump when embedded in another animation"); 732 } 733 734 lastPlayedFinished = false; 735 736 final Duration totalDuration = getTotalDuration(); 737 time = time.lessThan(Duration.ZERO) ? Duration.ZERO : time 738 .greaterThan(totalDuration) ? totalDuration : time; 739 final long ticks = fromDuration(time); 740 741 if (getStatus() == Status.STOPPED) { 742 syncClipEnvelope(); 743 } 744 clipEnvelope.jumpTo(ticks); 745 } 746 747 /** 748 * Jumps to a predefined position in this {@code Animation}. This method 749 * looks for an entry in cue points and jumps to the associated 750 * position, if it finds one. 751 * <p> 752 * If the cue point is behind the end of this {@code Animation}, calling 753 * {@code jumpTo} will result in a jump to the end. If the cue point has a 754 * negative {@link javafx.util.Duration} it will result in a jump to the 755 * beginning. If the cue point has a value of 756 * {@link javafx.util.Duration#UNKNOWN} calling {@code jumpTo} will have no 757 * effect for this cue point. 758 * <p> 759 * There are two predefined cue points {@code "start"} and {@code "end"} 760 * which are defined to be at the start respectively the end of this 761 * {@code Animation}. 762 * 763 * @param cuePoint 764 * the name of the cue point 765 * @throws NullPointerException 766 * if {@code cuePoint} is {@code null} 767 * @throws IllegalStateException 768 * if embedded in another animation, 769 * such as {@link SequentialTransition} or {@link ParallelTransition} 770 * @see #getCuePoints() 771 */ 772 public void jumpTo(String cuePoint) { 773 if (cuePoint == null) { 774 throw new NullPointerException("CuePoint needs to be specified"); 775 } 776 if ("start".equalsIgnoreCase(cuePoint)) { 777 jumpTo(Duration.ZERO); 778 } else if ("end".equalsIgnoreCase(cuePoint)) { 779 jumpTo(getTotalDuration()); 780 } else { 781 final Duration target = getCuePoints().get(cuePoint); 782 if (target != null) { 783 jumpTo(target); 784 } 785 } 786 } 787 788 /** 789 * A convenience method to play this {@code Animation} from a predefined 790 * position. The position has to be predefined in cue points. 791 * Calling this method is equivalent to 792 * 793 * <pre> 794 * <code> 795 * animation.jumpTo(cuePoint); 796 * animation.play(); 797 * </code> 798 * </pre> 799 * 800 * Note that unlike {@link #playFromStart()} calling this method will not 801 * change the playing direction of this {@code Animation}. 802 * 803 * @param cuePoint 804 * name of the cue point 805 * @throws NullPointerException 806 * if {@code cuePoint} is {@code null} 807 * @throws IllegalStateException 808 * if embedded in another animation, 809 * such as {@link SequentialTransition} or {@link ParallelTransition} 810 * @see #getCuePoints() 811 */ 812 public void playFrom(String cuePoint) { 813 jumpTo(cuePoint); 814 play(); 815 } 816 817 /** 818 * A convenience method to play this {@code Animation} from a specific 819 * position. Calling this method is equivalent to 820 * 821 * <pre> 822 * <code> 823 * animation.jumpTo(time); 824 * animation.play(); 825 * </code> 826 * </pre> 827 * 828 * Note that unlike {@link #playFromStart()} calling this method will not 829 * change the playing direction of this {@code Animation}. 830 * 831 * @param time 832 * position where to play from 833 * @throws NullPointerException 834 * if {@code time} is {@code null} 835 * @throws IllegalArgumentException 836 * if {@code time} is {@link Duration#UNKNOWN} 837 * @throws IllegalStateException 838 * if embedded in another animation, 839 * such as {@link SequentialTransition} or {@link ParallelTransition} 840 */ 841 public void playFrom(Duration time) { 842 jumpTo(time); 843 play(); 844 } 845 846 /** 847 * Plays {@code Animation} from current position in the direction indicated 848 * by {@code rate}. If the {@code Animation} is running, it has no effect. 849 * <p> 850 * When {@code rate} > 0 (forward play), if an {@code Animation} is already 851 * positioned at the end, the first cycle will not be played, it is 852 * considered to have already finished. This also applies to a backward ( 853 * {@code rate} < 0) cycle if an {@code Animation} is positioned at the beginning. 854 * However, if the {@code Animation} has {@code cycleCount} > 1, following 855 * cycle(s) will be played as usual. 856 * <p> 857 * When the {@code Animation} reaches the end, the {@code Animation} is stopped and 858 * the play head remains at the end. 859 * <p> 860 * To play an {@code Animation} backwards from the end:<br> 861 * <code> 862 * animation.setRate(negative rate);<br> 863 * animation.jumpTo(overall duration of animation);<br> 864 * animation.play();<br> 865 * </code> 866 * <p> 867 * Note: <ul> 868 * <li>{@code play()} is an asynchronous call, the {@code Animation} may not 869 * start immediately. </ul> 870 * 871 * @throws IllegalStateException 872 * if embedded in another animation, 873 * such as {@link SequentialTransition} or {@link ParallelTransition} 874 */ 875 public void play() { 876 play(true); 877 } 878 879 private void play(boolean forceSync) { 880 if (parent != null) { 881 throw new IllegalStateException("Cannot start when embedded in another animation"); 882 } 883 switch (getStatus()) { 884 case STOPPED: 885 if (impl_startable(forceSync)) { 886 final double rate = getRate(); 887 if (lastPlayedFinished) { 888 jumpTo((rate < 0)? getTotalDuration() : Duration.ZERO); 889 } 890 lastPlayedFinished = false; 891 impl_start(forceSync); 892 startReceiver(TickCalculation.fromDuration(getDelay())); 893 if (Math.abs(rate) < EPSILON) { 894 pauseReceiver(); 895 } else { 896 897 } 898 } else { 899 final EventHandler<ActionEvent> handler = getOnFinished(); 900 if (handler != null) { 901 handler.handle(new ActionEvent(this, null)); 902 } 903 } 904 break; 905 case PAUSED: 906 impl_resume(); 907 if (Math.abs(getRate()) >= EPSILON) { 908 resumeReceiver(); 909 } 910 break; 911 } 912 } 913 914 /** 915 * Plays an {@code Animation} from initial position in forward direction. 916 * <p> 917 * It is equivalent to 918 * <p> 919 * <code> 920 * animation.stop();<br> 921 * animation.setRate = setRate(Math.abs(animation.getRate())); </br> 922 * animation.jumpTo(Duration.ZERO);<br> 923 * animation.play();<br> 924 * </code> 925 * 926 * <p> 927 * Note: <ul> 928 * <li>{@code playFromStart()} is an asynchronous call, {@code Animation} may 929 * not start immediately. </ul> 930 * <p> 931 * 932 * @throws IllegalStateException 933 * if embedded in another animation, 934 * such as {@link SequentialTransition} or {@link ParallelTransition} 935 */ 936 public void playFromStart() { 937 stop(); 938 setRate(Math.abs(getRate())); 939 jumpTo(Duration.ZERO); 940 play(true); 941 } 942 943 /** 944 * Stops the animation and resets the play head to its initial position. If 945 * the animation is not currently running, this method has no effect. 946 * <p> 947 * Note: <ul> 948 * <li>{@code stop()} is an asynchronous call, the {@code Animation} may not stop 949 * immediately. </ul> 950 * @throws IllegalStateException 951 * if embedded in another animation, 952 * such as {@link SequentialTransition} or {@link ParallelTransition} 953 */ 954 public void stop() { 955 if (parent != null) { 956 throw new IllegalStateException("Cannot stop when embedded in another animation"); 957 } 958 if (getStatus() != Status.STOPPED) { 959 clipEnvelope.abortCurrentPulse(); 960 impl_stop(); 961 jumpTo(Duration.ZERO); 962 } 963 } 964 965 /** 966 * Pauses the animation. If the animation is not currently running, this 967 * method has no effect. 968 * <p> 969 * Note: <ul> 970 * <li>{@code pause()} is an asynchronous call, the {@code Animation} may not pause 971 * immediately. </ul> 972 * @throws IllegalStateException 973 * if embedded in another animation, 974 * such as {@link SequentialTransition} or {@link ParallelTransition} 975 */ 976 public void pause() { 977 if (parent != null) { 978 throw new IllegalStateException("Cannot pause when embedded in another animation"); 979 } 980 if (getStatus() == Status.RUNNING) { 981 clipEnvelope.abortCurrentPulse(); 982 pauseReceiver(); 983 impl_pause(); 984 } 985 } 986 987 /** 988 * The constructor of {@code Animation}. 989 * 990 * This constructor allows to define a target framerate. 991 * 992 * @param targetFramerate 993 * The custom target frame rate for this {@code Animation} 994 * @see #getTargetFramerate() 995 */ 996 protected Animation(double targetFramerate) { 997 this.targetFramerate = targetFramerate; 998 this.resolution = (int) Math.max(1, Math.round(TickCalculation.TICKS_PER_SECOND / targetFramerate)); 999 this.clipEnvelope = ClipEnvelope.create(this); 1000 this.timer = ToolkitAccessor.getMasterTimer(); 1001 } 1002 1003 /** 1004 * The constructor of {@code Animation}. 1005 */ 1006 protected Animation() { 1007 this.resolution = 1; 1008 this.targetFramerate = TickCalculation.TICKS_PER_SECOND / ToolkitAccessor.getMasterTimer().getDefaultResolution(); 1009 this.clipEnvelope = ClipEnvelope.create(this); 1010 this.timer = ToolkitAccessor.getMasterTimer(); 1011 } 1012 1013 // These constructors are only for testing purposes 1014 Animation(AbstractMasterTimer timer) { 1015 this.resolution = 1; 1016 this.targetFramerate = TickCalculation.TICKS_PER_SECOND / timer.getDefaultResolution(); 1017 this.clipEnvelope = ClipEnvelope.create(this); 1018 this.timer = timer; 1019 } 1020 1021 // These constructors are only for testing purposes 1022 Animation(AbstractMasterTimer timer, ClipEnvelope clipEnvelope, int resolution) { 1023 this.resolution = resolution; 1024 this.targetFramerate = TickCalculation.TICKS_PER_SECOND / resolution; 1025 this.clipEnvelope = clipEnvelope; 1026 this.timer = timer; 1027 } 1028 1029 boolean impl_startable(boolean forceSync) { 1030 return (fromDuration(getCycleDuration()) > 0L) 1031 || (!forceSync && clipEnvelope.wasSynched()); 1032 } 1033 1034 void impl_sync(boolean forceSync) { 1035 if (forceSync || !clipEnvelope.wasSynched()) { 1036 syncClipEnvelope(); 1037 } 1038 } 1039 1040 private void syncClipEnvelope() { 1041 final int publicCycleCount = getCycleCount(); 1042 final int internalCycleCount = (publicCycleCount <= 0) 1043 && (publicCycleCount != INDEFINITE) ? 1 : publicCycleCount; 1044 clipEnvelope = clipEnvelope.setCycleCount(internalCycleCount); 1045 clipEnvelope.setCycleDuration(getCycleDuration()); 1046 clipEnvelope.setAutoReverse(isAutoReverse()); 1047 } 1048 1049 void impl_start(boolean forceSync) { 1050 impl_sync(forceSync); 1051 setStatus(Status.RUNNING); 1052 clipEnvelope.start(); 1053 setCurrentRate(clipEnvelope.getCurrentRate()); 1054 lastPulse = 0; 1055 } 1056 1057 void impl_pause() { 1058 final double currentRate = getCurrentRate(); 1059 if (Math.abs(currentRate) >= EPSILON) { 1060 lastPlayedForward = Math.abs(getCurrentRate() - getRate()) < EPSILON; 1061 } 1062 setCurrentRate(0.0); 1063 setStatus(Status.PAUSED); 1064 } 1065 1066 void impl_resume() { 1067 setStatus(Status.RUNNING); 1068 setCurrentRate(lastPlayedForward ? getRate() : -getRate()); 1069 } 1070 1071 void impl_stop() { 1072 if (!paused) { 1073 timer.removePulseReceiver(pulseReceiver); 1074 } 1075 setStatus(Status.STOPPED); 1076 setCurrentRate(0.0); 1077 } 1078 1079 void impl_timePulse(long elapsedTime) { 1080 if (resolution == 1) { // fullspeed 1081 clipEnvelope.timePulse(elapsedTime); 1082 } else if (elapsedTime - lastPulse >= resolution) { 1083 lastPulse = (elapsedTime / resolution) * resolution; 1084 clipEnvelope.timePulse(elapsedTime); 1085 } 1086 } 1087 1088 abstract void impl_playTo(long currentTicks, long cycleTicks); 1089 1090 abstract void impl_jumpTo(long currentTicks, long cycleTicks, boolean forceJump); 1091 1092 void impl_setCurrentTicks(long ticks) { 1093 currentTicks = ticks; 1094 if (currentTime != null) { 1095 currentTime.fireValueChangedEvent(); 1096 } 1097 } 1098 1099 void impl_setCurrentRate(double currentRate) { 1100// if (getStatus() == Status.RUNNING) { 1101 setCurrentRate(currentRate); 1102// } 1103 } 1104 1105 final void impl_finished() { 1106 lastPlayedFinished = true; 1107 impl_stop(); 1108 final EventHandler<ActionEvent> handler = getOnFinished(); 1109 if (handler != null) { 1110 try { 1111 handler.handle(new ActionEvent(this, null)); 1112 } catch (Exception ex) { 1113 ex.printStackTrace(); 1114 } 1115 } 1116 } 1117}