Spec-Zone .ru
спецификации, руководства, описания, API
001/*
002 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.  Oracle designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Oracle in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
022 * or visit www.oracle.com if you need additional information or have any
023 * questions.
024 */
025
026package javafx.animation;
027
028import javafx.beans.property.DoubleProperty;
029import javafx.beans.property.ObjectProperty;
030import javafx.beans.property.ObjectPropertyBase;
031import javafx.beans.property.SimpleDoubleProperty;
032import javafx.beans.property.SimpleObjectProperty;
033import javafx.geometry.Point3D;
034import javafx.scene.Node;
035import javafx.util.Duration;
036
037/**
038 * This {@code Transition} creates a rotation animation that spans its
039 * {@code duration}. This is done by updating the {@code rotate} variable of the
040 * {@code node} at regular interval. The angle value is specified in degrees.
041 * <p>
042 * It starts from the {@code fromAngle} if provided else uses the {@code node}'s
043 * {@code rotate} value.
044 * <p>
045 * It stops at the {@code toAngle} value if provided else it will use start
046 * value plus {@code byAngle}.
047 * <p>
048 * The {@code toAngle} takes precedence if both {@code toAngle} and
049 * {@code byAngle} are specified.
050 * 
051 * <p>
052 * Code Segment Example:
053 * </p>
054 * 
055 * <pre>
056 * <code>
057 * import javafx.scene.shape.*;
058 * import javafx.animation.transition.*;
059 * 
060 * ...
061 * 
062 *     Rectangle rect = new Rectangle (100, 40, 100, 100);
063 *     rect.setArcHeight(50);
064 *     rect.setArcWidth(50);
065 *     rect.setFill(Color.VIOLET);
066 * 
067 *     RotateTransition rt = new RotateTransition(Duration.millis(3000), rect);
068 *     rt.setByAngle(180);
069 *     rt.setCycleCount(4);
070 *     rt.setAutoReverse(true);
071 * 
072 *     rt.play();
073 * 
074 * ...
075 * 
076 * </code>
077 * </pre>
078 * 
079 * @see Transition
080 * @see Animation
081 * 
082 */
083public final class RotateTransition extends Transition {
084
085    private static final double EPSILON = 1e-12;
086
087    private double start;
088    private double delta;
089
090    /**
091     * The target node of this {@code RotateTransition}.
092     * <p>
093     * It is not possible to change the target {@code node} of a running
094     * {@code RotateTransition}. If the value of {@code node} is changed for a
095     * running {@code RotateTransition}, the animation has to be stopped and
096     * started again to pick up the new value.
097     */
098    private ObjectProperty<Node> node;
099    private static final Node DEFAULT_NODE = null;
100
101    public final void setNode(Node value) {
102        if ((node != null) || (value != null /* DEFAULT_NODE */)) {
103            nodeProperty().set(value);
104        }
105    }
106
107    public final Node getNode() {
108        return (node == null)? DEFAULT_NODE : node.get();
109    }
110
111    public final ObjectProperty<Node> nodeProperty() {
112        if (node == null) {
113            node = new SimpleObjectProperty<Node>(this, "node", DEFAULT_NODE);
114        }
115        return node;
116    }
117
118    private Node cachedNode;
119
120    /**
121     * The duration of this {@code RotateTransition}.
122     * <p>
123     * It is not possible to change the {@code duration} of a running
124     * {@code RotateTransition}. If the value of {@code duration} is changed for
125     * a running {@code RotateTransition}, the animation has to be stopped and
126     * started again to pick up the new value.
127     * <p>
128     * Note: While the unit of {@code duration} is a millisecond, the
129     * granularity depends on the underlying operating system and will in
130     * general be larger. For example animations on desktop systems usually run
131     * with a maximum of 60fps which gives a granularity of ~17 ms.
132     *
133     * Setting duration to value lower than {@link Duration#ZERO} will result
134     * in {@link IllegalArgumentException}.
135     * 
136     * @defaultValue 400ms
137     */
138    private ObjectProperty<Duration> duration;
139    private static final Duration DEFAULT_DURATION = Duration.millis(400);
140
141    public final void setDuration(Duration value) {
142        if ((duration != null) || (!DEFAULT_DURATION.equals(value))) {
143            durationProperty().set(value);
144        }
145    }
146
147    public final Duration getDuration() {
148        return (duration == null)? DEFAULT_DURATION : duration.get();
149    }
150
151    public final ObjectProperty<Duration> durationProperty() {
152        if (duration == null) {
153            duration = new ObjectPropertyBase<Duration>(DEFAULT_DURATION) {
154
155                @Override
156                public void invalidated() {
157                    try {
158                        setCycleDuration(getDuration());
159                    } catch (IllegalArgumentException e) {
160                        if (isBound()) {
161                            unbind();
162                        }
163                        set(getCycleDuration());
164                        throw e;
165                    }
166                }
167
168                @Override
169                public Object getBean() {
170                    return RotateTransition.this;
171                }
172
173                @Override
174                public String getName() {
175                    return "duration";
176                }
177            };
178        }
179        return duration;
180    }
181
182    /**
183     * Specifies the axis of rotation for this {@code RotateTransition}. Use
184     * {@code node.rotationAxis} for axis of rotation if this {@code axis} is
185     * null.
186     * <p>
187     * It is not possible to change the {@code axis} of a running
188     * {@code RotateTransition}. If the value of {@code axis} is changed for a
189     * running {@code RotateTransition}, the animation has to be stopped and
190     * started again to pick up the new value.
191     * 
192     * @defaultValue null
193     */
194    private ObjectProperty<Point3D> axis;
195    private static final Point3D DEFAULT_AXIS = null;
196
197    public final void setAxis(Point3D value) {
198        if ((axis != null) || (value != null /* DEFAULT_AXIS */)) {
199            axisProperty().set(value);
200        }
201    }
202
203    public final Point3D getAxis() {
204        return (axis == null)? DEFAULT_AXIS : axis.get();
205    }
206
207    public final ObjectProperty<Point3D> axisProperty() {
208        if (axis == null) {
209            axis = new SimpleObjectProperty<Point3D>(this, "axis", DEFAULT_AXIS);
210        }
211        return axis;
212    }
213
214    /**
215     * Specifies the start angle value for this {@code RotateTransition}.
216     * <p>
217     * It is not possible to change {@code fromAngle} of a running
218     * {@code RotateTransition}. If the value of {@code fromAngle} is changed
219     * for a running {@code RotateTransition}, the animation has to be stopped
220     * and started again to pick up the new value.
221     * 
222     * @defaultValue {@code Double.NaN}
223     */
224    private DoubleProperty fromAngle;
225    private static final double DEFAULT_FROM_ANGLE = Double.NaN;
226
227    public final void setFromAngle(double value) {
228        if ((fromAngle != null) || (!Double.isNaN(value))) {
229            fromAngleProperty().set(value);
230        }
231    }
232
233    public final double getFromAngle() {
234        return (fromAngle == null)? DEFAULT_FROM_ANGLE : fromAngle.get();
235    }
236
237    public final DoubleProperty fromAngleProperty() {
238        if (fromAngle == null) {
239            fromAngle = new SimpleDoubleProperty(this, "fromAngle", DEFAULT_FROM_ANGLE);
240        }
241        return fromAngle;
242    }
243
244    /**
245     * Specifies the stop angle value for this {@code RotateTransition}.
246     * <p>
247     * It is not possible to change {@code toAngle} of a running
248     * {@code RotateTransition}. If the value of {@code toAngle} is changed for
249     * a running {@code RotateTransition}, the animation has to be stopped and
250     * started again to pick up the new value.
251     * 
252     * @defaultValue {@code Double.NaN}
253     */
254    private DoubleProperty toAngle;
255    private static final double DEFAULT_TO_ANGLE = Double.NaN;
256
257    public final void setToAngle(double value) {
258        if ((toAngle != null) || (!Double.isNaN(value))) {
259            toAngleProperty().set(value);
260        }
261    }
262
263    public final double getToAngle() {
264        return (toAngle == null)? DEFAULT_TO_ANGLE : toAngle.get();
265    }
266
267    public final DoubleProperty toAngleProperty() {
268        if (toAngle == null) {
269            toAngle = new SimpleDoubleProperty(this, "toAngle", DEFAULT_TO_ANGLE);
270        }
271        return toAngle;
272    }
273
274    /**
275     * Specifies the incremented stop angle value, from the start, of this
276     * {@code RotateTransition}.
277     * <p>
278     * It is not possible to change {@code byAngle} of a running
279     * {@code RotateTransition}. If the value of {@code byAngle} is changed for
280     * a running {@code RotateTransition}, the animation has to be stopped and
281     * started again to pick up the new value.
282     */
283    private DoubleProperty byAngle;
284    private static final double DEFAULT_BY_ANGLE = 0.0;
285
286    public final void setByAngle(double value) {
287        if ((byAngle != null) || (Math.abs(value - DEFAULT_BY_ANGLE) > EPSILON)) {
288            byAngleProperty().set(value);
289        }
290    }
291
292    public final double getByAngle() {
293        return (byAngle == null)? DEFAULT_BY_ANGLE : byAngle.get();
294    }
295
296    public final DoubleProperty byAngleProperty() {
297        if (byAngle == null) {
298            byAngle = new SimpleDoubleProperty(this, "byAngle", DEFAULT_BY_ANGLE);
299        }
300        return byAngle;
301    }
302
303    /**
304     * The constructor of {@code RotateTransition}
305     * 
306     * @param duration
307     *            The duration of the {@code RotateTransition}
308     * @param node
309     *            The {@code node} which will be rotated
310     */
311    public RotateTransition(Duration duration, Node node) {
312        setDuration(duration);
313        setNode(node);
314        setCycleDuration(duration);
315    }
316
317    /**
318     * The constructor of {@code RotateTransition}
319     * 
320     * @param duration
321     *            The duration of the {@code RotateTransition}
322     */
323    public RotateTransition(Duration duration) {
324        this(duration, null);
325    }
326
327    /**
328     * The constructor of {@code RotateTransition}
329     * 
330     */
331    public RotateTransition() {
332        this(DEFAULT_DURATION, null);
333    }
334
335    /**
336     * {@inheritDoc}
337     */
338    @Override
339    protected void interpolate(double frac) {
340        cachedNode.setRotate(start + frac * delta);
341    }
342
343    private Node getTargetNode() {
344        final Node node = getNode();
345        return (node != null) ? node : getParentTargetNode();
346    }
347
348    @Override
349    boolean impl_startable(boolean forceSync) {
350        return super.impl_startable(forceSync)
351                && ((getTargetNode() != null) || (!forceSync && (cachedNode != null)));
352    }
353
354    @Override
355    void impl_sync(boolean forceSync) {
356        super.impl_sync(forceSync);
357        if (forceSync || (cachedNode == null)) {
358            cachedNode = getTargetNode();
359            final double _fromAngle = getFromAngle();
360            final double _toAngle = getToAngle();
361            start = (!Double.isNaN(_fromAngle)) ? _fromAngle : cachedNode
362                    .getRotate();
363            delta = (!Double.isNaN(_toAngle)) ? _toAngle - start : getByAngle();
364            final Point3D _axis = getAxis();
365            if (_axis != null) {
366                node.get().setRotationAxis(_axis);
367            }
368        }
369    }
370
371}