Repository

js/Mobilizing/input/Mouse.js

import * as debug from '../core/util/Debug';
import Time from '../time/Time';
import Device from '../core/util/Device';
import * as _DOM from '../core/util/Dom';
import Component from '../core/Component';

// Events
/**
* Fired when the mouse postupdate is called
* @event mouseupdate
*/
const EVT_MOUSE_UPDATE = "mouseupdate";

/**
* Fired when a mouse move is detected
* @event mousemove
*/
const EVT_MOUSE_MOVE = 'mousemove';

/**
* Fired when the pointer moves onto the target element
* @event mouseover
*/
const EVT_MOUSE_OVER = 'mouseover';

/**
* Fired when the pointer moves off the target element
* @event mouseout
*/
const EVT_MOUSE_OUT = 'mouseout';

/**
* Fired when a button is pressed
* @event mousepress
* @param {Number} button The button number pressed
*/
const EVT_MOUSE_PRESS = 'mousepress';

/**
* Fired when a mouse move is detected and a button is being pressed
* @event mousedrag
*/
const EVT_MOUSE_DRAG = 'mousedrag';

/**
* Fired when a button is released
* @event mouserelease
*/
const EVT_MOUSE_RELEASE = 'mouserelease';

/**
* Fired when a button is pressed and released on the target element
* @event mouseclick
* @param {Number} button The button number pressed
*/
const EVT_MOUSE_CLICK = 'mouseclick';

/**
* Fired when a button is clicked twice on the target element
* @event mousedblclick
* @param {Number} button The button number pressed
*/
const EVT_MOUSE_DBLCLICK = 'mousedblclick';

/**
* Fired when the wheel button is rotated
* @event mousewheel
* @param {Number} deltaX The scroll amount for the x-axis
* @param {Number} deltaY The scroll amount for the y-axis
* @param {Number} deltaZ The scroll amount for the z-axis
*/
const EVT_MOUSE_WHEEL = 'mousewheel';

const EVT_SWIPE_UP = "swipeup";
const EVT_SWIPE_DOWN = "swipedown";
const EVT_SWIPE_LEFT = "swipeleft";
const EVT_SWIPE_RIGHT = "swiperight";

/**
* Give simple access to mouse events
*/
export default class Mouse extends Component {
    /**
    * @param {Object} params Parameters object, given by the constructor.
    * @param {DOMElement} params.target The DOM element that will be used to attach mouse events on
    */
    constructor({
        target = window,
    } = {}) {
        super(...arguments);

        this.target = target;
        this._time = new Time();
        this.chain(this._time);

        //window.addEventListener("resize", (event) => this.onWindowResize(event), false);
    }

    setup() {
        if (!this._setupDone) {
            this._time.setup();
            this._time.on();

            this._x = null;
            this._y = null;

            this._pX = null;
            this._pY = null;

            this._dragStartX = null;
            this._dragStartY = null;

            // keeps track of total wheel deltas between two updates
            this._wheelDeltaX = 0;
            this._wheelDeltaY = 0;
            this._wheelDeltaZ = 0;

            // keeps track of whether a button is being pressed
            this._pressed = false;

            // keeps track of whether the wheel has been activated since last update
            this._wheelActivated = false;

            // keeps track of whether the wheel values should be reset during next update
            this._resetWheel = false;

            this._swipeMaxTime = 250;
            this._swipeMinDist = 100;

            super.setup();
        }

    }

    on() {
        super.on();

        if (!this.target) {
            debug.error("Can't activate the mouse input component without a target");
            return;
        }

        this.target.addEventListener("mousemove", (event) => this.onMouseMove(event));
        this.target.addEventListener("mouseover", (event) => this.onMouseOver(event));
        this.target.addEventListener("mouseout", (event) => this.onMouseOut(event));
        this.target.addEventListener("mousedown", (event) => this.onMouseDown(event));
        this.target.addEventListener("mouseup", (event) => this.onMouseUp(event));
        this.target.addEventListener("click", (event) => this.onClick(event));
        this.target.addEventListener("dblclick", (event) => this.onDblClick(event));
        this.target.addEventListener("wheel", (event) => this.onWheel(event));
    }

    off() {
        super.off();

        if (!this.target) {
            debug.error("Can't deactivate the mouse input component without a target");
            return;
        }

        this.target.removeEventListener("mousemove", (event) => this.onMouseMove(event));
        this.target.removeEventListener("mouseover", (event) => this.onMouseOver(event));
        this.target.removeEventListener("mouseout", (event) => this.onMouseOut(event));
        this.target.removeEventListener("mousedown", (event) => this.onMouseDown(event));
        this.target.removeEventListener("mouseup", (event) => this.onMouseUp(event));
        this.target.removeEventListener("click", (event) => this.onClick(event));
        this.target.removeEventListener("dblclick", (event) => this.onDblClick(event));
        this.target.removeEventListener("wheel", (event) => this.onWheel(event));
    }

