ComponentDelegate.java
package swingtree;
import sprouts.Action;
import sprouts.Tuple;
import javax.swing.JComponent;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
* Instances of this are delegates for a specific components and events that
* are passed to user event action handlers (see {@link Action}),
* with the purpose of providing useful context information to the action handler.
* <br>
* You would typically use this to access and change the state of the component, schedule animations
* for the component or query the tree of neighboring components. <br>
* Here a nice usage example where the delegate is used to animate a button:
* <pre>{@code
* button("I turn green when you hover over me")
* .onMouseEnter( it ->
* it.animateFor(0.5, TimeUnit.SECONDS, status -> {
* double highlight = 1 - status.progress() * 0.5;
* it.setBackgroundColor(highlight, 1, highlight);
* })
* )
* .onMouseExit( it ->
* it.animateFor(0.5, TimeUnit.SECONDS, status -> {
* double highlight = 0.5 + status.progress() * 0.5;
* it.setBackgroundColor(highlight, 1, highlight);
* })
* )
* }</pre>
* In this example the {@code it} parameter is a {@code ComponentDelegate<JButton,MouseEvent>}
* which can be used to access/modify the button, the event, the sibling components...
* ...but also exposes a nice API to schedule animations for the button.
* <p>
* For some more examples <b>please take a look at the
* <a href="https://globaltcad.github.io/swing-tree/">living swing-tree documentation</a>
* where you can browse a large collection of examples demonstrating how to use the API of this class.</b>
*
* @param <C> The delegate (in most cases origin UI component) type parameter stored by this.
* @param <E> The event type parameter of the event stored by this.
*/
public class ComponentDelegate<C extends JComponent, E> extends AbstractDelegate<C>
{
private final E _event;
/**
* Creates a new delegate for the specified component and event.
*
* @param component The component for which the delegate is created.
* @param event The event that represents the action that was triggered either by the user or by the system.
*/
public ComponentDelegate( C component, E event ) {
super(false, component, component);
_event = Objects.requireNonNull(event);
}
/**
* Exposes the underlying {@link JComponent} from which this delegate
* and user event actions originate.
* <p>
* <b>Threading:</b> A {@link JComponent} is owned by the GUI thread (the AWT
* Event Dispatch Thread). Under a non-coupled threading model
* (see {@link swingtree.threading.EventProcessor}), event actions may be invoked
* on the application thread rather than on the EDT. To prevent unsafe access
* to Swing state, this method <b>requires the calling thread to be the GUI
* thread</b> and throws an {@link IllegalStateException} otherwise.
* <p>
* If you need to access the component from the application thread,
* use {@link #forComponent(Consumer)} instead, which will safely dispatch
* the supplied lambda to the GUI thread for you.
*
* @return The component for which the current {@link Action} originated.
* @throws IllegalStateException If this method is called from a non-Swing thread.
* @see #forComponent(Consumer)
*/
public final C getComponent() {
// We make sure that only the Swing thread can access the component:
if ( UI.thisIsUIThread() )
return _component();
else
throw new IllegalStateException(
"Component can only be accessed by the Swing thread. " +
"Use 'forComponent(Consumer)' to access the component from the application thread."
);
}
/**
* Provides a thread-safe way to access the underlying {@link JComponent} of this
* delegate by passing a {@link Consumer} lambda which will always be executed on
* the GUI thread (the AWT Event Dispatch Thread).
* <p>
* This is the recommended counterpart to {@link #getComponent()} when the calling
* code may run on the application thread (e.g. under a decoupled
* {@link swingtree.threading.EventProcessor}). If the current thread is already
* the GUI thread, the lambda is executed immediately; otherwise it is dispatched
* via {@link UI#run(Runnable)}.
* <p>
* Example:
* <pre>{@code
* it.forComponent( button -> button.setText("Updated on the EDT") );
* }</pre>
*
* @param action The action consuming the component,
* which will be executed by the Swing thread.
* @see #getComponent()
*/
public final void forComponent( Consumer<C> action ) {
if ( UI.thisIsUIThread() )
action.accept(_component());
else
UI.run( () -> action.accept(_component()) );
}
/**
* Exposes the event that represents the action that was triggered
* either by the user or by the system.
*
* @return An object holding relevant information about an event that was triggered.
*/
public final E getEvent() { return _event; }
/**
* Exposes the "siblings", which consist of all the child components
* of the parent of the delegated component, <b>except for the delegated
* component itself</b>.
* <p>
* <b>Threading:</b> Sibling components are part of the Swing component
* tree, which is owned by the GUI thread (the AWT Event Dispatch Thread).
* This method <b>requires the calling thread to be the GUI thread</b> and
* throws an {@link IllegalStateException} otherwise. Under a non-coupled
* {@link swingtree.threading.EventProcessor}, event actions are usually
* invoked on the application thread, in which case you must use
* {@link #forSiblings(Consumer)} instead, which dispatches the supplied
* lambda to the GUI thread for you.
*
* @return A tuple (immutable list) of all siblings excluding the component from which this instance originated.
* @throws IllegalStateException If this method is called from a non-Swing thread.
* @see #forSiblings(Consumer)
*/
public final Tuple<JComponent> getSiblings() {
// We make sure that only the Swing thread can access the sibling components:
if ( !UI.thisIsUIThread() )
throw new IllegalStateException(
"Sibling components can only be accessed by the Swing thread. " +
"Please use 'forSiblings(..)' methods instead."
);
return _siblingsSource().retainIf( s -> _component() != s );
}
/**
* Provides a thread-safe way to access the sibling components of this delegate
* by passing a {@link Consumer} lambda which will always be executed on the GUI
* thread (the AWT Event Dispatch Thread).
* <p>
* This is the recommended counterpart to {@link #getSiblings()} when the calling
* code may run on the application thread (e.g. under a decoupled
* {@link swingtree.threading.EventProcessor}). If the current thread is already
* the GUI thread, the lambda is executed immediately; otherwise it is dispatched
* via {@link UI#run(Runnable)}.
*
* @param action The action consuming a list of all siblings (excluding the
* component from which this instance originated),
* which will be executed by the Swing thread.
* @see #getSiblings()
*/
public final void forSiblings( Consumer<Tuple<JComponent>> action ) {
if ( UI.thisIsUIThread() )
action.accept(getSiblings());
else
UI.run( () -> action.accept(getSiblings()) );
}
/**
* Allows you to query the sibling components of the delegated component
* of the specified type. So a list of all siblings which are of the specified type
* will be returned, <b>excluding</b> the currently delegated component itself.
* <p>
* <b>Threading:</b> Sibling components are part of the Swing component tree,
* which is owned by the GUI thread (the AWT Event Dispatch Thread). This method
* <b>requires the calling thread to be the GUI thread</b> and throws an
* {@link IllegalStateException} otherwise. Under a non-coupled
* {@link swingtree.threading.EventProcessor}, event actions are usually invoked
* on the application thread, in which case you must use
* {@link #forSiblingsOfType(Class, Consumer)} instead, which dispatches the
* supplied lambda to the GUI thread for you.
*
* @param type The type class of the sibling components to return.
* @param <T> The type of the sibling components to return.
* @return A list of all siblings of the specified type, excluding the component from which this instance originated.
* @throws IllegalStateException If this method is called from a non-Swing thread.
* @see #forSiblingsOfType(Class, Consumer)
*/
public final <T extends JComponent> List<T> getSiblingsOfType(Class<T> type) {
// We make sure that only the Swing thread can access the sibling components:
if ( !UI.thisIsUIThread() )
throw new IllegalStateException(
"Sibling components can only be accessed by the Swing thread. " +
"Please use 'forSiblingsOfType(..)' methods instead."
);
return getSiblings()
.stream()
.filter( s -> type.isAssignableFrom(s.getClass()) )
.map( s -> (T) s )
.collect(Collectors.toList());
}
/**
* Provides a thread-safe way to access the sibling components of this delegate
* filtered by the given type by passing a {@link Consumer} lambda which will
* always be executed on the GUI thread (the AWT Event Dispatch Thread).
* <p>
* This is the recommended counterpart to {@link #getSiblingsOfType(Class)} when
* the calling code may run on the application thread (e.g. under a decoupled
* {@link swingtree.threading.EventProcessor}). If the current thread is already
* the GUI thread, the lambda is executed immediately; otherwise it is dispatched
* via {@link UI#run(Runnable)}.
*
* @param type The type class of the sibling components to return.
* @param <T> The {@link JComponent} type of the sibling components to return.
* @param action The action consuming a list of all siblings of the specified type,
* excluding the component from which this instance originated,
* which will be executed by the Swing thread.
* @see #getSiblingsOfType(Class)
*/
public final <T extends JComponent> void forSiblingsOfType(
Class<T> type, Consumer<List<T>> action
) {
if ( UI.thisIsUIThread() )
action.accept(getSiblingsOfType(type));
else
UI.run( () -> action.accept(getSiblingsOfType(type)) );
}
/**
* This method provides a convenient way to access all the children of the parent
* component of the component this delegate is for, <b>including the delegated
* component itself</b>.
* <p>
* <b>Threading:</b> Sibling components are part of the Swing component tree,
* which is owned by the GUI thread (the AWT Event Dispatch Thread). This method
* <b>requires the calling thread to be the GUI thread</b> and throws an
* {@link IllegalStateException} otherwise. Under a non-coupled
* {@link swingtree.threading.EventProcessor}, event actions are usually invoked
* on the application thread, in which case you must use
* {@link #forSiblinghood(Consumer)} instead, which dispatches the supplied
* lambda to the GUI thread for you.
*
* @return A tuple (immutable list) of all siblings including the component from which this instance originated.
* @throws IllegalStateException If this method is called from a non-Swing thread.
* @see #forSiblinghood(Consumer)
*/
public final Tuple<JComponent> getSiblinghood() {
// We make sure that only the Swing thread can access the sibling components:
if ( !UI.thisIsUIThread() )
throw new IllegalStateException(
"Sibling components can only be accessed by the Swing thread. " +
"Please use 'forSiblinghood(..)' methods instead."
);
return _siblingsSource();
}
/**
* Provides a thread-safe way to access the entire siblinghood of this delegate
* (the delegated component <i>and</i> its siblings) by passing a {@link Consumer}
* lambda which will always be executed on the GUI thread (the AWT Event Dispatch
* Thread).
* <p>
* This is the recommended counterpart to {@link #getSiblinghood()} when the
* calling code may run on the application thread (e.g. under a decoupled
* {@link swingtree.threading.EventProcessor}). If the current thread is already
* the GUI thread, the lambda is executed immediately; otherwise it is dispatched
* via {@link UI#run(Runnable)}.
*
* @param action The action consuming a list of all siblings (including the
* component from which this instance originated),
* which will be executed by the Swing thread.
* @see #getSiblinghood()
*/
public final void forSiblinghood( Consumer<Tuple<JComponent>> action ) {
if ( UI.thisIsUIThread() )
action.accept(getSiblinghood());
else
UI.run( () -> action.accept(getSiblinghood()) );
}
/**
* Allows you to query the entire siblinghood of the delegated component
* filtered by the specified type. So a list of all siblings which are of the
* specified type will be returned, <b>possibly including the delegated component
* itself</b>.
* <p>
* <b>Threading:</b> Sibling components are part of the Swing component tree,
* which is owned by the GUI thread (the AWT Event Dispatch Thread). This method
* <b>requires the calling thread to be the GUI thread</b> and throws an
* {@link IllegalStateException} otherwise. Under a non-coupled
* {@link swingtree.threading.EventProcessor}, event actions are usually invoked
* on the application thread, in which case you must use
* {@link #forSiblinghoodOfType(Class, Consumer)} instead, which dispatches the
* supplied lambda to the GUI thread for you.
*
* @param type The type of the sibling components to return.
* @param <T> The {@link JComponent} type of the sibling components to return.
* @return A list of all siblings of the specified type, including the component from which this instance originated.
* @throws IllegalStateException If this method is called from a non-Swing thread.
* @see #forSiblinghoodOfType(Class, Consumer)
*/
public final <T extends JComponent> Tuple<T> getSiblinghoodOfType( Class<T> type ) {
// We make sure that only the Swing thread can access the sibling components:
if ( !UI.thisIsUIThread() )
throw new IllegalStateException(
"Sibling components can only be accessed by the Swing thread. " +
"Please use 'forSiblinghoodOfType(..)' methods instead."
);
return _siblingsSource()
.stream()
.filter( s -> type.isAssignableFrom(s.getClass()) )
.map(type::cast)
.collect(Tuple.collectorOf(type));
}
/**
* Provides a thread-safe way to access the entire siblinghood of this delegate
* (including the delegated component itself) filtered by the given type, by
* passing a {@link Consumer} lambda which will always be executed on the GUI
* thread (the AWT Event Dispatch Thread).
* <p>
* This is the recommended counterpart to {@link #getSiblinghoodOfType(Class)}
* when the calling code may run on the application thread (e.g. under a
* decoupled {@link swingtree.threading.EventProcessor}). If the current thread
* is already the GUI thread, the lambda is executed immediately; otherwise it
* is dispatched via {@link UI#run(Runnable)}.
*
* @param type The type of the sibling components to return.
* @param <T> The {@link JComponent} type of the sibling components to return.
* @param action The action consuming a tuple (immutable list) of all siblings of the specified type,
* including the component from which this instance originated,
* which will be executed by the Swing thread.
* @see #getSiblinghoodOfType(Class)
*/
public final <T extends JComponent> void forSiblinghoodOfType(
Class<T> type, Consumer<Tuple<T>> action
) {
if ( UI.thisIsUIThread() )
action.accept(getSiblinghoodOfType(type));
else
UI.run( () -> action.accept(getSiblinghoodOfType(type)) );
}
}