Animator.java
package swingtree.animation;
import org.jspecify.annotations.Nullable;
import swingtree.ComponentDelegate;
import java.awt.*;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
/**
* An API for creating an {@link Animation} and defining how it should be executed.
* Instances of this class are intended to be created and used either by the
* {@link swingtree.UI} API or the user event delegation API (see {@link ComponentDelegate}). <br>
* The UI API can be used like so:
* <pre>{@code
* UI.schedule( 100, TimeUnit.MILLISECONDS ) // returns an Animate instance
* .until( it -> it.progress() >= 0.75 && someOtherCondition() )
* .go( state -> {
* // do something
* someComponent.setValue( it.progress() );
* // ...
* someComponent.repaint();
* });
* }</pre>
* The user event delegation API can be used like this:
* <pre>{@code
* panel()
* .onMouseClick( it -> {
* it.animateFor( 100, TimeUnit.MILLISECONDS )
* .goOnce( state -> {
* int width = (int) (100 * state.progress());
* it.getComponent().setSize( width, 100 );
* });
* })
* }</pre>
*/
public class Animator
{
private final LifeTime _lifeTime; // Never null
private final Stride _stride; // Never null
private final @Nullable Component _component; // may be null
private final @Nullable RunCondition _condition; // may be null
/**
* Creates an {@link Animator} instance which allows you to define the stop condition
* for an animation as well as an {@link Animation} that will be executed
* when passed to the {@link #go(Animation)} method.
*
* @param lifeTime The schedule that defines when the animation should be executed and for how long.
* @return An {@link Animator} instance that can be used to define how the animation should be executed.
*/
public static Animator animateFor( LifeTime lifeTime ) {
return animateFor( lifeTime, Stride.PROGRESSIVE );
}
/**
* Creates an {@link Animator} instance which allows you to define the stop condition
* for an animation as well as an {@link Animation} that will be executed
* when passed to the {@link #go(Animation)} method.
*
* @param lifeTime The schedule that defines when the animation should be executed and for how long.
* @param stride The stride of the animation, i.e. whether it should be executed progressively or regressively.
* @return An {@link Animator} instance that can be used to define how the animation should be executed.
*/
public static Animator animateFor( LifeTime lifeTime, Stride stride ) {
return new Animator( lifeTime, stride, null, null );
}
/**
* Creates an {@link Animator} instance which allows you to define the stop condition
* for an animation as well as an {@link Animation} that will be executed
* when passed to the {@link #go(Animation)} method.
*
* @param lifeTime The schedule that defines when the animation should be executed and for how long.
* @param component The component that should be repainted after each animation step.
* @return An {@link Animator} instance that can be used to define how the animation should be executed.
*/
public static Animator animateFor( LifeTime lifeTime, Component component ) {
return animateFor( lifeTime, Stride.PROGRESSIVE, component );
}
/**
* Creates an {@link Animator} instance which allows you to define the stop condition
* for an animation as well as an {@link Animation} that will be executed
* when passed to the {@link #go(Animation)} method.
*
* @param lifeTime The schedule that defines when the animation should be executed and for how long.
* @param stride The stride of the animation, i.e. whether it should be executed progressively or regressively.
* See {@link Stride} for more information.
* @param component The component that should be repainted after each animation step.
* @return An {@link Animator} instance that can be used to define how the animation should be executed.
*/
public static Animator animateFor( LifeTime lifeTime, Stride stride, Component component ) {
return new Animator( lifeTime, stride, component, null );
}
private Animator(
LifeTime lifeTime,
Stride stride,
@Nullable Component component,
@Nullable RunCondition animation
) {
_lifeTime = Objects.requireNonNull(lifeTime);
_stride = Objects.requireNonNull(stride);
_component = component; // may be null
_condition = animation; // may be null
}
/**
* Use this to define a stop condition for the animation.
*
* @param shouldStop The stop condition for the animation, i.e. the animation will be executed
* until this condition is true.
* @return A new {@link Animator} instance that will be executed until the given stop condition is true.
*/
public Animator until( Predicate<AnimationState> shouldStop ) {
return this.asLongAs( shouldStop.negate() );
}
/**
* Use this to define a running condition for the animation.
*
* @param shouldRun The running condition for the animation, i.e. the animation will be executed
* as long as this condition is true.
* @return A new {@link Animator} instance that will be executed as long as the given running condition is true.
*/
public Animator asLongAs( Predicate<AnimationState> shouldRun ) {
return new Animator(_lifeTime, _stride, _component, state -> {
if ( shouldRun.test(state) )
return _condition == null || _condition.shouldContinue(state);
return false;
});
}
/**
* Runs the given animation based on the stop condition defined by {@link #until(Predicate)} or {@link #asLongAs(Predicate)}.
* If no stop condition was defined, the animation will be executed once.
* If you want to run an animation forever, simply pass {@code state -> true} to
* the {@link #asLongAs(Predicate)} method, or {@code state -> false} to the {@link #until(Predicate)} method.
*
* @param animation The animation that should be executed.
*/
public void go( Animation animation ) {
RunCondition shouldRun = Optional.ofNullable(_condition).orElse( state -> state.repeats() == 0 );
AnimationRunner.add( new ComponentAnimator(
_component,
LifeSpan.startingNowWith(Objects.requireNonNull(_lifeTime)),
_stride,
shouldRun,
animation
));
}
/**
* Runs the given animation based on a time offset in the given time unit
* and the stop condition defined by {@link #until(Predicate)} or {@link #asLongAs(Predicate)}.
* If no stop condition was defined, the animation will be executed once.
* If you want to run an animation forever, simply pass {@code state -> true} to
* the {@link #asLongAs(Predicate)} method, or {@code state -> false} to the {@link #until(Predicate)} method.
* <p>
* This method is useful in cases where you want an animation to start in the future,
* or somewhere in the middle of their lifespan progress (see {@link AnimationState#progress()}).
*
* @param offset The offset in the given time unit after which the animation should be executed.
* This number may also be negative, in which case the animation will be executed
* immediately, and with a {@link AnimationState#progress()} value that is
* advanced according to the offset.
*
* @param unit The time unit in which the offset is specified.
* @param animation The animation that should be executed.
*/
public void goWithOffset( long offset, TimeUnit unit, Animation animation ) {
RunCondition shouldRun = Optional.ofNullable(_condition).orElse( state -> state.repeats() == 0 );
AnimationRunner.add( new ComponentAnimator(
_component,
LifeSpan.startingNowWithOffset(offset, unit, Objects.requireNonNull(_lifeTime)),
_stride,
shouldRun,
animation
));
}
}