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.ObjectProperty;
029import javafx.beans.property.ObjectPropertyBase;
030import javafx.beans.property.SimpleObjectProperty;
031import javafx.scene.Node;
032import javafx.scene.shape.Shape;
033import javafx.util.Duration;
034
035import com.sun.javafx.animation.transition.AnimationPathHelper;
036import com.sun.javafx.animation.transition.Position2D;
037import com.sun.javafx.geom.Path2D;
038import com.sun.javafx.geom.transform.BaseTransform;
039
040/**
041 * This {@code Transition} creates a path animation that spans its
042 * {@link #duration}. The translation along the path is done by updating the
043 * {@code translateX} and {@code translateY} variables of the {@code node}, and
044 * the {@code rotate} variable will get updated if {@code orientation} is set to
045 * {@code OrientationType.ORTHOGONAL_TO_TANGENT}, at regular interval.
046 * <p>
047 * The animated path is defined by the outline of a shape.
048 * 
049 * <p>
050 * Code Segment Example:
051 * </p>
052 * 
053 * <pre>
054 * <code>
055 * import javafx.scene.shape.*;
056 * import javafx.animation.transition.*;
057 * 
058 * ...
059 * 
060 *     Rectangle rect = new Rectangle (100, 40, 100, 100);
061 *     rect.setArcHeight(50);
062 *     rect.setArcWidth(50);
063 *     rect.setFill(Color.VIOLET);
064 * 
065 * 
066 *     Path path = new Path();
067 *     path.getElements().add (new MoveTo (0f, 50f));
068 *     path.getElements().add (new CubicCurveTo (40f, 10f, 390f, 240f, 1904, 50f));
069 * 
070 *     pathTransition.setDuration(Duration.millis(10000));
071 *     pathTransition.setNode(rect);
072 *     pathTransition.setPath(path);
073 *     pathTransition.setOrientation(OrientationType.ORTHOGONAL_TO_TANGENT);
074 *     pathTransition.setCycleCount(4f);
075 *     pathTransition.setAutoReverse(true);
076 * 
077 *     pathTransition.play();
078 * 
079 * ...
080 * 
081 * </code>
082 * </pre>
083 * 
084 * @see Transition
085 * @see Animation
086 * 
087 */
088public final class PathTransition extends Transition {
089
090    /**
091     * The target node of this {@code PathTransition}.
092     * <p>
093     * It is not possible to change the target {@code node} of a running
094     * {@code PathTransition}. If the value of {@code node} is changed for a
095     * running {@code PathTransition}, 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 Transition}.
122     * <p>
123     * It is not possible to change the {@code duration} of a running
124     * {@code PathTransition}. If the value of {@code duration} is changed for a
125     * running {@code PathTransition}, 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 PathTransition.this;
171                }
172
173                @Override
174                public String getName() {
175                    return "duration";
176                }
177            };
178        }
179        return duration;
180    }
181
182    /**
183     * The shape on which outline the node should be animated.
184     * <p>
185     * It is not possible to change the {@code path} of a running
186     * {@code PathTransition}. If the value of {@code path} is changed for a
187     * running {@code PathTransition}, the animation has to be stopped and
188     * started again to pick up the new value.
189     * 
190     * @defaultValue null
191     */
192    private ObjectProperty<Shape> path;
193    private static final Shape DEFAULT_PATH = null;
194
195    public final void setPath(Shape value) {
196        if ((path != null) || (value != null /* DEFAULT_PATH */)) {
197            pathProperty().set(value);
198        }
199    }
200
201    public final Shape getPath() {
202        return (path == null)? DEFAULT_PATH : path.get();
203    }
204
205    public final ObjectProperty<Shape> pathProperty() {
206        if (path == null) {
207            path = new SimpleObjectProperty<Shape>(this, "path", DEFAULT_PATH);
208        }
209        return path;
210    }
211
212    /**
213     * Specifies the upright orientation of {@code node} along the {@code path}.
214     */
215    public static enum OrientationType {
216
217        /**
218         * The targeted {@code node}'s rotation matrix stays unchange along the
219         * geometric path.
220         */
221        NONE,
222
223        /**
224         * The targeted node's rotation matrix is set to keep {@code node}
225         * perpendicular to the path's tangent along the geometric path.
226         */
227        ORTHOGONAL_TO_TANGENT
228    }
229
230    /**
231     * Specifies the upright orientation of {@code node} along the {@code path}.
232     * The default orientation is set to {@link OrientationType#NONE}.
233     * <p>
234     * It is not possible to change the {@code orientation} of a running
235     * {@code PathTransition}. If the value of {@code orientation} is changed
236     * for a running {@code PathTransition}, the animation has to be stopped and
237     * started again to pick up the new value.
238     * 
239     * @defaultValue NONE
240     */
241    private ObjectProperty<OrientationType> orientation;
242    private static final OrientationType DEFAULT_ORIENTATION = OrientationType.NONE;
243
244    public final void setOrientation(OrientationType value) {
245        if ((orientation != null) || (!DEFAULT_ORIENTATION.equals(value))) {
246            orientationProperty().set(value);
247        }
248    }
249
250    public final OrientationType getOrientation() {
251        return (orientation == null)? OrientationType.NONE : orientation.get();
252    }
253
254    public final ObjectProperty<OrientationType> orientationProperty() {
255        if (orientation == null) {
256            orientation = new SimpleObjectProperty<OrientationType>(this, "orientation", DEFAULT_ORIENTATION);
257        }
258        return orientation;
259    }
260
261    private boolean cachedIsNormalRequired;
262
263    private final Position2D posResult = new Position2D();
264    private AnimationPathHelper apHelper;
265
266    /**
267     * The constructor of {@code PathTransition}.
268     * 
269     * @param duration
270     *            The {@link #duration} of this {@code PathTransition}
271     * @param path
272     *            The {@link #path} of this {@code PathTransition}
273     * @param node
274     *            The {@link #node} of this {@code PathTransition}
275     */
276    public PathTransition(Duration duration, Shape path, Node node) {
277        setDuration(duration);
278        setPath(path);
279        setNode(node);
280        setCycleDuration(duration);
281    }
282
283    /**
284     * The constructor of {@code PathTransition}.
285     * 
286     * @param duration
287     *            The {@link #duration} of this {@code PathTransition}
288     * @param path
289     *            The {@link #path} of this {@code PathTransition}
290     */
291    public PathTransition(Duration duration, Shape path) {
292        this(duration, path, null);
293    }
294
295    /**
296     * The constructor of {@code PathTransition}.
297     */
298    public PathTransition() {
299        this(DEFAULT_DURATION, null, null);
300    }
301
302    /**
303     * {@inheritDoc}
304     */
305    @Override
306    public void interpolate(double frac) {
307        apHelper.getPosition2D(frac, cachedIsNormalRequired, posResult);
308        cachedNode.setTranslateX(posResult.x - cachedNode.impl_getPivotX());
309        cachedNode.setTranslateY(posResult.y - cachedNode.impl_getPivotY());
310        // Need to handle orientation if it is requested
311        if (cachedIsNormalRequired) {
312            cachedNode.setRotate(posResult.rotateAngle);
313        }
314    }
315
316    private Node getTargetNode() {
317        final Node node = getNode();
318        return (node != null) ? node : getParentTargetNode();
319    }
320
321    @Override
322    boolean impl_startable(boolean forceSync) {
323        return super.impl_startable(forceSync)
324                && (((getTargetNode() != null) && (getPath() != null) && !getPath().getLayoutBounds().isEmpty()) || (!forceSync
325                        && (cachedNode != null) && (apHelper != null)));
326    }
327
328    @Override
329    void impl_sync(boolean forceSync) {
330        super.impl_sync(forceSync);
331        if (forceSync || (cachedNode == null)) {
332            cachedNode = getTargetNode();
333            final Shape path = getPath();
334            final Path2D path2D = new Path2D(path.impl_configShape());
335            final BaseTransform tx = path.impl_getLeafTransform();
336            apHelper = new AnimationPathHelper(path2D, tx, 1.0);
337            cachedIsNormalRequired = getOrientation() == OrientationType.ORTHOGONAL_TO_TANGENT;
338        }
339    }
340
341}