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::()