    preUpdate() {
        // check if the wheel was already active at previous update and if it needs to be reset
        if (this._wheelActivated) {
            if (this._resetWheel) {
                this._wheelActivated = false;
                this._resetWheel = false;

                this._wheelDeltaX = 0;
                this._wheelDeltaY = 0;
                this._wheelDeltaZ = 0;
            }
            else {
                this._resetWheel = true;
            }
        }
    }

    // update()
    // {
    // }

    postUpdate() {
        // update the previous x and y coordinates
        this._pX = this._x;
        this._pY = this._y;

        this.events.trigger(EVT_MOUSE_UPDATE, {
            "pX": this.getPX(),
            "pY": this.getPY(),
            "x": this.getX(),
            "y": this.getY()
        });

    }

    /**
    mousemove event handler
    Fires a mousemove event and possibly a mousedrag event
    @private
    */
    onMouseMove(event) {
        if (!(this.target instanceof Window)) {
            const position = _DOM.getElementPosition(this.target);

            this._x = event.clientX - position.x;
            this._y = event.clientY - position.y;
        }
        else {
            this._x = event.clientX;
            this._y = event.clientY;
        }

        this.events.trigger(EVT_MOUSE_MOVE, {
            "pX": this.getPX(),
            "pY": this.getPY(),
            "x": this.getX(),
            "y": this.getY()
        });

        if (this.isPressed()) {
            this.events.trigger(EVT_MOUSE_DRAG, {
                "pX": this.getPX(),
                "pY": this.getPY(),
                "x": this.getX(),
                "y": this.getY()
            });
        }

        event.preventDefault();
    }

    /**
    mouseover event handler
    Fires a mouseover event
    @private
    */
    onMouseOver(event) {
        this.events.trigger(EVT_MOUSE_OVER);

        event.preventDefault();
    }

    /**
    mouseover event handler
    Fires a mouseout event
    @private
    */
    onMouseOut(event) {
        this.events.trigger(EVT_MOUSE_OUT);

        event.preventDefault();
    }

    /**
    mousedown event handler
    Fires a mousepress event and updates the pressed state
    @private
    */
    onMouseDown(event) {
        this._pressed = true;

        this._dragStartX = this.getX();
        this._dragStartY = this.getY();

        this.events.trigger(EVT_MOUSE_PRESS, event.button, this.getX(), this.getY());

        //swipe management
        this._swipeStartTime = this._time.currentTime;

        event.preventDefault();
    }

    /**
    mouseup event handler
    Fires a mouserelease event and updates the pressed state
    @private
    */
    onMouseUp(event) {
        //swipe
        let swiping = false;

        this._swipeCurrentTime = this._time.currentTime;
        this._swipeTime = this._swipeCurrentTime - this._swipeStartTime;

        if (this._swipeTime < this._swipeMaxTime) {
            let xAxisValidated = false;

            //x + axis validation
            if (this.getDragOffsetX() > this.getDragOffsetY()) {
                if (this.getDragOffsetX() > this._swipeMinDist) {
                    //console.log("swipeX+");
                    this.events.trigger(EVT_SWIPE_RIGHT);
                    xAxisValidated = true;
                    swiping = true;
                }
            }
            //x - axis validated
            else if (this.getDragOffsetX() < this.getDragOffsetY()) {
                if (this.getDragOffsetX() < -this._swipeMinDist) {
                    //console.log("swipeX-");
                    this.events.trigger(EVT_SWIPE_LEFT);
                    xAxisValidated = true;
                    swiping = true;
                }
            }

            //avoid double axis swipe
            if (!xAxisValidated) {
                //y + axis validation
                if (this.getDragOffsetY() > this.getDragOffsetX()) {
                    if (this.getDragOffsetY() > this._swipeMinDist) {
                        //console.log("swipeY+");
                        this.events.trigger(EVT_SWIPE_DOWN);
                        swiping = true;
                    }
                }
                //y - axis validated
                else if (this.getDragOffsetY() < -this.getDragOffsetX()) {
                    if (this.getDragOffsetY() < -this._swipeMinDist) {
                        //console.log("swipeY-");
                        this.events.trigger(EVT_SWIPE_UP);
                        swiping = true;
                    }
                }
            }
        }

        this._pressed = false;

        this._dragStartX = null;
        this._dragStartY = null;

        //to avoid unwanted double behavior : swipe is activated, but release also. "up" is not fired if swipe is ongoing
        if (!swiping) {
            this.events.trigger(EVT_MOUSE_RELEASE);
        }
        event.preventDefault();
    }

