feat(utils): useButtonNavigation now exposes pressed button

This commit is contained in:
2026-02-10 19:27:44 +01:00
parent 3b0e725752
commit 305ef81083
3 changed files with 75 additions and 2 deletions

View File

@@ -3,6 +3,7 @@ const canvas = useTemplateRef("canvas");
const renderCallbacks = new Map<RenderCallback, number>(); const renderCallbacks = new Map<RenderCallback, number>();
const screenClickCallbacks = new Set<ScreenClickCallback>(); const screenClickCallbacks = new Set<ScreenClickCallback>();
const screenMouseDownCallbacks = new Set<ScreenClickCallback>();
const screenMouseWheelCallbacks = new Set<ScreenMouseWheelCallback>(); const screenMouseWheelCallbacks = new Set<ScreenMouseWheelCallback>();
let ctx: CanvasRenderingContext2D | null = null; let ctx: CanvasRenderingContext2D | null = null;
@@ -20,6 +21,11 @@ const registerScreenClickCallback = (callback: ScreenClickCallback) => {
return () => screenClickCallbacks.delete(callback); return () => screenClickCallbacks.delete(callback);
}; };
const registerScreenMouseDownCallback = (callback: ScreenClickCallback) => {
screenMouseDownCallbacks.add(callback);
return () => screenMouseDownCallbacks.delete(callback);
};
const registerScreenMouseWheelCallback = ( const registerScreenMouseWheelCallback = (
callback: ScreenMouseWheelCallback, 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) => { const handleCanvasWheel = (event: WheelEvent) => {
for (const callback of screenMouseWheelCallbacks) { for (const callback of screenMouseWheelCallbacks) {
callback(event.deltaY, event.deltaX); callback(event.deltaY, event.deltaX);
@@ -87,6 +108,7 @@ const renderFrame = (timestamp: number) => {
provide("registerRenderCallback", registerRenderCallback); provide("registerRenderCallback", registerRenderCallback);
provide("registerScreenClickCallback", registerScreenClickCallback); provide("registerScreenClickCallback", registerScreenClickCallback);
provide("registerScreenMouseDownCallback", registerScreenMouseDownCallback);
provide("registerScreenMouseWheelCallback", registerScreenMouseWheelCallback); provide("registerScreenMouseWheelCallback", registerScreenMouseWheelCallback);
onMounted(() => { onMounted(() => {
@@ -98,6 +120,7 @@ onMounted(() => {
ctx.imageSmoothingEnabled = false; ctx.imageSmoothingEnabled = false;
canvas.value.addEventListener("click", handleCanvasClick); canvas.value.addEventListener("click", handleCanvasClick);
canvas.value.addEventListener("mousedown", handleCanvasMouseDown);
canvas.value.addEventListener("wheel", handleCanvasWheel, { passive: true }); canvas.value.addEventListener("wheel", handleCanvasWheel, { passive: true });
animationFrameId = requestAnimationFrame(renderFrame); animationFrameId = requestAnimationFrame(renderFrame);
@@ -110,6 +133,7 @@ onUnmounted(() => {
if (canvas.value) { if (canvas.value) {
canvas.value.removeEventListener("click", handleCanvasClick); canvas.value.removeEventListener("click", handleCanvasClick);
canvas.value.removeEventListener("mousedown", handleCanvasMouseDown);
canvas.value.removeEventListener("wheel", handleCanvasWheel); canvas.value.removeEventListener("wheel", handleCanvasWheel);
} }
}); });

View File

@@ -187,7 +187,40 @@ export const useButtonNavigation = <T extends Record<string, Rect>>({
selectedButton.value = targetButton; selectedButton.value = targetButton;
}; };
const { onClick } = useScreen(); const { onClick, onMouseDown } = useScreen();
const pressedButton = ref<Entry | null>(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) => { onClick((x: number, y: number) => {
if (blockInteractions.value) return; if (blockInteractions.value) return;
@@ -200,6 +233,9 @@ export const useButtonNavigation = <T extends Record<string, Rect>>({
if (x >= sx && x <= sx + sw && y >= sy && y <= sy + sh) { if (x >= sx && x <= sx + sw && y >= sy && y <= sy + sh) {
if (canClickButton && !canClickButton(buttonName)) continue; if (canClickButton && !canClickButton(buttonName)) continue;
if (lastPressedButton !== buttonName) break;
lastPressedButton = null;
if (selectedButton.value === buttonName) { if (selectedButton.value === buttonName) {
onActivate?.(buttonName); onActivate?.(buttonName);
} else { } else {
@@ -328,6 +364,7 @@ export const useButtonNavigation = <T extends Record<string, Rect>>({
return { return {
selected: readonly(selectedButton), selected: readonly(selectedButton),
pressed: readonly(pressedButton),
selectorPosition, selectorPosition,
select, select,
}; };

View File

@@ -15,6 +15,9 @@ export const useScreen = () => {
const registerClick = inject<(cb: ScreenClickCallback) => () => void>( const registerClick = inject<(cb: ScreenClickCallback) => () => void>(
"registerScreenClickCallback", "registerScreenClickCallback",
); );
const registerMouseDown = inject<(cb: ScreenClickCallback) => () => void>(
"registerScreenMouseDownCallback",
);
const registerWheel = inject<(cb: ScreenMouseWheelCallback) => () => void>( const registerWheel = inject<(cb: ScreenMouseWheelCallback) => () => void>(
"registerScreenMouseWheelCallback", "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) => { const onMouseWheel = (callback: ScreenMouseWheelCallback) => {
onMounted(() => { onMounted(() => {
if (!registerWheel) if (!registerWheel)
@@ -46,5 +58,5 @@ export const useScreen = () => {
}); });
}; };
return { onRender, onClick, onMouseWheel }; return { onRender, onClick, onMouseDown, onMouseWheel };
}; };