1 /* 2 * Copyright (c) 2010, 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.DoubleProperty; 29 import javafx.beans.property.ObjectProperty; 30 import javafx.beans.property.ObjectPropertyBase; 31 import javafx.beans.property.SimpleDoubleProperty; 32 import javafx.beans.property.SimpleObjectProperty; 33 import javafx.geometry.Point3D; 34 import javafx.scene.Node; 35 import javafx.util.Duration; 36 37 /** 38 * This {@code Transition} creates a rotation animation that spans its 39 * {@code duration}. This is done by updating the {@code rotate} variable of the 40 * {@code node} at regular interval. The angle value is specified in degrees. 41 * <p> 42 * It starts from the {@code fromAngle} if provided else uses the {@code node}'s 43 * {@code rotate} value. 44 * <p> 45 * It stops at the {@code toAngle} value if provided else it will use start 46 * value plus {@code byAngle}. 47 * <p> 48 * The {@code toAngle} takes precedence if both {@code toAngle} and 49 * {@code byAngle} are specified. 50 * 51 * <p> 52 * Code Segment Example: 53 * </p> 54 * 55 * <pre> 56 * <code> 57 * import javafx.scene.shape.*; 58 * import javafx.animation.*; 59 * 60 * ... 61 * 62 * Rectangle rect = new Rectangle (100, 40, 100, 100); 63 * rect.setArcHeight(50); 64 * rect.setArcWidth(50); 65 * rect.setFill(Color.VIOLET); 66 * 67 * RotateTransition rt = new RotateTransition(Duration.millis(3000), rect); 68 * rt.setByAngle(180); 69 * rt.setCycleCount(4); 70 * rt.setAutoReverse(true); 71 * 72 * rt.play(); 73 * 74 * ... 75 * 76 * </code> 77 * </pre> 78 * 79 * @see Transition 80 * @see Animation 81 * 82 * @since JavaFX 2.0 83 */ 84 public final class RotateTransition extends Transition { 85 86 private static final double EPSILON = 1e-12; 87 88 private double start; 89 private double delta; 90 91 /** 92 * The target node of this {@code RotateTransition}. 93 * <p> 94 * It is not possible to change the target {@code node} of a running 95 * {@code RotateTransition}. If the value of {@code node} is changed for a 96 * running {@code RotateTransition}, the animation has to be stopped and 97 * started again to pick up the new value. 98 */ 99 private ObjectProperty<Node> node; 100 private static final Node DEFAULT_NODE = null; 101 102 public final void setNode(Node value) { 103 if ((node != null) || (value != null /* DEFAULT_NODE */)) { 104 nodeProperty().set(value); 105 } 106 } 107 108 public final Node getNode() { 109 return (node == null)? DEFAULT_NODE : node.get(); 110 } 111 112 public final ObjectProperty<Node> nodeProperty() { 113 if (node == null) { 114 node = new SimpleObjectProperty<Node>(this, "node", DEFAULT_NODE); 115 } 116 return node; 117 } 118 119 private Node cachedNode; 120 121 /** 122 * The duration of this {@code RotateTransition}. 123 * <p> 124 * It is not possible to change the {@code duration} of a running 125 * {@code RotateTransition}. If the value of {@code duration} is changed for 126 * a running {@code RotateTransition}, the animation has to be stopped and 127 * started again to pick up the new value. 128 * <p> 129 * Note: While the unit of {@code duration} is a millisecond, the 130 * granularity depends on the underlying operating system and will in 131 * general be larger. For example animations on desktop systems usually run 132 * with a maximum of 60fps which gives a granularity of ~17 ms. 133 * 134 * Setting duration to value lower than {@link Duration#ZERO} will result 135 * in {@link IllegalArgumentException}. 136 * 137 * @defaultValue 400ms 138 */ 139 private ObjectProperty<Duration> duration; 140 private static final Duration DEFAULT_DURATION = Duration.millis(400); 141 142 public final void setDuration(Duration value) { 143 if ((duration != null) || (!DEFAULT_DURATION.equals(value))) { 144 durationProperty().set(value); 145 } 146 } 147 148 public final Duration getDuration() { 149 return (duration == null)? DEFAULT_DURATION : duration.get(); 150 } 151 152 public final ObjectProperty<Duration> durationProperty() { 153 if (duration == null) { 154 duration = new ObjectPropertyBase<Duration>(DEFAULT_DURATION) { 155 156 @Override 157 public void invalidated() { 158 try { 159 setCycleDuration(getDuration()); 160 } catch (IllegalArgumentException e) { 161 if (isBound()) { 162 unbind(); 163 } 164 set(getCycleDuration()); 165 throw e; 166 } 167 } 168 169 @Override 170 public Object getBean() { 171 return RotateTransition.this; 172 } 173 174 @Override 175 public String getName() { 176 return "duration"; 177 } 178 }; 179 } 180 return duration; 181 } 182 183 /** 184 * Specifies the axis of rotation for this {@code RotateTransition}. Use 185 * {@code node.rotationAxis} for axis of rotation if this {@code axis} is 186 * null. 187 * <p> 188 * It is not possible to change the {@code axis} of a running 189 * {@code RotateTransition}. If the value of {@code axis} is changed for a 190 * running {@code RotateTransition}, the animation has to be stopped and 191 * started again to pick up the new value. 192 * 193 * @defaultValue null 194 */ 195 private ObjectProperty<Point3D> axis; 196 private static final Point3D DEFAULT_AXIS = null; 197 198 public final void setAxis(Point3D value) { 199 if ((axis != null) || (value != null /* DEFAULT_AXIS */)) { 200 axisProperty().set(value); 201 } 202 } 203 204 public final Point3D getAxis() { 205 return (axis == null)? DEFAULT_AXIS : axis.get(); 206 } 207 208 public final ObjectProperty<Point3D> axisProperty() { 209 if (axis == null) { 210 axis = new SimpleObjectProperty<Point3D>(this, "axis", DEFAULT_AXIS); 211 } 212 return axis; 213 } 214 215 /** 216 * Specifies the start angle value for this {@code RotateTransition}. 217 * <p> 218 * It is not possible to change {@code fromAngle} of a running 219 * {@code RotateTransition}. If the value of {@code fromAngle} is changed 220 * for a running {@code RotateTransition}, the animation has to be stopped 221 * and started again to pick up the new value. 222 * 223 * @defaultValue {@code Double.NaN} 224 */ 225 private DoubleProperty fromAngle; 226 private static final double DEFAULT_FROM_ANGLE = Double.NaN; 227 228 public final void setFromAngle(double value) { 229 if ((fromAngle != null) || (!Double.isNaN(value))) { 230 fromAngleProperty().set(value); 231 } 232 } 233 234 public final double getFromAngle() { 235 return (fromAngle == null)? DEFAULT_FROM_ANGLE : fromAngle.get(); 236 } 237 238 public final DoubleProperty fromAngleProperty() { 239 if (fromAngle == null) { 240 fromAngle = new SimpleDoubleProperty(this, "fromAngle", DEFAULT_FROM_ANGLE); 241 } 242 return fromAngle; 243 } 244 245 /** 246 * Specifies the stop angle value for this {@code RotateTransition}. 247 * <p> 248 * It is not possible to change {@code toAngle} of a running 249 * {@code RotateTransition}. If the value of {@code toAngle} is changed for 250 * a running {@code RotateTransition}, the animation has to be stopped and 251 * started again to pick up the new value. 252 * 253 * @defaultValue {@code Double.NaN} 254 */ 255 private DoubleProperty toAngle; 256 private static final double DEFAULT_TO_ANGLE = Double.NaN; 257 258 public final void setToAngle(double value) { 259 if ((toAngle != null) || (!Double.isNaN(value))) { 260 toAngleProperty().set(value); 261 } 262 } 263 264 public final double getToAngle() { 265 return (toAngle == null)? DEFAULT_TO_ANGLE : toAngle.get(); 266 } 267 268 public final DoubleProperty toAngleProperty() { 269 if (toAngle == null) { 270 toAngle = new SimpleDoubleProperty(this, "toAngle", DEFAULT_TO_ANGLE); 271 } 272 return toAngle; 273 } 274 275 /** 276 * Specifies the incremented stop angle value, from the start, of this 277 * {@code RotateTransition}. 278 * <p> 279 * It is not possible to change {@code byAngle} of a running 280 * {@code RotateTransition}. If the value of {@code byAngle} is changed for 281 * a running {@code RotateTransition}, the animation has to be stopped and 282 * started again to pick up the new value. 283 */ 284 private DoubleProperty byAngle; 285 private static final double DEFAULT_BY_ANGLE = 0.0; 286 287 public final void setByAngle(double value) { 288 if ((byAngle != null) || (Math.abs(value - DEFAULT_BY_ANGLE) > EPSILON)) { 289 byAngleProperty().set(value); 290 } 291 } 292 293 public final double getByAngle() { 294 return (byAngle == null)? DEFAULT_BY_ANGLE : byAngle.get(); 295 } 296 297 public final DoubleProperty byAngleProperty() { 298 if (byAngle == null) { 299 byAngle = new SimpleDoubleProperty(this, "byAngle", DEFAULT_BY_ANGLE); 300 } 301 return byAngle; 302 } 303 304 /** 305 * The constructor of {@code RotateTransition} 306 * 307 * @param duration 308 * The duration of the {@code RotateTransition} 309 * @param node 310 * The {@code node} which will be rotated 311 */ 312 public RotateTransition(Duration duration, Node node) { 313 setDuration(duration); 314 setNode(node); 315 setCycleDuration(duration); 316 } 317 318 /** 319 * The constructor of {@code RotateTransition} 320 * 321 * @param duration 322 * The duration of the {@code RotateTransition} 323 */ 324 public RotateTransition(Duration duration) { 325 this(duration, null); 326 } 327 328 /** 329 * The constructor of {@code RotateTransition} 330 * 331 */ 332 public RotateTransition() { 333 this(DEFAULT_DURATION, null); 334 } 335 336 /** 337 * {@inheritDoc} 338 */ 339 @Override 340 protected void interpolate(double frac) { 341 cachedNode.setRotate(start + frac * delta); 342 } 343 344 private Node getTargetNode() { 345 final Node node = getNode(); 346 return (node != null) ? node : getParentTargetNode(); 347 } 348 349 @Override 350 boolean startable(boolean forceSync) { 351 return super.startable(forceSync) 352 && ((getTargetNode() != null) || (!forceSync && (cachedNode != null))); 353 } 354 355 @Override 356 void sync(boolean forceSync) { 357 super.sync(forceSync); 358 if (forceSync || (cachedNode == null)) { 359 cachedNode = getTargetNode(); 360 final double _fromAngle = getFromAngle(); 361 final double _toAngle = getToAngle(); 362 start = (!Double.isNaN(_fromAngle)) ? _fromAngle : cachedNode 363 .getRotate(); 364 delta = (!Double.isNaN(_toAngle)) ? _toAngle - start : getByAngle(); 365 final Point3D _axis = getAxis(); 366 if (_axis != null) { 367 node.get().setRotationAxis(_axis); 368 } 369 } 370 } 371 372 }