Files
Ely-GPUI-Components/examples/gallery/pages/media/devices.rs
T
ZacharyZhang-NY d1b8ad395d Media · Capture and devices: recorder, camera, mic meter, device choice and 3D model viewer
ScreenRecorderControls holds a recording in one pill: Record, then a live
dot and the time it has run beside pause or resume and stop, with mic and
camera toggles. CameraPreview shows host frames in the camera's shape, or
the camera off. MicLevelMeter lights segments up to the level, amber and
red near the top, with tones shared from data_display. DeviceSelector
picks a microphone, camera or speaker through Select, which may now be
empty while shut. ModelViewer shows the host's frame for an orbit that a
drag, the arrows, Command-scroll, + and - and a reset move; the orbit is
seeded from the host so fast drags add up. The gallery draws a stone
torus per orbit and frees each replaced frame.
2026-09-26 19:05:23 -04:00

245 lines
7.3 KiB
Rust

use std::{path::Path, sync::Arc};
use ely_gpui_component::{
media::{
CameraPreview, Device, DeviceKind, DeviceSelector, MicLevelMeter, ModelViewer, Orbit,
Recording, ScreenRecorderControls,
},
theme::{ActiveTheme, TextSize},
};
use gpui::{
App, ImageSource, IntoElement, ParentElement, RenderImage, SharedString, Styled, Window, div,
px,
};
use super::sculpt;
use crate::{
probe::probe,
ui::{change, keep, section},
};
const OLIVE: &str = concat!(
env!("CARGO_MANIFEST_DIR"),
"/examples/gallery/assets/atrium-olive.jpg"
);
/// The model's frame, in pixels: twice the viewer's points.
const FRAME: (u32, u32) = (960, 640);
/// The demo recording and what it takes in.
#[derive(Clone)]
struct Recorder {
state: Recording,
mic: bool,
camera: bool,
}
pub fn recorder(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
let state = keep(
"media-recorder",
|| Recorder {
state: Recording::Idle,
mic: true,
camera: false,
},
window,
cx,
);
let now = state.read(cx).clone();
let (record, pause, resume, stop, mic, camera) = (
state.clone(),
state.clone(),
state.clone(),
state.clone(),
state.clone(),
state.clone(),
);
let controls = ScreenRecorderControls::new("media-recorder", now.state)
.on_record(move |_, cx| {
let since = cx.background_executor().now();
change(&record, cx, |it| it.state = Recording::Running { since })
})
.on_pause(move |_, cx| {
let at = cx.background_executor().now();
change(&pause, cx, |it| {
if let Recording::Running { since } = it.state {
it.state = Recording::Paused { since, at };
}
})
})
.on_resume(move |_, cx| {
let now = cx.background_executor().now();
change(&resume, cx, |it| {
if let Recording::Paused { since, at } = it.state {
it.state = Recording::Running {
since: since + (now - at),
};
}
})
})
.on_stop(move |_, cx| change(&stop, cx, |it| it.state = Recording::Idle))
.mic(now.mic, move |on, _, cx| change(&mic, cx, |it| it.mic = on))
.camera(now.camera, move |on, _, cx| {
change(&camera, cx, |it| it.camera = on)
});
section(
"ScreenRecorderControls",
"Record, then pause, resume or stop. While it runs, a live dot and the time it has run; the mic and camera toggles choose what it takes.",
cx,
)
.child(probe("media-recorder", div().flex().child(controls)))
}
pub fn camera(cx: &mut App) -> impl IntoElement + use<> {
let preview = |key: &'static str| div().w(px(300.)).child(CameraPreview::new(key, 1.5));
section(
"CameraPreview",
"What a camera sees, in its own shape, from frames the host hands in. With no frame, the camera is off.",
cx,
)
.child(probe(
"media-camera",
div()
.flex()
.flex_wrap()
.gap_4()
.child(
div().w(px(300.)).child(
CameraPreview::new("media-camera-on", 1.5)
.frame(Path::new(OLIVE))
.name("Studio camera"),
),
)
.child(preview("media-camera-off")),
))
}
pub fn mic(cx: &mut App) -> impl IntoElement + use<> {
let theme = cx.theme();
let (small, muted) = (theme.text_size(TextSize::Sm), theme.colors.fg_muted);
let row = |label: &'static str, level: f32| {
div()
.flex()
.items_center()
.gap_3()
.child(
div()
.w(px(80.))
.text_size(small)
.text_color(muted)
.child(label),
)
.child(div().flex_1().child(MicLevelMeter::new(level)))
};
section(
"MicLevelMeter",
"A microphone's level as it comes in: amber near the top, red at the top.",
cx,
)
.child(probe(
"media-mic",
div()
.w(px(360.))
.flex()
.flex_col()
.gap_3()
.child(row("Quiet", 0.3))
.child(row("Speaking", 0.84))
.child(row("Too loud", 0.98)),
))
}
fn device(key: &str, name: &str) -> Device {
Device {
key: SharedString::from(key.to_string()),
name: SharedString::from(name.to_string()),
}
}
pub fn selectors(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
let chosen = keep(
"media-devices",
|| {
(
SharedString::from("mic-desk"),
SharedString::from("cam-studio"),
)
},
window,
cx,
);
let (mic_now, cam_now) = chosen.read(cx).clone();
let (mic, cam) = (chosen.clone(), chosen);
let mics = [
device("mic-desk", "Desk microphone"),
device("mic-headset", "Headset"),
];
section(
"DeviceSelector",
"A microphone, a camera or a speaker to use, each with its kind's icon. With none attached, it says so and stays shut.",
cx,
)
.child(probe(
"media-devices",
div()
.w(px(280.))
.flex()
.flex_col()
.gap_3()
.child(
DeviceSelector::new("media-mics", DeviceKind::Microphone, mics)
.chosen(mic_now)
.on_change(move |key, _, cx| change(&mic, cx, |it| it.0 = key.clone())),
)
.child(
DeviceSelector::new("media-cams", DeviceKind::Camera, [device("cam-studio", "Studio camera")])
.chosen(cam_now)
.on_change(move |key, _, cx| change(&cam, cx, |it| it.1 = key.clone())),
)
.child(DeviceSelector::new("media-speakers", DeviceKind::Speaker, [])),
))
}
/// The demo model: its orbit and the frame drawn for it.
struct Model {
orbit: Orbit,
frame: Arc<RenderImage>,
}
fn drawn(orbit: Orbit) -> Arc<RenderImage> {
Arc::new(RenderImage::new(vec![sculpt::draw(
orbit, FRAME.0, FRAME.1,
)]))
}
pub fn model(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
let state = keep(
"media-model",
|| Model {
orbit: Orbit::START,
frame: drawn(Orbit::START),
},
window,
cx,
);
let (orbit, frame) = {
let now = state.read(cx);
(now.orbit, now.frame.clone())
};
let viewer = ModelViewer::new("media-model", 1.5, orbit)
.frame(ImageSource::Render(frame))
.on_orbit(move |orbit, window, cx| {
let old = state.update(cx, |model, cx| {
model.orbit = orbit;
cx.notify();
std::mem::replace(&mut model.frame, drawn(orbit))
});
cx.drop_image(old, Some(window));
});
section(
"3DModelViewer",
"A model the host renders, a frame for each view. A drag or the arrows turn around it; Command-scroll, + and - bring it nearer; 0 or the reset button returns.",
cx,
)
.child(probe("media-model", div().w(px(480.)).child(viewer)))
}