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 com.sun.scenario.animation.AbstractMasterTimer; 29 import javafx.beans.property.ObjectProperty; 30 import javafx.beans.property.SimpleObjectProperty; 31 import javafx.scene.Node; 32 33 /** 34 * An abstract class that contains the basic functionalities required by all 35 * {@code Transition} based animations, such as {@link PathTransition} and 36 * {@link RotateTransition}. 37 * <p> 38 * This class offers a simple framework to define animation. It provides all the 39 * basic functionality defined in {@link Animation}. {@code Transition} requires 40 * the implementation of a method {@link #interpolate(double)} which is the 41 * called in each frame, while the {@code Transition} is running. 42 * <p> 43 * In addition an extending class needs to set the duration of a single cycle 44 * with {@link Animation#setCycleDuration(javafx.util.Duration)}. This duration 45 * is usually set by the user via a duration property (as in 46 * {@link FadeTransition#durationProperty() duration}) for example. But it can also be calculated 47 * by the extending class as is done in {@link ParallelTransition} and 48 * {@link FadeTransition}. 49 * <p> 50 * Below is a simple example. It creates a small animation that updates the 51 * {@code text} property of a {@link javafx.scene.text.Text} node. It starts 52 * with an empty {@code String} and adds gradually letter by letter until the 53 * full {@code String} was set when the animation finishes. 54 * 55 * <pre> 56 * {@code 57 * 58 * final String content = "Lorem ipsum"; 59 * final Text text = new Text(10, 20, ""); 60 * 61 * final Animation animation = new Transition() { 62 * { 63 * setCycleDuration(Duration.millis(2000)); 64 * } 65 * 66 * protected void interpolate(double frac) { 67 * final int length = content.length(); 68 * final int n = Math.round(length * (float) frac); 69 * text.setText(content.substring(0, n)); 70 * } 71 * 72 * }; 73 * 74 * animation.play(); 75 * }</pre> 76 * 77 * @see Animation 78 * 79 * @since JavaFX 2.0 80 */ 81 public abstract class Transition extends Animation { 82 83 /** 84 * Controls the timing for acceleration and deceleration at each 85 * {@code Transition} cycle. 86 * <p> 87 * This may only be changed prior to starting the transition or after the 88 * transition has ended. If the value of {@code interpolator} is changed for 89 * a running {@code Transition}, the animation has to be stopped and started again to 90 * pick up the new value. 91 * <p> 92 * Default interpolator is set to {@link Interpolator#EASE_BOTH}. 93 * 94 * @defaultValue EASE_BOTH 95 */ 96 private ObjectProperty<Interpolator> interpolator; 97 private static final Interpolator DEFAULT_INTERPOLATOR = Interpolator.EASE_BOTH; 98 99 public final void setInterpolator(Interpolator value) { 100 if ((interpolator != null) || (!DEFAULT_INTERPOLATOR.equals(value))) { 101 interpolatorProperty().set(value); 102 } 103 } 104 105 public final Interpolator getInterpolator() { 106 return (interpolator == null) ? DEFAULT_INTERPOLATOR : interpolator.get(); 107 } 108 109 public final ObjectProperty<Interpolator> interpolatorProperty() { 110 if (interpolator == null) { 111 interpolator = new SimpleObjectProperty<Interpolator>( 112 this, "interpolator", DEFAULT_INTERPOLATOR 113 ); 114 } 115 return interpolator; 116 } 117 118 private Interpolator cachedInterpolator; 119 120 /** 121 * Returns the {@link Interpolator}, that was set when the 122 * {@code Transition} was started. 123 * 124 * Changing the {@link #interpolatorProperty() interpolator} of a running {@code Transition} should 125 * have no immediate effect. Instead the running {@code Transition} should 126 * continue to use the original {@code Interpolator} until it is stopped and 127 * started again. 128 * 129 * @return the {@code Interpolator} that was set when this 130 * {@code Transition} was started 131 */ 132 protected Interpolator getCachedInterpolator() { 133 return cachedInterpolator; 134 } 135 136 /** 137 * The constructor of {@code Transition}. 138 * 139 * This constructor allows to define a {@link #getTargetFramerate() target framerate}. 140 * 141 * @param targetFramerate 142 * The custom target frame rate for this {@code Transition} 143 */ 144 public Transition(double targetFramerate) { 145 super(targetFramerate); 146 } 147 148 /** 149 * The constructor of {@code Transition}. 150 */ 151 public Transition() { 152 } 153 154 // For testing purposes 155 Transition(AbstractMasterTimer timer) { 156 super(timer); 157 } 158 159 /** 160 * Returns the target {@link Node} for animation of this {@code Transition}. 161 * This method returns {@code node} if it is set, else returns its 162 * {@code parent.getTargetNode()} otherwise null. 163 * @return the target {@code Node} 164 */ 165 protected Node getParentTargetNode() { 166 return (parent != null && parent instanceof Transition) ? 167 ((Transition)parent).getParentTargetNode() : null; 168 } 169 170 /** 171 * The method {@code interpolate()} has to be provided by implementations of 172 * {@code Transition}. While a {@code Transition} is running, this method is 173 * called in every frame. 174 * 175 * The parameter defines the current position with the animation. At the 176 * start, the fraction will be {@code 0.0} and at the end it will be 177 * {@code 1.0}. How the parameter increases, depends on the 178 * {@link #interpolatorProperty() interpolator}, e.g. if the 179 * {@code interpolator} is {@link Interpolator#LINEAR}, the fraction will 180 * increase linear. 181 * 182 * This method must not be called by the user directly. 183 * 184 * @param frac 185 * The relative position 186 */ 187 protected abstract void interpolate(double frac); 188 189 private double calculateFraction(long currentTicks, long cycleTicks) { 190 final double frac = cycleTicks <= 0 ? 1.0 : (double) currentTicks / cycleTicks; 191 return cachedInterpolator.interpolate(0.0, 1.0, frac); 192 } 193 194 @Override 195 boolean startable(boolean forceSync) { 196 return super.startable(forceSync) 197 && ((getInterpolator() != null) || (!forceSync && (cachedInterpolator != null))); 198 } 199 200 @Override 201 void sync(boolean forceSync) { 202 super.sync(forceSync); 203 if (forceSync || (cachedInterpolator == null)) { 204 cachedInterpolator = getInterpolator(); 205 } 206 } 207 208 @Override 209 void doPlayTo(long currentTicks, long cycleTicks) { 210 setCurrentTicks(currentTicks); 211 interpolate(calculateFraction(currentTicks, cycleTicks)); 212 } 213 214 @Override 215 void doJumpTo(long currentTicks, long cycleTicks, boolean forceJump) { 216 setCurrentTicks(currentTicks); 217 if (getStatus() != Status.STOPPED || forceJump) { 218 sync(false); 219 interpolate(calculateFraction(currentTicks, cycleTicks)); 220 } 221 } 222 }