Merge pull request #300 from meiiie/fix/hud-short-drag-passthrough

fix(launch): keep HUD interactive after short drags
This commit is contained in:
webadderall
2026-04-22 17:26:49 +10:00
committed by GitHub
3 changed files with 115 additions and 7 deletions
+30 -7
View File
@@ -1,27 +1,27 @@
import {
AppWindow,
ArrowCircleUp as ArrowUpCircle,
ArrowClockwise as RefreshCw,
CaretUp as ChevronUp,
CheckCircle as CheckCircle2,
DotsThreeVertical as MoreVertical,
CaretUp as ChevronUp,
Eye,
EyeSlash as EyeOff,
FolderOpen,
Translate as Languages,
Microphone as Mic,
MicrophoneSlash as MicOff,
Minus,
Monitor,
DotsThreeVertical as MoreVertical,
Pause,
Play,
SpeakerHigh as Volume2,
SpeakerX as VolumeX,
ArrowClockwise as RefreshCw,
Stop as Square,
Timer,
Translate as Languages,
VideoCamera as Video,
VideoCamera as VideoIcon,
VideoCameraSlash as VideoOff,
SpeakerHigh as Volume2,
SpeakerX as VolumeX,
X,
} from "@phosphor-icons/react";
import { AnimatePresence, motion } from "motion/react";
@@ -41,6 +41,10 @@ import { ContentClamp } from "../ui/content-clamp";
import ProjectBrowserDialog, {
type ProjectLibraryEntry,
} from "../video-editor/ProjectBrowserDialog";
import {
mergeHudInteractiveBounds,
shouldRestoreHudMousePassthroughAfterDrag,
} from "./hudMousePassthrough";
import styles from "./LaunchWindow.module.css";
interface DesktopSource {
@@ -465,7 +469,26 @@ export function LaunchWindow() {
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
event.currentTarget.releasePointerCapture(event.pointerId);
}
if (wasDragging) {
const hudBounds = mergeHudInteractiveBounds(
[
dropdownRef.current?.getBoundingClientRect(),
hudBarRef.current?.getBoundingClientRect(),
recordingWebcamPreviewContainerRef.current?.getBoundingClientRect(),
].map((bounds) =>
bounds
? {
left: bounds.left,
top: bounds.top,
right: bounds.right,
bottom: bounds.bottom,
}
: null,
),
);
if (
wasDragging &&
shouldRestoreHudMousePassthroughAfterDrag(hudBounds, event.clientX, event.clientY)
) {
window.electronAPI?.hudOverlaySetIgnoreMouse?.(true);
}
};
@@ -0,0 +1,46 @@
import { describe, expect, it } from "vitest";
import {
mergeHudInteractiveBounds,
shouldRestoreHudMousePassthroughAfterDrag,
} from "./hudMousePassthrough";
const hudBounds = { left: 100, top: 200, right: 300, bottom: 260 };
describe("mergeHudInteractiveBounds", () => {
it("returns null when there are no interactive bounds", () => {
expect(mergeHudInteractiveBounds([null, undefined])).toBeNull();
});
it("merges the dropdown, bar, and webcam preview bounds", () => {
expect(
mergeHudInteractiveBounds([
{ left: 120, top: 220, right: 260, bottom: 320 },
{ left: 100, top: 200, right: 300, bottom: 260 },
{ left: 140, top: 280, right: 340, bottom: 430 },
]),
).toEqual({ left: 100, top: 200, right: 340, bottom: 430 });
});
});
describe("shouldRestoreHudMousePassthroughAfterDrag", () => {
it("keeps the HUD interactive when the pointer is still inside the HUD", () => {
expect(shouldRestoreHudMousePassthroughAfterDrag(hudBounds, 180, 230)).toBe(false);
});
it("keeps the HUD interactive when the pointer ends on the HUD edge", () => {
expect(shouldRestoreHudMousePassthroughAfterDrag(hudBounds, 100, 200)).toBe(false);
expect(shouldRestoreHudMousePassthroughAfterDrag(hudBounds, 300, 260)).toBe(false);
});
it("restores passthrough when the pointer ends outside the HUD", () => {
expect(shouldRestoreHudMousePassthroughAfterDrag(hudBounds, 99, 230)).toBe(true);
expect(shouldRestoreHudMousePassthroughAfterDrag(hudBounds, 180, 199)).toBe(true);
expect(shouldRestoreHudMousePassthroughAfterDrag(hudBounds, 301, 230)).toBe(true);
expect(shouldRestoreHudMousePassthroughAfterDrag(hudBounds, 180, 261)).toBe(true);
});
it("restores passthrough when no HUD bounds are available", () => {
expect(shouldRestoreHudMousePassthroughAfterDrag(null, 180, 230)).toBe(true);
});
});
@@ -0,0 +1,39 @@
export interface HudInteractiveBounds {
left: number;
top: number;
right: number;
bottom: number;
}
export function mergeHudInteractiveBounds(
bounds: Array<HudInteractiveBounds | null | undefined>,
): HudInteractiveBounds | null {
const presentBounds = bounds.filter((value): value is HudInteractiveBounds => Boolean(value));
if (presentBounds.length === 0) {
return null;
}
return presentBounds.reduce((merged, current) => ({
left: Math.min(merged.left, current.left),
top: Math.min(merged.top, current.top),
right: Math.max(merged.right, current.right),
bottom: Math.max(merged.bottom, current.bottom),
}));
}
export function shouldRestoreHudMousePassthroughAfterDrag(
bounds: HudInteractiveBounds | null,
clientX: number,
clientY: number,
): boolean {
if (!bounds) {
return true;
}
return (
clientX < bounds.left ||
clientX > bounds.right ||
clientY < bounds.top ||
clientY > bounds.bottom
);
}