ComponentDelegate.java

package swingtree;

import sprouts.Action;

import javax.swing.JComponent;
import java.util.ArrayList;
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, state -> {
 *              double highlight = 1 - state.progress() * 0.5;
 *              it.setBackgroundColor(highlight, 1, highlight);
 *          })
 *      )
 *      .onMouseExit( it ->
 *          it.animateFor(0.5, TimeUnit.SECONDS, state -> {
 *              double highlight = 0.5 + state.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;


    public ComponentDelegate(
            C component, E event
    ) {
        super(false, component, component);
        _event = Objects.requireNonNull(event);
    }

    /**
     *  Exposes the underlying component from which this delegate
     *  and user event actions originate.
     *  This method may only be called by the Swing thread.
     *  If another thread calls this method, an exception will be thrown.
     *
     * @return The component for which the current {@link Action} originated.
     * @throws IllegalStateException If this method is called from a non-Swing thread.
     */
    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 this to access the component of this delegate in the swing thread.
     *  This method will make sure that the passed lambda will
     *  be executed by the Swing thread.
     *  <br><br>
     * @param action The action consuming the component,
     *               which will be executed by the Swing thread.
     */
    public final void forComponent( Consumer<C> action ) {
        if ( UI.thisIsUIThread() )
            action.accept(_component());
        else
            UI.run( () -> action.accept(_component()) );
    }

    public final E getEvent() { return _event; }

    /**
     *  Exposes the "siblings", which consist of all
     *  the child components of the parent of the delegated component
     *  except the for the delegated component itself.
     *
     * @return A list of all siblings excluding the component from which this instance originated.
     */
    public final List<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().stream().filter( s -> _component() != s ).collect(Collectors.toList());
    }
    
    /**
     *  Use this to access the sibling components of this delegate in the swing thread.
     *  This method will make sure that the passed lambda will
     *  be executed by the Swing thread.
     *  <br><br>
     * @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.
     */
    public final void forSiblings( Consumer<List<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. <br>
     *  Note that this method may only be called by the Swing thread.
     *  If another thread calls this method, an exception will be thrown.
     *  Use {@link #forSiblingsOfType(Class, Consumer)} to access the sibling components
     *  of the specified type in a thread-safe way.
     *
     * @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.
     */
    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());
    }

    /**
     *  Use this to access the sibling components of this delegate
     *  of the specified type in the swing thread.
     *  This method will make sure that the passed lambda will
     *  be executed by the Swing thread.
     *  <br><br>
     * @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.
     */
    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.
     *  Note that this method may only be called by the Swing thread.
     *  If another thread calls this method, an exception will be thrown.
     *  Use {@link #forSiblinghood(Consumer)} to access the sibling components
     *
     * @return A list of all siblings including the component from which this instance originated.
     */
    public final List<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 new ArrayList<>(_siblingsSource());
    }

    /**
     *  Use this to access the sibling components of this delegate in the swing thread.
     *  This method will make sure that the passed lambda will
     *  be executed by the Swing thread.
     *  <br><br>
     * @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.
     */
    public final void forSiblinghood( Consumer<List<JComponent>> action ) {
        if ( UI.thisIsUIThread() )
            action.accept(getSiblinghood());
        else
            UI.run( () -> action.accept(getSiblinghood()) );
    }

    /**
     *  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, possibly including the delegated component itself. <br>
     *  Note that this method may only be called by the Swing thread.
     *  If another thread calls this method, an exception will be thrown.
     *  Use {@link #forSiblinghoodOfType(Class, Consumer)} to access the sibling components
     *  of the specified type in a thread-safe way.
     *
     * @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.
     */
    public final <T extends JComponent> List<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 new ArrayList<>(_siblingsSource())
                .stream()
                .filter( s -> type.isAssignableFrom(s.getClass()) )
                .map( s -> (T) s )
                .collect(Collectors.toList());
    }

    /**
     *  Use this to access all sibling components (including the one represented by this delegate)
     *  of the specified type in the swing thread.
     *  This method will make sure that the passed lambda will
     *  be executed by the Swing thread.
     *  <br><br>
     * @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 list of all siblings of the specified type,
     *               including the component from which this instance originated,
     *               which will be executed by the Swing thread.
     */
    public final <T extends JComponent> void forSiblinghoodOfType(
        Class<T> type, Consumer<List<T>> action
    ) {
        if ( UI.thisIsUIThread() )
            action.accept(getSiblinghoodOfType(type));
        else
            UI.run( () -> action.accept(getSiblinghoodOfType(type)) );
    }

}