Skip to main content

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}