@spearwolf/twopoint5d
Version:
a library to create 2.5d realtime graphics and pixelart with three.js
259 lines • 9.64 kB
JavaScript
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