Package tripleplay.ui

Source Code of tripleplay.ui.Behavior$RapidFire

//
// Triple Play - utilities for use in PlayN-based games
// Copyright (c) 2011-2014, Three Rings Design, Inc. - All rights reserved.
// http://github.com/threerings/tripleplay/blob/master/LICENSE

package tripleplay.ui;

import playn.core.Pointer;
import playn.core.Pointer.Event;
import playn.core.Sound;
import pythagoras.f.IDimension;
import pythagoras.f.Point;

import react.Signal;
import react.Slot;
import react.Value;

/**
* Controls the behavior of a widget (how it responds to pointer events).
*/
public abstract class Behavior<T extends Element<T>> implements Pointer.Listener {
    /** Implements button-like behavior: selects the element when the pointer is in bounds, and
     * deselects on release. This is a pretty common case and inherited by {@link Click}. */
    public static class Select<T extends Element<T>> extends Behavior<T> {
        public Select (T owner) {
            super(owner);
        }

        @Override protected void onPress (Pointer.Event event) {
            updateSelected(true);
        }

        @Override protected void onHover (Pointer.Event event, boolean inBounds) {
            updateSelected(inBounds);
        }

        @Override protected boolean onRelease (Pointer.Event event) {
            // it's a click if we ended in bounds
            return updateSelected(false);
        }

        @Override protected void onCancel (Pointer.Event event) {
            updateSelected(false);
        }

        @Override protected void onClick (Pointer.Event event) {
            // nothing by default, subclasses wire this up as needed
        }
    }

    /** A behavior that ignores everything. This allows subclasses to easily implement a single
     * {@code onX} method. */
    public static class Ignore<T extends Element<T>> extends Behavior<T> {
        public Ignore (T owner) { super(owner); }
        @Override protected void onPress (Pointer.Event event) {}
        @Override protected void onHover (Pointer.Event event, boolean inBounds) {}
        @Override protected boolean onRelease (Pointer.Event event) { return false; }
        @Override protected void onCancel (Pointer.Event event) {}
        @Override protected void onClick (Pointer.Event event) {}
    }

    /** Implements clicking behavior. */
    public static class Click<T extends Element<T>> extends Select<T> {
        /** A delay (in milliseconds) during which the owner will remain unclickable after it has
         * been clicked. This ensures that users don't hammer away at a widget, triggering
         * multiple responses (which code rarely protects against). Inherited. */
        public static Style<Integer> DEBOUNCE_DELAY = Style.newStyle(true, 500);

        /** A signal emitted with our owner when clicked. */
        public Signal<T> clicked = Signal.create();

        public Click (T owner) {
            super(owner);
        }

        /** Triggers a click. */
        public void click () {
            soundAction();
            clicked.emit(_owner); // emit a click event
        }

        @Override public void layout () {
            super.layout();
            _debounceDelay = resolveStyle(DEBOUNCE_DELAY);
        }

        @Override protected void onPress (Pointer.Event event) {
            // ignore press events if we're still in our debounce interval
            if (event.time() - _lastClickStamp > _debounceDelay) super.onPress(event);
        }

        @Override protected void onClick (Pointer.Event event) {
            _lastClickStamp = event.time();
            click();
        }

        protected int _debounceDelay;
        protected double _lastClickStamp;
    }

    /** Implements toggling behavior. */
    public static class Toggle<T extends Element<T>> extends Behavior<T> {
        /** A signal emitted with our owner when clicked. */
        public final Signal<T> clicked = Signal.create();

        /** Indicates whether our owner is selected. It may be listened to, and updated. */
        public final Value<Boolean> selected = Value.create(false);

        public Toggle (T owner) {
            super(owner);
            selected.connect(selectedDidChange());
        }

        /** Triggers a click. */
        public void click () {
            soundAction();
            clicked.emit(_owner); // emit a click event
        }

        @Override protected void onPress (Pointer.Event event) {
            _anchorState = _owner.isSelected();
            selected.update(!_anchorState);
        }
        @Override protected void onHover (Pointer.Event event, boolean inBounds) {
            selected.update(inBounds ? !_anchorState : _anchorState);
        }
        @Override protected boolean onRelease (Pointer.Event event) {
            return _anchorState != _owner.isSelected();
        }
        @Override protected void onCancel (Pointer.Event event) {
            selected.update(_anchorState);
        }
        @Override protected void onClick (Pointer.Event event) {
            click();
        }

