UNPKG

@spearwolf/twopoint5d

Version:

a library to create 2.5d realtime graphics and pixelart with three.js

259 lines 9.64 kB
import { emit, eventize, off } from '@spearwolf/eventize'; import { Stylesheets } from '../display/Stylesheets.js'; import { InputControlBase } from './InputControlBase.js'; import { readOption } from './readOption.js'; var HideCursorState; (function (HideCursorState) { HideCursorState[HideCursorState["NO"] = 0] = "NO"; HideCursorState[HideCursorState["MAYBE"] = 1] = "MAYBE"; HideCursorState[HideCursorState["YES"] = 2] = "YES"; })(HideCursorState || (HideCursorState = {})); const mergePan = (states) => states.reduce(({ panX, panY }, state) => { panX += state.panX; panY += state.panY; state.panX = 0; state.panY = 0; return { panX, panY }; }, { panX: 0, panY: 0, }); const MOUSE = 'mouse'; const KEYUP = 'keyup'; const KEYDOWN = 'keydown'; const POINTERUP = 'pointerup'; const POINTERDOWN = 'pointerdown'; const POINTERMOVE = 'pointermove'; export class PanControl2D extends InputControlBase { #pointersDown; #cursorPanStyle; #cursorPanClass; #cursorStylesTarget; #hideCursorState; #pointerDisabled; #keyboardDisabled; constructor(options) { super(); this.pixelsPerSecond = 0; this.speedNorth = 0; this.speedEast = 0; this.speedSouth = 0; this.speedWest = 0; this.#pointersDown = new Map(); this.#hideCursorState = HideCursorState.NO; this.#pointerDisabled = false; this.#keyboardDisabled = false; this.#installCursorPanStyleRules = () => Stylesheets.installRule('PanControl2D', `cursor: ${this.#cursorPanStyle || 'auto'}`); this.#isFirstPanViewUpdate = true; this.#onPointerDown = (event) => { if (this.#isPanPointer(event)) { const pointersDown = this.#pointersDown; if (!pointersDown.has(event.pointerId)) { const { x: lastX, y: lastY } = this.#toRelativeCoords(event); pointersDown.set(event.pointerId, { pointerType: event.pointerType, lastX, lastY, panX: 0, panY: 0, }); } if (event.pointerType === MOUSE) { if (this.#hideCursorState === HideCursorState.NO) { this.#hideCursorState = HideCursorState.MAYBE; } } } }; this.#onPointerUp = (event) => { const pointersDown = this.#pointersDown; if (this.#isPanPointer(event)) { const state = pointersDown.get(event.pointerId); if (state) { this.#updatePanState(event, state); pointersDown.delete(event.pointerId); } } if (event.pointerType === MOUSE) { if (!Array.from(pointersDown.values()).find((state) => state.pointerType === MOUSE)) { this.#restoreCursorStyle(); } } }; this.#onPointerMove = (event) => { if (this.#isPanPointer(event)) { const state = this.#pointersDown.get(event.pointerId); if (state) { this.#updatePanState(event, state); if (this.#hideCursorState === HideCursorState.MAYBE) { this.#hideCursor(); } } } if (event.pointerType === MOUSE && event.buttons === 0) { this.#restoreCursorStyle(); } }; this.#onKeyDown = ({ keyCode }) => { const { pixelsPerSecond } = this; switch (keyCode) { case this.keyCodes[0]: this.speedNorth = pixelsPerSecond; break; case this.keyCodes[1]: this.speedSouth = pixelsPerSecond; break; case this.keyCodes[2]: this.speedWest = pixelsPerSecond; break; case this.keyCodes[3]: this.speedEast = pixelsPerSecond; break; } }; this.#onKeyUp = ({ keyCode }) => { switch (keyCode) { case this.keyCodes[0]: this.speedNorth = 0; break; case this.keyCodes[1]: this.speedSouth = 0; break; case this.keyCodes[2]: this.speedWest = 0; break; case this.keyCodes[3]: this.speedEast = 0; break; } }; eventize(this); this.panView = options?.state; this.pixelsPerSecond = readOption(options, 'speed', 100); this.cursorPanStyle = readOption(options, 'cursorPanStyle', 'none'); this.#cursorStylesTarget = readOption(options, 'cursorStylesTarget', document.body); this.mouseButton = readOption(options, 'mouseButton', 1); this.keyCodes = readOption(options, 'keyCodes', [87, 83, 65, 68]); this.pointerDisabled = readOption(options, 'disablePointer', false); this.keyboardDisabled = readOption(options, 'disableKeyboard', false); } get cursorPanStyle() { return this.#cursorPanStyle; } set cursorPanStyle(value) { if (this.#cursorPanStyle !== value) { this.#cursorPanStyle = value; this.#cursorPanClass = this.#installCursorPanStyleRules(); } } #installCursorPanStyleRules; #panView; #isFirstPanViewUpdate; get panView() { return this.#panView; } set panView(panView) { const prevPanView = this.#panView; this.#panView = panView ?? { x: 0, y: 0, pixelRatio: globalThis.devicePixelRatio ?? 1 }; this.#isFirstPanViewUpdate = prevPanView !== this.#panView; } get keyboardDisabled() { return this.#keyboardDisabled; } set keyboardDisabled(value) { this.#keyboardDisabled = value; if (!value) { this.addEventListener(document, KEYDOWN, this.#onKeyDown); this.addEventListener(document, KEYUP, this.#onKeyUp); } else { this.removeEventListener(document, KEYDOWN, this.#onKeyDown); this.removeEventListener(document, KEYUP, this.#onKeyUp); } } get pointerDisabled() { return this.#pointerDisabled; } set pointerDisabled(value) { this.#pointerDisabled = value; if (!value) { this.addEventListener(document, POINTERDOWN, this.#onPointerDown); this.addEventListener(document, POINTERUP, this.#onPointerUp); this.addEventListener(document, POINTERMOVE, this.#onPointerMove); } else { this.removeEventListener(document, POINTERDOWN, this.#onPointerDown); this.removeEventListener(document, POINTERUP, this.#onPointerUp); this.removeEventListener(document, POINTERMOVE, this.#onPointerMove); } } update(t) { const { x: prevX, y: prevY } = this.panView; this.panView.y -= this.speedNorth * t; this.panView.y += this.speedSouth * t; this.panView.x += this.speedEast * t; this.panView.x -= this.speedWest * t; if (!this.#pointerDisabled) { const { panX, panY } = mergePan(Array.from(this.#pointersDown.values())); const pixelRatio = this.panView.pixelRatio || 1; this.panView.x -= panX / pixelRatio; this.panView.y -= panY / pixelRatio; } if (!this.#keyboardDisabled || !this.#pointerDisabled) { if (this.#isFirstPanViewUpdate || prevX !== this.panView.x || prevY !== this.panView.y) { emit(this, 'update', { x: this.panView.x, y: this.panView.y }); } if (this.#isFirstPanViewUpdate) { this.#isFirstPanViewUpdate = false; } } } #isPanPointer(event) { if (event.isPrimary) { if (event.type !== POINTERUP && event.pointerType === MOUSE) { return event.buttons & this.mouseButton; } return true; } return false; } #onPointerDown; #hideCursor() { this.#hideCursorState = HideCursorState.YES; if (this.#cursorPanClass && this.#cursorStylesTarget) { this.#cursorStylesTarget.classList.add(this.#cursorPanClass); } emit(this, 'hideCursor', this); } #onPointerUp; #restoreCursorStyle() { this.#hideCursorState = HideCursorState.NO; if (this.#cursorPanClass && this.#cursorStylesTarget) { this.#cursorStylesTarget.classList.remove(this.#cursorPanClass); } emit(this, 'restoreCursor', this); } #onPointerMove; #updatePanState(event, state) { const { x, y } = this.#toRelativeCoords(event); state.panX += x - state.lastX; state.panY += y - state.lastY; state.lastX = x; state.lastY = y; } #toRelativeCoords(event) { const { clientX, clientY } = event; const { left, top } = event.target.getBoundingClientRect(); return { x: clientX - left, y: clientY - top, }; } #onKeyDown; #onKeyUp; dispose() { this.destroyAllListeners(); off(this); } } //# sourceMappingURL=PanControl2D.js.map