tarinai/js/ui_input_touch.js

126 lines
5.7 KiB
JavaScript

"use strict";
(function (global) {
function bindTouchInput(ctx) {
if (!ctx?.canvas) return false;
const { canvas, world, uiCache } = ctx;
const actions = global.TarinaiPointerActionSystem.create(ctx);
ctx.bindMobileModeControls();
const isMobileAutoScrollMode = () => {
const mode = ctx.mobileInputMode?.() || "auto";
const touchMobile = document.body.classList.contains("touch-mobile-ui") || window.matchMedia?.("(max-width: 980px)")?.matches;
return touchMobile && mode === "auto" && (!world.tool || world.tool === "observe");
};
canvas.addEventListener("touchstart", (e) => {
if (!e.touches?.length) return;
uiCache.touchMoved = false;
const mode = ctx.mobileInputMode();
const autoPageScroll = isMobileAutoScrollMode();
if (!autoPageScroll) e.preventDefault();
if (mode === "camera" && e.touches.length === 1) {
const t = e.touches[0];
uiCache.touchStartX = t.clientX;
uiCache.touchStartY = t.clientY;
uiCache.touchLastX = t.clientX;
uiCache.touchLastY = t.clientY;
uiCache.touchMode = "cameraPan";
return;
}
if (e.touches.length >= 2) {
const c = ctx.touchCenter(e.touches);
uiCache.touchMode = "pinchZoom";
uiCache.pinchStartDistance = Math.max(1, ctx.touchDistance(e.touches));
uiCache.pinchStartZoom = world.fieldZoom || 1;
uiCache.pinchLastCenterX = c?.clientX || 0;
uiCache.pinchLastCenterY = c?.clientY || 0;
return;
}
const t = e.touches[0];
const p = ctx.screenToWorldClient(t.clientX, t.clientY);
uiCache.touchStartX = t.clientX;
uiCache.touchStartY = t.clientY;
uiCache.touchLastX = t.clientX;
uiCache.touchLastY = t.clientY;
if (mode === "tool" && world.tool === "observe") {
uiCache.touchMode = "toolTapOnly";
return;
}
const touchModeByTool = { shoot: "shoot", area_delete: "areaDelete", water_hose: "hose", pinch: "grab" };
const touchMode = touchModeByTool[world.tool];
if (touchMode && actions?.beginToolAt?.(p, t.clientX, t.clientY, { touch: true, touchMode })) return;
uiCache.touchMode = autoPageScroll ? "autoTapOrScroll" : (mode === "tool" ? "toolTapOnly" : "panOrTap");
}, { passive: false });
canvas.addEventListener("touchmove", (e) => {
if (!e.touches?.length || !uiCache.touchMode) return;
if (uiCache.touchMode !== "autoTapOrScroll") e.preventDefault();
if (uiCache.touchMode === "pinchZoom" && e.touches.length >= 2) {
const c = ctx.touchCenter(e.touches);
const distNow = Math.max(1, ctx.touchDistance(e.touches));
const ratio = distNow / Math.max(1, uiCache.pinchStartDistance || distNow);
ctx.setZoomAroundClientPoint(c.clientX, c.clientY, (uiCache.pinchStartZoom || 1) * ratio);
const dx = c.clientX - (uiCache.pinchLastCenterX || c.clientX);
const dy = c.clientY - (uiCache.pinchLastCenterY || c.clientY);
uiCache.pinchLastCenterX = c.clientX;
uiCache.pinchLastCenterY = c.clientY;
if (Math.hypot(dx, dy) > 0.1) world.panCamera?.(-dx, -dy);
uiCache.touchMoved = true;
render();
return;
}
const t = e.touches[0];
const p = ctx.screenToWorldClient(t.clientX, t.clientY);
const screenDx = t.clientX - (uiCache.touchLastX || t.clientX);
const screenDy = t.clientY - (uiCache.touchLastY || t.clientY);
const totalMove = Math.hypot(t.clientX - (uiCache.touchStartX || t.clientX), t.clientY - (uiCache.touchStartY || t.clientY));
uiCache.touchLastX = t.clientX;
uiCache.touchLastY = t.clientY;
if (uiCache.touchMode === "cameraPan") {
if (totalMove > 2) uiCache.touchMoved = true;
world.panCamera?.(-screenDx, -screenDy);
render();
return;
}
if (uiCache.touchMode === "toolTapOnly") {
if (totalMove > 8) uiCache.touchMoved = true;
return;
}
if (actions?.moveActive?.(p, t.clientX, t.clientY, { touch: true, renderFrame: uiCache.touchMode === "grab" })) return;
if (uiCache.touchMode === "autoTapOrScroll") {
if (totalMove > 7) uiCache.touchMoved = true;
return;
}
if (uiCache.touchMode === "panOrTap") {
if (totalMove > 7) {
uiCache.touchMoved = true;
world.panCamera?.(-screenDx, -screenDy);
render();
}
}
}, { passive: false });
canvas.addEventListener("touchend", (e) => {
if (uiCache.touchMode !== "autoTapOrScroll") e.preventDefault();
if (actions?.endActive?.(e.changedTouches?.[0]?.clientX || uiCache.touchLastX || 0, e.changedTouches?.[0]?.clientY || uiCache.touchLastY || 0)) {
// Active tool ended; click suppression is handled through movement flags.
} else if (uiCache.touchMode === "toolTapOnly" && !uiCache.touchMoved) {
actions?.dispatchTapClient?.(uiCache.touchLastX || uiCache.touchStartX, uiCache.touchLastY || uiCache.touchStartY);
} else if (uiCache.touchMode === "autoTapOrScroll" && !uiCache.touchMoved) {
e.preventDefault();
actions?.dispatchTapClient?.(uiCache.touchLastX || uiCache.touchStartX, uiCache.touchLastY || uiCache.touchStartY);
} else if (uiCache.touchMode === "panOrTap" && !uiCache.touchMoved) {
actions?.dispatchTapClient?.(uiCache.touchLastX || uiCache.touchStartX, uiCache.touchLastY || uiCache.touchStartY);
}
if (!e.touches?.length) {
uiCache.touchMode = "";
uiCache.touchMoved = false;
}
}, { passive: false });
canvas.addEventListener("touchcancel", () => actions?.cancel?.(), { passive: true });
return true;
}
global.TarinaiTouchInput = Object.freeze({ bindTouchInput });
})(window);