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, StyledExt}; 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() .elevation_3(cx) .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))), ), ) } }