< prev index next >

modules/graphics/src/main/java/javafx/animation/SequentialTransition.java

Print this page


   1 /*
   2  * Copyright (c) 2011, 2014, 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


 337         final double frac = (double) currentTicks / cycleTicks;
 338         return (frac <= 0.0) ? 0 : (frac >= 1.0) ? 1.0 : frac;
 339     }
 340 
 341     private int findNewIndex(long ticks) {
 342         if ((curIndex != BEFORE)
 343                 && (curIndex != end)
 344                 && (startTimes[curIndex] <= ticks)
 345                 && (ticks <= startTimes[curIndex + 1])) {
 346             return curIndex;
 347         }
 348 
 349         final boolean indexUndefined = (curIndex == BEFORE) || (curIndex == end);
 350         final int fromIndex = (indexUndefined || (ticks < oldTicks)) ? 0 : curIndex + 1;
 351         final int toIndex = (indexUndefined || (oldTicks < ticks)) ? end : curIndex;
 352         final int index = Arrays.binarySearch(startTimes, fromIndex, toIndex, ticks);
 353         return (index < 0) ? -index - 2 : (index > 0) ? index - 1 : 0;
 354     }
 355 
 356     @Override
 357     void impl_sync(boolean forceSync) {
 358         super.impl_sync(forceSync);
 359 
 360         if ((forceSync && childrenChanged) || (startTimes == null)) {
 361             cachedChildren = getChildren().toArray(EMPTY_ANIMATION_ARRAY);
 362             end = cachedChildren.length;
 363             startTimes = new long[end + 1];
 364             durations = new long[end];
 365             delays = new long[end];
 366             rates = new double[end];
 367             forceChildSync = new boolean[end];
 368             long cycleTicks = 0L;
 369             int i = 0;
 370             for (final Animation animation : cachedChildren) {
 371                 startTimes[i] = cycleTicks;
 372                 rates[i] = Math.abs(animation.getRate());
 373                 if (rates[i] < EPSILON) {
 374                     rates[i] = 1;
 375                 }
 376                 durations[i] = fromDuration(animation.getTotalDuration(), rates[i]);
 377                 delays[i] = fromDuration(animation.getDelay());
 378                 if ((durations[i] == Long.MAX_VALUE) || (delays[i] == Long.MAX_VALUE) || (cycleTicks == Long.MAX_VALUE)) {
 379                     cycleTicks = Long.MAX_VALUE;
 380                 } else {
 381                     cycleTicks = add(cycleTicks, add(durations[i], delays[i]));
 382                 }
 383                 forceChildSync[i] = true;
 384                 i++;
 385             }
 386             startTimes[end] = cycleTicks;
 387             childrenChanged = false;
 388         } else if (forceSync) {
 389             final int n = forceChildSync.length;
 390             for (int i=0; i<n; i++) {
 391                 forceChildSync[i] = true;
 392             }
 393         }
 394     }
 395 
 396     @Override
 397     void impl_start(boolean forceSync) {
 398         super.impl_start(forceSync);
 399         toggledRate = false;
 400         rateProperty().addListener(rateListener);
 401         offsetTicks = 0L;
 402         double curRate = getCurrentRate();
 403         final long currentTicks = TickCalculation.fromDuration(getCurrentTime());
 404         if (curRate < 0) {
 405             jumpToEnd();
 406             curIndex = end;
 407             if (currentTicks < startTimes[end]) {
 408                 impl_jumpTo(currentTicks, startTimes[end], false);
 409             }
 410         } else {
 411             jumpToBefore();
 412             curIndex = BEFORE;
 413             if (currentTicks > 0) {
 414                 impl_jumpTo(currentTicks, startTimes[end], false);
 415             }
 416         }
 417     }
 418 
 419     @Override
 420     void impl_pause() {
 421         super.impl_pause();
 422         if ((curIndex != BEFORE) && (curIndex != end)) {
 423             final Animation current = cachedChildren[curIndex];
 424             if (current.getStatus() == Status.RUNNING) {
 425                 current.impl_pause();
 426             }
 427         }
 428     }
 429 
 430     @Override
 431     void impl_resume() {
 432         super.impl_resume();
 433         if ((curIndex != BEFORE) && (curIndex != end)) {
 434             final Animation current = cachedChildren[curIndex];
 435             if (current.getStatus() == Status.PAUSED) {
 436                 current.impl_resume();
 437                 current.clipEnvelope.setRate(rates[curIndex] * Math.signum(getCurrentRate()));
 438             }
 439         }
 440     }
 441 
 442     @Override
 443     void impl_stop() {
 444         super.impl_stop();
 445         if ((curIndex != BEFORE) && (curIndex != end)) {
 446             final Animation current = cachedChildren[curIndex];
 447             if (current.getStatus() != Status.STOPPED) {
 448                 current.impl_stop();
 449             }
 450         }
 451         if (childrenChanged) {
 452             setCycleDuration(computeCycleDuration());
 453         }
 454         rateProperty().removeListener(rateListener);
 455     }
 456 
 457     private boolean startChild(Animation child, int index) {
 458         final boolean forceSync = forceChildSync[index];
 459         if (child.impl_startable(forceSync)) {
 460             child.clipEnvelope.setRate(rates[index] * Math.signum(getCurrentRate()));
 461             child.impl_start(forceSync);
 462             forceChildSync[index] = false;
 463             return true;
 464         }
 465         return false;
 466     }
 467 
 468     @Override void impl_playTo(long currentTicks, long cycleTicks) {
 469         impl_setCurrentTicks(currentTicks);
 470         final double frac = calculateFraction(currentTicks, cycleTicks);
 471         final long newTicks = Math.max(0, Math.min(getCachedInterpolator().interpolate(0, cycleTicks, frac), cycleTicks));
 472         final int newIndex = findNewIndex(newTicks);
 473         final Animation current = ((curIndex == BEFORE) || (curIndex == end)) ? null : cachedChildren[curIndex];
 474         if (toggledRate) {
 475             if (current != null && current.getStatus() == Status.RUNNING) {
 476                 offsetTicks -= Math.signum(getCurrentRate()) * (durations[curIndex] - 2 * (oldTicks - delays[curIndex] - startTimes[curIndex]));
 477             }
 478             toggledRate = false;
 479         }
 480         if (curIndex == newIndex) {
 481             if (getCurrentRate() > 0) {
 482                 final long currentDelay = add(startTimes[curIndex], delays[curIndex]);
 483                 if (newTicks >= currentDelay) {
 484                     if ((oldTicks <= currentDelay) || (current.getStatus() == Status.STOPPED)) {
 485                         final boolean enteringCycle = oldTicks <= currentDelay;
 486                         if (enteringCycle) {
 487                             current.clipEnvelope.jumpTo(0);
 488                         }
 489                         if (!startChild(current, curIndex)) {
 490                             if (enteringCycle) {
 491                                 final EventHandler<ActionEvent> handler = current.getOnFinished();
 492                                 if (handler != null) {
 493                                     handler.handle(new ActionEvent(this, null));
 494                                 }
 495                             }
 496                             oldTicks = newTicks;
 497                             return;
 498                         }
 499                     }
 500                     if (newTicks >= startTimes[curIndex+1]) {
 501                         current.impl_timePulse(sub(durations[curIndex], offsetTicks));
 502                         if (newTicks == cycleTicks) {
 503                             curIndex = end;
 504                         }
 505                     } else {
 506                         final long localTicks = sub(newTicks - currentDelay, offsetTicks);
 507                         current.impl_timePulse(localTicks);
 508                     }
 509                 }
 510             } else { // getCurrentRate() < 0
 511                 final long currentDelay = add(startTimes[curIndex], delays[curIndex]);
 512                 if ((oldTicks >= startTimes[curIndex+1]) || ((oldTicks >= currentDelay) && (current.getStatus() == Status.STOPPED))){
 513                     final boolean enteringCycle = oldTicks >= startTimes[curIndex+1];
 514                     if (enteringCycle) {
 515                         current.clipEnvelope.jumpTo(Math.round(durations[curIndex] * rates[curIndex]));
 516                     }
 517                     if (!startChild(current, curIndex)) {
 518                         if (enteringCycle) {
 519                             final EventHandler<ActionEvent> handler = current.getOnFinished();
 520                             if (handler != null) {
 521                                 handler.handle(new ActionEvent(this, null));
 522                             }
 523                         }
 524                         oldTicks = newTicks;
 525                         return;
 526                     }
 527                 }
 528                 if (newTicks <= currentDelay) {
 529                     current.impl_timePulse(sub(durations[curIndex], offsetTicks));
 530                     if (newTicks == 0) {
 531                         curIndex = BEFORE;
 532                     }
 533                 } else {
 534                     final long localTicks = sub(startTimes[curIndex + 1] - newTicks, offsetTicks);
 535                     current.impl_timePulse(localTicks);
 536                 }
 537             }
 538         } else { // curIndex != newIndex
 539             if (curIndex < newIndex) {
 540                 if (current != null) {
 541                     final long oldDelay = add(startTimes[curIndex], delays[curIndex]);
 542                     if ((oldTicks <= oldDelay) || ((current.getStatus() == Status.STOPPED) && (oldTicks != startTimes[curIndex + 1]))) {
 543                         final boolean enteringCycle = oldTicks <= oldDelay;
 544                         if (enteringCycle) {
 545                             current.clipEnvelope.jumpTo(0);
 546                         }
 547                         if (!startChild(current, curIndex)) {
 548                             if (enteringCycle) {
 549                                 final EventHandler<ActionEvent> handler = current.getOnFinished();
 550                                 if (handler != null) {
 551                                     handler.handle(new ActionEvent(this, null));
 552                                 }
 553                             }
 554                         }
 555                     }
 556                     if (current.getStatus() == Status.RUNNING) {
 557                         current.impl_timePulse(sub(durations[curIndex], offsetTicks));
 558                     }
 559                     oldTicks = startTimes[curIndex + 1];
 560                 }
 561                 offsetTicks = 0;
 562                 curIndex++;
 563                 for (; curIndex < newIndex; curIndex++) {
 564                     final Animation animation = cachedChildren[curIndex];
 565                     animation.clipEnvelope.jumpTo(0);
 566                     if (startChild(animation, curIndex)) {
 567                         animation.impl_timePulse(durations[curIndex]); // No need to subtract offsetTicks ( == 0)
 568                     } else {
 569                         final EventHandler<ActionEvent> handler = animation.getOnFinished();
 570                         if (handler != null) {
 571                             handler.handle(new ActionEvent(this, null));
 572                         }
 573                     }
 574                     oldTicks = startTimes[curIndex + 1];
 575                 }
 576                 final Animation newAnimation = cachedChildren[curIndex];
 577                 newAnimation.clipEnvelope.jumpTo(0);
 578                 if (startChild(newAnimation, curIndex)) {
 579                     if (newTicks >= startTimes[curIndex+1]) {
 580                         newAnimation.impl_timePulse(durations[curIndex]); // No need to subtract offsetTicks ( == 0)
 581                         if (newTicks == cycleTicks) {
 582                             curIndex = end;
 583                         }
 584                     } else {
 585                         final long localTicks = sub(newTicks, add(startTimes[curIndex], delays[curIndex]));
 586                         newAnimation.impl_timePulse(localTicks);
 587                     }
 588                 } else {
 589                     final EventHandler<ActionEvent> handler = newAnimation.getOnFinished();
 590                     if (handler != null) {
 591                         handler.handle(new ActionEvent(this, null));
 592                     }
 593                 }
 594             } else {
 595                 if (current != null) {
 596                     final long oldDelay = add(startTimes[curIndex], delays[curIndex]);
 597                     if ((oldTicks >= startTimes[curIndex+1]) || ((oldTicks > oldDelay) && (current.getStatus() == Status.STOPPED))){
 598                         final boolean enteringCycle = oldTicks >= startTimes[curIndex+1];
 599                         if (enteringCycle) {
 600                             current.clipEnvelope.jumpTo(Math.round(durations[curIndex] * rates[curIndex]));
 601                         }
 602                         if (!startChild(current, curIndex)) {
 603                             if (enteringCycle) {
 604                                 final EventHandler<ActionEvent> handler = current.getOnFinished();
 605                                 if (handler != null) {
 606                                     handler.handle(new ActionEvent(this, null));
 607                                 }
 608                             }
 609                         }
 610                     }
 611                     if (current.getStatus() == Status.RUNNING) {
 612                         current.impl_timePulse(sub(durations[curIndex], offsetTicks));
 613                     }
 614                     oldTicks = startTimes[curIndex];
 615                 }
 616                 offsetTicks = 0;
 617                 curIndex--;
 618                 for (; curIndex > newIndex; curIndex--) {
 619                     final Animation animation = cachedChildren[curIndex];
 620                     animation.clipEnvelope.jumpTo(Math.round(durations[curIndex] * rates[curIndex]));
 621                     if (startChild(animation, curIndex)) {
 622                         animation.impl_timePulse(durations[curIndex]); // No need to subtract offsetTicks ( == 0)
 623                     } else {
 624                         final EventHandler<ActionEvent> handler = animation.getOnFinished();
 625                         if (handler != null) {
 626                             handler.handle(new ActionEvent(this, null));
 627                         }
 628                     }
 629                     oldTicks = startTimes[curIndex];
 630                 }
 631                 final Animation newAnimation = cachedChildren[curIndex];
 632                 newAnimation.clipEnvelope.jumpTo(Math.round(durations[curIndex] * rates[curIndex]));
 633                 if (startChild(newAnimation, curIndex)) {
 634                     if (newTicks <= add(startTimes[curIndex], delays[curIndex])) {
 635                         newAnimation.impl_timePulse(durations[curIndex]); // No need to subtract offsetTicks ( == 0)
 636                         if (newTicks == 0) {
 637                             curIndex = BEFORE;
 638                         }
 639                     } else {
 640                         final long localTicks = sub(startTimes[curIndex + 1], newTicks);
 641                         newAnimation.impl_timePulse(localTicks);
 642                     }
 643                 } else {
 644                     final EventHandler<ActionEvent> handler = newAnimation.getOnFinished();
 645                     if (handler != null) {
 646                         handler.handle(new ActionEvent(this, null));
 647                     }
 648                 }
 649             }
 650         }
 651         oldTicks = newTicks;
 652     }
 653 
 654     @Override void impl_jumpTo(long currentTicks, long cycleTicks, boolean forceJump) {
 655         impl_setCurrentTicks(currentTicks);
 656         final Status status = getStatus();
 657 
 658         if (status == Status.STOPPED && !forceJump) {
 659             return;
 660         }
 661 
 662         impl_sync(false);
 663         final double frac = calculateFraction(currentTicks, cycleTicks);
 664         final long newTicks = Math.max(0, Math.min(getCachedInterpolator().interpolate(0, cycleTicks, frac), cycleTicks));
 665         final int oldIndex = curIndex;
 666         curIndex = findNewIndex(newTicks);
 667         final Animation newAnimation = cachedChildren[curIndex];
 668         final double currentRate = getCurrentRate();
 669         final long currentDelay = add(startTimes[curIndex], delays[curIndex]);
 670         if (curIndex != oldIndex) {
 671             if (status != Status.STOPPED) {
 672                 if ((oldIndex != BEFORE) && (oldIndex != end)) {
 673                     final Animation oldChild = cachedChildren[oldIndex];
 674                     if (oldChild.getStatus() != Status.STOPPED) {
 675                         cachedChildren[oldIndex].impl_stop();
 676                     }
 677                 }
 678                 if (curIndex < oldIndex) {
 679                     for (int i = oldIndex == end ? end - 1 : oldIndex; i > curIndex; --i) {
 680                         cachedChildren[i].impl_jumpTo(0, durations[i], true);
 681                     }
 682                 } else { //curIndex > oldIndex as curIndex != oldIndex
 683                     for (int i = oldIndex == BEFORE? 0 : oldIndex; i < curIndex; ++i) {
 684                         cachedChildren[i].impl_jumpTo(durations[i], durations[i], true);
 685                     }
 686                 }
 687                 if (newTicks >= currentDelay) {
 688                     startChild(newAnimation, curIndex);
 689                     if (status == Status.PAUSED) {
 690                         newAnimation.impl_pause();
 691                     }
 692                 }
 693             }
 694         }
 695         if (oldIndex == curIndex) {
 696             if (currentRate == 0) {
 697                 offsetTicks += (newTicks - oldTicks) * Math.signum(this.clipEnvelope.getCurrentRate());
 698             } else {
 699                 offsetTicks += currentRate > 0 ? newTicks - oldTicks : oldTicks - newTicks;
 700             }
 701         } else {
 702             if (currentRate == 0) {
 703                 if (this.clipEnvelope.getCurrentRate() > 0) {
 704                     offsetTicks = Math.max(0, newTicks - currentDelay);
 705                 } else {
 706                     offsetTicks = startTimes[curIndex] + durations[curIndex] - newTicks;
 707                 }
 708             } else {
 709                 offsetTicks = currentRate > 0 ? Math.max(0, newTicks - currentDelay) : startTimes[curIndex + 1] - newTicks;
 710             }
 711         }
 712         newAnimation.clipEnvelope.jumpTo(Math.round(sub(newTicks, currentDelay) * rates[curIndex]));
 713         oldTicks = newTicks;
 714     }
 715 
 716     private void jumpToEnd() {
 717         for (int i = 0 ; i < end; ++i) {
 718             if (forceChildSync[i]) {
 719                 cachedChildren[i].impl_sync(true);
 720                 //NOTE: do not clean up forceChildSync[i] here. Another sync will be needed during the play
 721                 // The reason is we have 2 different use-cases for jumping (1)play from start, (2)play next cycle.
 722                 // and 2 different types of sub-transitions (A)"by" transitions that need to synchronize on
 723                 // the current state and move property by certain value and (B)"from-to" transitions that
 724                 // move from one point to another on each play/cycle. We can't query if transition is A or B.
 725                 //
 726                 // Now for combination 1A we need to synchronize here, as the subsequent jump would move
 727                 // the property to the previous value. 1B doesn't need to sync here, but it's not unsafe to
 728                 // do it. As forceChildSync is set only in case (1) and not in case (2), the cycles are always equal.
 729                 //
 730                 // Now the reason why we cannot clean forceChildSync[i] here is that while we need to sync here,
 731                 // there might be children of (A)-"by" type that operate on the same property, but fail to synchronize
 732                 // them when they start would mean they all would have the same value at the beginning.
 733             }
 734             cachedChildren[i].impl_jumpTo(durations[i], durations[i], true);
 735 
 736         }
 737     }
 738 
 739     private void jumpToBefore() {
 740         for (int i = end - 1 ; i >= 0; --i) {
 741             if (forceChildSync[i]) {
 742                 cachedChildren[i].impl_sync(true);
 743                 //NOTE: do not clean up forceChildSync[i] here. Another sync will be needed during the play
 744                 // See explanation in jumpToEnd
 745             }
 746             cachedChildren[i].impl_jumpTo(0, durations[i], true);
 747         }
 748     }
 749 
 750     /**
 751      * {@inheritDoc}
 752      */
 753     @Override
 754     protected void interpolate(double frac) {
 755         // no-op
 756     }
 757 
 758 }
   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


 337         final double frac = (double) currentTicks / cycleTicks;
 338         return (frac <= 0.0) ? 0 : (frac >= 1.0) ? 1.0 : frac;
 339     }
 340 
 341     private int findNewIndex(long ticks) {
 342         if ((curIndex != BEFORE)
 343                 && (curIndex != end)
 344                 && (startTimes[curIndex] <= ticks)
 345                 && (ticks <= startTimes[curIndex + 1])) {
 346             return curIndex;
 347         }
 348 
 349         final boolean indexUndefined = (curIndex == BEFORE) || (curIndex == end);
 350         final int fromIndex = (indexUndefined || (ticks < oldTicks)) ? 0 : curIndex + 1;
 351         final int toIndex = (indexUndefined || (oldTicks < ticks)) ? end : curIndex;
 352         final int index = Arrays.binarySearch(startTimes, fromIndex, toIndex, ticks);
 353         return (index < 0) ? -index - 2 : (index > 0) ? index - 1 : 0;
 354     }
 355 
 356     @Override
 357     void sync(boolean forceSync) {
 358         super.sync(forceSync);
 359 
 360         if ((forceSync && childrenChanged) || (startTimes == null)) {
 361             cachedChildren = getChildren().toArray(EMPTY_ANIMATION_ARRAY);
 362             end = cachedChildren.length;
 363             startTimes = new long[end + 1];
 364             durations = new long[end];
 365             delays = new long[end];
 366             rates = new double[end];
 367             forceChildSync = new boolean[end];
 368             long cycleTicks = 0L;
 369             int i = 0;
 370             for (final Animation animation : cachedChildren) {
 371                 startTimes[i] = cycleTicks;
 372                 rates[i] = Math.abs(animation.getRate());
 373                 if (rates[i] < EPSILON) {
 374                     rates[i] = 1;
 375                 }
 376                 durations[i] = fromDuration(animation.getTotalDuration(), rates[i]);
 377                 delays[i] = fromDuration(animation.getDelay());
 378                 if ((durations[i] == Long.MAX_VALUE) || (delays[i] == Long.MAX_VALUE) || (cycleTicks == Long.MAX_VALUE)) {
 379                     cycleTicks = Long.MAX_VALUE;
 380                 } else {
 381                     cycleTicks = add(cycleTicks, add(durations[i], delays[i]));
 382                 }
 383                 forceChildSync[i] = true;
 384                 i++;
 385             }
 386             startTimes[end] = cycleTicks;
 387             childrenChanged = false;
 388         } else if (forceSync) {
 389             final int n = forceChildSync.length;
 390             for (int i=0; i<n; i++) {
 391                 forceChildSync[i] = true;
 392             }
 393         }
 394     }
 395 
 396     @Override
 397     void doStart(boolean forceSync) {
 398         super.doStart(forceSync);
 399         toggledRate = false;
 400         rateProperty().addListener(rateListener);
 401         offsetTicks = 0L;
 402         double curRate = getCurrentRate();
 403         final long currentTicks = TickCalculation.fromDuration(getCurrentTime());
 404         if (curRate < 0) {
 405             jumpToEnd();
 406             curIndex = end;
 407             if (currentTicks < startTimes[end]) {
 408                 doJumpTo(currentTicks, startTimes[end], false);
 409             }
 410         } else {
 411             jumpToBefore();
 412             curIndex = BEFORE;
 413             if (currentTicks > 0) {
 414                 doJumpTo(currentTicks, startTimes[end], false);
 415             }
 416         }
 417     }
 418 
 419     @Override
 420     void doPause() {
 421         super.doPause();
 422         if ((curIndex != BEFORE) && (curIndex != end)) {
 423             final Animation current = cachedChildren[curIndex];
 424             if (current.getStatus() == Status.RUNNING) {
 425                 current.doPause();
 426             }
 427         }
 428     }
 429 
 430     @Override
 431     void doResume() {
 432         super.doResume();
 433         if ((curIndex != BEFORE) && (curIndex != end)) {
 434             final Animation current = cachedChildren[curIndex];
 435             if (current.getStatus() == Status.PAUSED) {
 436                 current.doResume();
 437                 current.clipEnvelope.setRate(rates[curIndex] * Math.signum(getCurrentRate()));
 438             }
 439         }
 440     }
 441 
 442     @Override
 443     void doStop() {
 444         super.doStop();
 445         if ((curIndex != BEFORE) && (curIndex != end)) {
 446             final Animation current = cachedChildren[curIndex];
 447             if (current.getStatus() != Status.STOPPED) {
 448                 current.doStop();
 449             }
 450         }
 451         if (childrenChanged) {
 452             setCycleDuration(computeCycleDuration());
 453         }
 454         rateProperty().removeListener(rateListener);
 455     }
 456 
 457     private boolean startChild(Animation child, int index) {
 458         final boolean forceSync = forceChildSync[index];
 459         if (child.startable(forceSync)) {
 460             child.clipEnvelope.setRate(rates[index] * Math.signum(getCurrentRate()));
 461             child.doStart(forceSync);
 462             forceChildSync[index] = false;
 463             return true;
 464         }
 465         return false;
 466     }
 467 
 468     @Override void doPlayTo(long currentTicks, long cycleTicks) {
 469         setCurrentTicks(currentTicks);
 470         final double frac = calculateFraction(currentTicks, cycleTicks);
 471         final long newTicks = Math.max(0, Math.min(getCachedInterpolator().interpolate(0, cycleTicks, frac), cycleTicks));
 472         final int newIndex = findNewIndex(newTicks);
 473         final Animation current = ((curIndex == BEFORE) || (curIndex == end)) ? null : cachedChildren[curIndex];
 474         if (toggledRate) {
 475             if (current != null && current.getStatus() == Status.RUNNING) {
 476                 offsetTicks -= Math.signum(getCurrentRate()) * (durations[curIndex] - 2 * (oldTicks - delays[curIndex] - startTimes[curIndex]));
 477             }
 478             toggledRate = false;
 479         }
 480         if (curIndex == newIndex) {
 481             if (getCurrentRate() > 0) {
 482                 final long currentDelay = add(startTimes[curIndex], delays[curIndex]);
 483                 if (newTicks >= currentDelay) {
 484                     if ((oldTicks <= currentDelay) || (current.getStatus() == Status.STOPPED)) {
 485                         final boolean enteringCycle = oldTicks <= currentDelay;
 486                         if (enteringCycle) {
 487                             current.clipEnvelope.jumpTo(0);
 488                         }
 489                         if (!startChild(current, curIndex)) {
 490                             if (enteringCycle) {
 491                                 final EventHandler<ActionEvent> handler = current.getOnFinished();
 492                                 if (handler != null) {
 493                                     handler.handle(new ActionEvent(this, null));
 494                                 }
 495                             }
 496                             oldTicks = newTicks;
 497                             return;
 498                         }
 499                     }
 500                     if (newTicks >= startTimes[curIndex+1]) {
 501                         current.doTimePulse(sub(durations[curIndex], offsetTicks));
 502                         if (newTicks == cycleTicks) {
 503                             curIndex = end;
 504                         }
 505                     } else {
 506                         final long localTicks = sub(newTicks - currentDelay, offsetTicks);
 507                         current.doTimePulse(localTicks);
 508                     }
 509                 }
 510             } else { // getCurrentRate() < 0
 511                 final long currentDelay = add(startTimes[curIndex], delays[curIndex]);
 512                 if ((oldTicks >= startTimes[curIndex+1]) || ((oldTicks >= currentDelay) && (current.getStatus() == Status.STOPPED))){
 513                     final boolean enteringCycle = oldTicks >= startTimes[curIndex+1];
 514                     if (enteringCycle) {
 515                         current.clipEnvelope.jumpTo(Math.round(durations[curIndex] * rates[curIndex]));
 516                     }
 517                     if (!startChild(current, curIndex)) {
 518                         if (enteringCycle) {
 519                             final EventHandler<ActionEvent> handler = current.getOnFinished();
 520                             if (handler != null) {
 521                                 handler.handle(new ActionEvent(this, null));
 522                             }
 523                         }
 524                         oldTicks = newTicks;
 525                         return;
 526                     }
 527                 }
 528                 if (newTicks <= currentDelay) {
 529                     current.doTimePulse(sub(durations[curIndex], offsetTicks));
 530                     if (newTicks == 0) {
 531                         curIndex = BEFORE;
 532                     }
 533                 } else {
 534                     final long localTicks = sub(startTimes[curIndex + 1] - newTicks, offsetTicks);
 535                     current.doTimePulse(localTicks);
 536                 }
 537             }
 538         } else { // curIndex != newIndex
 539             if (curIndex < newIndex) {
 540                 if (current != null) {
 541                     final long oldDelay = add(startTimes[curIndex], delays[curIndex]);
 542                     if ((oldTicks <= oldDelay) || ((current.getStatus() == Status.STOPPED) && (oldTicks != startTimes[curIndex + 1]))) {
 543                         final boolean enteringCycle = oldTicks <= oldDelay;
 544                         if (enteringCycle) {
 545                             current.clipEnvelope.jumpTo(0);
 546                         }
 547                         if (!startChild(current, curIndex)) {
 548                             if (enteringCycle) {
 549                                 final EventHandler<ActionEvent> handler = current.getOnFinished();
 550                                 if (handler != null) {
 551                                     handler.handle(new ActionEvent(this, null));
 552                                 }
 553                             }
 554                         }
 555                     }
 556                     if (current.getStatus() == Status.RUNNING) {
 557                         current.doTimePulse(sub(durations[curIndex], offsetTicks));
 558                     }
 559                     oldTicks = startTimes[curIndex + 1];
 560                 }
 561                 offsetTicks = 0;
 562                 curIndex++;
 563                 for (; curIndex < newIndex; curIndex++) {
 564                     final Animation animation = cachedChildren[curIndex];
 565                     animation.clipEnvelope.jumpTo(0);
 566                     if (startChild(animation, curIndex)) {
 567                         animation.doTimePulse(durations[curIndex]); // No need to subtract offsetTicks ( == 0)
 568                     } else {
 569                         final EventHandler<ActionEvent> handler = animation.getOnFinished();
 570                         if (handler != null) {
 571                             handler.handle(new ActionEvent(this, null));
 572                         }
 573                     }
 574                     oldTicks = startTimes[curIndex + 1];
 575                 }
 576                 final Animation newAnimation = cachedChildren[curIndex];
 577                 newAnimation.clipEnvelope.jumpTo(0);
 578                 if (startChild(newAnimation, curIndex)) {
 579                     if (newTicks >= startTimes[curIndex+1]) {
 580                         newAnimation.doTimePulse(durations[curIndex]); // No need to subtract offsetTicks ( == 0)
 581                         if (newTicks == cycleTicks) {
 582                             curIndex = end;
 583                         }
 584                     } else {
 585                         final long localTicks = sub(newTicks, add(startTimes[curIndex], delays[curIndex]));
 586                         newAnimation.doTimePulse(localTicks);
 587                     }
 588                 } else {
 589                     final EventHandler<ActionEvent> handler = newAnimation.getOnFinished();
 590                     if (handler != null) {
 591                         handler.handle(new ActionEvent(this, null));
 592                     }
 593                 }
 594             } else {
 595                 if (current != null) {
 596                     final long oldDelay = add(startTimes[curIndex], delays[curIndex]);
 597                     if ((oldTicks >= startTimes[curIndex+1]) || ((oldTicks > oldDelay) && (current.getStatus() == Status.STOPPED))){
 598                         final boolean enteringCycle = oldTicks >= startTimes[curIndex+1];
 599                         if (enteringCycle) {
 600                             current.clipEnvelope.jumpTo(Math.round(durations[curIndex] * rates[curIndex]));
 601                         }
 602                         if (!startChild(current, curIndex)) {
 603                             if (enteringCycle) {
 604                                 final EventHandler<ActionEvent> handler = current.getOnFinished();
 605                                 if (handler != null) {
 606                                     handler.handle(new ActionEvent(this, null));
 607                                 }
 608                             }
 609                         }
 610                     }
 611                     if (current.getStatus() == Status.RUNNING) {
 612                         current.doTimePulse(sub(durations[curIndex], offsetTicks));
 613                     }
 614                     oldTicks = startTimes[curIndex];
 615                 }
 616                 offsetTicks = 0;
 617                 curIndex--;
 618                 for (; curIndex > newIndex; curIndex--) {
 619                     final Animation animation = cachedChildren[curIndex];
 620                     animation.clipEnvelope.jumpTo(Math.round(durations[curIndex] * rates[curIndex]));
 621                     if (startChild(animation, curIndex)) {
 622                         animation.doTimePulse(durations[curIndex]); // No need to subtract offsetTicks ( == 0)
 623                     } else {
 624                         final EventHandler<ActionEvent> handler = animation.getOnFinished();
 625                         if (handler != null) {
 626                             handler.handle(new ActionEvent(this, null));
 627                         }
 628                     }
 629                     oldTicks = startTimes[curIndex];
 630                 }
 631                 final Animation newAnimation = cachedChildren[curIndex];
 632                 newAnimation.clipEnvelope.jumpTo(Math.round(durations[curIndex] * rates[curIndex]));
 633                 if (startChild(newAnimation, curIndex)) {
 634                     if (newTicks <= add(startTimes[curIndex], delays[curIndex])) {
 635                         newAnimation.doTimePulse(durations[curIndex]); // No need to subtract offsetTicks ( == 0)
 636                         if (newTicks == 0) {
 637                             curIndex = BEFORE;
 638                         }
 639                     } else {
 640                         final long localTicks = sub(startTimes[curIndex + 1], newTicks);
 641                         newAnimation.doTimePulse(localTicks);
 642                     }
 643                 } else {
 644                     final EventHandler<ActionEvent> handler = newAnimation.getOnFinished();
 645                     if (handler != null) {
 646                         handler.handle(new ActionEvent(this, null));
 647                     }
 648                 }
 649             }
 650         }
 651         oldTicks = newTicks;
 652     }
 653 
 654     @Override void doJumpTo(long currentTicks, long cycleTicks, boolean forceJump) {
 655         setCurrentTicks(currentTicks);
 656         final Status status = getStatus();
 657 
 658         if (status == Status.STOPPED && !forceJump) {
 659             return;
 660         }
 661 
 662         sync(false);
 663         final double frac = calculateFraction(currentTicks, cycleTicks);
 664         final long newTicks = Math.max(0, Math.min(getCachedInterpolator().interpolate(0, cycleTicks, frac), cycleTicks));
 665         final int oldIndex = curIndex;
 666         curIndex = findNewIndex(newTicks);
 667         final Animation newAnimation = cachedChildren[curIndex];
 668         final double currentRate = getCurrentRate();
 669         final long currentDelay = add(startTimes[curIndex], delays[curIndex]);
 670         if (curIndex != oldIndex) {
 671             if (status != Status.STOPPED) {
 672                 if ((oldIndex != BEFORE) && (oldIndex != end)) {
 673                     final Animation oldChild = cachedChildren[oldIndex];
 674                     if (oldChild.getStatus() != Status.STOPPED) {
 675                         cachedChildren[oldIndex].doStop();
 676                     }
 677                 }
 678                 if (curIndex < oldIndex) {
 679                     for (int i = oldIndex == end ? end - 1 : oldIndex; i > curIndex; --i) {
 680                         cachedChildren[i].doJumpTo(0, durations[i], true);
 681                     }
 682                 } else { //curIndex > oldIndex as curIndex != oldIndex
 683                     for (int i = oldIndex == BEFORE? 0 : oldIndex; i < curIndex; ++i) {
 684                         cachedChildren[i].doJumpTo(durations[i], durations[i], true);
 685                     }
 686                 }
 687                 if (newTicks >= currentDelay) {
 688                     startChild(newAnimation, curIndex);
 689                     if (status == Status.PAUSED) {
 690                         newAnimation.doPause();
 691                     }
 692                 }
 693             }
 694         }
 695         if (oldIndex == curIndex) {
 696             if (currentRate == 0) {
 697                 offsetTicks += (newTicks - oldTicks) * Math.signum(this.clipEnvelope.getCurrentRate());
 698             } else {
 699                 offsetTicks += currentRate > 0 ? newTicks - oldTicks : oldTicks - newTicks;
 700             }
 701         } else {
 702             if (currentRate == 0) {
 703                 if (this.clipEnvelope.getCurrentRate() > 0) {
 704                     offsetTicks = Math.max(0, newTicks - currentDelay);
 705                 } else {
 706                     offsetTicks = startTimes[curIndex] + durations[curIndex] - newTicks;
 707                 }
 708             } else {
 709                 offsetTicks = currentRate > 0 ? Math.max(0, newTicks - currentDelay) : startTimes[curIndex + 1] - newTicks;
 710             }
 711         }
 712         newAnimation.clipEnvelope.jumpTo(Math.round(sub(newTicks, currentDelay) * rates[curIndex]));
 713         oldTicks = newTicks;
 714     }
 715 
 716     private void jumpToEnd() {
 717         for (int i = 0 ; i < end; ++i) {
 718             if (forceChildSync[i]) {
 719                 cachedChildren[i].sync(true);
 720                 //NOTE: do not clean up forceChildSync[i] here. Another sync will be needed during the play
 721                 // The reason is we have 2 different use-cases for jumping (1)play from start, (2)play next cycle.
 722                 // and 2 different types of sub-transitions (A)"by" transitions that need to synchronize on
 723                 // the current state and move property by certain value and (B)"from-to" transitions that
 724                 // move from one point to another on each play/cycle. We can't query if transition is A or B.
 725                 //
 726                 // Now for combination 1A we need to synchronize here, as the subsequent jump would move
 727                 // the property to the previous value. 1B doesn't need to sync here, but it's not unsafe to
 728                 // do it. As forceChildSync is set only in case (1) and not in case (2), the cycles are always equal.
 729                 //
 730                 // Now the reason why we cannot clean forceChildSync[i] here is that while we need to sync here,
 731                 // there might be children of (A)-"by" type that operate on the same property, but fail to synchronize
 732                 // them when they start would mean they all would have the same value at the beginning.
 733             }
 734             cachedChildren[i].doJumpTo(durations[i], durations[i], true);
 735 
 736         }
 737     }
 738 
 739     private void jumpToBefore() {
 740         for (int i = end - 1 ; i >= 0; --i) {
 741             if (forceChildSync[i]) {
 742                 cachedChildren[i].sync(true);
 743                 //NOTE: do not clean up forceChildSync[i] here. Another sync will be needed during the play
 744                 // See explanation in jumpToEnd
 745             }
 746             cachedChildren[i].doJumpTo(0, durations[i], true);
 747         }
 748     }
 749 
 750     /**
 751      * {@inheritDoc}
 752      */
 753     @Override
 754     protected void interpolate(double frac) {
 755         // no-op
 756     }
 757 
 758 }
< prev index next >