app_ui/bubble.rs
1//! `<BubbleRoot />` — the Leptos tree mounted into the `webcam-bubble`
2//! Tauri window (M-BUBBLE.0 / AUT-273 + the M-QUAL.4 overlay redesign).
3//!
4//! There is **no card**. The circle itself is the widget: a
5//! `.bubble-stage` (the circle's bounding square) holds the clipped
6//! circular camera feed plus three overlays painted on top of it —
7//! - `PREVIEW` pill near the top of the circle
8//! - pause / settings icon cluster at the upper-right edge
9//! - three recording-control buttons in a dark pill near the bottom
10//!
11//! …plus a floating device caption (`LuCamera` + `FaceTime HD ·
12//! bottom-left`) below the circle on the transparent page background.
13//!
14//! The overlays are **siblings** of `.bubble-canvas-wrap`, not children
15//! — the wrap is `overflow: hidden` to clip the feed to a circle, so
16//! anything inside it would be clipped too. Keeping the overlays as
17//! siblings (positioned against the stage) lets them sit on top of the
18//! circle without being clipped. The circle mask here is the preview's
19//! own; wisp owns the recorded frame's mask (CLAUDE.md "Mask is
20//! wisp's, not CSS").
21//!
22//! The whole thing is draggable via `data-tauri-drag-region`; the
23//! interactive clusters (icon cluster, controls) opt OUT of drag so a
24//! click on a button doesn't initiate a window drag.
25
26use leptos::prelude::*;
27use leptos_icons::Icon;
28
29use crate::camera_preview::CameraPreview;
30
31/// Leptos component mounted at `?mount=bubble`. Renders the
32/// borderless circular webcam surface with overlaid controls.
33#[component]
34pub fn BubbleRoot() -> impl IntoView {
35 view! {
36 <div
37 class="bubble-root bubble-root--design"
38 data-tauri-drag-region="true"
39 aria-label="Webcam bubble overlay"
40 >
41 <div class="bubble-stage" data-tauri-drag-region="true">
42 <div
43 class="bubble-canvas-wrap"
44 data-tauri-drag-region="true"
45 aria-hidden="true"
46 >
47 <CameraPreview />
48 </div>
49
50 <span class="bubble-preview-chip" aria-hidden="true">
51 <span class="bubble-preview-chip-dot"></span>
52 "PREVIEW"
53 </span>
54
55 <span class="bubble-header-actions" data-tauri-drag-region="false">
56 <button
57 type="button"
58 class="bubble-icon-btn"
59 aria-label="Pause preview"
60 title="Pause preview"
61 >
62 <Icon icon=icondata::LuPause />
63 </button>
64 <button
65 type="button"
66 class="bubble-icon-btn"
67 aria-label="Bubble settings"
68 title="Bubble settings"
69 >
70 <Icon icon=icondata::LuSettings />
71 </button>
72 </span>
73
74 <div
75 class="bubble-controls"
76 data-tauri-drag-region="false"
77 role="toolbar"
78 aria-label="Recording controls"
79 >
80 <button
81 type="button"
82 class="bubble-control bubble-control--active"
83 aria-label="Record"
84 >
85 <Icon icon=icondata::LuCircle />
86 </button>
87 <button type="button" class="bubble-control" aria-label="Pause recording">
88 <Icon icon=icondata::LuPause />
89 </button>
90 <button type="button" class="bubble-control" aria-label="Stop recording">
91 <Icon icon=icondata::LuSquare />
92 </button>
93 </div>
94 </div>
95
96 <div class="bubble-caption" data-tauri-drag-region="false">
97 <span class="bubble-caption-icon" aria-hidden="true">
98 <Icon icon=icondata::LuCamera />
99 </span>
100 <span class="bubble-caption-label">"FaceTime HD"</span>
101 <span class="bubble-caption-sep" aria-hidden="true">"·"</span>
102 <span class="bubble-caption-corner">"bottom-left"</span>
103 </div>
104 </div>
105 }
106}