        protected boolean _anchorState;
    }

    /**
     * Tracks the pressed position as an anchor and delegates to subclasses to update state based
     * on anchor and drag position.
     */
    public static abstract class Track<T extends Element<T>> extends Ignore<T>
    {
        /** A distance, in event coordinates, used to decide if tracking should be temporarily
         * cancelled. If the pointer is hovered more than this distance outside of the owner's
         * bounds, the tracking will revert to the anchor position, just like when the pointer is
         * cancelled. A null value indicates that the tracking will be unconfined in this way.
         * TODO: default to 35 if no Slider uses are relying on lack of hover limit. */
        public static Style<Float> HOVER_LIMIT = Style.newStyle(true, (Float)null);

        /** Holds the necessary data for the currently active press. {@code Track} subclasses can
         * derive if more transient information is needed. */
        public class State {
            /** Time the press started. */
            public final double pressTime;

            /** The press and drag positions. */
            public final Point press, drag;

            /** How far the pointer strayed from the starting point, squared. */
            public float maxDistanceSq;

            /** Creates a new tracking state with the given starting press event. */
            public State (Pointer.Event event) {
                pressTime = event.time();
                toPoint(event, press = new Point());
                drag = new Point(press);
            }
            /** Updates the state to the current event value and called {@link Track#onTrack()}. */
            public void update (Pointer.Event event) {
                boolean cancel = false;
                toPoint(event, drag);
                if (_hoverLimit != null) {
                    float lim = _hoverLimit;
                    IDimension size = _owner.size();
                    cancel = drag.x + lim < 0 || drag.y + lim < 0 ||
                            drag.x - lim >= size.width() || drag.y - lim >= size.height();
                }
                maxDistanceSq = Math.max(maxDistanceSq, press.distanceSq(drag));
                onTrack(press, cancel ? press : drag);
            }
        }

        protected Track (T owner) {
            super(owner);
        }

        /**
         * Called when the pointer is dragged. After cancel or if the pointer goes outside the
         * hover limit, drag will be equal to anchor.
         * @param anchor the pointer position when initially pressed
         * @param drag the current pointer position
         */
        abstract protected void onTrack (Point anchor, Point drag);

        /**
         * Creates the state instance for the given press. Subclasses may return an instance
         * of a derived {@code State} if more information is needed during tracking.
         */
        protected State createState (Pointer.Event press) {
            return new State(press);
        }

        /**
         * Converts an event to coordinates consumed by {@link #onTrack(Point, Point)}. By
         * default, simply uses the local x, y.
         */
        protected void toPoint (Pointer.Event event, Point dest) {
            dest.set(event.localX(), event.localY());
        }

        @Override protected void onPress (Event event) {
            _state = createState(event);
        }

        @Override protected void onHover (Event event, boolean inBounds) {
            if (_state != null) _state.update(event);
        }

        @Override protected boolean onRelease (Event event) {
            _state = null;
            return false;
        }

        @Override protected void onCancel (Event event) {
            // track to the press position to cancel
            if (_state != null) onTrack(_state.press, _state.press);
            _state = null;
        }

        @Override public void layout () {
            super.layout();
            _hoverLimit = resolveStyle(HOVER_LIMIT);
        }

        protected State _state;
        protected Float _hoverLimit;
    }

    /** A click behavior that captures the pointer and optionally issues clicks based on some time
     * based function. */
    public static abstract class Capturing<T extends Element<T>> extends Click<T>
        implements Interface.Task
    {
        protected Capturing (T owner) {
            super(owner);
        }

        @Override protected void onPress (Event event) {
            super.onPress(event);
            event.capture();
            _task = _owner.root().iface().addTask(this);
        }

        @Override protected void onCancel (Event event) {
            super.onCancel(event);
            cancelTask();
        }

        @Override protected boolean onRelease (Event event) {
            super.onRelease(event);
            cancelTask();
            return false;
        }

        /** Cancels the time-based task. This is called automatically by the pointer release
         * and cancel events. */
        protected void cancelTask () {
            if (_task == null) return;
            _task.remove();
            _task = null;
        }

        protected Interface.TaskHandle _task;
    }

