MouseManager.js

import { EventSystem } from "201flaviosilva-utils";

/**
 * Mouse button keys
 * @memberof DOM
 */
export const MouseButton = {
	left: 0,
	middle: 1,
	right: 2,
};

/**
 * @class MouseManager
 * @classdesc Manages the mouse input.
 * 
 * @example
 * const mouse = new DOM.MouseManager();
 * const { x, y } = mouse;
 * console.log(x, y);
 * 
 * @example
 * import { DOM } from "201flaviosilva-labs";
 * const { MouseManager, MouseManagerInstance, MouseButton, } = DOM
 * const beepDiv = document.getElementById("MyDiv");
 * const mouse = new MouseManager(true);
 * setInterval(() => console.log(mouse.getPosition(beepDiv)), 1000);
 * MouseManagerInstance.events.on("buttonDown-left", () => console.log("down -> left"));
 * MouseManagerInstance.events.on("buttonDown-right", () => console.log("up -> right"));

 * 
 * @param {Boolean} [preventDefault=false] - Prevent the Default behavior on press right button
 * @memberof DOM
 */
export class MouseManager {
	constructor(preventDefault = false) {
		this.x = 0;
		this.y = 0;

		this.MouseButton = MouseButton;

		/**
		 * All pressed buttons will appear here
		 * 
		 * @example console.log(DOM.MouseManager.isPressed["left"]);
		 * @example console.log(DOM.MouseManager.isPressed[DOM.MouseManager.getNameByKeyCode(0)]);
		 * 
		 * @memberof DOM.MouseManager
		 */
		this.isDown = {};

		/**
		 * A event system to get on a key is pressed
		 * 
		 * @example DOM.MouseManagerInstance.events.on("buttonDown-left", () => console.log("down -> left"));
		 * @example DOM.MouseManagerInstance.events.on("buttonUp-left", () => console.log("up -> left"));
		 * 
		 * @memberof DOM.MouseManager
		 */
		this.events = new EventSystem();

		// --- Private Events
		window.addEventListener("mousemove", this._mousemove.bind(this));
		window.addEventListener("mousedown", this._mousedown.bind(this));
		window.addEventListener("mouseup", this._mouseup.bind(this));
		preventDefault && window.addEventListener("contextmenu", e => e.preventDefault()); // Right click show options
	}

	/**
	 * Returns the mouse position to the given DOM Element
	 * 
	 * If no given Element is passed returns de mouse in the window localization
	 * 
	 * @example
	 * const { x, y } = DOM.MouseManager.getPosition();
	 * const { x, y } = DOM.MouseManager.getPosition(document.getElementById("myDiv"));
	 * 
	 * @param {HTMLElement} [DOMElement=undefined] - The DOM Element to check the mouse position
	 * @returns {Object} The current mouse position {x, y}
	 * @memberof DOM.MouseManager
	 */
	getPosition(DOMElement) {
		if (!DOMElement) return { x: this.x, y: this.y, };

		const { x, y } = DOMElement.getBoundingClientRect();
		return { x: this.x - x, y: this.y - y, }
	}

	/**
	 * Returns the name of the button by the button code.
	 * 
	 * @example
	 * console.log(DOM.MouseManager.getNameByButtonCode(1)); // "middle"
	 * 
	 * @param {number} buttonCode - The button code
	 * @returns {string} The name of the button
	 * @memberof DOM.MouseManager
	 */
	getNameByButtonCode(buttonCode) {
		switch (buttonCode) {
			case MouseButton.left: return "left";
			case MouseButton.middle: return "middle";
			case MouseButton.right: return "right";
			default: return "";
		}
	}

	/**
	 * Returns the code of the button by the button name.
	 * 
	 * @example
	 * console.log(DOM.MouseManager.getButtonCodeByName("left")); // 0
	 * 
	 * @param {string} buttonName - The name of the button
	 * @returns {number} The button code
	 * @memberof DOM.MouseManager
	 */
	getButtonKeyByName(name) {
		switch (name) {
			case "left": return MouseButton.left;
			case "middle": return MouseButton.middle;
			case "right": return MouseButton.right;
			default: return -1;
		}
	}

	/**
	 * Returns if the button is pressed.
	 * 
	 * @example
	 * DOM.MouseManager.isButtonPressed("left") // True
	 * 
	 * @param {string|number} button - The button name or code
	 * @returns {boolean} True if the button is pressed
	 * @memberof DOM.MouseManager
	 */
	isButtonDown(button) {
		if (typeof button === "string") return this.isButtonDownByName(button);
		else if (typeof button === "number") return this.isButtonDownByButtonCode(button);
	}

	/**
	 * Returns if the button is pressed by the button name.
	 * 
	 * @example
	 * DOM.MouseManager.isButtonDownByName("left") // True
	 * 
	 * @param {string|number} button - The button name
	 * @returns {boolean} True if the button is pressed
	 * @memberof DOM.MouseManager
	 */
	isButtonDownByName(name) { return !!this.isDown[name]; }

	/**
	 * Returns if the button is pressed by the button code.
	 * 
	 * @example
	 * DOM.MouseManager.isButtonDownByName(2) // True
	 * 
	 * @param {string|number} button - The button code
	 * @returns {boolean} True if the button is pressed
	 * @memberof DOM.MouseManager
	 */
	isButtonDownByButtonCode(buttonCode) { return !!this.isDown[this.getNameByButtonCode(buttonCode)]; }


	// ------
	/**
	 * Private (Core) function to handle the mouse position.
	 * 
	 * @memberof KeyBoard
	 * @private
	 */
	_updateMousePosition(e) {
		const { clientX, clientY } = e;
		this.x = clientX;
		this.y = clientY;
	}

	/**
	 * Private (Core) function to handle the mouse position.
	 * 
	 * @memberof KeyBoard
	 * @private
	 */
	_mousemove(e) { this._updateMousePosition(e); }

	/**
	 * Private (Core) function to handle the mouse position.
	 * 
	 * @memberof KeyBoard
	 * @private
	 */
	_mousedown(e) {
		this._updateMousePosition(e);

		const name = this.getNameByButtonCode(e.button);

		this.isDown[name] = true;
		this.events.emit(`buttonDown-${name}`, { code: e.button, name, });
	}

	/**
	 * Private (Core) function to handle the mouse position.
	 * 
	 * @memberof KeyBoard
	 * @private
	 */
	_mouseup(e) {
		this._updateMousePosition(e);

		const name = this.getNameByButtonCode(e.button);

		this.isDown[name] = false;
		this.events.emit(`buttonUp-${name}`, { code: e.button, name, });
	}
}

/**
 * @example
 * const { x, y } = MouseManagerInstance;
 * console.log(x, y);
 * 
 * @memberof DOM
 */
export const MouseManagerInstance = new MouseManager();