1 /* 2 * Copyright (c) 2011, 2016, 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.beans.property.ObjectProperty; 29 import javafx.beans.property.ObjectPropertyBase; 30 import javafx.beans.property.SimpleObjectProperty; 31 import javafx.scene.Node; 32 import javafx.scene.paint.Color; 33 import javafx.scene.shape.Shape; 34 import javafx.util.Duration; 35 36 /** 37 * This {@code Transition} creates an animation, that changes the stroke color 38 * of a shape over a {@code duration}. This is done by updating the 39 * {@code stroke} variable of the {@code shape} at regular intervals. 40 * <p> 41 * It starts from the {@code fromValue} if provided else uses the {@code shape} 42 * 's {@code stroke} value. (The {@code stroke} value has to be a 43 * {@link javafx.scene.paint.Color} in this case). 44 * <p> 45 * It stops at the {@code toValue} value. 46 * 47 * <p> 48 * Code Segment Example: 49 * </p> 50 * 51 * <pre> 52 * <code> 53 * import javafx.scene.shape.*; 54 * import javafx.animation.*; 55 * 56 * ... 57 * 58 * Rectangle rect = new Rectangle (100, 40, 100, 100); 59 * rect.setArcHeight(50); 60 * rect.setArcWidth(50); 61 * rect.setFill(null); 62 * 63 * StrokeTransition st = new StrokeTransition(Duration.millis(3000), rect, Color.RED, Color.BLUE); 64 * st.setCycleCount(4); 65 * st.setAutoReverse(true); 66 * 67 * st.play(); 68 * 69 * ... 70 * 71 * </code> 72 * </pre> 73 * 74 * @see Transition 75 * @see Animation 76 * 77 * @since JavaFX 2.0 78 */ 79 public final class StrokeTransition extends Transition { 80 81 private Color start; 82 private Color end; 83 84 /** 85 * The target shape of this {@code StrokeTransition}. 86 * <p> 87 * It is not possible to change the target {@code shape} of a running 88 * {@code StrokeTransition}. If the value of {@code shape} is changed for a 89 * running {@code StrokeTransition}, the animation has to be stopped and 90 * started again to pick up the new value. 91 */ 92 private ObjectProperty<Shape> shape; 93 private static final Shape DEFAULT_SHAPE = null; 94 95 public final void setShape(Shape value) { 96 if ((shape != null) || (value != null /* DEFAULT_SHAPE */)) { 97 shapeProperty().set(value); 98 } 99 } 100 101 public final Shape getShape() { 102 return (shape == null)? DEFAULT_SHAPE : shape.get(); 103 } 104 105 public final ObjectProperty<Shape> shapeProperty() { 106 if (shape == null) { 107 shape = new SimpleObjectProperty<Shape>(this, "shape", DEFAULT_SHAPE); 108 } 109 return shape; 110 } 111 112 private Shape cachedShape; 113 114 /** 115 * The duration of this {@code StrokeTransition}. 116 * <p> 117 * It is not possible to change the {@code duration} of a running 118 * {@code StrokeTransition}. If the value of {@code duration} is changed for 119 * a running {@code StrokeTransition}, the animation has to be stopped and 120 * started again to pick up the new value. 121 * <p> 122 * Note: While the unit of {@code duration} is a millisecond, the 123 * granularity depends on the underlying operating system and will in 124 * general be larger. For example animations on desktop systems usually run 125 * with a maximum of 60fps which gives a granularity of ~17 ms. 126 * 127 * Setting duration to value lower than {@link Duration#ZERO} will result 128 * in {@link IllegalArgumentException}. 129 * 130 * @defaultValue 400ms 131 */ 132 private ObjectProperty<Duration> duration; 133 private static final Duration DEFAULT_DURATION = Duration.millis(400); 134 135 public final void setDuration(Duration value) { 136 if ((duration != null) || (!DEFAULT_DURATION.equals(value))) { 137 durationProperty().set(value); 138 } 139 } 140 141 public final Duration getDuration() { 142 return (duration == null)? DEFAULT_DURATION : duration.get(); 143 } 144 145 public final ObjectProperty<Duration> durationProperty() { 146 if (duration == null) { 147 duration = new ObjectPropertyBase<Duration>(DEFAULT_DURATION) { 148 149 @Override 150 public void invalidated() { 151 try { 152 setCycleDuration(getDuration()); 153 } catch (IllegalArgumentException e) { 154 if (isBound()) { 155 unbind(); 156 } 157 set(getCycleDuration()); 158 throw e; 159 } 160 } 161 162 @Override 163 public Object getBean() { 164 return StrokeTransition.this; 165 } 166 167 @Override 168 public String getName() { 169 return "duration"; 170 } 171 }; 172 } 173 return duration; 174 } 175 176 /** 177 * Specifies the start color value for this {@code StrokeTransition}. 178 * <p> 179 * It is not possible to change {@code fromValue} of a running 180 * {@code StrokeTransition}. If the value of {@code fromValue} is changed 181 * for a running {@code StrokeTransition}, the animation has to be stopped 182 * and started again to pick up the new value. 183 * 184 * @defaultValue {@code null} 185 */ 186 private ObjectProperty<Color> fromValue; 187 private static final Color DEFAULT_FROM_VALUE = null; 188 189 public final void setFromValue(Color value) { 190 if ((fromValue != null) || (value != null /* DEFAULT_FROM_VALUE */)) { 191 fromValueProperty().set(value); 192 } 193 } 194 195 public final Color getFromValue() { 196 return (fromValue == null) ? DEFAULT_FROM_VALUE : fromValue.get(); 197 } 198 199 public final ObjectProperty<Color> fromValueProperty() { 200 if (fromValue == null) { 201 fromValue = new SimpleObjectProperty<Color>(this, "fromValue", DEFAULT_FROM_VALUE); 202 } 203 return fromValue; 204 } 205 206 /** 207 * Specifies the stop color value for this {@code StrokeTransition}. 208 * <p> 209 * It is not possible to change {@code toValue} of a running 210 * {@code StrokeTransition}. If the value of {@code toValue} is changed for 211 * a running {@code StrokeTransition}, the animation has to be stopped and 212 * started again to pick up the new value. 213 * 214 * @defaultValue {@code null} 215 */ 216 private ObjectProperty<Color> toValue; 217 private static final Color DEFAULT_TO_VALUE = null; 218 219 public final void setToValue(Color value) { 220 if ((toValue != null) || (value != null /* DEFAULT_TO_VALUE */)) { 221 toValueProperty().set(value); 222 } 223 } 224 225 public final Color getToValue() { 226 return (toValue == null)? DEFAULT_TO_VALUE : toValue.get(); 227 } 228 229 public final ObjectProperty<Color> toValueProperty() { 230 if (toValue == null) { 231 toValue = new SimpleObjectProperty<Color>(this, "toValue", DEFAULT_TO_VALUE); 232 } 233 return toValue; 234 } 235 236 /** 237 * The constructor of {@code StrokeTransition} 238 * @param duration The duration of the {@code StrokeTransition} 239 * @param shape The {@code shape} which filling will be animated 240 * @param fromValue The start value of the color-animation 241 * @param toValue The end value of the color-animation 242 */ 243 public StrokeTransition(Duration duration, Shape shape, Color fromValue, 244 Color toValue) { 245 setDuration(duration); 246 setShape(shape); 247 setFromValue(fromValue); 248 setToValue(toValue); 249 setCycleDuration(duration); 250 } 251 252 /** 253 * The constructor of {@code StrokeTransition} 254 * @param duration The duration of the {@code StrokeTransition} 255 * @param fromValue The start value of the color-animation 256 * @param toValue The end value of the color-animation 257 */ 258 public StrokeTransition(Duration duration, Color fromValue, Color toValue) { 259 this(duration, null, fromValue, toValue); 260 } 261 262 /** 263 * The constructor of {@code StrokeTransition} 264 * 265 * @param duration 266 * The duration of the {@code StrokeTransition} 267 * @param shape 268 * The {@code shape} which stroke paint will be animated 269 */ 270 public StrokeTransition(Duration duration, Shape shape) { 271 this(duration, shape, null, null); 272 } 273 274 /** 275 * The constructor of {@code StrokeTransition} 276 * 277 * @param duration 278 * The duration of the {@code StrokeTransition} 279 */ 280 public StrokeTransition(Duration duration) { 281 this(duration, null); 282 } 283 284 /** 285 * The constructor of {@code StrokeTransition} 286 */ 287 public StrokeTransition() { 288 this(DEFAULT_DURATION, null); 289 } 290 291 /** 292 * {@inheritDoc} 293 */ 294 @Override 295 protected void interpolate(double frac) { 296 final Color newColor = start.interpolate(end, frac); 297 cachedShape.setStroke(newColor); 298 } 299 300 private Shape getTargetShape() { 301 Shape shape = getShape(); 302 if (shape == null) { 303 final Node node = getParentTargetNode(); 304 if (node instanceof Shape) { 305 shape = (Shape) node; 306 } 307 } 308 return shape; 309 } 310 311 @Override 312 boolean impl_startable(boolean forceSync) { 313 if (!super.impl_startable(forceSync)) { 314 return false; 315 } 316 // check if synchronization is not forced and cached values are valid 317 if (!forceSync && (cachedShape != null)) { 318 return true; 319 } 320 321 // we have to synchronize 322 final Shape shape = getTargetShape(); 323 return ((shape != null) // shape is defined? 324 && ((getFromValue() != null) || (shape.getStroke() instanceof Color)) // fromValue 325 // defined 326 // or 327 // current 328 // stroke 329 // paint 330 // is 331 // Color? 332 && (getToValue() != null)); // toValue defined? 333 } 334 335 @Override 336 void impl_sync(boolean forceSync) { 337 super.impl_sync(forceSync); 338 if (forceSync || (cachedShape == null)) { 339 cachedShape = getTargetShape(); 340 final Color _fromValue = getFromValue(); 341 start = (_fromValue != null) ? _fromValue : (Color) cachedShape 342 .getStroke(); 343 end = getToValue(); 344 } 345 } 346 }