javafx.animation.Timeline Maven / Gradle / Ivy
/*
* Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javafx.animation;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.util.Duration;
import com.sun.javafx.collections.TrackableObservableList;
import com.sun.scenario.animation.AbstractPrimaryTimer;
import com.sun.scenario.animation.shared.TimelineClipCore;
/**
* A {@code Timeline} can be used to define a free form animation of any
* {@link javafx.beans.value.WritableValue}, for example, all
* {@link javafx.beans.property.Property JavaFX Properties}.
*
* A {@code Timeline}, defined by one or more {@link KeyFrame}s, processes
* individual {@code KeyFrame}s sequentially, in the order specified by
* {@code KeyFrame.time}. The animated properties, defined as key values in
* {@code KeyFrame.values}, are interpolated
* to/from the targeted key values at the specified time of the {@code KeyFrame}
* to {@code Timeline}'s initial position, depends on {@code Timeline}'s
* direction.
*
* {@code Timeline} processes an individual {@code KeyFrame} at or after the specified
* time interval elapsed, it does not guarantee the exact time when a {@code KeyFrame}
* is processed.
*
* If a {@code KeyFrame} is not provided for the {@code time==0s} instant, one
* will be synthesized using the target values that are current at the time
* {@link #play()} or {@link #playFromStart()} are called.
*
* It is not possible to change the {@code keyFrames} of a running {@code Timeline}.
* If the value of {@code keyFrames} is changed for a running {@code Timeline}, it
* has to be stopped and started again to pick up the new value.
*
* The {@link #cycleDurationProperty()} will be set to the largest time value
* of Timeline's keyFrames.
*
* A simple Timeline can be created like this:
*
{@code final Timeline timeline = new Timeline();
* timeline.setCycleCount(2);
* timeline.setAutoReverse(true);
* timeline.getKeyFrames().add(new KeyFrame(Duration.millis(5000),
* new KeyValue (node.translateXProperty(), 25)));
* timeline.play();}
*
* This Timeline will run for 10s, animating the node by x axis to value 25 and then back to 0 on the second cycle.
*
* Warning: A running Timeline is being referenced from the FX runtime. Infinite Timeline
* might result in a memory leak if not stopped properly. All the objects with animated properties would not be garbage collected.
*
* @see Animation
* @see KeyFrame
* @see KeyValue
*
* @since JavaFX 2.0
*/
public final class Timeline extends Animation {
/* Package-private for testing purposes */
final TimelineClipCore clipCore;
/**
* Returns the {@link KeyFrame KeyFrames} of this {@code Timeline}.
* @return the {@link KeyFrame KeyFrames}
*/
public final ObservableList getKeyFrames() {
return keyFrames;
}
private final ObservableList keyFrames = new TrackableObservableList<>() {
@Override
protected void onChanged(Change c) {
while (c.next()) {
if (!c.wasPermutated()) {
for (final KeyFrame keyFrame : c.getRemoved()) {
final String cuePoint = keyFrame.getName();
if (cuePoint != null) {
getCuePoints().remove(cuePoint);
}
}
for (final KeyFrame keyFrame : c.getAddedSubList()) {
final String cuePoint = keyFrame.getName();
if (cuePoint != null) {
getCuePoints().put(cuePoint, keyFrame.getTime());
}
}
final Duration duration = clipCore.setKeyFrames(getKeyFrames());
setCycleDuration(duration);
}
}
}
};
/**
* Creates a {@code Timeline} with the provided key frames and a {@linkplain Animation#getTargetFramerate() target framerate}.
* The key frames do not need to be ordered.
*
* @param targetFramerate
* the custom target frame rate for this {@code Timeline}
* @param keyFrames
* the keyframes of this {@code Timeline}
*/
public Timeline(double targetFramerate, KeyFrame... keyFrames) {
super(targetFramerate);
clipCore = new TimelineClipCore(this);
getKeyFrames().setAll(keyFrames);
}
/**
* Creates a {@code Timeline} with the provided key frames. The key frames do not need to be ordered.
*
* @param keyFrames
* the keyframes of this {@code Timeline}
*/
public Timeline(KeyFrame... keyFrames) {
super();
clipCore = new TimelineClipCore(this);
getKeyFrames().setAll(keyFrames);
}
/**
* Creates a {@code Timeline} with no key frames and a {@linkplain Animation#getTargetFramerate() target framerate}.
*
* @param targetFramerate
* the custom target frame rate for this {@code Timeline}
*/
public Timeline(double targetFramerate) {
super(targetFramerate);
clipCore = new TimelineClipCore(this);
}
/**
* Creates a {@code Timeline} with no key frames.
*/
public Timeline() {
super();
clipCore = new TimelineClipCore(this);
}
// This constructor is only for testing purposes
Timeline(final AbstractPrimaryTimer timer) {
super(timer);
clipCore = new TimelineClipCore(this);
}
@Override
void doPlayTo(long currentTicks, long cycleTicks) {
clipCore.playTo(currentTicks);
}
@Override
void doJumpTo(long currentTicks, long cycleTicks, boolean forceJump) {
sync(false);
setCurrentTicks(currentTicks);
clipCore.jumpTo(currentTicks, forceJump);
}
@Override
void setCurrentRate(double currentRate) {
super.setCurrentRate(currentRate);
clipCore.notifyCurrentRateChanged();
}
@Override
void doStart(boolean forceSync) {
super.doStart(forceSync);
clipCore.start(forceSync);
}
@Override
void stopImpl() {
if (parent != null) {
throw new IllegalStateException("Cannot stop when embedded in another animation");
}
if (getStatus() == Status.RUNNING) {
clipCore.abort();
}
super.stopImpl();
}
}