other_Inputs.js

// TODO: Keyup events with multiple buttons work even if the letter key wasn't what was released first
/**
 * Handles key and mouse inputs
 */
class Inputs {
	constructor() {
		window.addEventListener("keydown", event => this.#handleKeydown.call(this, event));
		window.addEventListener("keyup", event => this.#handleKeyup.call(this, event));

		window.addEventListener("mousedown", event => this.#handleMousedown.call(this, event))
		window.addEventListener("mouseup", event => this.#handleMouseup.call(this, event))
	}
	#handleKeydown(event) {
		if (event.repeat) return;

		let key = event.key.toLowerCase();
		let fullKeyName = (event.ctrlKey ? "ctrl" : "") + (event.altKey ? "alt" : "") + (event.shiftKey ? "shift" : "") + key;
		this.#pressed.add(key);

		
		if (this.#binds[fullKeyName]) {
			this.trigger(fullKeyName, true);
		}
		else if (this.#binds[key]) {
			this.trigger(key, true);
		}
	}
	#handleKeyup(event) {
		if (event.repeat) return;

		let key = event.key.toLowerCase();
		let fullKeyName = (event.ctrlKey ? "ctrl" : "") + (event.altKey ? "alt" : "") + (event.shiftKey ? "shift" : "") + key;

		this.#pressed.delete(key);
		
		if (this.#binds[fullKeyName]) {
			this.trigger(fullKeyName, false);
		}
		else if (this.#binds[key]) {
			this.trigger(key, false);
		}
	}
	#handleMousedown(event) {
		let fullName = "mouse" + event.button;
		if (this.#binds[fullName]) {
			this.trigger(fullName, true);
		}
	}
	#handleMouseup(event) {
		let fullName = "mouse" + event.button;
		if (this.#binds[fullName]) {
			this.trigger(fullName, false);
		}
	}
	
	/**
	 * Call to disable the context menu when the user right clicks the window
	 */
	blockRightClick() {
		window.addEventListener("contextmenu", event => {
			event.preventDefault();
		});
	}

	/**
	 * Checks if a key input name is valid and formatted correctly
	 * @param {string} event - Name of key bind
	 * @returns {boolean} If the event is formatted correctly
	 */
	isValidKeyEvent(event) {
		if (event === " ") return true;
		return event.replace(/(ctrl)?(alt)?(shift)?[a-zA-Z]+/i, "").length === 0;
	}
	/**
	 * Checks if a mouse input name is valid and formatted correctly
	 * @param {string} event - Name of key bind
	 * @returns {boolean} If the event is formatted correctly
	 */
	isValidMouseEvent(event) {
		return event.replace(/(mouse)\d+/i, "").length === 0;
	}

	/**
	 * Check if key(s) are currently being pressed
	 * @param {...string} keys - Key to check
	 * @returns {boolean} If the set of keys is pressed
	 * @example
	 * inputs.isPressed("d");
	 * inputs.isPressed("ctrl", "alt", "shift", "s"); // can be in any order
	 */
	isPressed(...keys) {
		if (keys.length === 0) return false;
		for (let k of keys) {
			if (!this.#pressed.has(k)) // A key is not pressed
				return false;
		}
		// All keys are pressed
		return true;
	}

	#pressed = new Set();
	#binds = {};

	/**
	 * Bind a callback to an event
	 * @param {string} event - Keys pressed in event
	 * @param {Function} callback - Callback run when event is fired
	 * @example
	 * // key events
	 * inputs.on("a", keydown => { // called when 'a' is pressed down or up
	 * 	if (keydown) { // 'a' key is depressed
	 * 		// do some logic
	 * 	}
	 * 	else { // 'a' key is no longer depressed
	 * 		// logic
	 * 	}
	 * });
	 * inputs.on("altW", keydown => {}); // alt + w
	 * inputs.on("ctrlAltShiftH", keydown => {}); // ctrl + alt + shift + h. Must be in this order, but can take out ctrl/alt/shift as needed
	 * inputs.on(" ", keydown => {}); // space is pressed
	 * 
	 * // mouse events
	 * inputs.on("mouse0", mousedown => {}); // left click
	 * inputs.on("mouse1", mousedown => {}); // middle click
	 * inputs.on("mouse2", mousedown => {}); // right click
	 * 
	 */
	on(event, callback) {
		event = event.toLowerCase();
		if (this.isValidKeyEvent(event) || this.isValidMouseEvent(event)) {
			if (!this.#binds[event]) this.#binds[event] = [];
			this.#binds[event.toLowerCase()].push(callback);
		}
		else {
			console.warn(event + " is not a valid event");
		}
	}
	/**
	 * Unbinds a callback from an event
	 * @param {string} event - Keys pressed in event
	 * @param {Function} callback - Function to unbind
	 */
	off(event, callback) {
		let events = this.#binds[event];
		if (events.includes(callback)) {
			events.splice(events.indexOf(callback), 1);
		}
	}
	/**
	 * Triggers an event, firing all bound callbacks
	 * @param {string} event - Name of the event
	 * @param {...*} args - Arguments passed to callbacks
	 */
	trigger(event, ...args) {
		// Trigger each event
		if (this.#binds[event]) {
			this.#binds[event].forEach(callback => {
				callback(...args);
			});
		}
	}
}
module.exports = Inputs;