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 }
|