You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
243 lines
6.6 KiB
243 lines
6.6 KiB
/* 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<InputReceiver>} */
|
|
this.recieverStack = [];
|
|
|
|
/** @type {Array<function(any) : boolean>} */
|
|
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));
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
/**
|
|
* @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,
|
|
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,
|
|
});
|
|
}
|
|
}
|