AnimatedItemStyler.java

package swingtree.api;

import swingtree.animation.AnimationStatus;
import swingtree.style.ComponentStyleDelegate;

import javax.swing.JComponent;
import java.util.Objects;

/**
 * An {@link AnimatedItemStyler} is conceptually a union of the {@link ItemStyler}
 * and {@link AnimatedStyler} functions, which is to say that it takes the current item
 * of a bound {@link sprouts.Val} property, an {@link AnimationStatus} and a
 * {@link ComponentStyleDelegate} to produce a new {@link ComponentStyleDelegate}
 * with some style properties applied to it (usually based on both the item and the
 * animation progress). <br>
 * All parameters are immutable value oriented objects, so the function is pure and
 * does not modify any of them. <br>
 * <p>
 * This interface is used by {@link swingtree.UIForAnySwing#withStyle(sprouts.Val, swingtree.animation.LifeTime, AnimatedItemStyler)},
 * where every change of the bound property item restarts a transition animation whose
 * {@link AnimationStatus#progress()} runs from {@code 0} to {@code 1} over the given
 * {@link swingtree.animation.LifeTime}. This allows a style to not only follow the
 * application state, but to <i>animate towards</i> it. <br>
 * Like the {@link ItemStyler}, this function receives the property item as an explicit
 * argument, captured from the property change event on the property's owning thread,
 * which is what makes it safe to evaluate on the UI thread in the decoupled threading
 * mode (see {@link swingtree.threading.EventProcessor#DECOUPLED}). Never read the
 * property itself inside this function!
 * <p>
 * When a single {@link AnimatedItemStyler} should follow <b>several</b> properties at once, merge them
 * into one record with the composite view builder
 * {@link sprouts.Viewable#of(Object, java.util.function.Function)} (requires Sprouts 2.7.0 or above) and
 * bind that one merged property, so that any of its inputs changing restarts the transition towards the
 * newly merged item.
 *
 * @param <T> the type of the item of the bound property, supplied to this function as the first argument.
 * @param <C> the type of the {@link JComponent} that the {@link ComponentStyleDelegate} is delegating to.
 * @see swingtree.UIForAnySwing#withStyle(sprouts.Val, swingtree.animation.LifeTime, AnimatedItemStyler) The builder method consuming this function.
 * @see ItemStyler The non animated counterpart which follows a property item without a transition.
 * @see AnimatedStyler The animation status driven styler used for transitional and transitory styles.
 * @see Styler The plain, non property driven and non animated styler.
 */
@FunctionalInterface
public interface AnimatedItemStyler<T, C extends JComponent>
{
    /**
     * An {@link AnimatedItemStyler} that does nothing, meaning it simply returns the given
     * {@link ComponentStyleDelegate} without applying any style to it.
     * Conceptually speaking, this returns the null object of the {@link AnimatedItemStyler} type.
     *
     * @param <T> The type of the item of the bound property.
     * @param <C> The type of the {@link JComponent} that the {@link ComponentStyleDelegate} is delegating to.
     * @return An {@link AnimatedItemStyler} that does nothing.
     */
    static <T, C extends JComponent> AnimatedItemStyler<T, C> none() {
        return (AnimatedItemStyler<T, C>) Constants.ANIMATED_ITEM_STYLER_NONE;
    }

    /**
     * Applies some style to the given {@link ComponentStyleDelegate}, usually based on the
     * supplied property item and the progress of the supplied {@link AnimationStatus},
     * and returns a new {@link ComponentStyleDelegate} that has the style applied (if any). <br>
     * Note that this method deliberately requires the handling of checked exceptions
     * at its invocation sites because there may be any number of implementations
     * hiding behind this interface and so it is unwise to assume that
     * all of them will be able to execute gracefully without throwing exceptions.
     *
     * @param item The current item of the bound property, captured from the property change
     *             event on the property's owning thread. Style based on this argument only,
     *             never on the property itself!
     * @param status The {@link AnimationStatus} of the transition towards the supplied item,
     *               whose {@link AnimationStatus#progress()} runs from {@code 0} to {@code 1}.
     * @param delegate The {@link ComponentStyleDelegate} to apply the style to.
     * @return A new {@link ComponentStyleDelegate} that has the style applied.
     * @throws Exception if the style could not be applied by the client code.
     */
    ComponentStyleDelegate<C> style( T item, AnimationStatus status, ComponentStyleDelegate<C> delegate ) throws Exception;

    /**
     * Returns a new {@link AnimatedItemStyler} that applies the style of this {@link AnimatedItemStyler}
     * and then applies the style of the given {@link AnimatedItemStyler}.
     *
     * @param other the {@link AnimatedItemStyler} to apply after this one.
     * @return a new {@link AnimatedItemStyler} that applies the style of this and then the given one.
     */
    default AnimatedItemStyler<T, C> andThen( AnimatedItemStyler<T, C> other ) {
        Objects.requireNonNull(other);
        if ( this == none() )
            return other;
        if ( other == none() )
            return this;

        return ( item, status, delegate ) -> {
            ComponentStyleDelegate<C> result = delegate;
            try {
                result = style( item, status, delegate );
            } catch ( Exception e ) {
                // Exceptions inside a styler should not be fatal.
                Constants.LOG.error(
                    "Error trying to run '"+delegate+"' through animated item styler '"+this+"'.",
                    e
                );
            }
            return other.style( item, status, result );
        };
    }
}