1 /* 2 * Copyright (c) 2010, 2017, 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.collections.ListChangeListener.Change; 29 import javafx.collections.ObservableList; 30 import javafx.util.Duration; 31 32 import com.sun.javafx.collections.TrackableObservableList; 33 import com.sun.scenario.animation.AbstractMasterTimer; 34 import com.sun.scenario.animation.shared.TimelineClipCore; 35 36 /** 37 * A {@code Timeline} can be used to define a free form animation of any 38 * {@link javafx.beans.value.WritableValue}, e.g. all 39 * {@link javafx.beans.property.Property JavaFX Properties}. 40 * <p> 41 * A {@code Timeline}, defined by one or more {@link KeyFrame}s, processes 42 * individual {@code KeyFrame} sequentially, in the order specified by 43 * {@code KeyFrame.time}. The animated properties, defined as key values in 44 * {@code KeyFrame.values}, are interpolated 45 * to/from the targeted key values at the specified time of the {@code KeyFrame} 46 * to {@code Timeline}'s initial position, depends on {@code Timeline}'s 47 * direction. 48 * <p> 49 * {@code Timeline} processes individual {@code KeyFrame} at or after specified 50 * time interval elapsed, it does not guarantee the timing when {@code KeyFrame} 51 * is processed. 52 * <p> 53 * The {@link #cycleDurationProperty()} will be set to the largest time value 54 * of Timeline's keyFrames. 55 * <p> 56 * If a {@code KeyFrame} is not provided for the {@code time==0s} instant, one 57 * will be synthesized using the target values that are current at the time 58 * {@link #play()} or {@link #playFromStart()} is called. 59 * <p> 60 * It is not possible to change the {@code keyFrames} of a running {@code Timeline}. 61 * If the value of {@code keyFrames} is changed for a running {@code Timeline}, it 62 * has to be stopped and started again to pick up the new value. 63 * <p> 64 * A simple Timeline can be created like this: 65 * <pre>{@code 66 * final Timeline timeline = new Timeline(); 67 * timeline.setCycleCount(2); 68 * timeline.setAutoReverse(true); 69 * timeline.getKeyFrames().add(new KeyFrame(Duration.millis(5000), 70 * new KeyValue (node.translateXProperty(), 25))); 71 * timeline.play(); 72 * }</pre> 73 * <p> 74 * This Timeline will run for 10s, animating the node by x axis to value 25 and then back to 0 on the second cycle. 75 * <p> 76 * <b>Warning :</b> A running Timeline is being referenced from the FX runtime. Infinite Timeline 77 * might result in a memory leak if not stopped properly. All the objects with animated properties would not be garbage collected. 78 * 79 * @see Animation 80 * @see KeyFrame 81 * @see KeyValue 82 * 83 * @since JavaFX 2.0 84 */ 85 public final class Timeline extends Animation { 86 /* Package-private for testing purposes */ 87 final TimelineClipCore clipCore; 88 89 /** 90 * Returns the {@link KeyFrame KeyFrames} of this {@code Timeline}. 91 * @return the {@link KeyFrame KeyFrames} 92 */ 93 public final ObservableList<KeyFrame> getKeyFrames() { 94 return keyFrames; 95 } 96 private final ObservableList<KeyFrame> keyFrames = new TrackableObservableList<KeyFrame>() { 97 @Override 98 protected void onChanged(Change<KeyFrame> c) { 99 while (c.next()) { 100 if (!c.wasPermutated()) { 101 for (final KeyFrame keyFrame : c.getRemoved()) { 102 final String cuePoint = keyFrame.getName(); 103 if (cuePoint != null) { 104 getCuePoints().remove(cuePoint); 105 } 106 } 107 for (final KeyFrame keyFrame : c.getAddedSubList()) { 108 final String cuePoint = keyFrame.getName(); 109 if (cuePoint != null) { 110 getCuePoints().put(cuePoint, keyFrame.getTime()); 111 } 112 } 113 final Duration duration = clipCore.setKeyFrames(getKeyFrames()); 114 setCycleDuration(duration); 115 } 116 } 117 } 118 }; 119 120 /** 121 * The constructor of {@code Timeline}. 122 * 123 * This constructor allows to define a {@link Animation#targetFramerate}. 124 * 125 * @param targetFramerate 126 * The custom target frame rate for this {@code Timeline} 127 * @param keyFrames 128 * The keyframes of this {@code Timeline} 129 */ 130 public Timeline(double targetFramerate, KeyFrame... keyFrames) { 131 super(targetFramerate); 132 clipCore = new TimelineClipCore(this); 133 getKeyFrames().setAll(keyFrames); 134 } 135 136 /** 137 * The constructor of {@code Timeline}. 138 * 139 * @param keyFrames 140 * The keyframes of this {@code Timeline} 141 */ 142 public Timeline(KeyFrame... keyFrames) { 143 super(); 144 clipCore = new TimelineClipCore(this); 145 getKeyFrames().setAll(keyFrames); 146 } 147 148 /** 149 * The constructor of {@code Timeline}. 150 * 151 * This constructor allows to define a {@link Animation#targetFramerate}. 152 * 153 * @param targetFramerate 154 * The custom target frame rate for this {@code Timeline} 155 */ 156 public Timeline(double targetFramerate) { 157 super(targetFramerate); 158 clipCore = new TimelineClipCore(this); 159 } 160 161 /** 162 * The constructor of {@code Timeline}. 163 */ 164 public Timeline() { 165 super(); 166 clipCore = new TimelineClipCore(this); 167 } 168 169 // This constructor is only for testing purposes 170 Timeline(final AbstractMasterTimer timer) { 171 super(timer); 172 clipCore = new TimelineClipCore(this); 173 } 174 175 @Override 176 void doPlayTo(long currentTicks, long cycleTicks) { 177 clipCore.playTo(currentTicks); 178 } 179 180 @Override 181 void doJumpTo(long currentTicks, long cycleTicks, boolean forceJump) { 182 sync(false); 183 setCurrentTicks(currentTicks); 184 clipCore.jumpTo(currentTicks, forceJump); 185 } 186 187 @Override 188 void setCurrentRate(double currentRate) { 189 super.setCurrentRate(currentRate); 190 clipCore.notifyCurrentRateChanged(); 191 } 192 193 @Override 194 void doStart(boolean forceSync) { 195 super.doStart(forceSync); 196 clipCore.start(forceSync); 197 } 198 199 /** 200 * {@inheritDoc} 201 */ 202 @Override 203 public void stop() { 204 if (parent != null) { 205 throw new IllegalStateException("Cannot stop when embedded in another animation"); 206 } 207 if (getStatus() == Status.RUNNING) { 208 clipCore.abort(); 209 } 210 super.stop(); 211 } 212 }