fix(hud): keep controls visible when recording starts

This commit is contained in:
wiiiii123
2026-07-10 20:34:57 +07:00
parent 87f72c719b
commit e120d8cd05
3 changed files with 116 additions and 0 deletions
@@ -1,5 +1,6 @@
import { useCallback, useEffect, useRef, useState, type PointerEvent, type RefObject } from "react";
import { mergeHudInteractiveBounds, shouldRestoreHudMousePassthroughAfterDrag } from "../hudMousePassthrough";
import { clampHudOffsetToViewport } from "../hudViewportBounds";
const DEFAULT_RECORDING_HUD_OFFSET = { x: 0, y: 0 };
@@ -41,6 +42,47 @@ export function useHudBarDrag({
}
}, [recordingHudOffset]);
const keepHudBarInsideViewport = useCallback(() => {
if (isHudDraggingRef.current || !hudBarRef.current) {
return;
}
const bounds = hudBarRef.current.getBoundingClientRect();
const nextOffset = clampHudOffsetToViewport(
recordingHudOffsetRef.current,
bounds,
{ width: window.innerWidth, height: window.innerHeight },
);
if (
nextOffset.x === recordingHudOffsetRef.current.x &&
nextOffset.y === recordingHudOffsetRef.current.y
) {
return;
}
recordingHudOffsetRef.current = nextOffset;
if (hudBarTransformRef.current) {
hudBarTransformRef.current.style.transform = `translate3d(${nextOffset.x}px, ${nextOffset.y}px, 0)`;
}
setRecordingHudOffset(nextOffset);
}, [hudBarRef]);
useEffect(() => {
const resizeObserver =
typeof ResizeObserver === "undefined"
? null
: new ResizeObserver(keepHudBarInsideViewport);
if (hudBarRef.current) {
resizeObserver?.observe(hudBarRef.current);
}
window.addEventListener("resize", keepHudBarInsideViewport);
return () => {
window.removeEventListener("resize", keepHudBarInsideViewport);
resizeObserver?.disconnect();
};
}, [hudBarRef, keepHudBarInsideViewport]);
const handleHudBarPointerDown = useCallback((event: PointerEvent<HTMLDivElement>) => {
if (event.button !== 0) {
return;
@@ -0,0 +1,35 @@
import { describe, expect, it } from "vitest";
import { clampHudOffsetToViewport } from "./hudViewportBounds";
describe("clampHudOffsetToViewport", () => {
it("moves a dragged HUD back onscreen after the recording window becomes compact", () => {
expect(
clampHudOffsetToViewport(
{ x: -463, y: -710 },
{ left: -244, top: -656, right: 179, bottom: -586 },
{ width: 860, height: 160 },
),
).toEqual({ x: -219, y: -54 });
});
it("leaves an already visible HUD unchanged", () => {
expect(
clampHudOffsetToViewport(
{ x: 12, y: -8 },
{ left: 218, top: 54, right: 641, bottom: 124 },
{ width: 860, height: 160 },
),
).toEqual({ x: 12, y: -8 });
});
it("clamps the right and bottom edges after a viewport shrink", () => {
expect(
clampHudOffsetToViewport(
{ x: 500, y: 200 },
{ left: 700, top: 140, right: 1120, bottom: 210 },
{ width: 860, height: 160 },
),
).toEqual({ x: 240, y: 150 });
});
});
@@ -0,0 +1,39 @@
export interface HudOffset {
x: number;
y: number;
}
export interface HudViewportBounds {
left: number;
top: number;
right: number;
bottom: number;
}
export interface HudViewportSize {
width: number;
height: number;
}
function clampOffsetAxis(offset: number, start: number, end: number, viewportSize: number) {
if (start < 0) {
return offset - start;
}
if (end > viewportSize) {
return offset + viewportSize - end;
}
return offset;
}
export function clampHudOffsetToViewport(
offset: HudOffset,
bounds: HudViewportBounds,
viewport: HudViewportSize,
): HudOffset {
return {
x: clampOffsetAxis(offset.x, bounds.left, bounds.right, viewport.width),
y: clampOffsetAxis(offset.y, bounds.top, bounds.bottom, viewport.height),
};
}