From 99a2dc4880636900173b400d5405fd4bd27cb51e Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 14 Sep 2021 17:47:43 -0600 Subject: [PATCH] Render an offline icon in titlebar when connection is lost Co-Authored-By: Max Brunsfeld --- gpui/src/elements/container.rs | 12 ++++---- gpui/src/views/select.rs | 4 +-- zed/assets/icons/offline-14.svg | 3 ++ zed/assets/themes/_base.toml | 8 +++-- zed/src/chat_panel.rs | 16 +++++----- zed/src/file_finder.rs | 8 ++--- zed/src/theme.rs | 10 ++++++- zed/src/theme_selector.rs | 8 ++--- zed/src/workspace.rs | 52 ++++++++++++++++++++++++++++----- zed/src/workspace/pane.rs | 17 ++++++++--- zed/src/workspace/sidebar.rs | 4 +-- 11 files changed, 102 insertions(+), 40 deletions(-) create mode 100644 zed/assets/icons/offline-14.svg diff --git a/gpui/src/elements/container.rs b/gpui/src/elements/container.rs index 48dcfa1b137986df78690a1adaf8e06249dae314..eeda6f206d139c210259744f9604fb30f90d1fae 100644 --- a/gpui/src/elements/container.rs +++ b/gpui/src/elements/container.rs @@ -13,7 +13,7 @@ use crate::{ Element, ElementBox, Event, EventContext, LayoutContext, PaintContext, SizeConstraint, }; -#[derive(Clone, Debug, Default, Deserialize)] +#[derive(Clone, Copy, Debug, Default, Deserialize)] pub struct ContainerStyle { #[serde(default)] pub margin: Margin, @@ -42,8 +42,8 @@ impl Container { } } - pub fn with_style(mut self, style: &ContainerStyle) -> Self { - self.style = style.clone(); + pub fn with_style(mut self, style: ContainerStyle) -> Self { + self.style = style; self } @@ -242,7 +242,7 @@ impl ToJson for ContainerStyle { } } -#[derive(Clone, Debug, Default)] +#[derive(Clone, Copy, Debug, Default)] pub struct Margin { pub top: f32, pub left: f32, @@ -269,7 +269,7 @@ impl ToJson for Margin { } } -#[derive(Clone, Debug, Default)] +#[derive(Clone, Copy, Debug, Default)] pub struct Padding { pub top: f32, pub left: f32, @@ -367,7 +367,7 @@ impl ToJson for Padding { } } -#[derive(Clone, Debug, Default, Deserialize)] +#[derive(Clone, Copy, Debug, Default, Deserialize)] pub struct Shadow { #[serde(default, deserialize_with = "deserialize_vec2f")] offset: Vector2F, diff --git a/gpui/src/views/select.rs b/gpui/src/views/select.rs index b9e099a75c17f954a294f3150c6e264ea612ad17..e257455a7afc3ede7f02fc3489d3855eb5444438 100644 --- a/gpui/src/views/select.rs +++ b/gpui/src/views/select.rs @@ -111,7 +111,7 @@ impl View for Select { mouse_state.hovered, cx, )) - .with_style(&style.header) + .with_style(style.header) .boxed() }) .on_click(move |cx| cx.dispatch_action(ToggleSelect)) @@ -158,7 +158,7 @@ impl View for Select { .with_max_height(200.) .boxed(), ) - .with_style(&style.menu) + .with_style(style.menu) .boxed(), ) .boxed(), diff --git a/zed/assets/icons/offline-14.svg b/zed/assets/icons/offline-14.svg new file mode 100644 index 0000000000000000000000000000000000000000..5349f65ead5f2ef87331f97352ef770ca0f33656 --- /dev/null +++ b/zed/assets/icons/offline-14.svg @@ -0,0 +1,3 @@ + + + diff --git a/zed/assets/themes/_base.toml b/zed/assets/themes/_base.toml index dfd27f396d35b5f68b0e5744c9ed424103be7839..485d3bf2a79e7269fb24462c1160d370c7bf1b89 100644 --- a/zed/assets/themes/_base.toml +++ b/zed/assets/themes/_base.toml @@ -9,9 +9,13 @@ pane_divider = { width = 1, color = "$border.0" } border = { width = 1, bottom = true, color = "$border.0" } title = "$text.0" avatar_width = 20 -icon_signed_out = "$text.2.color" +icon_color = "$text.2.color" avatar = { corner_radius = 10, border = { width = 1, color = "#00000088" } } +[workspace.titlebar.offline_icon] +padding = { right = 4 } +width = 16 + [workspace.tab] text = "$text.2" padding = { left = 10, right = 10 } @@ -29,7 +33,7 @@ background = "$surface.1" text = "$text.0" [workspace.sidebar] -width = 38 +width = 32 border = { right = true, width = 1, color = "$border.0" } [workspace.sidebar.resize_handle] diff --git a/zed/src/chat_panel.rs b/zed/src/chat_panel.rs index 200a35fccae69b89be1e35e5655eb03642cf8aa2..5ccc014ca787119f6866254b20adc1ae79cab98f 100644 --- a/zed/src/chat_panel.rs +++ b/zed/src/chat_panel.rs @@ -209,7 +209,7 @@ impl ChatPanel { Flex::column() .with_child( Container::new(ChildView::new(self.channel_select.id()).boxed()) - .with_style(&theme.chat_panel.channel_select.container) + .with_style(theme.chat_panel.channel_select.container) .boxed(), ) .with_child(self.render_active_channel_messages()) @@ -243,7 +243,7 @@ impl ChatPanel { ) .boxed(), ) - .with_style(&theme.sender.container) + .with_style(theme.sender.container) .boxed(), ) .with_child( @@ -254,7 +254,7 @@ impl ChatPanel { ) .boxed(), ) - .with_style(&theme.timestamp.container) + .with_style(theme.timestamp.container) .boxed(), ) .boxed(), @@ -262,14 +262,14 @@ impl ChatPanel { .with_child(Text::new(message.body.clone(), theme.body.clone()).boxed()) .boxed(), ) - .with_style(&theme.container) + .with_style(theme.container) .boxed() } fn render_input_box(&self) -> ElementBox { let theme = &self.settings.borrow().theme; Container::new(ChildView::new(self.input_editor.id()).boxed()) - .with_style(&theme.chat_panel.input_editor.container) + .with_style(theme.chat_panel.input_editor.container) .boxed() } @@ -293,13 +293,13 @@ impl ChatPanel { Flex::row() .with_child( Container::new(Label::new("#".to_string(), theme.hash.text.clone()).boxed()) - .with_style(&theme.hash.container) + .with_style(theme.hash.container) .boxed(), ) .with_child(Label::new(channel.name.clone(), theme.name.clone()).boxed()) .boxed(), ) - .with_style(&theme.container) + .with_style(theme.container) .boxed() } @@ -387,7 +387,7 @@ impl View for ChatPanel { }; ConstrainedBox::new( Container::new(element) - .with_style(&theme.chat_panel.container) + .with_style(theme.chat_panel.container) .boxed(), ) .with_min_width(150.) diff --git a/zed/src/file_finder.rs b/zed/src/file_finder.rs index 7e552d4748eef5368ade73f14b279c2ae834de7f..23bec79e0076046f3d483f9baa4972c7e418a2f1 100644 --- a/zed/src/file_finder.rs +++ b/zed/src/file_finder.rs @@ -88,13 +88,13 @@ impl View for FileFinder { Flex::new(Axis::Vertical) .with_child( Container::new(ChildView::new(self.query_editor.id()).boxed()) - .with_style(&settings.theme.selector.input_editor.container) + .with_style(settings.theme.selector.input_editor.container) .boxed(), ) .with_child(Flexible::new(1.0, self.render_matches()).boxed()) .boxed(), ) - .with_style(&settings.theme.selector.container) + .with_style(settings.theme.selector.container) .boxed(), ) .with_max_width(500.0) @@ -127,7 +127,7 @@ impl FileFinder { ) .boxed(), ) - .with_style(&settings.theme.selector.empty.container) + .with_style(settings.theme.selector.empty.container) .named("empty matches"); } @@ -200,7 +200,7 @@ impl FileFinder { ) .boxed(), ) - .with_style(&style.container); + .with_style(style.container); let action = Select(Entry { worktree_id: path_match.tree_id, diff --git a/zed/src/theme.rs b/zed/src/theme.rs index 96e562a55ffc45ebb593010699fed06bedf65160..88f385a05461b18a0db3f6182e9b3d08effb97b2 100644 --- a/zed/src/theme.rs +++ b/zed/src/theme.rs @@ -48,10 +48,18 @@ pub struct Titlebar { pub container: ContainerStyle, pub title: TextStyle, pub avatar_width: f32, - pub icon_signed_out: Color, + pub offline_icon: OfflineIcon, + pub icon_color: Color, pub avatar: ImageStyle, } +#[derive(Clone, Deserialize)] +pub struct OfflineIcon { + #[serde(flatten)] + pub container: ContainerStyle, + pub width: f32, +} + #[derive(Clone, Deserialize)] pub struct Tab { #[serde(flatten)] diff --git a/zed/src/theme_selector.rs b/zed/src/theme_selector.rs index 09f259281a45e3451b2d9810230f4bddfa35fb52..6a623b120e6a3fcdc5eb679b2fcdd22c18ba6ec4 100644 --- a/zed/src/theme_selector.rs +++ b/zed/src/theme_selector.rs @@ -214,7 +214,7 @@ impl ThemeSelector { ) .boxed(), ) - .with_style(&settings.theme.selector.empty.container) + .with_style(settings.theme.selector.empty.container) .named("empty matches"); } @@ -259,9 +259,9 @@ impl ThemeSelector { .boxed(), ) .with_style(if index == self.selected_index { - &theme.selector.active_item.container + theme.selector.active_item.container } else { - &theme.selector.item.container + theme.selector.item.container }); container.boxed() @@ -288,7 +288,7 @@ impl View for ThemeSelector { .with_child(Flexible::new(1.0, self.render_matches(cx)).boxed()) .boxed(), ) - .with_style(&settings.theme.selector.container) + .with_style(settings.theme.selector.container) .boxed(), ) .with_max_width(600.0) diff --git a/zed/src/workspace.rs b/zed/src/workspace.rs index bb69d18424b58e14ebcdf0f3ffd24837d53d48d2..cbdf3b149a532421b92c37a39e1c07106cccbcfd 100644 --- a/zed/src/workspace.rs +++ b/zed/src/workspace.rs @@ -31,7 +31,6 @@ pub use pane::*; pub use pane_group::*; use postage::{prelude::Stream, watch}; use sidebar::{Side, Sidebar, ToggleSidebarItem}; -use smol::prelude::*; use std::{ collections::{hash_map::Entry, HashMap, HashSet}, future::Future, @@ -391,9 +390,14 @@ impl Workspace { right_sidebar.add_item("icons/user-16.svg", cx.add_view(|_| ProjectBrowser).into()); let mut current_user = app_state.user_store.current_user().clone(); + let mut connection_status = app_state.rpc.status().clone(); let _observe_current_user = cx.spawn_weak(|this, mut cx| async move { current_user.recv().await; - while current_user.recv().await.is_some() { + connection_status.recv().await; + let mut stream = + Stream::map(current_user, drop).merge(Stream::map(connection_status, drop)); + + while stream.recv().await.is_some() { cx.update(|cx| { if let Some(this) = this.upgrade(&cx) { this.update(cx, |_, cx| cx.notify()); @@ -642,7 +646,7 @@ impl Workspace { if let Some(load_result) = watch.borrow().as_ref() { break load_result.clone(); } - watch.next().await; + watch.recv().await; }; this.update(&mut cx, |this, cx| { @@ -954,7 +958,34 @@ impl Workspace { &self.active_pane } - fn render_current_user(&self, cx: &mut RenderContext) -> ElementBox { + fn render_connection_status(&self) -> Option { + let theme = &self.settings.borrow().theme; + match dbg!(&*self.rpc.status().borrow()) { + rpc::Status::ConnectionError + | rpc::Status::ConnectionLost + | rpc::Status::Reauthenticating + | rpc::Status::Reconnecting { .. } + | rpc::Status::ReconnectionError { .. } => Some( + Container::new( + Align::new( + ConstrainedBox::new( + Svg::new("icons/offline-14.svg") + .with_color(theme.workspace.titlebar.icon_color) + .boxed(), + ) + .with_width(theme.workspace.titlebar.offline_icon.width) + .boxed(), + ) + .boxed(), + ) + .with_style(theme.workspace.titlebar.offline_icon.container) + .boxed(), + ), + _ => None, + } + } + + fn render_avatar(&self, cx: &mut RenderContext) -> ElementBox { let theme = &self.settings.borrow().theme; let avatar = if let Some(avatar) = self .user_store @@ -969,7 +1000,7 @@ impl Workspace { } else { MouseEventHandler::new::(0, cx, |_, _| { Svg::new("icons/signed-out-12.svg") - .with_color(theme.workspace.titlebar.icon_signed_out) + .with_color(theme.workspace.titlebar.icon_color) .boxed() }) .on_click(|cx| cx.dispatch_action(Authenticate)) @@ -1019,11 +1050,18 @@ impl View for Workspace { .boxed(), ) .with_child( - Align::new(self.render_current_user(cx)).right().boxed(), + Align::new( + Flex::row() + .with_children(self.render_connection_status()) + .with_child(self.render_avatar(cx)) + .boxed(), + ) + .right() + .boxed(), ) .boxed(), ) - .with_style(&theme.workspace.titlebar.container) + .with_style(theme.workspace.titlebar.container) .boxed(), ) .with_height(32.) diff --git a/zed/src/workspace/pane.rs b/zed/src/workspace/pane.rs index 66a062fb65d5355e3c98f8c62f41153f71fe1f42..c0cd6bb9fd7e47141cc633a55cb9eeb00e48ca81 100644 --- a/zed/src/workspace/pane.rs +++ b/zed/src/workspace/pane.rs @@ -1,6 +1,14 @@ use super::{ItemViewHandle, SplitDirection}; use crate::settings::Settings; -use gpui::{Border, Entity, MutableAppContext, Quad, RenderContext, View, ViewContext, ViewHandle, action, color::Color, elements::*, geometry::{rect::RectF, vector::vec2f}, keymap::Binding, platform::CursorStyle}; +use gpui::{ + action, + color::Color, + elements::*, + geometry::{rect::RectF, vector::vec2f}, + keymap::Binding, + platform::CursorStyle, + Border, Entity, MutableAppContext, Quad, RenderContext, View, ViewContext, ViewHandle, +}; use postage::watch; use std::{cmp, path::Path, sync::Arc}; @@ -256,7 +264,7 @@ impl Pane { ) .boxed(), ) - .with_style(&ContainerStyle { + .with_style(ContainerStyle { margin: Margin { left: style.spacing, right: style.spacing, @@ -283,7 +291,8 @@ impl Pane { icon.with_color(style.icon_close).boxed() } }, - ).with_cursor_style(CursorStyle::PointingHand) + ) + .with_cursor_style(CursorStyle::PointingHand) .on_click(move |cx| { cx.dispatch_action(CloseItem(item_id)) }) @@ -298,7 +307,7 @@ impl Pane { ) .boxed(), ) - .with_style(&style.container) + .with_style(style.container) .boxed(), ) .on_mouse_down(move |cx| { diff --git a/zed/src/workspace/sidebar.rs b/zed/src/workspace/sidebar.rs index 7cdb6447a7ff55f8deece33ab5dcbf9738badeb0..3ceba15df0051701873c472a993ced0fe0b0ed64 100644 --- a/zed/src/workspace/sidebar.rs +++ b/zed/src/workspace/sidebar.rs @@ -113,7 +113,7 @@ impl Sidebar { })) .boxed(), ) - .with_style(&theme.container) + .with_style(theme.container) .boxed(), ) .with_width(theme.width) @@ -165,7 +165,7 @@ impl Sidebar { let side = self.side; MouseEventHandler::new::(self.side.id(), &mut cx, |_, _| { Container::new(Empty::new().boxed()) - .with_style(&self.theme(settings).resize_handle) + .with_style(self.theme(settings).resize_handle) .boxed() }) .with_padding(Padding {