diff --git a/assets/icons/bell.svg b/assets/icons/bell.svg new file mode 100644 index 00000000..eecc3ef4 --- /dev/null +++ b/assets/icons/bell.svg @@ -0,0 +1 @@ + diff --git a/crates/app/src/story_workspace.rs b/crates/app/src/story_workspace.rs index a6df1d5a..47c3dbee 100644 --- a/crates/app/src/story_workspace.rs +++ b/crates/app/src/story_workspace.rs @@ -10,13 +10,14 @@ use workspace::{TitleBar, Workspace}; use std::sync::Arc; use ui::{ - button::{Button, ButtonStyle}, + button::Button, drawer::Drawer, + h_flex, modal::Modal, popover::Popover, popup_menu::PopupMenu, theme::{ActiveTheme, Theme}, - ContextModal as _, IconName, Root, Sizable, + ContextModal, IconName, Root, Sizable, }; use crate::app_state::AppState; @@ -273,10 +274,11 @@ pub fn open_new( impl Render for StoryWorkspace { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - let active_modal = cx.active_modal(); - let active_drawer = cx.active_drawer(); + let active_modal = Root::read(cx).active_modal.clone(); + let active_drawer = Root::read(cx).active_drawer.clone(); let has_active_modal = active_modal.is_some(); - let notification_view = cx.notification_view(); + let notification_view = Root::read(cx).notification.clone(); + let notifications_count = cx.notifications().len(); div() .relative() @@ -303,7 +305,6 @@ impl Render for StoryWorkspace { .items_center() .justify_end() .px_2() - .mr_3() .gap_2() .child(self.locale_selector.clone()) .child( @@ -330,10 +331,38 @@ impl Render for StoryWorkspace { Button::new("github", cx) .icon(IconName::GitHub) .small() - .style(ButtonStyle::Ghost) + .ghost() .on_click(|_, cx| { cx.open_url("https://github.com/huacnlee/gpui-component") }), + ) + .child( + div() + .relative() + .child( + Button::new("bell", cx) + .small() + .ghost() + .compact() + .icon(IconName::Bell), + ) + .when(notifications_count > 0, |this| { + this.child( + h_flex() + .absolute() + .rounded_full() + .top(px(-2.)) + .right(px(-2.)) + .p(px(1.)) + .min_w(px(12.)) + .bg(ui::red_500()) + .text_color(ui::white()) + .justify_center() + .text_size(px(10.)) + .line_height(relative(1.)) + .child(format!("{}", notifications_count.min(99))), + ) + }), ), ), ) diff --git a/crates/ui/src/icon.rs b/crates/ui/src/icon.rs index 63b49e69..c51cbbb0 100644 --- a/crates/ui/src/icon.rs +++ b/crates/ui/src/icon.rs @@ -11,6 +11,7 @@ pub enum IconName { ArrowRight, ArrowUp, Asterisk, + Bell, Calendar, Check, ChevronDown, @@ -18,6 +19,7 @@ pub enum IconName { ChevronRight, ChevronUp, ChevronsUpDown, + CircleCheck, CircleX, Close, Copy, @@ -50,7 +52,6 @@ pub enum IconName { ThumbsDown, ThumbsUp, TriangleAlert, - CircleCheck, } impl IconName { @@ -61,6 +62,7 @@ impl IconName { IconName::ArrowRight => "icons/arrow-right.svg", IconName::ArrowUp => "icons/arrow-up.svg", IconName::Asterisk => "icons/asterisk.svg", + IconName::Bell => "icons/bell.svg", IconName::Calendar => "icons/calendar.svg", IconName::Check => "icons/check.svg", IconName::ChevronDown => "icons/chevron-down.svg", @@ -68,6 +70,7 @@ impl IconName { IconName::ChevronRight => "icons/chevron-right.svg", IconName::ChevronUp => "icons/chevron-up.svg", IconName::ChevronsUpDown => "icons/chevrons-up-down.svg", + IconName::CircleCheck => "icons/circle-check.svg", IconName::CircleX => "icons/circle-x.svg", IconName::Close => "icons/close.svg", IconName::Copy => "icons/copy.svg", @@ -100,7 +103,6 @@ impl IconName { IconName::ThumbsDown => "icons/thumbs-down.svg", IconName::ThumbsUp => "icons/thumbs-up.svg", IconName::TriangleAlert => "icons/triangle-alert.svg", - IconName::CircleCheck => "icons/circle-check.svg", } .into() } diff --git a/crates/ui/src/notification.rs b/crates/ui/src/notification.rs index 397d293d..d9c8a569 100644 --- a/crates/ui/src/notification.rs +++ b/crates/ui/src/notification.rs @@ -247,7 +247,7 @@ impl Render for Notification { /// A list of notifications. pub struct NotificationList { - notifications: Vec>, + pub(crate) notifications: Vec>, } impl NotificationList { diff --git a/crates/ui/src/root.rs b/crates/ui/src/root.rs index cc9f7c95..d2eec5d5 100644 --- a/crates/ui/src/root.rs +++ b/crates/ui/src/root.rs @@ -21,8 +21,8 @@ pub trait ContextModal: Sized { where F: Fn(Drawer, &mut WindowContext) -> Drawer + 'static; - /// Return the active Drawer builder, you must add the Drawer to the view. - fn active_drawer(&self) -> Option Drawer + 'static>>; + /// Return true, if there is an active Drawer. + fn has_active_drawer(&self) -> bool; /// Closes the active Drawer. fn close_drawer(&mut self); @@ -32,8 +32,8 @@ pub trait ContextModal: Sized { where F: Fn(Modal, &mut WindowContext) -> Modal + 'static; - /// Return the active Modal builder, you must add the Modal to the view. - fn active_modal(&self) -> Option Modal + 'static>>; + /// Return true, if there is an active Modal. + fn has_active_modal(&self) -> bool; /// Closes the active Modal. fn close_modal(&mut self); @@ -41,8 +41,8 @@ pub trait ContextModal: Sized { /// Pushes a notification to the notification list. fn push_notification(&mut self, note: impl Into); fn clear_notifications(&mut self); - /// Returns the notification list view. - fn notification_view(&self) -> AnyView; + /// Returns number of notifications. + fn notifications(&self) -> Rc>>; } impl<'a> ContextModal for WindowContext<'a> { @@ -50,19 +50,19 @@ impl<'a> ContextModal for WindowContext<'a> { where F: Fn(Drawer, &mut WindowContext) -> Drawer + 'static, { - Root::update_root(self, move |root, cx| { + Root::update(self, move |root, cx| { root.previous_focus_handle = cx.focused(); root.active_drawer = Some(Rc::new(build)); cx.notify(); }) } - fn active_drawer(&self) -> Option Drawer + 'static>> { - Root::read_root(&self).active_drawer.clone() + fn has_active_drawer(&self) -> bool { + Root::read(&self).active_drawer.is_some() } fn close_drawer(&mut self) { - Root::update_root(self, |root, cx| { + Root::update(self, |root, cx| { root.active_drawer = None; root.focus_back(cx); cx.notify(); @@ -73,19 +73,19 @@ impl<'a> ContextModal for WindowContext<'a> { where F: Fn(Modal, &mut WindowContext) -> Modal + 'static, { - Root::update_root(self, move |root, cx| { + Root::update(self, move |root, cx| { root.previous_focus_handle = cx.focused(); root.active_modal = Some(Rc::new(build)); cx.notify(); }) } - fn active_modal(&self) -> Option Modal + 'static>> { - Root::read_root(&self).active_modal.clone() + fn has_active_modal(&self) -> bool { + Root::read(&self).active_modal.is_some() } fn close_modal(&mut self) { - Root::update_root(self, |root, cx| { + Root::update(self, |root, cx| { root.active_modal = None; root.focus_back(cx); cx.notify(); @@ -94,22 +94,27 @@ impl<'a> ContextModal for WindowContext<'a> { fn push_notification(&mut self, note: impl Into) { let note = note.into(); - Root::update_root(self, move |root, cx| { - root.notification_list - .update(cx, |view, cx| view.push(note, cx)); + Root::update(self, move |root, cx| { + root.notification.update(cx, |view, cx| view.push(note, cx)); cx.notify(); }) } fn clear_notifications(&mut self) { - Root::update_root(self, move |root, cx| { - root.notification_list.update(cx, |view, cx| view.clear(cx)); + Root::update(self, move |root, cx| { + root.notification.update(cx, |view, cx| view.clear(cx)); cx.notify(); }) } - fn notification_view(&self) -> AnyView { - Root::read_root(&self).notification_list.clone().into() + fn notifications(&self) -> Rc>> { + Rc::new( + Root::read(&self) + .notification + .read(&self) + .notifications + .clone(), + ) } } impl<'a, V> ContextModal for ViewContext<'a, V> { @@ -120,8 +125,8 @@ impl<'a, V> ContextModal for ViewContext<'a, V> { self.deref_mut().open_drawer(build) } - fn active_modal(&self) -> Option Modal + 'static>> { - self.deref().active_modal() + fn has_active_modal(&self) -> bool { + self.deref().has_active_modal() } fn close_drawer(&mut self) { @@ -135,8 +140,8 @@ impl<'a, V> ContextModal for ViewContext<'a, V> { self.deref_mut().open_modal(build) } - fn active_drawer(&self) -> Option Drawer + 'static>> { - self.deref().active_drawer() + fn has_active_drawer(&self) -> bool { + self.deref().has_active_drawer() } fn close_modal(&mut self) { @@ -151,8 +156,8 @@ impl<'a, V> ContextModal for ViewContext<'a, V> { self.deref_mut().clear_notifications() } - fn notification_view(&self) -> AnyView { - self.deref().notification_view() + fn notifications(&self) -> Rc>> { + self.deref().notifications() } } @@ -163,9 +168,9 @@ pub struct Root { /// Used to store the focus handle of the previus revious view. /// When the Modal, Drawer closes, we will focus back to the previous view. previous_focus_handle: Option, - active_drawer: Option Drawer + 'static>>, - active_modal: Option Modal + 'static>>, - notification_list: View, + pub active_drawer: Option Drawer + 'static>>, + pub active_modal: Option Modal + 'static>>, + pub notification: View, child: AnyView, } @@ -175,12 +180,12 @@ impl Root { previous_focus_handle: None, active_drawer: None, active_modal: None, - notification_list: cx.new_view(NotificationList::new), + notification: cx.new_view(NotificationList::new), child, } } - fn update_root(cx: &mut WindowContext, f: F) + pub fn update(cx: &mut WindowContext, f: F) where F: FnOnce(&mut Self, &mut ViewContext) + 'static, { @@ -193,7 +198,7 @@ impl Root { root.update(cx, |root, cx| f(root, cx)) } - fn read_root<'a>(cx: &'a WindowContext) -> &'a Self { + pub fn read<'a>(cx: &'a WindowContext) -> &'a Self { let root = cx .window_handle() .downcast::()