UNPKG

@teaui/core

Version:

A high-level terminal UI library for Node

313 lines 11.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MouseManager = void 0; const geometry_1 = require("../geometry"); const events_1 = require("../events"); function mouseKey(x, y) { return `${~~x},${~~y}`; } class MouseManager { #prevListener; #mouseListeners = new Map(); #mouseMoveViews = []; #mouseDownEvent; #mousePosition; reset() { if (this.#mouseDownEvent || !this.#mousePosition) { this.#prevListener = undefined; } else { this.#prevListener = this.getMouseListener(this.#mousePosition.x, this.#mousePosition.y); } this.#mouseListeners = new Map(); } /** * @return boolean Whether the mouse.move targets changed */ commit(system) { if (this.#mouseDownEvent || !this.#mousePosition) { return false; } const listener = this.getMouseListener(this.#mousePosition.x, this.#mousePosition.y); const prev = new Set(this.#prevListener?.move.map(target => target.view) ?? []); const next = new Set(listener?.move.map(target => target.view) ?? []); let same = prev.size === next.size; if (same) { for (const view of prev) { if (!next.has(view)) { same = false; break; } } } if (!same) { this.trigger({ type: 'mouse', name: 'mouse.move.in', button: 'unknown', ctrl: false, meta: false, shift: false, ...this.#mousePosition, }, system); } return !same; } /** * Multiple views can claim the mouse.move event; they will all receive it. * Only the last view to claim button or wheel events will receive those events. */ registerMouse(view, offset, point, eventNames) { const resolved = offset.offset(point); const key = mouseKey(resolved.x, resolved.y); const target = { view, offset, }; const listener = this.#mouseListeners.get(key) ?? { move: [] }; for (const eventName of eventNames) { if (eventName === 'mouse.move') { // search listener.move - only keep views that are in the current views // ancestors listener.move.unshift(target); } else if (eventName.startsWith('mouse.button.')) { switch (eventName) { case 'mouse.button.left': listener.buttonLeft = target; break; case 'mouse.button.middle': listener.buttonMiddle = target; break; case 'mouse.button.right': listener.buttonRight = target; break; case 'mouse.button.all': listener.buttonAll = target; break; } } else if (eventName === 'mouse.wheel') { listener.wheel = target; } this.#mouseListeners.set(key, listener); } } checkMouse(view, x, y) { const listener = this.getMouseListener(x, y); if (!listener) { return; } const ancestors = new Set([view]); for (let parent = view.parent; !!parent; parent = parent.parent) { ancestors.add(parent); } ; [ 'buttonAll', 'buttonLeft', 'buttonMiddle', 'buttonRight', 'wheel', ].forEach(prop => { const target = listener[prop]; if (!target) { return; } listener[prop] = ancestors.has(target.view) ? target : undefined; }); listener.move = listener.move.filter(({ view }) => ancestors.has(view)); this.#mouseListeners.set(mouseKey(x, y), listener); } hasMouseDownListener(x, y, event) { const listener = this.getMouseListener(x, y); if (!listener) { return false; } switch (event.button) { case 'left': return Boolean(listener.buttonLeft || listener.buttonAll); case 'middle': return Boolean(listener.buttonMiddle || listener.buttonAll); case 'right': return Boolean(listener.buttonRight || listener.buttonAll); } return false; } getMouseListener(x, y) { return this.#mouseListeners.get(mouseKey(x, y)); } trigger(systemEvent, system) { if (systemEvent.name === 'mouse.button.down' && !this.hasMouseDownListener(systemEvent.x, systemEvent.y, systemEvent)) { system.focusManager.unfocus(); return; } this.#mousePosition = new geometry_1.Point(systemEvent.x, systemEvent.y); if (systemEvent.name === 'mouse.move.in' && this.#mouseDownEvent) { return this.trigger({ ...systemEvent, name: 'mouse.button.up', button: this.#mouseDownEvent.button, }, system); } if (this.#mouseDownEvent) { // ignore scroll wheel if (!(0, events_1.isMouseButton)(systemEvent)) { return; } this.#dragMouse(systemEvent, this.#mouseDownEvent, system); if (systemEvent.name === 'mouse.button.up') { this.#moveMouse({ ...systemEvent, name: 'mouse.move.in' }, system); } } else if ((0, events_1.isMouseButton)(systemEvent)) { this.#pressMouse(systemEvent, system); } else if ((0, events_1.isMouseWheel)(systemEvent)) { this.#scrollMouse(systemEvent, system); } else { this.#moveMouse(systemEvent, system); } } #getListener(systemEvent) { return this.#getListeners(systemEvent)[0]; } #getListeners(systemEvent) { const listener = this.getMouseListener(systemEvent.x, systemEvent.y); if (!listener) { return []; } if ((0, events_1.isMouseButton)(systemEvent)) { let target; switch (systemEvent.button) { case 'left': target = listener.buttonLeft ?? listener.buttonAll; break; case 'middle': target = listener.buttonMiddle ?? listener.buttonAll; break; case 'right': target = listener.buttonRight ?? listener.buttonAll; break; default: return []; } return target ? [target] : []; } else if ((0, events_1.isMouseWheel)(systemEvent)) { return listener.wheel ? [listener.wheel] : []; } else { return listener.move; } } #sendMouse(systemEvent, eventName, target, system) { const position = new geometry_1.Point(systemEvent.x - target.offset.x, systemEvent.y - target.offset.y); const event = { ...systemEvent, name: eventName, position, }; target.view.receiveMouse(event, system); } #dragMouse(systemEvent, mouseDown, unboundSystem) { if (systemEvent.name === 'mouse.button.up') { this.#mouseDownEvent = undefined; } const { target } = mouseDown; if (!target) { return; } const isInside = this.#getListener(systemEvent)?.view === target.view; const system = unboundSystem.bind(target.view); if (systemEvent.name === 'mouse.button.up') { if (isInside) { this.#sendMouse(systemEvent, 'mouse.button.up', target, system); } else { this.#sendMouse(systemEvent, 'mouse.button.cancel', target, system); } } else { if (isInside && target.wasInside) { this.#sendMouse(systemEvent, 'mouse.button.dragInside', target, system); } else if (isInside) { this.#sendMouse(systemEvent, 'mouse.button.enter', target, system); } else if (target.wasInside) { this.#sendMouse(systemEvent, 'mouse.button.exit', target, system); } else { this.#sendMouse(systemEvent, 'mouse.button.dragOutside', target, system); } target.wasInside = isInside; this.#mouseDownEvent = { ...mouseDown, target }; } } #pressMouse(systemEvent, system) { const listener = this.#getListener(systemEvent); if (listener) { this.#sendMouse(systemEvent, 'mouse.button.down', listener, system.bind(listener.view)); this.#mouseDownEvent = { target: { view: listener.view, offset: listener.offset, wasInside: true }, button: systemEvent.button, }; } } #scrollMouse(systemEvent, system) { const listener = this.#getListener(systemEvent); if (listener) { this.#sendMouse(systemEvent, systemEvent.name, listener, system.bind(listener.view)); } } #moveMouse(systemEvent, unboundSystem) { const listeners = this.#getListeners(systemEvent); let prevListeners = this.#mouseMoveViews; let isFirst = true; for (const listener of listeners) { let didEnter = true; prevListeners = prevListeners.filter(prev => { if (prev.view === listener.view) { didEnter = false; return false; } return true; }); const system = unboundSystem.bind(listener.view); if (didEnter) { this.#sendMouse(systemEvent, 'mouse.move.enter', listener, system); } if (isFirst) { this.#sendMouse(systemEvent, 'mouse.move.in', listener, system); } else { this.#sendMouse(systemEvent, 'mouse.move.below', listener, system); } isFirst = false; } this.#mouseMoveViews = listeners; for (const listener of prevListeners) { const system = unboundSystem.bind(listener.view); this.#sendMouse(systemEvent, 'mouse.move.exit', listener, system); } } } exports.MouseManager = MouseManager; function checkEventNames(systemEvent) { switch (systemEvent.name) { case 'mouse.move.in': return ['mouse.move']; case 'mouse.button.down': case 'mouse.button.up': return [`mouse.button.${systemEvent.button}`, 'mouse.button.all']; case 'mouse.wheel.up': case 'mouse.wheel.down': case 'mouse.wheel.left': case 'mouse.wheel.right': return ['mouse.wheel']; } } //# sourceMappingURL=MouseManager.js.map