From b3f62bf096c571a41159c2b2fd8248916c2c6e56 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Thu, 15 Aug 2024 19:52:37 +0800 Subject: [PATCH] Export `with_type` and impl FluentBuilder for Notification. (#154) --- crates/story/src/modal_story.rs | 8 +- crates/ui/src/notification.rs | 89 +++++----- crates/workspace/src/lib.rs | 2 - crates/workspace/src/model_layer.rs | 179 -------------------- crates/workspace/src/notification.rs | 235 --------------------------- crates/workspace/src/workspace.rs | 41 +---- 6 files changed, 45 insertions(+), 509 deletions(-) delete mode 100644 crates/workspace/src/model_layer.rs delete mode 100644 crates/workspace/src/notification.rs diff --git a/crates/story/src/modal_story.rs b/crates/story/src/modal_story.rs index 5cd4ab0c..cda52a35 100644 --- a/crates/story/src/modal_story.rs +++ b/crates/story/src/modal_story.rs @@ -485,10 +485,10 @@ impl Render for ModalStory { Button::new("show-notify-warning", cx) .label("Warning Notify...") .on_click(cx.listener(|_, _, cx| { - cx.push_notification(( - NotificationType::Warning, + struct WarningNotification; + cx.push_notification(Notification::warning( "The network is not stable, please check your connection.", - )) + ).id1::("test")) })), ) .child( @@ -501,7 +501,7 @@ impl Render for ModalStory { Notification::new( "你已经成功保存了文件,但是有一些警告信息需要你注意。", ) - .type_id::() + .id::() .title("保存成功") .icon(IconName::Inbox) .autohide(false) diff --git a/crates/ui/src/notification.rs b/crates/ui/src/notification.rs index 82dc8d7c..627b433d 100644 --- a/crates/ui/src/notification.rs +++ b/crates/ui/src/notification.rs @@ -1,7 +1,7 @@ use std::{any::TypeId, sync::Arc, time::Duration}; use gpui::{ - div, prelude::FluentBuilder as _, px, Animation, AnimationExt, ClickEvent, DismissEvent, + div, prelude::FluentBuilder, px, Animation, AnimationExt, ClickEvent, DismissEvent, ElementId, EventEmitter, InteractiveElement as _, IntoElement, ParentElement as _, Render, SharedString, StatefulInteractiveElement, Styled, View, ViewContext, VisualContext, WindowContext, }; @@ -21,25 +21,19 @@ pub enum NotificationType { #[derive(Debug, PartialEq, Clone)] pub(crate) enum NotificationId { - Type(TypeId), - Id(SharedString), + Id(TypeId), + IdAndElementId(TypeId, ElementId), } impl From for NotificationId { fn from(type_id: TypeId) -> Self { - Self::Type(type_id) + Self::Id(type_id) } } -impl From for NotificationId { - fn from(id: SharedString) -> Self { - Self::Id(id) - } -} - -impl From<&'static str> for NotificationId { - fn from(id: &'static str) -> Self { - Self::Id(id.into()) +impl From<(TypeId, ElementId)> for NotificationId { + fn from((type_id, id): (TypeId, ElementId)) -> Self { + Self::IdAndElementId(type_id, id) } } @@ -51,7 +45,7 @@ pub struct Notification { id: NotificationId, type_: NotificationType, title: Option, - content: SharedString, + message: SharedString, icon: Option, autohide: bool, on_click: Option>, @@ -81,17 +75,19 @@ impl From<(NotificationType, SharedString)> for Notification { } } +struct DefaultIdType; impl Notification { /// Create a new notification with the given content. /// /// default width is 320px. - pub fn new(content: impl Into) -> Self { + pub fn new(message: impl Into) -> Self { let id: SharedString = uuid::Uuid::new_v4().to_string().into(); + let id = (TypeId::of::(), id.into()); Self { id: id.into(), title: None, - content: content.into(), + message: message.into(), type_: NotificationType::Info, icon: None, autohide: true, @@ -99,22 +95,36 @@ impl Notification { } } - /// Set the id of the notification, used to uniquely identify the notification. - pub fn id(mut self, id: impl Into) -> Self { - let id: SharedString = id.into(); - self.id = id.into(); - self + pub fn info(message: impl Into) -> Self { + Self::new(message).with_type(NotificationType::Info) } - /// Set the type id of the notification, used to uniquely identify the notification. + pub fn success(message: impl Into) -> Self { + Self::new(message).with_type(NotificationType::Success) + } + + pub fn warning(message: impl Into) -> Self { + Self::new(message).with_type(NotificationType::Warning) + } + + pub fn error(message: impl Into) -> Self { + Self::new(message).with_type(NotificationType::Error) + } + + /// Set the type for unique identification of the notification. /// /// ```rs /// struct MyNotificationKind; - /// let notification = Notification::new("Hello").type_id::(); + /// let notification = Notification::new("Hello").id::(); /// ``` - pub fn type_id(mut self) -> Self { - let type_id = TypeId::of::(); - self.id = type_id.into(); + pub fn id(mut self) -> Self { + self.id = TypeId::of::().into(); + self + } + + /// Set the type and id of the notification, used to uniquely identify the notification. + pub fn id1(mut self, key: impl Into) -> Self { + self.id = (TypeId::of::(), key.into()).into(); self } @@ -134,31 +144,12 @@ impl Notification { self } - fn with_type(mut self, type_: NotificationType) -> Self { + /// Set the type of the notification, default is NotificationType::Info. + pub fn with_type(mut self, type_: NotificationType) -> Self { self.type_ = type_; self } - pub fn info(mut self) -> Self { - self.type_ = NotificationType::Info; - self - } - - pub fn success(mut self) -> Self { - self.type_ = NotificationType::Success; - self - } - - pub fn warning(mut self) -> Self { - self.type_ = NotificationType::Warning; - self - } - - pub fn error(mut self) -> Self { - self.type_ = NotificationType::Error; - self - } - /// Set the auto hide of the notification, default is true. pub fn autohide(mut self, autohide: bool) -> Self { self.autohide = autohide; @@ -192,7 +183,7 @@ impl Notification { } } impl EventEmitter for Notification {} - +impl FluentBuilder for Notification {} impl Render for Notification { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { let group_id = "notification-group"; @@ -245,7 +236,7 @@ impl Render for Notification { this.child(div().text_sm().font_semibold().child(title)) }) .overflow_hidden() - .child(div().text_sm().child(self.content.clone())), + .child(div().text_sm().child(self.message.clone())), ) .when_some(self.on_click.clone(), |this, on_click| { this.cursor_pointer() diff --git a/crates/workspace/src/lib.rs b/crates/workspace/src/lib.rs index 78926f98..a5e9b027 100644 --- a/crates/workspace/src/lib.rs +++ b/crates/workspace/src/lib.rs @@ -1,7 +1,5 @@ pub mod dock; pub mod item; -pub mod model_layer; -pub mod notification; pub mod pane; pub mod pane_group; mod title_bar; diff --git a/crates/workspace/src/model_layer.rs b/crates/workspace/src/model_layer.rs deleted file mode 100644 index c4094fd6..00000000 --- a/crates/workspace/src/model_layer.rs +++ /dev/null @@ -1,179 +0,0 @@ -use gpui::{ - div, prelude::*, px, AnyView, DismissEvent, FocusHandle, ManagedView, Render, Subscription, - View, ViewContext, WindowContext, -}; -use ui::theme::ActiveTheme as _; -use ui::{h_flex, v_flex}; - -pub enum DismissDecision { - Dismiss(bool), - Pending, -} - -pub trait ModalView: ManagedView { - fn on_before_dismiss(&mut self, _: &mut ViewContext) -> DismissDecision { - DismissDecision::Dismiss(true) - } - - fn fade_out_background(&self) -> bool { - false - } -} - -trait ModalViewHandle { - fn on_before_dismiss(&mut self, cx: &mut WindowContext) -> DismissDecision; - fn view(&self) -> AnyView; - fn fade_out_background(&self, cx: &WindowContext) -> bool; -} - -impl ModalViewHandle for View { - fn on_before_dismiss(&mut self, cx: &mut WindowContext) -> DismissDecision { - self.update(cx, |this, cx| this.on_before_dismiss(cx)) - } - - fn view(&self) -> AnyView { - self.clone().into() - } - - fn fade_out_background(&self, cx: &WindowContext) -> bool { - self.read(cx).fade_out_background() - } -} - -pub struct ActiveModal { - modal: Box, - _subscriptions: [Subscription; 2], - previous_focus_handle: Option, - focus_handle: FocusHandle, -} - -pub struct ModalLayer { - active_modal: Option, - dismiss_on_focus_lost: bool, -} - -impl ModalLayer { - pub fn new() -> Self { - Self { - active_modal: None, - dismiss_on_focus_lost: false, - } - } - - pub fn toggle_modal(&mut self, cx: &mut ViewContext, build_view: B) - where - V: ModalView, - B: FnOnce(&mut ViewContext) -> V, - { - if let Some(active_modal) = &self.active_modal { - let is_close = active_modal.modal.view().downcast::().is_ok(); - let did_close = self.hide_modal(cx); - if is_close || !did_close { - return; - } - } - let new_modal = cx.new_view(build_view); - self.show_modal(new_modal, cx); - } - - fn show_modal(&mut self, new_modal: View, cx: &mut ViewContext) - where - V: ModalView, - { - let focus_handle = cx.focus_handle(); - self.active_modal = Some(ActiveModal { - modal: Box::new(new_modal.clone()), - _subscriptions: [ - cx.subscribe(&new_modal, |this, _, _: &DismissEvent, cx| { - this.hide_modal(cx); - }), - cx.on_focus_out(&focus_handle, |this, _event, cx| { - if this.dismiss_on_focus_lost { - this.hide_modal(cx); - } - }), - ], - previous_focus_handle: cx.focused(), - focus_handle, - }); - cx.defer(move |_, cx| { - cx.focus_view(&new_modal); - }); - cx.notify(); - } - - fn hide_modal(&mut self, cx: &mut ViewContext) -> bool { - let Some(active_modal) = self.active_modal.as_mut() else { - self.dismiss_on_focus_lost = false; - return false; - }; - - match active_modal.modal.on_before_dismiss(cx) { - DismissDecision::Dismiss(dismiss) => { - self.dismiss_on_focus_lost = !dismiss; - if !dismiss { - return false; - } - } - DismissDecision::Pending => { - self.dismiss_on_focus_lost = false; - return false; - } - } - - if let Some(active_modal) = self.active_modal.take() { - if let Some(previous_focus) = active_modal.previous_focus_handle { - if active_modal.focus_handle.contains_focused(cx) { - previous_focus.focus(cx); - } - } - cx.notify(); - } - true - } - - pub fn active_modal(&self) -> Option> - where - V: 'static, - { - let active_modal = self.active_modal.as_ref()?; - active_modal.modal.view().downcast::().ok() - } - - pub fn has_active_modal(&self) -> bool { - self.active_modal.is_some() - } -} - -impl Render for ModalLayer { - fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - let Some(active_modal) = &self.active_modal else { - return div(); - }; - - div() - .absolute() - .size_full() - .top_0() - .left_0() - .when(active_modal.modal.fade_out_background(cx), |el| { - let mut background = cx.theme().popover; - background.fade_out(0.2); - el.bg(background) - .occlude() - .on_mouse_down_out(cx.listener(|this, _, cx| { - this.hide_modal(cx); - })) - }) - .child( - v_flex() - .h(px(0.0)) - .top_20() - .flex() - .flex_col() - .items_center() - .track_focus(&active_modal.focus_handle) - .child(h_flex().occlude().child(active_modal.modal.view())), - ) - } -} diff --git a/crates/workspace/src/notification.rs b/crates/workspace/src/notification.rs deleted file mode 100644 index 8531fcc3..00000000 --- a/crates/workspace/src/notification.rs +++ /dev/null @@ -1,235 +0,0 @@ -use std::any::TypeId; -use std::borrow::Cow; -use std::sync::Arc; - -use gpui::{ - div, AnyView, DismissEvent, ElementId, Entity, EntityId, EventEmitter, InteractiveElement, - IntoElement, ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, View, - ViewContext, VisualContext, WindowContext, -}; - -use ui::{h_flex, label::Label, theme::ActiveTheme, v_flex, Icon, IconName}; - -use crate::Workspace; - -#[derive(Debug, PartialEq, Clone)] -pub struct NotificationId { - /// A [`TypeId`] used to uniquely identify this notification. - type_id: TypeId, - /// A supplementary ID used to distinguish between multiple - /// notifications that have the same [`type_id`](Self::type_id); - id: Option, -} - -impl NotificationId { - /// Returns a unique [`NotificationId`] for the given type. - pub fn unique() -> Self { - Self { - type_id: TypeId::of::(), - id: None, - } - } - - /// Returns a [`NotificationId`] for the given type that is also identified - /// by the provided ID. - pub fn identified(id: impl Into) -> Self { - Self { - type_id: TypeId::of::(), - id: Some(id.into()), - } - } -} - -impl Workspace { - pub fn show_notification( - &mut self, - id: NotificationId, - cx: &mut ViewContext, - build_notification: impl FnOnce(&mut ViewContext) -> View, - ) { - self.dismiss_notification_internal(&id, cx); - - let notification = build_notification(cx); - cx.subscribe(¬ification, { - let id = id.clone(); - move |this, _, _: &DismissEvent, cx| { - this.dismiss_notification_internal(&id, cx); - } - }) - .detach(); - self.notifications.push((id, Box::new(notification))); - cx.notify(); - } - - pub fn dismiss_notification(&mut self, id: &NotificationId, cx: &mut ViewContext) { - self.dismiss_notification_internal(id, cx) - } - - fn dismiss_notification_internal(&mut self, id: &NotificationId, cx: &mut ViewContext) { - self.notifications.retain(|(existing_id, _)| { - if existing_id == id { - cx.notify(); - false - } else { - true - } - }); - } - - pub fn show_toast(&mut self, toast: Toast, cx: &mut ViewContext) { - self.dismiss_notification(&toast.id, cx); - self.show_notification(toast.id, cx, |cx| { - cx.new_view(|_cx| match toast.on_click.as_ref() { - Some((click_msg, on_click)) => { - let on_click = on_click.clone(); - MessageNotification::new(toast.msg.clone()) - .with_click_message(click_msg.clone()) - .on_click(move |cx| on_click(cx)) - } - None => MessageNotification::new(toast.msg.clone()), - }) - }) - } - - pub fn dismiss_toast(&mut self, id: &NotificationId, cx: &mut ViewContext) { - self.dismiss_notification(id, cx); - } - - pub fn clear_all_notifications(&mut self, cx: &mut ViewContext) { - self.notifications.clear(); - cx.notify(); - } -} - -pub struct Toast { - id: NotificationId, - msg: Cow<'static, str>, - on_click: Option<(Cow<'static, str>, Arc)>, -} - -impl Toast { - pub fn new>>(id: NotificationId, msg: I) -> Self { - Toast { - id, - msg: msg.into(), - on_click: None, - } - } - - pub fn on_click(mut self, message: M, on_click: F) -> Self - where - M: Into>, - F: Fn(&mut WindowContext) + 'static, - { - self.on_click = Some((message.into(), Arc::new(on_click))); - self - } -} - -impl PartialEq for Toast { - fn eq(&self, other: &Self) -> bool { - self.id == other.id - && self.msg == other.msg - && self.on_click.is_some() == other.on_click.is_some() - } -} - -impl Clone for Toast { - fn clone(&self) -> Self { - Toast { - id: self.id.clone(), - msg: self.msg.clone(), - on_click: self.on_click.clone(), - } - } -} - -pub trait Notification: EventEmitter + Render {} - -impl + Render> Notification for V {} - -pub trait NotificationHandle: Send { - fn id(&self) -> EntityId; - fn to_any(&self) -> AnyView; -} - -impl NotificationHandle for View { - fn id(&self) -> EntityId { - self.entity_id() - } - - fn to_any(&self) -> AnyView { - self.clone().into() - } -} - -impl From<&dyn NotificationHandle> for AnyView { - fn from(val: &dyn NotificationHandle) -> Self { - val.to_any() - } -} - -pub struct MessageNotification { - message: SharedString, - on_click: Option)>>, - click_message: Option, -} - -impl EventEmitter for MessageNotification {} - -impl MessageNotification { - pub fn new(message: impl Into) -> Self { - Self { - message: message.into(), - on_click: None, - click_message: None, - } - } - - pub fn with_click_message(mut self, message: S) -> Self - where - S: Into, - { - self.click_message = Some(message.into()); - self - } - - pub fn on_click(mut self, on_click: F) -> Self - where - F: 'static + Fn(&mut ViewContext), - { - self.on_click = Some(Arc::new(on_click)); - self - } - - pub fn dismiss(&mut self, cx: &mut ViewContext) { - cx.emit(DismissEvent); - } -} - -impl Render for MessageNotification { - fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - v_flex() - .bg(cx.theme().popover) - .border_1() - .border_color(cx.theme().border) - .shadow_xl() - .rounded_xl() - .p_4() - .max_w_80() - .bg(cx.theme().background) - .child( - h_flex() - .justify_between() - .gap_2() - .child(div().max_w_80().child(Label::new(self.message.clone()))) - .child( - div() - .id("cancel") - .child(Icon::new(IconName::Close)) - .cursor_pointer() - .on_click(cx.listener(|this, _, cx| this.dismiss(cx))), - ), - ) - } -} diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 312b04a4..7ad1c938 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -7,8 +7,6 @@ use std::{ use crate::{ dock::{Panel, PanelHandle}, - model_layer::ModalLayer, - notification::{NotificationHandle, NotificationId}, pane_group, }; use anyhow::Result; @@ -33,7 +31,6 @@ actions!( [ ActivateNextPane, ActivatePreviousPane, - ClearAllNotifications, CloseAllDocks, ToggleBottomDock, ToggleCenteredLayout, @@ -83,8 +80,6 @@ pub struct Workspace { pub(crate) zoomed_position: Option, database_id: Option, bounds: Bounds, - pub(crate) notifications: Vec<(NotificationId, Box)>, - modal_layer: View, workspace_actions: Vec) -> Div>>, bounds_save_task_queued: Option>, _subscriptions: Vec, @@ -243,9 +238,7 @@ impl Render for Workspace { Some(DockPosition::Bottom) => div.top_2().border_t_1(), None => div.top_2().bottom_2().left_2().right_2().border_1(), }) - })) - .child(self.modal_layer.clone()) - .children(self.render_notifications(cx)), + })), ) } } @@ -275,8 +268,6 @@ impl Workspace { // let bottom_dock_buttons = cx.new_view(|cx| PanelButtons::new(bottom_dock.clone(), cx)); // let right_dock_buttons = cx.new_view(|cx| PanelButtons::new(right_dock.clone(), cx)); - let modal_layer = cx.new_view(|_| ModalLayer::new()); - let subscriptions = vec![ cx.observe_window_activation(Self::on_window_activation_changed), cx.observe_window_bounds(move |this, cx| { @@ -337,8 +328,6 @@ impl Workspace { panes_by_item: Default::default(), active_pane: center_pane.clone(), last_active_center_pane: Some(center_pane.downgrade()), - modal_layer, - notifications: Default::default(), left_dock, bottom_dock, right_dock, @@ -425,11 +414,6 @@ impl Workspace { workspace.close_all_docks(cx); }), ) - .on_action( - cx.listener(|workspace: &mut Workspace, _: &ClearAllNotifications, cx| { - workspace.clear_all_notifications(cx); - }), - ) .on_action(cx.listener(Workspace::activate_pane_at_index)) .on_action( cx.listener(|_workspace: &mut Workspace, _: &ReopenClosedItem, _cx| { @@ -1020,27 +1004,4 @@ impl Workspace { // })); // } } - - fn render_notifications(&self, _cx: &ViewContext) -> Option
{ - if self.notifications.is_empty() { - None - } else { - Some( - div() - .absolute() - .right_3() - .bottom_3() - .h_full() - .flex() - .flex_col() - .justify_end() - .gap_2() - .children( - self.notifications - .iter() - .map(|(_, notification)| notification.to_any()), - ), - ) - } - } }