ItemStyler.java
package swingtree.api;
import swingtree.style.ComponentStyleDelegate;
import javax.swing.JComponent;
import java.util.Objects;
/**
* An {@link ItemStyler} is conceptually a union of a property observer and the
* {@link Styler} function, which is to say that it takes both the current item
* of a bound {@link sprouts.Val} property and a {@link ComponentStyleDelegate}
* to produce a new {@link ComponentStyleDelegate} with some style properties
* applied to it (usually based on the supplied item). <br>
* Note that both parameters are immutable value oriented objects, so the function
* is pure and does not modify the original {@link ComponentStyleDelegate} or item. <br>
* <p>
* This interface exists specifically for property driven styling through
* {@link swingtree.UIForAnySwing#withStyle(sprouts.Val, ItemStyler)}, whose whole
* purpose is thread safety in the decoupled threading mode
* (see {@link swingtree.threading.EventProcessor#DECOUPLED}): <br>
* Style gathering is owned by the UI thread, which means a plain {@link Styler}
* lambda which reads a property from its enclosing scope (through {@code myProperty.get()})
* leaks the UI thread into application thread owned state. An {@link ItemStyler}
* closes that leak by receiving the item <b>as an explicit argument</b>, captured
* from the property change event on the property's owning thread and published
* to the UI thread, where this function is then evaluated safely. <br>
* So instead of:
* <pre>{@code
* UI.label("...")
* .withRepaintOn(person)
* .withStyle( it -> it.backgroundColor(person.get().color()) ) // <- live read on the UI thread!
* }</pre>
* ...you write:
* <pre>{@code
* UI.label("...")
* .withStyle( person, (p, it) -> it.backgroundColor(p.color()) ) // <- captured item, no property access
* }</pre>
* ...which also repaints automatically whenever the property changes, making a
* separate {@link swingtree.UIForAnySwing#withRepaintOn(sprouts.Observable)} binding unnecessary.
* <p>
* When a single {@link ItemStyler} 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, instead of chaining a
* {@link swingtree.UIForAnySwing#withStyle(sprouts.Val, ItemStyler)} per property.
*
* @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, ItemStyler) The builder method consuming this function.
* @see Styler The plain, non property driven counterpart of this function.
* @see AnimatedItemStyler The animated counterpart which also receives an animation status per repaint.
* @see AnimatedStyler The animation status driven styler used for transitional and transitory styles.
*/
@FunctionalInterface
public interface ItemStyler<T, C extends JComponent>
{
/**
* An {@link ItemStyler} 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 ItemStyler} 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 ItemStyler} that does nothing.
*/
static <T, C extends JComponent> ItemStyler<T, C> none() {
return (ItemStyler<T, C>) Constants.ITEM_STYLER_NONE;
}
/**
* Applies some style to the given {@link ComponentStyleDelegate}, usually based on
* the supplied property item, 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 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, ComponentStyleDelegate<C> delegate ) throws Exception;
/**
* Returns a new {@link ItemStyler} that applies the style of this {@link ItemStyler}
* and then applies the style of the given {@link ItemStyler}.
*
* @param other the {@link ItemStyler} to apply after this one.
* @return a new {@link ItemStyler} that applies the style of this and then the given one.
*/
default ItemStyler<T, C> andThen( ItemStyler<T, C> other ) {
Objects.requireNonNull(other);
if ( this == none() )
return other;
if ( other == none() )
return this;
return ( item, delegate ) -> {
ComponentStyleDelegate<C> result = delegate;
try {
result = style( item, delegate );
} catch ( Exception e ) {
// Exceptions inside a styler should not be fatal.
Constants.LOG.error(
"Error trying to run '"+delegate+"' through item styler '"+this+"'.",
e
);
}
return other.style( item, result );
};
}
}