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 }