Skip to content

Commit 1d36b2f

Browse files
authored
Merge pull request webadderallorg#300 from meiiie/fix/hud-short-drag-passthrough
fix(launch): keep HUD interactive after short drags
2 parents d92846c + e684465 commit 1d36b2f

3 files changed

Lines changed: 115 additions & 7 deletions

File tree

‎src/components/launch/LaunchWindow.tsx‎

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
11
import {
22
AppWindow,
33
ArrowCircleUp as ArrowUpCircle,
4-
ArrowClockwise as RefreshCw,
5-
CaretUp as ChevronUp,
64
CheckCircle as CheckCircle2,
7-
DotsThreeVertical as MoreVertical,
5+
CaretUp as ChevronUp,
86
Eye,
97
EyeSlash as EyeOff,
108
FolderOpen,
9+
Translate as Languages,
1110
Microphone as Mic,
1211
MicrophoneSlash as MicOff,
1312
Minus,
1413
Monitor,
14+
DotsThreeVertical as MoreVertical,
1515
Pause,
1616
Play,
17-
SpeakerHigh as Volume2,
18-
SpeakerX as VolumeX,
17+
ArrowClockwise as RefreshCw,
1918
Stop as Square,
2019
Timer,
21-
Translate as Languages,
2220
VideoCamera as Video,
2321
VideoCamera as VideoIcon,
2422
VideoCameraSlash as VideoOff,
23+
SpeakerHigh as Volume2,
24+
SpeakerX as VolumeX,
2525
X,
2626
} from "@phosphor-icons/react";
2727
import { AnimatePresence, motion } from "motion/react";
@@ -41,6 +41,10 @@ import { ContentClamp } from "../ui/content-clamp";
4141
import ProjectBrowserDialog, {
4242
type ProjectLibraryEntry,
4343
} from "../video-editor/ProjectBrowserDialog";
44+
import {
45+
mergeHudInteractiveBounds,
46+
shouldRestoreHudMousePassthroughAfterDrag,
47+
} from "./hudMousePassthrough";
4448
import styles from "./LaunchWindow.module.css";
4549

4650
interface DesktopSource {
@@ -465,7 +469,26 @@ export function LaunchWindow() {
465469
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
466470
event.currentTarget.releasePointerCapture(event.pointerId);
467471
}
468-
if (wasDragging) {
472+
const hudBounds = mergeHudInteractiveBounds(
473+
[
474+
dropdownRef.current?.getBoundingClientRect(),
475+
hudBarRef.current?.getBoundingClientRect(),
476+
recordingWebcamPreviewContainerRef.current?.getBoundingClientRect(),
477+
].map((bounds) =>
478+
bounds
479+
? {
480+
left: bounds.left,
481+
top: bounds.top,
482+
right: bounds.right,
483+
bottom: bounds.bottom,
484+
}
485+
: null,
486+
),
487+
);
488+
if (
489+
wasDragging &&
490+
shouldRestoreHudMousePassthroughAfterDrag(hudBounds, event.clientX, event.clientY)
491+
) {
469492
window.electronAPI?.hudOverlaySetIgnoreMouse?.(true);
470493
}
471494
};
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { describe, expect, it } from "vitest";
2+
3+
import {
4+
mergeHudInteractiveBounds,
5+
shouldRestoreHudMousePassthroughAfterDrag,
6+
} from "./hudMousePassthrough";
7+
8+
const hudBounds = { left: 100, top: 200, right: 300, bottom: 260 };
9+
10+
describe("mergeHudInteractiveBounds", () => {
11+
it("returns null when there are no interactive bounds", () => {
12+
expect(mergeHudInteractiveBounds([null, undefined])).toBeNull();
13+
});
14+
15+
it("merges the dropdown, bar, and webcam preview bounds", () => {
16+
expect(
17+
mergeHudInteractiveBounds([
18+
{ left: 120, top: 220, right: 260, bottom: 320 },
19+
{ left: 100, top: 200, right: 300, bottom: 260 },
20+
{ left: 140, top: 280, right: 340, bottom: 430 },
21+
]),
22+
).toEqual({ left: 100, top: 200, right: 340, bottom: 430 });
23+
});
24+
});
25+
26+
describe("shouldRestoreHudMousePassthroughAfterDrag", () => {
27+
it("keeps the HUD interactive when the pointer is still inside the HUD", () => {
28+
expect(shouldRestoreHudMousePassthroughAfterDrag(hudBounds, 180, 230)).toBe(false);
29+
});
30+
31+
it("keeps the HUD interactive when the pointer ends on the HUD edge", () => {
32+
expect(shouldRestoreHudMousePassthroughAfterDrag(hudBounds, 100, 200)).toBe(false);
33+
expect(shouldRestoreHudMousePassthroughAfterDrag(hudBounds, 300, 260)).toBe(false);
34+
});
35+
36+
it("restores passthrough when the pointer ends outside the HUD", () => {
37+
expect(shouldRestoreHudMousePassthroughAfterDrag(hudBounds, 99, 230)).toBe(true);
38+
expect(shouldRestoreHudMousePassthroughAfterDrag(hudBounds, 180, 199)).toBe(true);
39+
expect(shouldRestoreHudMousePassthroughAfterDrag(hudBounds, 301, 230)).toBe(true);
40+
expect(shouldRestoreHudMousePassthroughAfterDrag(hudBounds, 180, 261)).toBe(true);
41+
});
42+
43+
it("restores passthrough when no HUD bounds are available", () => {
44+
expect(shouldRestoreHudMousePassthroughAfterDrag(null, 180, 230)).toBe(true);
45+
});
46+
});
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
export interface HudInteractiveBounds {
2+
left: number;
3+
top: number;
4+
right: number;
5+
bottom: number;
6+
}
7+
8+
export function mergeHudInteractiveBounds(
9+
bounds: Array<HudInteractiveBounds | null | undefined>,
10+
): HudInteractiveBounds | null {
11+
const presentBounds = bounds.filter((value): value is HudInteractiveBounds => Boolean(value));
12+
if (presentBounds.length === 0) {
13+
return null;
14+
}
15+
16+
return presentBounds.reduce((merged, current) => ({
17+
left: Math.min(merged.left, current.left),
18+
top: Math.min(merged.top, current.top),
19+
right: Math.max(merged.right, current.right),
20+
bottom: Math.max(merged.bottom, current.bottom),
21+
}));
22+
}
23+
24+
export function shouldRestoreHudMousePassthroughAfterDrag(
25+
bounds: HudInteractiveBounds | null,
26+
clientX: number,
27+
clientY: number,
28+
): boolean {
29+
if (!bounds) {
30+
return true;
31+
}
32+
33+
return (
34+
clientX < bounds.left ||
35+
clientX > bounds.right ||
36+
clientY < bounds.top ||
37+
clientY > bounds.bottom
38+
);
39+
}

0 commit comments

Comments
 (0)