Animatable.java
package swingtree.animation;
import org.jspecify.annotations.Nullable;
import sprouts.Var;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
/**
* This defines what is needed for animating an immutable
* value type {@code T} through a transformation function
* {@link AnimationTransformation} and a {@link LifeTime} determining
* when the animation should run.
* It is essentially just an immutable wrapper for {@code T},
* {@link AnimationTransformation} and a {@link LifeTime}.
* <p>
* An {@link Animatable} is designed to be provided by an immutable
* view model to animate its properties using the {@link swingtree.UI#animate(Var, Animatable)}
* or {@link swingtree.UI#animate(Var, Function)} methods. <br>
* Where the {@link Var} property is used as the mutable animation state
* and the animatable defines how the property item should be transformed
* repeatedly during the iterations of the entire animation lifetime.
*
* @param <M> The type of the model that is continuously transformed
* by the {@link AnimationTransformation}.
*/
public final class Animatable<M>
{
private static final Animatable<?> _NONE = new Animatable<>(LifeTime.none(), null, (state, value) -> value);
private final LifeTime _lifeTime;
private final @Nullable M _initialValue;
private final AnimationTransformation<M> _animation;
/**
* Returns an {@link Animatable} instance that does nothing
* when run due to a {@link LifeTime} of {@link LifeTime#none()}.
* This is equivalent to a no-op and may also be used instead
* of {@code null} in cases where an {@link Animatable} is expected.
*
* @param <T> The type of model that is animated.
* @return An {@link Animatable} instance that does nothing.
*/
public static <T> Animatable<T> none() {
return (Animatable<T>) _NONE;
}
/**
* Returns an {@link Animatable} instance that merely holds the
* supplied value and does nothing when run due to a no-op
* transformation function and a {@link LifeTime} of {@link LifeTime#none()}. <br>
* You can think of this as an instantaneous animation to the
* specified value as the first and final state of the "animation".
*
* @param value The value that should be used for setting the new application/animator state.
* @param <T> The type of model used as new animation/application state.
* @return An {@link Animatable} instance that only holds a single animation state,
* which is the supplied value.
* @throws NullPointerException If the supplied value is {@code null}.
*/
public static <T> Animatable<T> of( T value ) {
Objects.requireNonNull(value);
return of( LifeTime.none(), value, (state, v) -> v );
}
/**
* Returns an {@link Animatable} instance that animates the supplied
* value through the provided {@link AnimationTransformation} function during
* the specified {@link LifeTime} starting with the supplied initial value
* as initial application/animation state.
*
* @param lifeTime The lifetime of the animation.
* @param initialValue The initial value of the animation.
* @param animation The transformation function that is called repeatedly
* during the lifetime of the animation.
* @param <T> The type of model that is animated.
* @return An {@link Animatable} instance that animates the supplied value.
* @throws NullPointerException If any of the arguments is {@code null}.
* @see #of(LifeTime, AnimationTransformation) for an {@link Animatable} without an initial value.
*/
public static <T> Animatable<T> of(
LifeTime lifeTime,
T initialValue,
AnimationTransformation<T> animation
) {
Objects.requireNonNull(lifeTime);
Objects.requireNonNull(animation);
Objects.requireNonNull(initialValue);
return new Animatable<>(lifeTime, initialValue, animation);
}
/**
* Returns an {@link Animatable} instance that is used to
* transform {@code T} values using the supplied {@link AnimationTransformation} function
* during the specified {@link LifeTime}.
*
* @param lifeTime The lifetime of the animation.
* @param animation The transformation function that is called repeatedly
* during the lifetime of the animation.
* @param <T> The type model that is animated.
* @return An {@link Animatable} instance that animates the supplied value.
* @throws NullPointerException If any of the arguments is {@code null}.
* @see #of(LifeTime, Object, AnimationTransformation) for an {@link Animatable} with an initial value.
*/
public static <T> Animatable<T> of(
LifeTime lifeTime,
AnimationTransformation<T> animation
) {
Objects.requireNonNull(lifeTime);
Objects.requireNonNull(animation);
return new Animatable<>(lifeTime, null, animation);
}
private Animatable(
LifeTime lifeTime,
@Nullable M initialValue,
AnimationTransformation<M> animation
) {
_lifeTime = lifeTime;
_initialValue = initialValue;
_animation = animation;
}
/**
* Returns the {@link LifeTime} of the animation,
* which is used by {@link swingtree.UI#animate(Var, Animatable)}
* to determine for how long the animation should run.
*
* @return The {@link LifeTime} of the animation.
*/
public LifeTime lifeTime() {
return _lifeTime;
}
/**
* Returns the initial value of the animation
* or an empty {@link Optional} if no initial value is set.
* This is the value that is used as the initial state
* of the animation before the first transformation is applied.<br>
* If there is no initial value set, the result of the first
* transformation is used as the initial state of the animation.
*
* @return The initial model instance of the animation state or an empty {@link Optional}.
* @see #of(LifeTime, AnimationTransformation) for an {@link Animatable} without an initial value.
* @see #of(LifeTime, Object, AnimationTransformation) for an {@link Animatable} with an initial value.
*/
public Optional<M> initialState() {
return Optional.ofNullable(_initialValue);
}
/**
* Returns the transformation function that is called repeatedly
* during the lifetime of the animation to transform the item
* of a {@link Var} property when passed to the
* {@link swingtree.UI#animate(Var, Animatable)} method.
*
* @return The transformation function of the animation.
*/
public AnimationTransformation<M> animator() {
return _animation;
}
@Override
public String toString() {
return this.getClass().getSimpleName() + "[" +
"lifeTime=" + _lifeTime + ", " +
"initialValue=" + _initialValue + ", " +
"animation=" + _animation +
"]";
}
@Override
public int hashCode() {
return Objects.hash(_lifeTime, _initialValue, _animation);
}
@Override
public boolean equals( Object obj ) {
if ( this == obj )
return true;
if ( obj == null || getClass() != obj.getClass() )
return false;
Animatable<?> other = (Animatable<?>) obj;
return Objects.equals(_lifeTime, other._lifeTime ) &&
Objects.equals(_initialValue, other._initialValue) &&
Objects.equals(_animation, other._animation );
}
}