Messaging · Calls: controls, tiles, a grid, a shared screen, a bar, a ringing dialog, a huddle
CallControls holds the microphone, camera and a shared screen as toggles, then Leave. ParticipantTile shows a camera's frame from the host or the person's initials, their name with a mark when muted, and a ring while they speak. VideoCallGrid lays tiles in even rows as far as they fit across its measured width; a short last row centers at the full rows' width. ScreenShareView names who presents and offers Stop presenting for your own share, the call's tiles beside or below. VoiceCallBar tells where a call is, how long it has run and who is in it, with the microphone and Leave. IncomingCallDialog rings with the caller's picture; Accept or Decline answers each ring once, and Escape declines. HuddleIndicator rings while a huddle is live; a press joins. The recorder's device toggle and the camera's name chip now have one home in media. People: member names sort whatever their case, a status's words save trimmed, and a profile card's local time ticks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
37e56c7ab7
commit
48d02c4903
26 files changed
+1535
-51
No files matched your search
+22
-12
@@ -1,7 +1,7 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use gpui::{
|
||||
App, ElementId, ImageSource, IntoElement, ParentElement, RenderOnce, SharedString, Styled,
|
||||
App, Div, ElementId, ImageSource, IntoElement, ParentElement, RenderOnce, SharedString, Styled,
|
||||
Window, div, img, prelude::*,
|
||||
};
|
||||
|
||||
@@ -12,6 +12,26 @@ use crate::{
|
||||
typography::Ellipsis,
|
||||
};
|
||||
|
||||
/// A name's chip over a frame's lower corner, light on a dark wash.
|
||||
pub(crate) fn name_chip(cx: &App) -> Div {
|
||||
let theme = cx.theme();
|
||||
let colors = &theme.colors;
|
||||
div()
|
||||
.absolute()
|
||||
.left_2()
|
||||
.bottom_2()
|
||||
.max_w(gpui::relative(0.8))
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.px_2()
|
||||
.py_0p5()
|
||||
.rounded(theme.radius(Radius::Sm))
|
||||
.bg(colors.media_backdrop.alpha(0.6))
|
||||
.text_color(colors.on_media)
|
||||
.text_size(theme.text_size(TextSize::Xs))
|
||||
}
|
||||
|
||||
/// What a camera sees, in its own shape, from frames the host hands in; the host mirrors them if it will. With no frame it shows the camera off.
|
||||
#[derive(IntoElement)]
|
||||
pub struct CameraPreview {
|
||||
@@ -58,18 +78,8 @@ impl RenderOnce for CameraPreview {
|
||||
.rounded(round)
|
||||
});
|
||||
let name = self.name.map(|name| {
|
||||
div()
|
||||
name_chip(cx)
|
||||
.debug_selector(|| "camera-name".into())
|
||||
.absolute()
|
||||
.left_2()
|
||||
.bottom_2()
|
||||
.max_w(gpui::relative(0.8))
|
||||
.px_2()
|
||||
.py_0p5()
|
||||
.rounded(theme.radius(Radius::Sm))
|
||||
.bg(colors.media_backdrop.alpha(0.6))
|
||||
.text_color(colors.on_media)
|
||||
.text_size(theme.text_size(TextSize::Xs))
|
||||
.child(Ellipsis::new(name))
|
||||
});
|
||||
framed(self.ratio, cx)
|
||||
|
||||
@@ -22,6 +22,7 @@ mod tests;
|
||||
|
||||
pub use annotate::ImageAnnotator;
|
||||
pub use audio::{AudioSpectrum, AudioWaveform};
|
||||
pub(crate) use camera::name_chip;
|
||||
pub use camera::{CameraPreview, Device, DeviceKind, DeviceSelector};
|
||||
pub use controls::{MediaControls, PlaybackSpeedControl, Repeat, VolumeControl};
|
||||
pub use crop::Crop;
|
||||
@@ -30,6 +31,7 @@ pub use listen::{AudioPlayer, Playlist, Track};
|
||||
pub use marks::{Mark, Tool};
|
||||
pub use model::{ModelViewer, Orbit};
|
||||
pub use player::{SPEEDS, VideoPlayer};
|
||||
pub(crate) use record::device_toggle;
|
||||
pub use record::{MicLevelMeter, Recording, ScreenRecorderControls};
|
||||
pub use scrubber::Scrubber;
|
||||
pub use strip::VideoThumbnailStrip;
|
||||
|
||||
+21
-11
@@ -25,6 +25,26 @@ pub(crate) fn lit(level: f32) -> usize {
|
||||
(level * SEGMENTS as f32).round() as usize
|
||||
}
|
||||
|
||||
/// A device toggle, such as a microphone's: its icon shows on or off, and a press flips it, logged under `owner`.
|
||||
pub(crate) fn device_toggle(
|
||||
owner: &'static str,
|
||||
id: &ElementId,
|
||||
key: &'static str,
|
||||
icons: (IconName, IconName),
|
||||
tip: &'static str,
|
||||
(on, set): (bool, OnFlag),
|
||||
) -> ToggleButton {
|
||||
let item = ToggleItem::new(key)
|
||||
.icon(if on { icons.0 } else { icons.1 })
|
||||
.tooltip(tip);
|
||||
ToggleButton::new((id.clone(), key), item, on)
|
||||
.size(ControlSize::Sm)
|
||||
.on_toggle(move |to, window, cx| {
|
||||
log::info!("{owner}: {key} {}", if to { "on" } else { "off" });
|
||||
set(to, window, cx)
|
||||
})
|
||||
}
|
||||
|
||||
/// Where a screen recording stands.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Recording {
|
||||
@@ -177,17 +197,7 @@ impl RenderOnce for ScreenRecorderControls {
|
||||
icons: (IconName, IconName),
|
||||
tip: &'static str,
|
||||
set: Option<(bool, OnFlag)>| {
|
||||
set.map(|(on, set)| {
|
||||
let item = ToggleItem::new(key)
|
||||
.icon(if on { icons.0 } else { icons.1 })
|
||||
.tooltip(tip);
|
||||
ToggleButton::new((id.clone(), key), item, on)
|
||||
.size(ControlSize::Sm)
|
||||
.on_toggle(move |to, window, cx| {
|
||||
log::info!("screen recorder: {key} {}", if to { "on" } else { "off" });
|
||||
set(to, window, cx)
|
||||
})
|
||||
})
|
||||
set.map(|set| device_toggle("screen recorder", &id, key, icons, tip, set))
|
||||
};
|
||||
let devices = self.mic.is_some() || self.camera.is_some();
|
||||
div()
|
||||
|
||||
Reference in new issue
Block a user