tarinai/js/ui_pointer_action_system.js
2026-06-29 21:02:04 +09:00

121 lines
3.9 KiB
JavaScript

"use strict";
// Shared pointer action router for mouse and touch. Keeps long-hold timers,
// drag movement thresholds, and tap suppression consistent across input types.
(function (global) {
const DRAG_THRESHOLD = 3;
function createPointerActionManager(ctx) {
if (!ctx?.canvas || !ctx?.world || !ctx?.uiCache) return null;
const { canvas, world, uiCache } = ctx;
function clearHoseHoldTimer() {
if (uiCache.hoseHoldTimer) {
clearInterval(uiCache.hoseHoldTimer);
uiCache.hoseHoldTimer = null;
}
}
function ensureHoseHoldTimer(clientX, clientY, options = {}) {
clearHoseHoldTimer();
uiCache.hoseHoldTimer = setInterval(() => {
if (!uiCache.hosing) {
clearHoseHoldTimer();
return;
}
const cx = uiCache.hoseLastClientX || uiCache.touchLastX || clientX;
const cy = uiCache.hoseLastClientY || uiCache.touchLastY || clientY;
const hp = ctx.screenToWorldClient(cx, cy);
ctx.moveHose(hp, cx, cy, { touch: Boolean(options.touch) });
}, 90);
}
function beginToolAt(p, clientX, clientY, options = {}) {
if (!p?.inside) return false;
const tool = String(world.tool || "");
if (tool === "area_delete") {
return Boolean(ctx.beginAreaDelete(p, clientX, clientY, options));
}
if (tool === "water_hose") {
const started = Boolean(ctx.beginHose(p, clientX, clientY, options));
if (started) ensureHoseHoldTimer(clientX, clientY, options);
return started;
}
if (tool === "shoot") {
return Boolean(ctx.beginShoot(p, clientX, clientY, options));
}
if (tool === "pinch") {
const started = Boolean(ctx.beginGrab(p, clientX, clientY, options));
if (started) canvas.style.cursor = "grabbing";
return started;
}
return false;
}
function moveActive(p, clientX, clientY, options = {}) {
if (uiCache.areaDeleting) return Boolean(ctx.moveAreaDelete(p, clientX, clientY, options) || true);
if (uiCache.hosing) return Boolean(ctx.moveHose(p, clientX, clientY, options) || true);
if (uiCache.shooting) return Boolean(ctx.moveShoot(p, clientX, clientY, options));
if (uiCache.grabbing) {
const sx = uiCache.grabStartX || clientX;
const sy = uiCache.grabStartY || clientY;
if (Math.hypot(clientX - sx, clientY - sy) > DRAG_THRESHOLD) {
uiCache.grabMoved = true;
if (options.touch) uiCache.touchMoved = true;
}
return Boolean(ctx.moveGrab(p, clientX, clientY, options));
}
return false;
}
function endActive(clientX = 0, clientY = 0) {
if (ctx.endAreaDelete()) return true;
if (ctx.endHose()) {
clearHoseHoldTimer();
return true;
}
if (ctx.endShoot()) return true;
if (ctx.endGrab(clientX, clientY)) return true;
return false;
}
function shouldSuppressClick() {
const flags = ["areaDeleteMoved", "hoseMoved", "grabMoved", "shootSuppressClick", "panMoved"];
for (const key of flags) {
if (uiCache[key]) {
uiCache[key] = false;
return true;
}
}
return false;
}
function dispatchTapAt(p) {
if (!p?.inside) return false;
global.TarinaiCommands.dispatch(world, { type: "pointer.primary", x: p.x, y: p.y });
global.renderStats?.();
return true;
}
function dispatchTapClient(clientX, clientY) {
return dispatchTapAt(ctx.screenToWorldClient(clientX, clientY));
}
function cancel() {
clearHoseHoldTimer();
ctx.cancelPointerActions?.();
}
return Object.freeze({
beginToolAt,
moveActive,
endActive,
shouldSuppressClick,
dispatchTapAt,
dispatchTapClient,
cancel,
});
}
global.TarinaiPointerActionSystem = Object.freeze({ create: createPointerActionManager });
})(typeof window !== "undefined" ? window : globalThis);