/* typehints:start */ import { Application } from "../application"; import { InputReceiver } from "./input_receiver"; /* typehints:end */ import { Signal, STOP_PROPAGATION } from "./signal"; import { createLogger } from "./logging"; import { arrayDeleteValue, fastArrayDeleteValue } from "./utils"; const logger = createLogger("input_distributor"); export class InputDistributor { /** * * @param {Application} app */ constructor(app) { this.app = app; /** @type {Array} */ this.recieverStack = []; /** @type {Array} */ this.filters = []; /** * All keys which are currently down */ this.keysDown = new Set(); this.bindToEvents(); } /** * Attaches a new filter which can filter and reject events * @param {function(any): boolean} filter */ installFilter(filter) { this.filters.push(filter); } /** * Removes an attached filter * @param {function(any) : boolean} filter */ dismountFilter(filter) { fastArrayDeleteValue(this.filters, filter); } /** * @param {InputReceiver} reciever */ pushReciever(reciever) { if (this.isRecieverAttached(reciever)) { assert(false, "Can not add reciever " + reciever.context + " twice"); logger.error("Can not add reciever", reciever.context, "twice"); return; } this.recieverStack.push(reciever); if (this.recieverStack.length > 10) { logger.error( "Reciever stack is huge, probably some dead receivers arround:", this.recieverStack.map(x => x.context) ); } } /** * @param {InputReceiver} reciever */ popReciever(reciever) { if (this.recieverStack.indexOf(reciever) < 0) { assert(false, "Can not pop reciever " + reciever.context + " since its not contained"); logger.error("Can not pop reciever", reciever.context, "since its not contained"); return; } if (this.recieverStack[this.recieverStack.length - 1] !== reciever) { logger.warn( "Popping reciever", reciever.context, "which is not on top of the stack. Stack is: ", this.recieverStack.map(x => x.context) ); } arrayDeleteValue(this.recieverStack, reciever); } /** * @param {InputReceiver} reciever */ isRecieverAttached(reciever) { return this.recieverStack.indexOf(reciever) >= 0; } /** * @param {InputReceiver} reciever */ isRecieverOnTop(reciever) { return ( this.isRecieverAttached(reciever) && this.recieverStack[this.recieverStack.length - 1] === reciever ); } /** * @param {InputReceiver} reciever */ makeSureAttachedAndOnTop(reciever) { this.makeSureDetached(reciever); this.pushReciever(reciever); } /** * @param {InputReceiver} reciever */ makeSureDetached(reciever) { if (this.isRecieverAttached(reciever)) { arrayDeleteValue(this.recieverStack, reciever); } } /** * * @param {InputReceiver} reciever */ destroyReceiver(reciever) { this.makeSureDetached(reciever); reciever.cleanup(); } // Internal getTopReciever() { if (this.recieverStack.length > 0) { return this.recieverStack[this.recieverStack.length - 1]; } return null; } bindToEvents() { window.addEventListener("popstate", this.handleBackButton.bind(this), false); document.addEventListener("backbutton", this.handleBackButton.bind(this), false); window.addEventListener("keydown", this.handleKeyMouseDown.bind(this)); window.addEventListener("keyup", this.handleKeyMouseUp.bind(this)); window.addEventListener("mousedown", this.handleKeyMouseDown.bind(this)); window.addEventListener("mouseup", this.handleKeyMouseUp.bind(this)); window.addEventListener("blur", this.handleBlur.bind(this)); document.addEventListener("paste", this.handlePaste.bind(this)); } forwardToReceiver(eventId, payload = null) { // Check filters for (let i = 0; i < this.filters.length; ++i) { if (!this.filters[i](eventId)) { return STOP_PROPAGATION; } } const reciever = this.getTopReciever(); if (!reciever) { logger.warn("Dismissing event because not reciever was found:", eventId); return; } const signal = reciever[eventId]; assert(signal instanceof Signal, "Not a valid event id"); return signal.dispatch(payload); } /** * @param {Event} event */ handleBackButton(event) { event.preventDefault(); event.stopPropagation(); this.forwardToReceiver("backButton"); } /** * Handles when the page got blurred */ handleBlur() { this.forwardToReceiver("pageBlur", {}); this.keysDown.clear(); } /** * */ handlePaste(ev) { this.forwardToReceiver("paste", ev); } /** * @param {KeyboardEvent | MouseEvent} event */ handleKeyMouseDown(event) { const keyCode = event instanceof MouseEvent ? event.button + 1 : event.keyCode; if ( keyCode === 4 || // MB4 keyCode === 5 || // MB5 keyCode === 9 || // TAB keyCode === 16 || // SHIFT keyCode === 17 || // CTRL keyCode === 18 || // ALT (keyCode >= 112 && keyCode < 122) // F1 - F10 ) { event.preventDefault(); } const isInitial = !this.keysDown.has(keyCode); this.keysDown.add(keyCode); if ( this.forwardToReceiver("keydown", { keyCode: keyCode, shift: event.shiftKey, alt: event.altKey, ctrl: event.ctrlKey, initial: isInitial, event, }) === STOP_PROPAGATION ) { return; } if (keyCode === 27) { // Escape key event.preventDefault(); event.stopPropagation(); return this.forwardToReceiver("backButton"); } } /** * @param {KeyboardEvent | MouseEvent} event */ handleKeyMouseUp(event) { const keyCode = event instanceof MouseEvent ? event.button + 1 : event.keyCode; this.keysDown.delete(keyCode); this.forwardToReceiver("keyup", { keyCode: keyCode, shift: event.shiftKey, alt: event.altKey, }); } }