AnimationDispatcher.java

  1. package swingtree.animation;

  2. import org.jspecify.annotations.Nullable;
  3. import swingtree.ComponentDelegate;

  4. import java.awt.*;
  5. import java.util.Objects;
  6. import java.util.Optional;
  7. import java.util.concurrent.TimeUnit;
  8. import java.util.function.Predicate;

  9. /**
  10.  *  An API for creating an {@link Animation} and defining how it should be executed.
  11.  *  Instances of this class are intended to be created and used either by the
  12.  *  {@link swingtree.UI} API or the user event delegation API (see {@link ComponentDelegate}). <br>
  13.  *  The UI API can be used like so:
  14.  *  <pre>{@code
  15.  *    UI.animateFor( 100, TimeUnit.MILLISECONDS ) // returns an Animate instance
  16.  *       .until( it -> it.progress() >= 0.75 && someOtherCondition() )
  17.  *       .go( status -> {
  18.  *          // do something
  19.  *          someComponent.setValue( it.progress() );
  20.  *          // ...
  21.  *          someComponent.repaint();
  22.  *       });
  23.  *   }</pre>
  24.  *   The user event delegation API can be used like this:
  25.  *   <pre>{@code
  26.  *       panel()
  27.  *       .onMouseClick( it -> {
  28.  *           it.animateFor( 100, TimeUnit.MILLISECONDS )
  29.  *           .goOnce( status -> {
  30.  *               int width = (int) (100 * state.progress());
  31.  *               it.getComponent().setSize( width, 100 );
  32.  *           });
  33.  *       })
  34.  *   }</pre>
  35.  */
  36. public final class AnimationDispatcher
  37. {
  38.     private final LifeTime                _lifeTime;
  39.     private final Stride                  _stride;
  40.     private final @Nullable Component     _component;
  41.     private final @Nullable RunCondition  _condition;


  42.     /**
  43.      * Creates an {@link AnimationDispatcher} instance which allows you to define the stop condition
  44.      * for an animation as well as an {@link Animation} that will be executed
  45.      * when passed to the {@link #go(Animation)} method.
  46.      *
  47.      * @param lifeTime The schedule that defines when the animation should be executed and for how long.
  48.      * @return An {@link AnimationDispatcher} instance that can be used to define how the animation should be executed.
  49.      */
  50.     public static AnimationDispatcher animateFor( LifeTime lifeTime ) {
  51.         return animateFor( lifeTime, Stride.PROGRESSIVE );
  52.     }

  53.     /**
  54.      * Creates an {@link AnimationDispatcher} instance which allows you to define the stop condition
  55.      * for an animation as well as an {@link Animation} that will be executed
  56.      * when passed to the {@link #go(Animation)} method.
  57.      *
  58.      * @param lifeTime The schedule that defines when the animation should be executed and for how long.
  59.      * @param stride   The stride of the animation, i.e. whether it should be executed progressively or regressively.
  60.      * @return An {@link AnimationDispatcher} instance that can be used to define how the animation should be executed.
  61.      */
  62.     public static AnimationDispatcher animateFor( LifeTime lifeTime, Stride stride ) {
  63.         return new AnimationDispatcher( lifeTime, stride, null, null );
  64.     }

  65.     /**
  66.      * Creates an {@link AnimationDispatcher} instance which allows you to define the stop condition
  67.      * for an animation as well as an {@link Animation} that will be executed
  68.      * when passed to the {@link #go(Animation)} method.
  69.      *
  70.      * @param lifeTime  The schedule that defines when the animation should be executed and for how long.
  71.      * @param component The component that should be repainted after each animation step.
  72.      * @return An {@link AnimationDispatcher} instance that can be used to define how the animation should be executed.
  73.      */
  74.     public static AnimationDispatcher animateFor( LifeTime lifeTime, Component component ) {
  75.         return animateFor( lifeTime, Stride.PROGRESSIVE, component );
  76.     }

  77.     /**
  78.      * Creates an {@link AnimationDispatcher} instance which allows you to define the stop condition
  79.      * for an animation as well as an {@link Animation} that will be executed
  80.      * when passed to the {@link #go(Animation)} method.
  81.      *
  82.      * @param lifeTime The schedule that defines when the animation should be executed and for how long.
  83.      * @param stride   The stride of the animation, i.e. whether it should be executed progressively or regressively.
  84.      *                 See {@link Stride} for more information.
  85.      * @param component The component that should be repainted after each animation step.
  86.      * @return An {@link AnimationDispatcher} instance that can be used to define how the animation should be executed.
  87.      */
  88.     public static AnimationDispatcher animateFor( LifeTime lifeTime, Stride stride, Component component ) {
  89.         return new AnimationDispatcher( lifeTime, stride, component, null );
  90.     }


  91.     private AnimationDispatcher(
  92.         LifeTime               lifeTime,
  93.         Stride                 stride,
  94.         @Nullable Component    component,
  95.         @Nullable RunCondition animation
  96.     ) {
  97.         _lifeTime  = Objects.requireNonNull(lifeTime);
  98.         _stride    = Objects.requireNonNull(stride);
  99.         _component = component; // may be null
  100.         _condition = animation; // may be null
  101.     }

  102.     /**
  103.      *  Use this to define a stop condition for the animation.
  104.      *
  105.      * @param shouldStop The stop condition for the animation, i.e. the animation will be executed
  106.      *                   until this condition is true.
  107.      * @return A new {@link AnimationDispatcher} instance that will be executed until the given stop condition is true.
  108.      */
  109.     public AnimationDispatcher until( Predicate<AnimationStatus> shouldStop ) {
  110.         return this.asLongAs( shouldStop.negate() );
  111.     }

  112.     /**
  113.      *  Use this to define a running condition for the animation.
  114.      *
  115.      * @param shouldRun The running condition for the animation, i.e. the animation will be executed
  116.      *                  as long as this condition is true.
  117.      * @return A new {@link AnimationDispatcher} instance that will be executed as long as the given running condition is true.
  118.      */
  119.     public AnimationDispatcher asLongAs( Predicate<AnimationStatus> shouldRun ) {
  120.         return new AnimationDispatcher(_lifeTime, _stride, _component, status -> {
  121.                     if ( shouldRun.test(status) )
  122.                         return _condition == null || _condition.shouldContinue(status);

  123.                     return false;
  124.                 });
  125.     }

  126.     /**
  127.      *  Runs the given animation based on the stop condition defined by {@link #until(Predicate)} or {@link #asLongAs(Predicate)}.
  128.      *  If no stop condition was defined, the animation will be executed once.
  129.      *  If you want to run an animation forever, simply pass {@code status -> true} to
  130.      *  the {@link #asLongAs(Predicate)} method, or {@code status -> false} to the {@link #until(Predicate)} method.
  131.      *
  132.      * @param animation The animation that should be executed.
  133.      */
  134.     public void go( Animation animation ) {
  135.         RunCondition shouldRun = Optional.ofNullable(_condition).orElse( status -> status.repeats() == 0 );
  136.         AnimationRunner.add( new RunningAnimation(
  137.                 _component,
  138.                 LifeSpan.startingNowWith(Objects.requireNonNull(_lifeTime)),
  139.                 _stride,
  140.                 shouldRun,
  141.                 animation
  142.             ));
  143.     }

  144.     /**
  145.      *  Runs the given animation based on a time offset in the given time unit
  146.      *  and the stop condition defined by {@link #until(Predicate)} or {@link #asLongAs(Predicate)}.
  147.      *  If no stop condition was defined, the animation will be executed once.
  148.      *  If you want to run an animation forever, simply pass {@code status -> true} to
  149.      *  the {@link #asLongAs(Predicate)} method, or {@code status -> false} to the {@link #until(Predicate)} method.
  150.      *  <p>
  151.      *  This method is useful in cases where you want an animation to start in the future,
  152.      *  or somewhere in the middle of their lifespan progress (see {@link AnimationStatus#progress()}).
  153.      *
  154.      * @param offset The offset in the given time unit after which the animation should be executed.
  155.      *               This number may also be negative, in which case the animation will be executed
  156.      *               immediately, and with a {@link AnimationStatus#progress()} value that is
  157.      *               advanced according to the offset.
  158.      *
  159.      * @param unit The time unit in which the offset is specified.
  160.      * @param animation The animation that should be executed.
  161.      */
  162.     public void goWithOffset( long offset, TimeUnit unit, Animation animation ) {
  163.         RunCondition shouldRun = Optional.ofNullable(_condition).orElse( status -> status.repeats() == 0 );
  164.         AnimationRunner.add( new RunningAnimation(
  165.                 _component,
  166.                 LifeSpan.startingNowWithOffset(offset, unit, Objects.requireNonNull(_lifeTime)),
  167.                 _stride,
  168.                 shouldRun,
  169.                 animation
  170.             ));
  171.     }

  172. }