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.
113 lines
3.6 KiB
Rust
113 lines
3.6 KiB
Rust
use std::{cell::Cell, rc::Rc};
|
|
|
|
use gpui::{
|
|
App, ElementId, ImageSource, InteractiveElement, IntoElement, ParentElement, RenderOnce,
|
|
SharedString, Styled, Window, div,
|
|
};
|
|
|
|
use crate::{
|
|
buttons::{Button, ButtonVariant},
|
|
data_display::Avatar,
|
|
forms::Run,
|
|
motion::Pulse,
|
|
overlays::Dialog,
|
|
primitives::IconName,
|
|
theme::AvatarSize,
|
|
};
|
|
|
|
/// A call coming in: the caller's picture ringing, their name and the kind of call, with Decline and Accept; one of the two answers each ring. Escape declines, a press on the scrim does not. Render it while the call rings.
|
|
#[derive(IntoElement)]
|
|
pub struct IncomingCallDialog {
|
|
id: ElementId,
|
|
caller: SharedString,
|
|
picture: Option<ImageSource>,
|
|
video: bool,
|
|
on_accept: Run,
|
|
on_decline: Run,
|
|
}
|
|
|
|
impl IncomingCallDialog {
|
|
pub fn new(
|
|
id: impl Into<ElementId>,
|
|
caller: impl Into<SharedString>,
|
|
on_accept: impl Fn(&mut Window, &mut App) + 'static,
|
|
on_decline: impl Fn(&mut Window, &mut App) + 'static,
|
|
) -> Self {
|
|
Self {
|
|
id: id.into(),
|
|
caller: caller.into(),
|
|
picture: None,
|
|
video: false,
|
|
on_accept: Rc::new(on_accept),
|
|
on_decline: Rc::new(on_decline),
|
|
}
|
|
}
|
|
|
|
pub fn picture(mut self, source: impl Into<ImageSource>) -> Self {
|
|
self.picture = Some(source.into());
|
|
self
|
|
}
|
|
|
|
/// A video call; a voice call otherwise.
|
|
pub fn video(mut self) -> Self {
|
|
self.video = true;
|
|
self
|
|
}
|
|
}
|
|
|
|
impl RenderOnce for IncomingCallDialog {
|
|
fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
|
|
let avatar =
|
|
Avatar::new((self.id.clone(), "avatar"), self.caller.clone()).size(AvatarSize::Xl);
|
|
let avatar = match self.picture {
|
|
Some(picture) => avatar.image(picture),
|
|
None => avatar,
|
|
};
|
|
let (kind, icon) = if self.video {
|
|
("Incoming video call", IconName::Video)
|
|
} else {
|
|
("Incoming voice call", IconName::Phone)
|
|
};
|
|
let (id, caller) = (self.id.clone(), self.caller.clone());
|
|
let (decline, accept) = (self.on_decline, self.on_accept);
|
|
let told = caller.clone();
|
|
let (decline_id, accept_id) = (id.clone(), id.clone());
|
|
// Every way out runs this close; only Accept answers first.
|
|
let answered = Rc::new(Cell::new(false));
|
|
let answer = answered.clone();
|
|
Dialog::new(self.id, self.caller, move |window, cx| {
|
|
if !answered.get() {
|
|
log::info!("incoming call {caller}: declined");
|
|
decline(window, cx)
|
|
}
|
|
})
|
|
.detail(kind)
|
|
.held()
|
|
.child(
|
|
div()
|
|
.debug_selector(|| "incoming-call".into())
|
|
.flex()
|
|
.justify_center()
|
|
.py_4()
|
|
.child(Pulse::new((id, "ring")).rounded_full().child(avatar)),
|
|
)
|
|
.action(move |close| {
|
|
Button::new((decline_id, "decline"), "Decline")
|
|
.icon(IconName::PhoneOff)
|
|
.variant(ButtonVariant::Danger)
|
|
.on_click(move |_, window, cx| close(window, cx))
|
|
})
|
|
.action(move |close| {
|
|
Button::new((accept_id, "accept"), "Accept")
|
|
.icon(icon)
|
|
.variant(ButtonVariant::Success)
|
|
.on_click(move |_, window, cx| {
|
|
log::info!("incoming call {told}: accepted");
|
|
answer.set(true);
|
|
close(window, cx);
|
|
accept(window, cx)
|
|
})
|
|
})
|
|
}
|
|
}
|