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