1 /* 2 * Copyright (c) 2010, 2014, 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 */ 92 public final ObservableList<KeyFrame> getKeyFrames() { 93 return keyFrames; 94 } 95 private final ObservableList<KeyFrame> keyFrames = new TrackableObservableList<KeyFrame>() { 96 @Override 97 protected void onChanged(Change<KeyFrame> c) { 98 while (c.next()) { 99 if (!c.wasPermutated()) { 100 for (final KeyFrame keyFrame : c.getRemoved()) { 101 final String cuePoint = keyFrame.getName(); 102 if (cuePoint != null) { 103 getCuePoints().remove(cuePoint); 104 } 105 } 106 for (final KeyFrame keyFrame : c.getAddedSubList()) { 107 final String cuePoint = keyFrame.getName(); 108 if (cuePoint != null) { 109 getCuePoints().put(cuePoint, keyFrame.getTime()); 110 } 111 } 112 final Duration duration = clipCore.setKeyFrames(getKeyFrames()); 113 setCycleDuration(duration); 114 } 115 } 116 } 117 }; 118 119 /** 120 * The constructor of {@code Timeline}. 121 * 122 * This constructor allows to define a {@link Animation#targetFramerate}. 123 * 124 * @param targetFramerate 125 * The custom target frame rate for this {@code Timeline} 126 * @param keyFrames 127 * The keyframes of this {@code Timeline} 128 */ 129 public Timeline(double targetFramerate, KeyFrame... keyFrames) { 130 super(targetFramerate); 131 clipCore = new TimelineClipCore(this); 132 getKeyFrames().setAll(keyFrames); 133 } 134 135 /** 136 * The constructor of {@code Timeline}. 137 * 138 * @param keyFrames 139 * The keyframes of this {@code Timeline} 140 */ 141 public Timeline(KeyFrame... keyFrames) { 142 super(); 143 clipCore = new TimelineClipCore(this); 144 getKeyFrames().setAll(keyFrames); 145 } 146 147 /** 148 * The constructor of {@code Timeline}. 149 * 150 * This constructor allows to define a {@link Animation#targetFramerate}. 151 * 152 * @param targetFramerate 153 * The custom target frame rate for this {@code Timeline} 154 */ 155 public Timeline(double targetFramerate) { 156 super(targetFramerate); 157 clipCore = new TimelineClipCore(this); 158 } 159 160 /** 161 * The constructor of {@code Timeline}. 162 */ 163 public Timeline() { 164 super(); 165 clipCore = new TimelineClipCore(this); 166 } 167 168 // This constructor is only for testing purposes 169 Timeline(final AbstractMasterTimer timer) { 170 super(timer); 171 clipCore = new TimelineClipCore(this); 172 } 173 174 @Override 175 void impl_playTo(long currentTicks, long cycleTicks) { 176 clipCore.playTo(currentTicks); 177 } 178 179 @Override 180 void impl_jumpTo(long currentTicks, long cycleTicks, boolean forceJump) { 181 impl_sync(false); 182 impl_setCurrentTicks(currentTicks); 183 clipCore.jumpTo(currentTicks, forceJump); 184 } 185 186 @Override 187 void impl_setCurrentRate(double currentRate) { 188 super.impl_setCurrentRate(currentRate); 189 clipCore.notifyCurrentRateChanged(); 190 } 191 192 @Override 193 void impl_start(boolean forceSync) { 194 super.impl_start(forceSync); 195 clipCore.start(forceSync); 196 } 197 198 /** 199 * {@inheritDoc} 200 */ 201 @Override 202 public void stop() { 203 if (parent != null) { 204 throw new IllegalStateException("Cannot stop when embedded in another animation"); 205 } 206 if (getStatus() == Status.RUNNING) { 207 clipCore.abort(); 208 } 209 super.stop(); 210 } 211 }