    /** Captures the pointer and dispatches one click on press, a second after an initial delay
     * and at regular intervals after that. */
    public static class RapidFire<T extends Element<T>> extends Capturing<T>
    {
        /** Milliseconds after the first click that the second click is dispatched. */
        public static final Style<Integer> INITIAL_DELAY = Style.newStyle(true, 200);

        /** Milliseconds between repeated click dispatches. */
        public static final Style<Integer> REPEAT_DELAY = Style.newStyle(true, 75);

        /** Creates a new rapid fire behavior for the given owner. */
        public RapidFire (T owner) {
            super(owner);
        }

        @Override protected void onPress (Event event) {
            super.onPress(event);
            _timeInBounds = 0;
            click();
        }

        @Override protected void onHover (Event event, boolean inBounds) {
            super.onHover(event, inBounds);
            if (!inBounds) _timeInBounds = -1;
            else if (_timeInBounds < 0) {
                _timeInBounds = 0;
                click();
            }
        }

        @Override public void update (int delta) {
            if (_timeInBounds < 0) return;
            int was = _timeInBounds;
            _timeInBounds += delta;
            int limit = was < _initDelay ? _initDelay :
                _initDelay + _repDelay * ((was - _initDelay) / _repDelay + 1);
            if (was < limit && _timeInBounds >= limit) click();
        }

        @Override public void layout () {
            super.layout();
            _initDelay = _owner.resolveStyle(INITIAL_DELAY);
            _repDelay = _owner.resolveStyle(REPEAT_DELAY);
        }

        protected int _initDelay, _repDelay, _timeInBounds;
    }

    public Behavior (T owner) {
        _owner = owner;
    }

    @Override public void onPointerStart (Pointer.Event event) {
        if (_owner.isEnabled()) onPress(event);
    }

    @Override public void onPointerDrag (Pointer.Event event) {
        if (_owner.isEnabled()) onHover(event, _owner.contains(event.localX(), event.localY()));
    }

    @Override public void onPointerEnd (Pointer.Event event) {
        if (onRelease(event)) onClick(event);
    }

    @Override public void onPointerCancel (Pointer.Event event) {
        onCancel(event);
    }

    /** Called when our owner is laid out. If the behavior needs to resolve configuration via
     * styles, this is where it should do it. */
    public void layout () {
        _actionSound = resolveStyle(Style.ACTION_SOUND);
    }

    /** Emits the action sound for our owner, if one is configured. */
    public void soundAction () {
        if (_actionSound != null) _actionSound.play();
    }

    /** Resolves the value for the supplied style via our owner. */
    protected <V> V resolveStyle (Style<V> style) {
        return Styles.resolveStyle(_owner, style);
    }

    /** Returns the {@link Root} to which our owning element is added, or null. */
    protected Root root () {
        return _owner.root();
    }

    /** Called when the pointer is pressed down on our element. */
    protected abstract void onPress (Pointer.Event event);

    /** Called as the user drags the pointer around after pressing. Derived classes map this onto
     * the widget state, such as updating selectedness. */
    protected abstract void onHover (Pointer.Event event, boolean inBounds);

    /** Called when the pointer is released after having been pressed on this widget. This should
     * return true if the gesture is considered a click, in which case {@link #onClick} will
     * be called automatically. */
    protected abstract boolean onRelease (Pointer.Event event);

    /** Called when the interaction is canceled after having been pressed on this widget. This
     * should not result in a call to {@link #onClick}. */
    protected abstract void onCancel (Pointer.Event event);

    /** Called when the pointer is released and the subclass decides that it is a click, i.e.
     * returns true from {@link #onRelease(Pointer.Event)}. */
    protected abstract void onClick (Pointer.Event event);

    /** Updates the selected state of our owner, invalidating if selectedness changes.
     * @return true if the owner was selected on entry. */
    protected boolean updateSelected (boolean selected) {
        boolean wasSelected = _owner.isSelected();
        if (selected != wasSelected) {
            _owner.set(Element.Flag.SELECTED, selected);
            _owner.invalidate();
        }
        return wasSelected;
    }

    /** Slot for calling {@link #updateSelected(boolean)}. */
    protected Slot<Boolean> selectedDidChange () {
        return new Slot<Boolean>() {
            @Override public void onEmit (Boolean selected) {
                updateSelected(selected);
            }
        };
    }

    protected final T _owner;
    protected Sound _actionSound;
}
TOP

Related Classes of tripleplay.ui.Behavior$RapidFire

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.