From 3a7f37ab5e95b68aace571dad7997697be35f01f Mon Sep 17 00:00:00 2001 From: Pihkaal Date: Tue, 10 Feb 2026 19:27:44 +0100 Subject: [PATCH] feat(utils): useButtonNavigation now exposes pressed button --- app/components/Screen.vue | 24 ++++++++++++++++ app/composables/useButtonNavigation.ts | 39 +++++++++++++++++++++++++- app/composables/useScreen.ts | 14 ++++++++- 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/app/components/Screen.vue b/app/components/Screen.vue index 7510ac5..2e591b5 100644 --- a/app/components/Screen.vue +++ b/app/components/Screen.vue @@ -3,6 +3,7 @@ const canvas = useTemplateRef("canvas"); const renderCallbacks = new Map(); const screenClickCallbacks = new Set(); +const screenMouseDownCallbacks = new Set(); const screenMouseWheelCallbacks = new Set(); let ctx: CanvasRenderingContext2D | null = null; @@ -20,6 +21,11 @@ const registerScreenClickCallback = (callback: ScreenClickCallback) => { return () => screenClickCallbacks.delete(callback); }; +const registerScreenMouseDownCallback = (callback: ScreenClickCallback) => { + screenMouseDownCallbacks.add(callback); + return () => screenMouseDownCallbacks.delete(callback); +}; + const registerScreenMouseWheelCallback = ( callback: ScreenMouseWheelCallback, ) => { @@ -42,6 +48,21 @@ const handleCanvasClick = (event: MouseEvent) => { } }; +const handleCanvasMouseDown = (event: MouseEvent) => { + if (!canvas.value) return; + + const rect = canvas.value.getBoundingClientRect(); + const scaleX = LOGICAL_WIDTH / rect.width; + const scaleY = LOGICAL_HEIGHT / rect.height; + + const x = (event.clientX - rect.left) * scaleX; + const y = (event.clientY - rect.top) * scaleY; + + for (const callback of screenMouseDownCallbacks) { + callback(x, y); + } +}; + const handleCanvasWheel = (event: WheelEvent) => { for (const callback of screenMouseWheelCallbacks) { callback(event.deltaY, event.deltaX); @@ -87,6 +108,7 @@ const renderFrame = (timestamp: number) => { provide("registerRenderCallback", registerRenderCallback); provide("registerScreenClickCallback", registerScreenClickCallback); +provide("registerScreenMouseDownCallback", registerScreenMouseDownCallback); provide("registerScreenMouseWheelCallback", registerScreenMouseWheelCallback); onMounted(() => { @@ -98,6 +120,7 @@ onMounted(() => { ctx.imageSmoothingEnabled = false; canvas.value.addEventListener("click", handleCanvasClick); + canvas.value.addEventListener("mousedown", handleCanvasMouseDown); canvas.value.addEventListener("wheel", handleCanvasWheel, { passive: true }); animationFrameId = requestAnimationFrame(renderFrame); @@ -110,6 +133,7 @@ onUnmounted(() => { if (canvas.value) { canvas.value.removeEventListener("click", handleCanvasClick); + canvas.value.removeEventListener("mousedown", handleCanvasMouseDown); canvas.value.removeEventListener("wheel", handleCanvasWheel); } }); diff --git a/app/composables/useButtonNavigation.ts b/app/composables/useButtonNavigation.ts index d177a7d..db7b33d 100644 --- a/app/composables/useButtonNavigation.ts +++ b/app/composables/useButtonNavigation.ts @@ -187,7 +187,40 @@ export const useButtonNavigation = >({ selectedButton.value = targetButton; }; - const { onClick } = useScreen(); + const { onClick, onMouseDown } = useScreen(); + + const pressedButton = ref(null); + let lastPressedButton: Entry | null = null; + + onMouseDown((x: number, y: number) => { + if (blockInteractions.value) return; + + for (const [buttonName, buttonRect] of Object.entries(buttons) as [ + Entry, + Rect, + ][]) { + const [sx, sy, sw, sh] = buttonRect; + if (x >= sx && x <= sx + sw && y >= sy && y <= sy + sh) { + if (canClickButton && !canClickButton(buttonName)) continue; + + pressedButton.value = buttonName; + lastPressedButton = buttonName; + break; + } + } + }); + + const handleMouseUp = () => { + pressedButton.value = null; + }; + + onMounted(() => { + document.addEventListener("mouseup", handleMouseUp); + }); + + onUnmounted(() => { + document.removeEventListener("mouseup", handleMouseUp); + }); onClick((x: number, y: number) => { if (blockInteractions.value) return; @@ -200,6 +233,9 @@ export const useButtonNavigation = >({ if (x >= sx && x <= sx + sw && y >= sy && y <= sy + sh) { if (canClickButton && !canClickButton(buttonName)) continue; + if (lastPressedButton !== buttonName) break; + lastPressedButton = null; + if (selectedButton.value === buttonName) { onActivate?.(buttonName); } else { @@ -328,6 +364,7 @@ export const useButtonNavigation = >({ return { selected: readonly(selectedButton), + pressed: readonly(pressedButton), selectorPosition, select, }; diff --git a/app/composables/useScreen.ts b/app/composables/useScreen.ts index 8951b50..d51a826 100644 --- a/app/composables/useScreen.ts +++ b/app/composables/useScreen.ts @@ -15,6 +15,9 @@ export const useScreen = () => { const registerClick = inject<(cb: ScreenClickCallback) => () => void>( "registerScreenClickCallback", ); + const registerMouseDown = inject<(cb: ScreenClickCallback) => () => void>( + "registerScreenMouseDownCallback", + ); const registerWheel = inject<(cb: ScreenMouseWheelCallback) => () => void>( "registerScreenMouseWheelCallback", ); @@ -37,6 +40,15 @@ export const useScreen = () => { }); }; + const onMouseDown = (callback: ScreenClickCallback) => { + onMounted(() => { + if (!registerMouseDown) + throw new Error("useScreen must be used within a Screen component"); + const unregister = registerMouseDown(callback); + onUnmounted(unregister); + }); + }; + const onMouseWheel = (callback: ScreenMouseWheelCallback) => { onMounted(() => { if (!registerWheel) @@ -46,5 +58,5 @@ export const useScreen = () => { }); }; - return { onRender, onClick, onMouseWheel }; + return { onRender, onClick, onMouseDown, onMouseWheel }; };