    /**
    cick event handler
    Fires a mouseclick event
    @private
    */
    onClick(event) {
        this.events.trigger(EVT_MOUSE_CLICK, event.button, this.getX(), this.getY());

        //swipe management
        this._swipeStartTime = this._time.currentTime;

        event.preventDefault();
    }

    /**
    dblcick event handler
    Fires a mousedblclick event
    @private
    */
    onDblClick(event) {
        this.events.trigger(EVT_MOUSE_DBLCLICK, event.button, this.getX(), this.getY());

        event.preventDefault();
    }

    /**
    wheel event handler
    Fires a mousewheel event
    @private
    */
    onWheel(event) {
        this._wheelActivated = true;

        this._wheelDeltaX += event.deltaX;
        this._wheelDeltaY += event.deltaY;
        this._wheelDeltaZ += event.deltaZ;

        this.events.trigger(EVT_MOUSE_WHEEL, event.deltaX, event.deltaY, event.deltaZ);

        //event.preventDefault();
    }

    /**
    returns whether a mouse button is currently pressed or not
    @return {Boolean} True if a mouse button is currently pressed
    */
    isPressed() {
        return this._pressed;
    }

    /**
    returns the current x coordinate of the mouse
    @return {Number} The x coordinate of the mouse position
    */
    getX() {
        return this._x;
    }

    /**
    returns the current y coordinate of the mouse
    @return {Number} The y coordinate of the mouse position
    */
    getY() {
        return this._y;
    }

    /**
    returns the previous x coordinate of the mouse
    @return {Number} The x coordinate of the previous mouse position
    */
    getPX() {
        return this._pX;
    }

    /**
    returns the previous y coordinate of the mouse
    @return {Number} The y coordinate of the previous mouse position
    */
    getPY() {
        return this._pY;
    }

    /**
    returns the x delta between the previous and current mouse positions
    @return {Number} The x delta value
    */
    getDeltaX() {
        return this._x - this._pX;
    }

    /**
    returns the y delta between the previous and current mouse positions
    @return {Number} The y delta value
    */
    getDeltaY() {
        return this._y - this._pY;
    }

    /**
    returns the x and y deltas between the previous and current mouse positions
    @return {Object} The x and y delta values
    */
    getDelta() {
        return {
            "x": this.getDeltaX(),
            "y": this.getDeltaY()
        };
    }

    /**
    returns the x offset between the current mouse position and the position at the begining of the drag
    @return {Number} The x offset value
    */
    getDragOffsetX() {
        if (!this.isPressed()) {
            return 0;
        }

        return this.getX() - this._dragStartX;
    }

    /**
    returns the y offset between the current mouse position and the position at the begining of the drag
    @return {Number} The y offset value
    */
    getDragOffsetY() {
        if (!this.isPressed()) {
            return 0;
        }

        return this.getY() - this._dragStartY;
    }

    /**
    returns the x and y offsets between the current mouse position and the position at the begining of the drag
    @return {Object} The x and y offset values
    */
    getDragOffset() {
        return {
            'x': this.getDragOffsetX(),
            'y': this.getDragOffsetY()
        };
    }

    /**
    helper method to check if the wheel is supported on the device
    @return {Boolean} True if the wheel is supported, false otherwise
    */
    isWheelSupported() {
        return (Device.getType() === "desktop") && (window.WheelEvent !== undefined);
    }

    /**
    helper method to check if the wheel has been rotated
    @return {Boolean} True if the wheel has been rotated, false otherwise
    */
    isWheelActivated() {
        return this._wheelActivated;
    }

    /**
    returns the scroll amount for the x-axis
    @return {Number} The scroll amount for the x-axis
    */
    getWheelDeltaX() {
        return this._wheelDeltaX;
    }

    /**
    returns the scroll amount for the y-axis
    @return {Number} The scroll amount for the y-axis
    */
    getWheelDeltaY() {
        return this._wheelDeltaY;
    }

    /**
    returns the scroll amount for the z-axis
    @return {Number} The scroll amount for the z-axis
    */
    getWheelDeltaZ() {
        return this._wheelDeltaZ;
    }

    /**
    returns the scroll amounts for the x, y and z axes
    @return {Object} The scroll amounts for the x, y and z axes
    */
    getWheelDelta() {
        return {
            "x": this.getWheelDeltaX(),
            "y": this.getWheelDeltaY(),
            "z": this.getWheelDeltaZ()
        };
    }
}