All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.fxmisc.wellbehaved.event.InputMap Maven / Gradle / Ivy

package org.fxmisc.wellbehaved.event;

import java.util.Arrays;
import java.util.Objects;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;

import javafx.event.Event;
import javafx.event.EventType;

import org.fxmisc.wellbehaved.event.InputHandler.Result;

/**
 *
 * @param  type of events that this {@linkplain InputMap} may handle.
 * That is, {@code InputMap} certainly does not handle any events that
 * are not of type {@code E}; it does not mean it handles any
 * event of type {@code E}.
 */
@FunctionalInterface
public interface InputMap {

    static final InputMap EMPTY = handlerConsumer -> {};
    static  InputMap empty() { return (InputMap) EMPTY; }

    @FunctionalInterface
    static interface HandlerConsumer {
         void accept(EventType t, InputHandler h);
    }

    void forEachEventType(HandlerConsumer f);

    default InputMap orElse(InputMap that) {
        return sequence(this, that);
    }

    default InputMap without(InputMap that) {
        return this.equals(that) ? empty() : this;
    }

    /**
     * Executes some additional handler if the event was consumed
     */
    default InputMap ifConsumed(Consumer postConsumption) {
        return handlerConsumer -> InputMap.this.forEachEventType(new HandlerConsumer() {

            @Override
            public  void accept(EventType t, InputHandler h) {
                InputHandler h2 = e -> {
                    Result res = h.process(e);
                    if(res == Result.CONSUME) {
                        postConsumption.accept(e);
                    }
                    return res;
                };
                handlerConsumer.accept(t, h2);
            }

        });
    }

    static  InputMap upCast(InputMap inputMap) {
        // Unsafe cast is justified by this type-safe equivalent expression:
        // InputMap res = f -> inputMap.forEachEventType(f);
        @SuppressWarnings("unchecked")
        InputMap res = (InputMap) inputMap;
        return res;
    }

    @SafeVarargs
    static  InputMap sequence(InputMap... inputMaps) {
        return new InputMapChain<>(inputMaps);
    }

    public static  InputMap process(
            EventPattern eventPattern,
            Function action) {
        return new PatternActionMap<>(eventPattern, action);
    }

    public static  InputMap process(
            EventType eventType,
            Function action) {
        return process(EventPattern.eventType(eventType), action);
    }

    public static  InputMap consume(
            EventPattern eventPattern,
            Consumer action) {
        return process(eventPattern, u -> {
            action.accept(u);
            return Result.CONSUME;
        });
    }

    public static  InputMap consume(
            EventType eventType,
            Consumer action) {
        return consume(EventPattern.eventType(eventType), action);
    }

    public static  InputMap consume(
            EventPattern eventPattern) {
        return process(eventPattern, u -> Result.CONSUME);
    }

    public static  InputMap consume(
            EventType eventType) {
        return consume(EventPattern.eventType(eventType));
    }

    public static  InputMap consumeWhen(
            EventPattern eventPattern,
            BooleanSupplier condition,
            Consumer action) {
        return process(eventPattern, u -> {
            if(condition.getAsBoolean()) {
                action.accept(u);
                return Result.CONSUME;
            } else {
                return Result.PROCEED;
            }
        });
    }

    public static  InputMap consumeWhen(
            EventType eventType,
            BooleanSupplier condition,
            Consumer action) {
        return consumeWhen(EventPattern.eventType(eventType), condition, action);
    }

    public static  InputMap consumeUnless(
            EventPattern eventPattern,
            BooleanSupplier condition,
            Consumer action) {
        return consumeWhen(eventPattern, () -> !condition.getAsBoolean(), action);
    }

    public static  InputMap consumeUnless(
            EventType eventType,
            BooleanSupplier condition,
            Consumer action) {
        return consumeUnless(EventPattern.eventType(eventType), condition, action);
    }

    public static  InputMap ignore(
            EventPattern eventPattern) {
        return new PatternActionMap<>(eventPattern, PatternActionMap.CONST_IGNORE);
    }

    public static  InputMap ignore(
            EventType eventType) {
        return ignore(EventPattern.eventType(eventType));
    }

    public static  InputMap when(
            BooleanSupplier condition, InputMap im) {

        return new InputMap() {

            @Override
            public void forEachEventType(HandlerConsumer f) {
                HandlerConsumer g = new HandlerConsumer() {

                    @Override
                    public  void accept(
                            EventType t, InputHandler h) {
                        f.accept(t, evt -> condition.getAsBoolean() ? h.process(evt) : Result.PROCEED);
                    }

                };

                im.forEachEventType(g);
            }
        };
    }

    public static  InputMap unless(
            BooleanSupplier condition, InputMap im) {
        return when(() -> !condition.getAsBoolean(), im);
    }
}

class PatternActionMap implements InputMap {
    static final Function CONST_IGNORE = x -> Result.IGNORE;

    private final EventPattern pattern;
    private final Function action;

    PatternActionMap(EventPattern pattern, Function action) {
        this.pattern = pattern;
        this.action  = action;
    }

    @Override
    public void forEachEventType(HandlerConsumer f) {
        InputHandler h = t -> pattern.match(t).map(action::apply).orElse(Result.PROCEED);
        pattern.getEventTypes().forEach(et -> f.accept(et, h));
    }

    @Override
    public boolean equals(Object other) {
        if(other instanceof PatternActionMap) {
            PatternActionMap that = (PatternActionMap) other;
            return Objects.equals(this.pattern, that.pattern)
                && Objects.equals(this.action,  that.action);
        } else {
            return false;
        }
    }

    @Override
    public int hashCode() {
        return Objects.hash(pattern, action);
    }
}

class InputMapChain implements InputMap {
    private final InputMap[] inputMaps;

    @SafeVarargs
    InputMapChain(InputMap... inputMaps) {
        this.inputMaps = inputMaps;
    }

    @Override
    public void forEachEventType(HandlerConsumer f) {
        InputHandlerMap ihm = new InputHandlerMap();
        for(InputMap im: inputMaps) {
            im.forEachEventType(ihm::insertAfter);
        }
        ihm.forEach(f);
    }

    @Override
    public InputMap without(InputMap that) {
        if(this.equals(that)) {
            return InputMap.empty();
        } else {
            @SuppressWarnings("unchecked")
            InputMap[] ims = (InputMap[]) Stream.of(inputMaps)
                    .map(im -> im.without(that))
                    .filter(im -> im != EMPTY)
                    .toArray(n -> new InputMap[n]);
            switch(ims.length) {
                case 0: return InputMap.empty();
                case 1: return InputMap.upCast(ims[0]);
                default: return new InputMapChain<>(ims);
            }
        }
    }

    @Override
    public boolean equals(Object other) {
        if(other instanceof InputMapChain) {
            InputMapChain that = (InputMapChain) other;
            return Arrays.equals(this.inputMaps, that.inputMaps);
        } else {
            return false;
        }
    }

    @Override
    public int hashCode() {
        return Arrays.hashCode(inputMaps);
    }
}