PageRenderTime 39ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/javafx-ui-common/src/javafx/animation/ParallelTransition.java

https://bitbucket.org/rbair/rbair-controls-8
Java | 593 lines | 408 code | 43 blank | 142 comment | 123 complexity | b0e2cb65037861212231a88c113e9ae6 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.1, GPL-2.0, LGPL-2.0
  1. /*
  2. * Copyright (c) 2010, 2013, 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. package javafx.animation;
  26. import com.sun.javafx.animation.TickCalculation;
  27. import static com.sun.javafx.animation.TickCalculation.*;
  28. import javafx.beans.InvalidationListener;
  29. import javafx.beans.Observable;
  30. import javafx.beans.property.ObjectProperty;
  31. import javafx.collections.ListChangeListener.Change;
  32. import javafx.collections.ObservableList;
  33. import javafx.event.ActionEvent;
  34. import javafx.event.EventHandler;
  35. import javafx.scene.Node;
  36. import javafx.util.Duration;
  37. import com.sun.javafx.collections.TrackableObservableList;
  38. import com.sun.javafx.collections.VetoableListDecorator;
  39. import com.sun.scenario.animation.AbstractMasterTimer;
  40. import java.util.HashSet;
  41. import java.util.List;
  42. import java.util.Set;
  43. import javafx.beans.value.ChangeListener;
  44. import javafx.beans.value.ObservableValue;
  45. /**
  46. * This {@link Transition} plays a list of {@link javafx.animation.Animation
  47. * Animations} in parallel.
  48. * <p>
  49. * Children of this {@code Transition} inherit {@link #nodeProperty() node}, if their
  50. * {@code node} property is not specified.
  51. *
  52. * <p>
  53. * Code Segment Example:
  54. * </p>
  55. *
  56. * <pre>
  57. * <code>
  58. * Rectangle rect = new Rectangle (100, 40, 100, 100);
  59. * rect.setArcHeight(50);
  60. * rect.setArcWidth(50);
  61. * rect.setFill(Color.VIOLET);
  62. *
  63. * final Duration SEC_2 = Duration.millis(2000);
  64. * final Duration SEC_3 = Duration.millis(3000);
  65. *
  66. * FadeTransition ft = new FadeTransition(SEC_3);
  67. * ft.setFromValue(1.0f);
  68. * ft.setToValue(0.3f);
  69. * ft.setCycleCount(2f);
  70. * ft.setAutoReverse(true);
  71. * TranslateTransition tt = new TranslateTransition(SEC_2);
  72. * tt.setFromX(-100f);
  73. * tt.setToX(100f);
  74. * tt.setCycleCount(2f);
  75. * tt.setAutoReverse(true);
  76. * RotateTransition rt = new RotateTransition(SEC_3);
  77. * rt.setByAngle(180f);
  78. * rt.setCycleCount(4f);
  79. * rt.setAutoReverse(true);
  80. * ScaleTransition st = new ScaleTransition(SEC_2);
  81. * st.setByX(1.5f);
  82. * st.setByY(1.5f);
  83. * st.setCycleCount(2f);
  84. * st.setAutoReverse(true);
  85. *
  86. * ParallelTransition pt = new ParallelTransition(rect, ft, tt, rt, st);
  87. * pt.play();
  88. * </code>
  89. * </pre>
  90. *
  91. * @see Transition
  92. * @see Animation
  93. *
  94. * @since JavaFX 2.0
  95. */
  96. public final class ParallelTransition extends Transition {
  97. private static final Animation[] EMPTY_ANIMATION_ARRAY = new Animation[0];
  98. private static final double EPSILON = 1e-12;
  99. private Animation[] cachedChildren = EMPTY_ANIMATION_ARRAY;
  100. private long[] durations;
  101. private long[] delays;
  102. private double[] rates;
  103. private long[] offsetTicks;
  104. private boolean[] forceChildSync;
  105. private long oldTicks;
  106. private long cycleTime;
  107. private boolean childrenChanged = true;
  108. private boolean toggledRate;
  109. private final InvalidationListener childrenListener = new InvalidationListener() {
  110. @Override
  111. public void invalidated(Observable observable) {
  112. childrenChanged = true;
  113. if (getStatus() == Status.STOPPED) {
  114. setCycleDuration(computeCycleDuration());
  115. }
  116. }
  117. };
  118. private final ChangeListener<Number> rateListener = new ChangeListener<Number>() {
  119. @Override
  120. public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
  121. if (oldValue.doubleValue() * newValue.doubleValue() < 0) {
  122. for (int i = 0; i < cachedChildren.length; ++i) {
  123. Animation child = cachedChildren[i];
  124. child.clipEnvelope.setRate(rates[i] * Math.signum(getCurrentRate()));
  125. }
  126. toggledRate = true;
  127. }
  128. }
  129. };
  130. /**
  131. * This {@link javafx.scene.Node} is used in all child {@link Transition
  132. * Transitions}, that do not define a target {@code Node} themselves. This
  133. * can be used if a number of {@code Transitions} should be applied to a
  134. * single {@code Node}.
  135. * <p>
  136. * It is not possible to change the target {@code node} of a running
  137. * {@code Transition}. If the value of {@code node} is changed for a running
  138. * {@code Transition}, the animation has to be stopped and started again to
  139. * pick up the new value.
  140. */
  141. private ObjectProperty<Node> node;
  142. private static final Node DEFAULT_NODE = null;
  143. public final void setNode(Node value) {
  144. if ((node != null) || (value != null /* DEFAULT_NODE */)) {
  145. nodeProperty().set(value);
  146. }
  147. }
  148. public final Node getNode() {
  149. return (node == null)? DEFAULT_NODE : node.get();
  150. }
  151. public final ObjectProperty<Node> nodeProperty() {
  152. if (node == null) {
  153. node = new javafx.beans.property.SimpleObjectProperty<Node>(this, "node", DEFAULT_NODE);
  154. }
  155. return node;
  156. }
  157. private final Set<Animation> childrenSet = new HashSet<Animation>();
  158. private final ObservableList<Animation> children = new VetoableListDecorator<Animation>(new TrackableObservableList<Animation>() {
  159. @Override
  160. protected void onChanged(Change<Animation> c) {
  161. while (c.next()) {
  162. for (final Animation animation : c.getRemoved()) {
  163. animation.parent = null;
  164. animation.rateProperty().removeListener(childrenListener);
  165. animation.totalDurationProperty().removeListener(childrenListener);
  166. animation.delayProperty().removeListener(childrenListener);
  167. }
  168. for (final Animation animation : c.getAddedSubList()) {
  169. animation.parent = ParallelTransition.this;
  170. animation.rateProperty().addListener(childrenListener);
  171. animation.totalDurationProperty().addListener(childrenListener);
  172. animation.delayProperty().addListener(childrenListener);
  173. }
  174. }
  175. childrenListener.invalidated(children);
  176. }
  177. }) {
  178. @Override
  179. protected void onProposedChange(List<Animation> toBeAdded, int... indexes) {
  180. IllegalArgumentException exception = null;
  181. for (int i = 0; i < indexes.length; i+=2) {
  182. for (int idx = indexes[i]; idx < indexes[i+1]; ++idx) {
  183. childrenSet.remove(children.get(idx));
  184. }
  185. }
  186. for (Animation child : toBeAdded) {
  187. if (child == null) {
  188. exception = new IllegalArgumentException("Child cannot be null");
  189. break;
  190. }
  191. if (!childrenSet.add(child)) {
  192. exception = new IllegalArgumentException("Attempting to add a duplicate to the list of children");
  193. break;
  194. }
  195. if (checkCycle(child, ParallelTransition.this)) {
  196. exception = new IllegalArgumentException("This change would create cycle");
  197. break;
  198. }
  199. }
  200. if (exception != null) {
  201. childrenSet.clear();
  202. childrenSet.addAll(children);
  203. throw exception;
  204. }
  205. }
  206. };
  207. private static boolean checkCycle(Animation child, Animation parent) {
  208. Animation a = parent;
  209. while (a != child) {
  210. if (a.parent != null) {
  211. a = a.parent;
  212. } else {
  213. return false;
  214. }
  215. }
  216. return true;
  217. }
  218. /**
  219. * A list of {@link javafx.animation.Animation Animations} that will be
  220. * played sequentially.
  221. * <p>
  222. * It is not possible to change the children of a running
  223. * {@code ParallelTransition}. If the children are changed for a running
  224. * {@code ParallelTransition}, the animation has to be stopped and started
  225. * again to pick up the new value.
  226. *
  227. * @return the list of {@link javafx.animation.Animation Animations}
  228. */
  229. public final ObservableList<Animation> getChildren() {
  230. return children;
  231. }
  232. /**
  233. * The constructor of {@code ParallelTransition}.
  234. *
  235. * @param node
  236. * The target {@link javafx.scene.Node} to be used in child
  237. * {@link Transition Transitions} that have no {@code Node} specified
  238. * themselves
  239. * @param children
  240. * The child {@link javafx.animation.Animation Animations} of
  241. * this {@code ParallelTransition}
  242. */
  243. public ParallelTransition(Node node, Animation... children) {
  244. setInterpolator(Interpolator.LINEAR);
  245. setNode(node);
  246. getChildren().setAll(children);
  247. }
  248. /**
  249. * The constructor of {@code ParallelTransition}.
  250. *
  251. * @param children
  252. * The child {@link javafx.animation.Animation Animations} of
  253. * this {@code ParallelTransition}
  254. */
  255. public ParallelTransition(Animation... children) {
  256. this(null, children);
  257. }
  258. /**
  259. * The constructor of {@code ParallelTransition}.
  260. *
  261. * @param node
  262. * The target {@link javafx.scene.Node} to be used in child
  263. * {@link Transition Transitions} that have no {@code Node} specified
  264. * themselves
  265. */
  266. public ParallelTransition(Node node) {
  267. setInterpolator(Interpolator.LINEAR);
  268. setNode(node);
  269. }
  270. /**
  271. * The constructor of {@code ParallelTransition}.
  272. */
  273. public ParallelTransition() {
  274. this((Node) null);
  275. }
  276. // For testing purposes
  277. ParallelTransition(AbstractMasterTimer timer) {
  278. super(timer);
  279. setInterpolator(Interpolator.LINEAR);
  280. }
  281. /**
  282. * {@inheritDoc}
  283. */
  284. @Override
  285. protected Node getParentTargetNode() {
  286. final Node node = getNode();
  287. return (node != null) ? node : (parent != null && parent instanceof Transition) ?
  288. ((Transition)parent).getParentTargetNode() : null;
  289. }
  290. private Duration computeCycleDuration() {
  291. Duration maxTime = Duration.ZERO;
  292. for (final Animation animation : getChildren()) {
  293. final double absRate = Math.abs(animation.getRate());
  294. final Duration totalDuration = (absRate < EPSILON) ?
  295. animation.getTotalDuration() : animation.getTotalDuration().divide(absRate);
  296. final Duration childDuration = totalDuration.add(animation.getDelay());
  297. if (childDuration.isIndefinite()) {
  298. return Duration.INDEFINITE;
  299. } else {
  300. if (childDuration.greaterThan(maxTime)) {
  301. maxTime = childDuration;
  302. }
  303. }
  304. }
  305. return maxTime;
  306. }
  307. private double calculateFraction(long currentTicks, long cycleTicks) {
  308. final double frac = (double) currentTicks / cycleTicks;
  309. return (frac <= 0.0) ? 0 : (frac >= 1.0) ? 1.0 : frac;
  310. }
  311. private boolean startChild(Animation child, int index) {
  312. final boolean forceSync = forceChildSync[index];
  313. if (child.impl_startable(forceSync)) {
  314. child.clipEnvelope.setRate(rates[index] * Math.signum(getCurrentRate()));
  315. child.impl_start(forceSync);
  316. forceChildSync[index] = false;
  317. return true;
  318. }
  319. return false;
  320. }
  321. @Override
  322. void impl_sync(boolean forceSync) {
  323. super.impl_sync(forceSync);
  324. if ((forceSync && childrenChanged) || (durations == null)) {
  325. cachedChildren = getChildren().toArray(EMPTY_ANIMATION_ARRAY);
  326. final int n = cachedChildren.length;
  327. durations = new long[n];
  328. delays = new long[n];
  329. rates = new double[n];
  330. offsetTicks = new long[n];
  331. forceChildSync = new boolean[n];
  332. cycleTime = 0;
  333. int i = 0;
  334. for (final Animation animation : cachedChildren) {
  335. rates[i] = Math.abs(animation.getRate());
  336. if (rates[i] < EPSILON) {
  337. rates[i] = 1;
  338. }
  339. durations[i] = fromDuration(animation.getTotalDuration(), rates[i]);
  340. delays[i] = fromDuration(animation.getDelay());
  341. cycleTime = Math.max(cycleTime, add(durations[i], delays[i]));
  342. forceChildSync[i] = true;
  343. i++;
  344. }
  345. childrenChanged = false;
  346. } else if (forceSync) {
  347. final int n = forceChildSync.length;
  348. for (int i=0; i<n; i++) {
  349. forceChildSync[i] = true;
  350. }
  351. }
  352. }
  353. @Override
  354. void impl_pause() {
  355. super.impl_pause();
  356. for (final Animation animation : cachedChildren) {
  357. if (animation.getStatus() == Status.RUNNING) {
  358. animation.impl_pause();
  359. }
  360. }
  361. }
  362. @Override
  363. void impl_resume() {
  364. super.impl_resume();
  365. int i = 0;
  366. for (final Animation animation : cachedChildren) {
  367. if (animation.getStatus() == Status.PAUSED) {
  368. animation.impl_resume();
  369. animation.clipEnvelope.setRate(rates[i] * Math.signum(getCurrentRate()));
  370. }
  371. i++;
  372. }
  373. }
  374. @Override
  375. void impl_start(boolean forceSync) {
  376. super.impl_start(forceSync);
  377. toggledRate = false;
  378. rateProperty().addListener(rateListener);
  379. double curRate = getCurrentRate();
  380. final long currentTicks = TickCalculation.fromDuration(getCurrentTime());
  381. if (curRate < 0) {
  382. jumpToEnd();
  383. if (currentTicks < cycleTime) {
  384. impl_jumpTo(currentTicks, cycleTime, false);
  385. }
  386. } else {
  387. jumpToStart();
  388. if (currentTicks > 0) {
  389. impl_jumpTo(currentTicks, cycleTime, false);
  390. }
  391. }
  392. }
  393. @Override
  394. void impl_stop() {
  395. super.impl_stop();
  396. for (final Animation animation : cachedChildren) {
  397. if (animation.getStatus() != Status.STOPPED) {
  398. animation.impl_stop();
  399. }
  400. }
  401. if (childrenChanged) {
  402. setCycleDuration(computeCycleDuration());
  403. }
  404. rateProperty().removeListener(rateListener);
  405. }
  406. /**
  407. * @treatAsPrivate implementation detail
  408. * @deprecated This is an internal API that is not intended for use and will be removed in the next version
  409. */
  410. @Deprecated
  411. @Override public void impl_playTo(long currentTicks, long cycleTicks) {
  412. impl_setCurrentTicks(currentTicks);
  413. final double frac = calculateFraction(currentTicks, cycleTicks);
  414. final long newTicks = Math.max(0, Math.min(getCachedInterpolator().interpolate(0, cycleTicks, frac), cycleTicks));
  415. if (toggledRate) {
  416. for (int i = 0; i < cachedChildren.length; ++i) {
  417. if (cachedChildren[i].getStatus() == Status.RUNNING) {
  418. offsetTicks[i] -= Math.signum(getCurrentRate()) * (durations[i] - 2 * (oldTicks - delays[i]));
  419. }
  420. }
  421. toggledRate = false;
  422. }
  423. if (getCurrentRate() > 0) {
  424. int i = 0;
  425. for (final Animation animation : cachedChildren) {
  426. if ((newTicks >= delays[i]) && ((oldTicks <= delays[i]) ||
  427. ((newTicks < add(delays[i], durations[i])) && (animation.getStatus() == Status.STOPPED)))) {
  428. final boolean enteringCycle = oldTicks <= delays[i];
  429. if (startChild(animation, i)) {
  430. animation.clipEnvelope.jumpTo(0);
  431. } else {
  432. if (enteringCycle) {
  433. final EventHandler<ActionEvent> handler = animation.getOnFinished();
  434. if (handler != null) {
  435. handler.handle(new ActionEvent(this, null));
  436. }
  437. }
  438. continue;
  439. }
  440. }
  441. if (newTicks >= add(durations[i], delays[i])) {
  442. if (animation.getStatus() == Status.RUNNING) {
  443. animation.impl_timePulse(sub(durations[i], offsetTicks[i]));
  444. offsetTicks[i] = 0;
  445. }
  446. } else if (newTicks > delays[i]) {
  447. animation.impl_timePulse(sub(newTicks - delays[i], offsetTicks[i]));
  448. }
  449. i++;
  450. }
  451. } else {
  452. int i = 0;
  453. for (final Animation animation : cachedChildren) {
  454. if (newTicks < add(durations[i], delays[i])) {
  455. if ((oldTicks >= add(durations[i], delays[i])) || ((newTicks >= delays[i]) && (animation.getStatus() == Status.STOPPED))){
  456. final boolean enteringCycle = oldTicks >= add(durations[i], delays[i]);
  457. if (startChild(animation, i)) {
  458. animation.clipEnvelope.jumpTo(Math.round(durations[i] * rates[i]));
  459. } else {
  460. if (enteringCycle) {
  461. final EventHandler<ActionEvent> handler = animation.getOnFinished();
  462. if (handler != null) {
  463. handler.handle(new ActionEvent(this, null));
  464. }
  465. }
  466. continue;
  467. }
  468. }
  469. if (newTicks <= delays[i]) {
  470. if (animation.getStatus() == Status.RUNNING) {
  471. animation.impl_timePulse(sub(durations[i], offsetTicks[i]));
  472. offsetTicks[i] = 0;
  473. }
  474. } else {
  475. animation.impl_timePulse(sub( add(durations[i], delays[i]) - newTicks, offsetTicks[i]));
  476. }
  477. }
  478. i++;
  479. }
  480. }
  481. oldTicks = newTicks;
  482. }
  483. /**
  484. * @treatAsPrivate implementation detail
  485. * @deprecated This is an internal API that is not intended for use and will be removed in the next version
  486. */
  487. @Deprecated
  488. @Override public void impl_jumpTo(long currentTicks, long cycleTicks, boolean forceJump) {
  489. impl_setCurrentTicks(currentTicks);
  490. if (getStatus() == Status.STOPPED && !forceJump) {
  491. return;
  492. }
  493. impl_sync(false);
  494. final double frac = calculateFraction(currentTicks, cycleTicks);
  495. final long newTicks = Math.max(0, Math.min(getCachedInterpolator().interpolate(0, cycleTicks, frac), cycleTicks));
  496. int i = 0;
  497. for (final Animation animation : cachedChildren) {
  498. final Status status = animation.getStatus();
  499. if (newTicks <= delays[i]) {
  500. offsetTicks[i] = 0;
  501. if (status != Status.STOPPED) {
  502. animation.clipEnvelope.jumpTo(0);
  503. animation.impl_stop();
  504. } else if(TickCalculation.fromDuration(animation.getCurrentTime()) != 0) {
  505. animation.impl_jumpTo(0, durations[i], true);
  506. }
  507. } else if (newTicks >= add(durations[i], delays[i])) {
  508. offsetTicks[i] = 0;
  509. if (status != Status.STOPPED) {
  510. animation.clipEnvelope.jumpTo(Math.round(durations[i] * rates[i]));
  511. animation.impl_stop();
  512. } else if (TickCalculation.fromDuration(animation.getCurrentTime()) != durations[i]) {
  513. animation.impl_jumpTo(durations[i], durations[i], true);
  514. }
  515. } else {
  516. if (status == Status.STOPPED) {
  517. startChild(animation, i);
  518. if (getStatus() == Status.PAUSED) {
  519. animation.impl_pause();
  520. }
  521. offsetTicks[i] = (getCurrentRate() > 0)? newTicks - delays[i] : add(durations[i], delays[i]) - newTicks;
  522. } else if (status == Status.PAUSED) {
  523. offsetTicks[i] += (newTicks - oldTicks) * Math.signum(this.clipEnvelope.getCurrentRate());
  524. } else {
  525. offsetTicks[i] += (getCurrentRate() > 0) ? newTicks - oldTicks : oldTicks - newTicks;
  526. }
  527. animation.clipEnvelope.jumpTo(Math.round(sub(newTicks, delays[i]) * rates[i]));
  528. }
  529. i++;
  530. }
  531. oldTicks = newTicks;
  532. }
  533. /**
  534. * {@inheritDoc}
  535. */
  536. @Override
  537. protected void interpolate(double frac) {
  538. // no-op
  539. }
  540. private void jumpToEnd() {
  541. for (int i = 0 ; i < cachedChildren.length; ++i) {
  542. cachedChildren[i].impl_jumpTo(durations[i], durations[i], true);
  543. }
  544. }
  545. private void jumpToStart() {
  546. for (int i = cachedChildren.length - 1 ; i >= 0; --i) {
  547. cachedChildren[i].impl_jumpTo(0, durations[i], true);
  548. }
  549. }
  550. }