From c1919438494b181ee812a6fd7992d0a355add5e9 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 23 Nov 2023 15:00:13 +0100 Subject: [PATCH] Add basic call/user UI in top-right corner. Allow ui::Avatar to take custom data instead of always relying on URI resolution --- crates/collab_ui2/src/collab_titlebar_item.rs | 45 +++++++++- crates/gpui2/src/elements/img.rs | 86 +++++++++++++------ crates/ui2/src/components/avatar.rs | 8 +- crates/ui2/src/components/stories/avatar.rs | 4 +- crates/util/src/channel.rs | 2 +- crates/workspace2/src/workspace2.rs | 4 + 6 files changed, 113 insertions(+), 36 deletions(-) diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs index 94db2f4d9f60659cc2e520a2ed3533caa508f9af..d9eff16e8e4c5db81923deca5256bec3a1132485 100644 --- a/crates/collab_ui2/src/collab_titlebar_item.rs +++ b/crates/collab_ui2/src/collab_titlebar_item.rs @@ -37,7 +37,7 @@ use gpui::{ }; use project::Project; use theme::ActiveTheme; -use ui::{h_stack, Button, ButtonVariant, Color, KeyBinding, Label, Tooltip}; +use ui::{h_stack, Button, ButtonVariant, Color, IconButton, KeyBinding, Tooltip}; use workspace::Workspace; // const MAX_PROJECT_NAME_LENGTH: usize = 40; @@ -85,6 +85,13 @@ impl Render for CollabTitlebarItem { type Element = Stateful
; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + let is_in_room = self + .workspace + .update(cx, |this, cx| this.call_state().is_in_room(cx)) + .unwrap_or_default(); + let is_shared = is_in_room && self.project.read(cx).is_shared(); + let current_user = self.user_store.read(cx).current_user(); + let client = self.client.clone(); h_stack() .id("titlebar") .justify_between() @@ -149,8 +156,40 @@ impl Render for CollabTitlebarItem { .into() }), ), - ) // self.titlebar_item - .child(h_stack().child(Label::new("Right side titlebar item"))) + ) + .map(|this| { + if let Some(user) = current_user { + this.when_some(user.avatar.clone(), |this, avatar| { + this.child(ui::Avatar::new(avatar)) + }) + } else { + this.child(Button::new("Sign in").on_click(move |_, cx| { + let client = client.clone(); + cx.spawn(move |cx| async move { + client.authenticate_and_connect(true, &cx).await?; + Ok::<(), anyhow::Error>(()) + }) + .detach_and_log_err(cx); + })) + } + }) // that's obviously wrong as we should check for current call,not current user + .when(is_in_room, |this| { + this.child( + h_stack() + .child( + h_stack() + .child(Button::new(if is_shared { "Unshare" } else { "Share" })) + .child(IconButton::new("leave-call", ui::Icon::Exit)), + ) + .child( + h_stack() + .child(IconButton::new("mute-microphone", ui::Icon::Mic)) + .child(IconButton::new("mute-sound", ui::Icon::AudioOn)) + .child(IconButton::new("screen-share", ui::Icon::Screen)) + .pl_2(), + ), + ) + }) } } diff --git a/crates/gpui2/src/elements/img.rs b/crates/gpui2/src/elements/img.rs index 3c0f4c00be852ed2df5295ce83d47b583582f747..c28f0dca3091488dbcd500ffd3a691b0ff9e6ee6 100644 --- a/crates/gpui2/src/elements/img.rs +++ b/crates/gpui2/src/elements/img.rs @@ -1,30 +1,59 @@ +use std::sync::Arc; + use crate::{ - Bounds, Element, InteractiveElement, InteractiveElementState, Interactivity, LayoutId, Pixels, - RenderOnce, SharedString, StyleRefinement, Styled, WindowContext, + Bounds, Element, ImageData, InteractiveElement, InteractiveElementState, Interactivity, + LayoutId, Pixels, RenderOnce, SharedString, StyleRefinement, Styled, WindowContext, }; use futures::FutureExt; use util::ResultExt; +#[derive(Clone, Debug)] +pub enum ImageSource { + /// Image content will be loaded from provided URI at render time. + Uri(SharedString), + Data(Arc), +} + +impl From for ImageSource { + fn from(value: SharedString) -> Self { + Self::Uri(value) + } +} + +impl From> for ImageSource { + fn from(value: Arc) -> Self { + Self::Data(value) + } +} + pub struct Img { interactivity: Interactivity, - uri: Option, + source: Option, grayscale: bool, } pub fn img() -> Img { Img { interactivity: Interactivity::default(), - uri: None, + source: None, grayscale: false, } } impl Img { pub fn uri(mut self, uri: impl Into) -> Self { - self.uri = Some(uri.into()); + self.source = Some(ImageSource::from(uri.into())); + self + } + pub fn data(mut self, data: Arc) -> Self { + self.source = Some(ImageSource::from(data)); self } + pub fn source(mut self, source: impl Into) -> Self { + self.source = Some(source.into()); + self + } pub fn grayscale(mut self, grayscale: bool) -> Self { self.grayscale = grayscale; self @@ -58,28 +87,33 @@ impl Element for Img { |style, _scroll_offset, cx| { let corner_radii = style.corner_radii; - if let Some(uri) = self.uri.clone() { - // eprintln!(">>> image_cache.get({uri}"); - let image_future = cx.image_cache.get(uri.clone()); - // eprintln!("<<< image_cache.get({uri}"); - if let Some(data) = image_future - .clone() - .now_or_never() - .and_then(|result| result.ok()) - { - let corner_radii = corner_radii.to_pixels(bounds.size, cx.rem_size()); - cx.with_z_index(1, |cx| { - cx.paint_image(bounds, corner_radii, data, self.grayscale) - .log_err() - }); - } else { - cx.spawn(|mut cx| async move { - if image_future.await.ok().is_some() { - cx.on_next_frame(|cx| cx.notify()); + if let Some(source) = self.source { + let image = match source { + ImageSource::Uri(uri) => { + let image_future = cx.image_cache.get(uri.clone()); + if let Some(data) = image_future + .clone() + .now_or_never() + .and_then(|result| result.ok()) + { + data + } else { + cx.spawn(|mut cx| async move { + if image_future.await.ok().is_some() { + cx.on_next_frame(|cx| cx.notify()); + } + }) + .detach(); + return; } - }) - .detach() - } + } + ImageSource::Data(image) => image, + }; + let corner_radii = corner_radii.to_pixels(bounds.size, cx.rem_size()); + cx.with_z_index(1, |cx| { + cx.paint_image(bounds, corner_radii, image, self.grayscale) + .log_err() + }); } }, ) diff --git a/crates/ui2/src/components/avatar.rs b/crates/ui2/src/components/avatar.rs index 364a1454946d51970723938da672b21993f9484f..872d62fa129e083b3c692cc920f5673e6a0767cf 100644 --- a/crates/ui2/src/components/avatar.rs +++ b/crates/ui2/src/components/avatar.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use gpui::{img, Img, RenderOnce}; +use gpui::{img, ImageSource, Img, RenderOnce}; #[derive(Debug, Default, PartialEq, Clone)] pub enum Shape { @@ -10,7 +10,7 @@ pub enum Shape { #[derive(RenderOnce)] pub struct Avatar { - src: SharedString, + src: ImageSource, shape: Shape, } @@ -26,7 +26,7 @@ impl Component for Avatar { img = img.rounded_md(); } - img.uri(self.src.clone()) + img.source(self.src.clone()) .size_4() // todo!(Pull the avatar fallback background from the theme.) .bg(gpui::red()) @@ -34,7 +34,7 @@ impl Component for Avatar { } impl Avatar { - pub fn new(src: impl Into) -> Self { + pub fn new(src: impl Into) -> Self { Self { src: src.into(), shape: Shape::Circle, diff --git a/crates/ui2/src/components/stories/avatar.rs b/crates/ui2/src/components/stories/avatar.rs index ad9c3ccb3928b43cdb7ee066f9a296d96ef59b68..177065cfcb96aaab262bb35001c63eb75b500842 100644 --- a/crates/ui2/src/components/stories/avatar.rs +++ b/crates/ui2/src/components/stories/avatar.rs @@ -14,10 +14,10 @@ impl Render for AvatarStory { .child(Story::title_for::()) .child(Story::label("Default")) .child(Avatar::new( - "https://avatars.githubusercontent.com/u/1714999?v=4", + "https://avatars.githubusercontent.com/u/1714999?v=4".into(), )) .child(Avatar::new( - "https://avatars.githubusercontent.com/u/326587?v=4", + "https://avatars.githubusercontent.com/u/326587?v=4".into(), )) } } diff --git a/crates/util/src/channel.rs b/crates/util/src/channel.rs index 55f13df0846ff1b9c9396a28826ae488ce074304..94260c71db3e9255aa0089070e75b54bde098568 100644 --- a/crates/util/src/channel.rs +++ b/crates/util/src/channel.rs @@ -19,7 +19,7 @@ lazy_static! { pub struct AppCommitSha(pub String); -#[derive(Copy, Clone, PartialEq, Eq, Default)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] pub enum ReleaseChannel { #[default] Dev, diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index b09b47d24c47551e530ff3d9d08c3e129b3f1a22..761b09365dab48012824d6fd29efab0dc47bec05 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -3408,6 +3408,10 @@ impl Workspace { self.modal_layer .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build)) } + + pub fn call_state(&mut self) -> &mut dyn CallHandler { + &mut *self.call_handler + } } fn window_bounds_env_override(cx: &AsyncAppContext) -> Option {