input_handler_mouse_handler.js

import InputHandler from "./input_handler.js";
import EventHandlerLayer from "../../event/event_handler_layer.js";
import Vec2 from "../../game/vector/vec2.js";

/**
 * @module MouseHandler
 * @fileoverview Contains MouseHandler class.
 */

/**
 * Mouse input handler.
 * @class
 * @extends InputHandler
 */
class MouseHandler extends InputHandler {
    /**
     * Create a new mouse input handler.
     * @param {EventHandler|EventHandlerLayer} parentHandler Parent event handler or layer
     * @param {function} condition Function for checking whether events should run
     * @constructor
     */
    constructor(parentHandler, condition = () => { return true; }) {
        const handler = new EventHandlerLayer(parentHandler, "mouse/");
        super(handler, "", condition);

        this._other = handler;

        this._pos = new Vec2();

        this._captured = false;

        this._downHandler = this._handleDown.bind(this);
        this._upHandler = this._handleUp.bind(this);
        this._moveHandler = this._handleMove.bind(this);
        this._wheelHandler = this._handleWheel.bind(this);

        this._blurHandler = this._handleBlur.bind(this);

        this._init();
    }

    destructor() {
        super.destructor();

        document.removeEventListener("mousedown", this._downHandler);
        document.removeEventListener("mouseup", this._upHandler);
        document.removeEventListener("mousemove", this._moveHandler);
        document.removeEventListener("wheel", this._wheelHandler);

        document.removeEventListener("blur", this._blurHandler);
    }

    /**
     * Set up mouse input listening.
     * @private
     */
    _init() {
        document.addEventListener("mousedown", this._downHandler);
        document.addEventListener("mouseup", this._upHandler);
        document.addEventListener("mousemove", this._moveHandler);
        document.addEventListener("wheel", this._wheelHandler);

        window.addEventListener("blur", this._blurHandler);
    }


    // Getters

    /**
     * Get the current position of the mouse.
     * @returns {Vec2} Current position of mouse.
     */
    get pos() {
        return this._pos.copy();
    }

    /**
     * Check if the mouse is currently within the canvas.
     * @returns {boolean} Whether mouse is within canvas
     */
    isInWindow() {
        const pos = this.pos;
        const c = $$.canvas;

        return pos.x >= 0 && pos.x <= c.width && pos.y >= 0 && pos.y <= c.height;
    }


    // Mouse movement

    /**
     * Add a function to listen for mouse movement.
     * @param {function} func Function to run upon mouse movement
     */
    onMove(func = () => {}) {
        this._other.on("move", func);
    }

    /**
     * Handle mouse movement.
     * @param {number} x Current mouse position along the x-axis
     * @param {number} y Current mouse position along the y-axis
     * @private
     */
    _doMove(x, y) {
        if (!this._condition())
            return;

        const newPos = new Vec2(x, y);
        const d = newPos.sub(this._pos);

        this._pos = newPos;

        this._other.do("move", d);
    }


    // Mouse scroll wheel

    /**
     * Add a function to listen for the mouse scroll wheel.
     * @param {function} func Function to run upon scrolling
     */
    onScroll(func = () => {}) {
        this._other.on("scroll", func);
    }

    /**
     * Handle scrolling.
     * @param {number} val Scroll value
     * @private
     */
    _doScroll(val) {
        if (!this._condition())
            return;

        this._other.do("scroll", val);
    }


    // Cursor

    /**
     * Set the mouse cursor ("default" by default).
     * @param {string} cursor New cursor
     */
    setCursor(cursor = "default") {
        if (!this._condition())
            return;

        document.body.style.cursor = cursor;
    }


    // Handling button presses

    /**
     * Handle the event of pressing down a button.
     * @param {MouseEvent} event Event from document
     * @private
     */
    _handleDown(event) {
        if (!this.gameFocused())
            return;

        const button = event.button;

        if (!this._currentlyDown.has(button))
            this._doPress(button);
        this._currentlyDown.add(button);
        this._doDown(button);
    }

    /**
     * Handle the event of releasing a button.
     * @param {MouseEvent} event Event from document
     * @private
     */
    _handleUp(event) {
        const button = event.button;

        if (!this.gameFocused()) {
            this._currentlyDown.delete(button);
            return;
        }

        this._currentlyDown.delete(button);
        this._doRelease(button);
    }

    /**
     * Handle the event of mouse movement.
     * @param {MouseEvent} event Event from document
     * @private
     */
    _handleMove(event) {
        this._doMove(event.pageX - $$.app.canvas.offsetLeft, event.pageY - $$.app.canvas.offsetTop);
    }

    /**
     * Handle the event of pressing down a button.
     * @param {WheelEvent} event Event from document
     * @private
     */
    _handleWheel(event) {
        this._doScroll(-Math.sign(event.deltaY));
    }

    /**
     * Handle the event of the page losing focus.
     * @private
     */
    _handleBlur() {
        this._currentlyDown.clear();
    }


    // Capture mouse

    /**
     * Capture and "claim" the mouse
     * @returns {boolean} Whether capture was successful
     */
    capture() {
        if (this._captured)
            return false;

        this._captured = true;
        return true;
    }

    /**
     * Release the mouse.
     */
    release() {
        this._captured = false;
    }

    /**
     * Check if the mouse is currently captured.
     * @returns {boolean} Whether the mouse is captured
     */
    isCaptured() {
        return this._captured;
    }
}

export default MouseHandler;