Detailed changes
@@ -1,12 +1,13 @@
-use gpui::{div, Div, EventEmitter, ParentElement, Render, SemanticVersion, ViewContext};
+use gpui::{
+ div, DismissEvent, Div, EventEmitter, ParentElement, Render, SemanticVersion, ViewContext,
+};
use menu::Cancel;
-use workspace::notifications::NotificationEvent;
pub struct UpdateNotification {
_version: SemanticVersion,
}
-impl EventEmitter<NotificationEvent> for UpdateNotification {}
+impl EventEmitter<DismissEvent> for UpdateNotification {}
impl Render for UpdateNotification {
type Element = Div;
@@ -82,6 +83,6 @@ impl UpdateNotification {
}
pub fn _dismiss(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
- cx.emit(NotificationEvent::Dismiss);
+ cx.emit(DismissEvent::Dismiss);
}
}
@@ -1,8 +1,9 @@
use collections::{CommandPaletteFilter, HashMap};
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
- actions, div, prelude::*, Action, AppContext, Div, EventEmitter, FocusHandle, FocusableView,
- Keystroke, Manager, ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView,
+ actions, div, prelude::*, Action, AppContext, DismissEvent, Div, EventEmitter, FocusHandle,
+ FocusableView, Keystroke, ParentElement, Render, Styled, View, ViewContext, VisualContext,
+ WeakView,
};
use picker::{Picker, PickerDelegate};
use std::{
@@ -68,7 +69,7 @@ impl CommandPalette {
}
}
-impl EventEmitter<Manager> for CommandPalette {}
+impl EventEmitter<DismissEvent> for CommandPalette {}
impl FocusableView for CommandPalette {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
@@ -268,7 +269,7 @@ impl PickerDelegate for CommandPaletteDelegate {
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
self.command_palette
- .update(cx, |_, cx| cx.emit(Manager::Dismiss))
+ .update(cx, |_, cx| cx.emit(DismissEvent::Dismiss))
.log_err();
}
@@ -2,8 +2,8 @@ use collections::HashMap;
use editor::{scroll::autoscroll::Autoscroll, Bias, Editor};
use fuzzy::{CharBag, PathMatch, PathMatchCandidate};
use gpui::{
- actions, div, AppContext, Div, EventEmitter, FocusHandle, FocusableView, InteractiveElement,
- IntoElement, Manager, Model, ParentElement, Render, Styled, Task, View, ViewContext,
+ actions, div, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView,
+ InteractiveElement, IntoElement, Model, ParentElement, Render, Styled, Task, View, ViewContext,
VisualContext, WeakView,
};
use picker::{Picker, PickerDelegate};
@@ -111,7 +111,7 @@ impl FileFinder {
}
}
-impl EventEmitter<Manager> for FileFinder {}
+impl EventEmitter<DismissEvent> for FileFinder {}
impl FocusableView for FileFinder {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.picker.focus_handle(cx)
@@ -690,7 +690,7 @@ impl PickerDelegate for FileFinderDelegate {
}
}
finder
- .update(&mut cx, |_, cx| cx.emit(Manager::Dismiss))
+ .update(&mut cx, |_, cx| cx.emit(DismissEvent::Dismiss))
.ok()?;
Some(())
@@ -702,7 +702,7 @@ impl PickerDelegate for FileFinderDelegate {
fn dismissed(&mut self, cx: &mut ViewContext<Picker<FileFinderDelegate>>) {
self.file_finder
- .update(cx, |_, cx| cx.emit(Manager::Dismiss))
+ .update(cx, |_, cx| cx.emit(DismissEvent::Dismiss))
.log_err();
}
@@ -1,7 +1,8 @@
use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Editor};
use gpui::{
- actions, div, prelude::*, AppContext, Div, EventEmitter, FocusHandle, FocusableView, Manager,
- Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext, WindowContext,
+ actions, div, prelude::*, AppContext, DismissEvent, Div, EventEmitter, FocusHandle,
+ FocusableView, Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext,
+ WindowContext,
};
use text::{Bias, Point};
use theme::ActiveTheme;
@@ -28,7 +29,7 @@ impl FocusableView for GoToLine {
self.active_editor.focus_handle(cx)
}
}
-impl EventEmitter<Manager> for GoToLine {}
+impl EventEmitter<DismissEvent> for GoToLine {}
impl GoToLine {
fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
@@ -88,7 +89,7 @@ impl GoToLine {
) {
match event {
// todo!() this isn't working...
- editor::EditorEvent::Blurred => cx.emit(Manager::Dismiss),
+ editor::EditorEvent::Blurred => cx.emit(DismissEvent::Dismiss),
editor::EditorEvent::BufferEdited { .. } => self.highlight_current_line(cx),
_ => {}
}
@@ -123,7 +124,7 @@ impl GoToLine {
}
fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
- cx.emit(Manager::Dismiss);
+ cx.emit(DismissEvent::Dismiss);
}
fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
@@ -140,7 +141,7 @@ impl GoToLine {
self.prev_scroll_position.take();
}
- cx.emit(Manager::Dismiss);
+ cx.emit(DismissEvent::Dismiss);
}
}
@@ -1,7 +1,7 @@
use crate::{
- AnyView, AnyWindowHandle, AppCell, AppContext, BackgroundExecutor, Context, FocusableView,
- ForegroundExecutor, Manager, Model, ModelContext, Render, Result, Task, View, ViewContext,
- VisualContext, WindowContext, WindowHandle,
+ AnyView, AnyWindowHandle, AppCell, AppContext, BackgroundExecutor, Context, DismissEvent,
+ FocusableView, ForegroundExecutor, Model, ModelContext, Render, Result, Task, View,
+ ViewContext, VisualContext, WindowContext, WindowHandle,
};
use anyhow::{anyhow, Context as _};
use derive_more::{Deref, DerefMut};
@@ -326,7 +326,7 @@ impl VisualContext for AsyncWindowContext {
V: crate::ManagedView,
{
self.window.update(self, |_, cx| {
- view.update(cx, |_, cx| cx.emit(Manager::Dismiss))
+ view.update(cx, |_, cx| cx.emit(DismissEvent::Dismiss))
})
}
}
@@ -611,7 +611,7 @@ impl<'a> VisualContext for VisualTestContext<'a> {
{
self.window
.update(self.cx, |_, cx| {
- view.update(cx, |_, cx| cx.emit(crate::Manager::Dismiss))
+ view.update(cx, |_, cx| cx.emit(crate::DismissEvent::Dismiss))
})
.unwrap()
}
@@ -193,11 +193,11 @@ pub trait FocusableView: 'static + Render {
/// ManagedView is a view (like a Modal, Popover, Menu, etc.)
/// where the lifecycle of the view is handled by another view.
-pub trait ManagedView: FocusableView + EventEmitter<Manager> {}
+pub trait ManagedView: FocusableView + EventEmitter<DismissEvent> {}
-impl<M: FocusableView + EventEmitter<Manager>> ManagedView for M {}
+impl<M: FocusableView + EventEmitter<DismissEvent>> ManagedView for M {}
-pub enum Manager {
+pub enum DismissEvent {
Dismiss,
}
@@ -1663,7 +1663,7 @@ impl VisualContext for WindowContext<'_> {
where
V: ManagedView,
{
- self.update_view(view, |_, cx| cx.emit(Manager::Dismiss))
+ self.update_view(view, |_, cx| cx.emit(DismissEvent::Dismiss))
}
}
@@ -2349,7 +2349,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
where
V: ManagedView,
{
- self.defer(|_, cx| cx.emit(Manager::Dismiss))
+ self.defer(|_, cx| cx.emit(DismissEvent::Dismiss))
}
pub fn listener<E>(
@@ -4,9 +4,9 @@ use std::rc::Rc;
use crate::{prelude::*, v_stack, Label, List};
use crate::{ListItem, ListSeparator, ListSubHeader};
use gpui::{
- overlay, px, Action, AnchorCorner, AnyElement, AppContext, Bounds, ClickEvent, DispatchPhase,
- Div, EventEmitter, FocusHandle, FocusableView, IntoElement, LayoutId, ManagedView, Manager,
- MouseButton, MouseDownEvent, Pixels, Point, Render, View, VisualContext,
+ overlay, px, Action, AnchorCorner, AnyElement, AppContext, Bounds, ClickEvent, DismissEvent,
+ DispatchPhase, Div, EventEmitter, FocusHandle, FocusableView, IntoElement, LayoutId,
+ ManagedView, MouseButton, MouseDownEvent, Pixels, Point, Render, View, VisualContext,
};
pub enum ContextMenuItem {
@@ -26,7 +26,7 @@ impl FocusableView for ContextMenu {
}
}
-impl EventEmitter<Manager> for ContextMenu {}
+impl EventEmitter<DismissEvent> for ContextMenu {}
impl ContextMenu {
pub fn build(
@@ -74,11 +74,11 @@ impl ContextMenu {
pub fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
// todo!()
- cx.emit(Manager::Dismiss);
+ cx.emit(DismissEvent::Dismiss);
}
pub fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
- cx.emit(Manager::Dismiss);
+ cx.emit(DismissEvent::Dismiss);
}
}
@@ -111,7 +111,7 @@ impl Render for ContextMenu {
}
ContextMenuItem::Entry(entry, callback) => {
let callback = callback.clone();
- let dismiss = cx.listener(|_, _, cx| cx.emit(Manager::Dismiss));
+ let dismiss = cx.listener(|_, _, cx| cx.emit(DismissEvent::Dismiss));
ListItem::new(entry.clone())
.child(Label::new(entry.clone()))
@@ -265,7 +265,7 @@ impl<M: ManagedView> Element for MenuHandle<M> {
let new_menu = (builder)(cx);
let menu2 = menu.clone();
cx.subscribe(&new_menu, move |modal, e, cx| match e {
- &Manager::Dismiss => {
+ &DismissEvent::Dismiss => {
*menu2.borrow_mut() = None;
cx.notify();
}
@@ -63,7 +63,7 @@ use crate::{
};
use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
use lazy_static::lazy_static;
-use notifications::{NotificationHandle, NotifyResultExt};
+use notifications::{simple_message_notification, NotificationHandle, NotifyResultExt};
pub use pane::*;
pub use pane_group::*;
use persistence::{model::SerializedItem, DB};
@@ -776,7 +776,23 @@ impl Workspace {
}),
];
- cx.defer(|this, cx| this.update_window_title(cx));
+ cx.defer(|this, cx| {
+ this.update_window_title(cx);
+
+ this.show_notification(0, cx, |cx| {
+ cx.add_view(|_cx| {
+ simple_message_notification::MessageNotification::new(format!(
+ "Error: what happens if this message is very very very very very long "
+ ))
+ .with_click_message("Click here because!")
+ })
+ });
+ this.show_notification(1, cx, |cx| {
+ cx.add_view(|_cx| {
+ simple_message_notification::MessageNotification::new(format!("Nope"))
+ })
+ });
+ });
Workspace {
weak_self: weak_handle.clone(),
modal: None,
@@ -1,6 +1,9 @@
use crate::{Toast, Workspace};
use collections::HashMap;
-use gpui::{AnyView, AppContext, Entity, EntityId, EventEmitter, Render, View, ViewContext};
+use gpui::{
+ AnyView, AppContext, AsyncWindowContext, DismissEvent, Entity, EntityId, EventEmitter, Render,
+ View, ViewContext, VisualContext,
+};
use std::{any::TypeId, ops::DerefMut};
pub fn init(cx: &mut AppContext) {
@@ -9,13 +12,9 @@ pub fn init(cx: &mut AppContext) {
// simple_message_notification::init(cx);
}
-pub enum NotificationEvent {
- Dismiss,
-}
-
-pub trait Notification: EventEmitter<NotificationEvent> + Render {}
+pub trait Notification: EventEmitter<DismissEvent> + Render {}
-impl<V: EventEmitter<NotificationEvent> + Render> Notification for V {}
+impl<V: EventEmitter<DismissEvent> + Render> Notification for V {}
pub trait NotificationHandle: Send {
fn id(&self) -> EntityId;
@@ -107,8 +106,8 @@ impl Workspace {
let notification = build_notification(cx);
cx.subscribe(
¬ification,
- move |this, handle, event: &NotificationEvent, cx| match event {
- NotificationEvent::Dismiss => {
+ move |this, handle, event: &DismissEvent, cx| match event {
+ DismissEvent::Dismiss => {
this.dismiss_notification_internal(type_id, id, cx);
}
},
@@ -120,6 +119,17 @@ impl Workspace {
}
}
+ pub fn show_error<E>(&mut self, err: &E, cx: &mut ViewContext<Self>)
+ where
+ E: std::fmt::Debug,
+ {
+ self.show_notification(0, cx, |cx| {
+ cx.build_view(|_cx| {
+ simple_message_notification::MessageNotification::new(format!("Error: {err:?}"))
+ })
+ });
+ }
+
pub fn dismiss_notification<V: Notification>(&mut self, id: usize, cx: &mut ViewContext<Self>) {
let type_id = TypeId::of::<V>();
@@ -166,13 +176,14 @@ impl Workspace {
}
pub mod simple_message_notification {
- use super::NotificationEvent;
- use gpui::{AnyElement, AppContext, Div, EventEmitter, Render, TextStyle, ViewContext};
+ use gpui::{
+ div, AnyElement, AppContext, DismissEvent, Div, EventEmitter, InteractiveElement,
+ ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, TextStyle,
+ ViewContext,
+ };
use serde::Deserialize;
use std::{borrow::Cow, sync::Arc};
-
- // todo!()
- // actions!(message_notifications, [CancelMessageNotification]);
+ use ui::{h_stack, v_stack, Button, Icon, IconElement, Label, StyledExt};
#[derive(Clone, Default, Deserialize, PartialEq)]
pub struct OsOpen(pub Cow<'static, str>);
@@ -197,22 +208,22 @@ pub mod simple_message_notification {
// }
enum NotificationMessage {
- Text(Cow<'static, str>),
+ Text(SharedString),
Element(fn(TextStyle, &AppContext) -> AnyElement),
}
pub struct MessageNotification {
message: NotificationMessage,
on_click: Option<Arc<dyn Fn(&mut ViewContext<Self>) + Send + Sync>>,
- click_message: Option<Cow<'static, str>>,
+ click_message: Option<SharedString>,
}
- impl EventEmitter<NotificationMessage> for MessageNotification {}
+ impl EventEmitter<DismissEvent> for MessageNotification {}
impl MessageNotification {
pub fn new<S>(message: S) -> MessageNotification
where
- S: Into<Cow<'static, str>>,
+ S: Into<SharedString>,
{
Self {
message: NotificationMessage::Text(message.into()),
@@ -221,19 +232,20 @@ pub mod simple_message_notification {
}
}
- pub fn new_element(
- message: fn(TextStyle, &AppContext) -> AnyElement,
- ) -> MessageNotification {
- Self {
- message: NotificationMessage::Element(message),
- on_click: None,
- click_message: None,
- }
- }
+ // not needed I think (only for the "new panel" toast, which is outdated now)
+ // pub fn new_element(
+ // message: fn(TextStyle, &AppContext) -> AnyElement,
+ // ) -> MessageNotification {
+ // Self {
+ // message: NotificationMessage::Element(message),
+ // on_click: None,
+ // click_message: None,
+ // }
+ // }
pub fn with_click_message<S>(mut self, message: S) -> Self
where
- S: Into<Cow<'static, str>>,
+ S: Into<SharedString>,
{
self.click_message = Some(message.into());
self
@@ -247,17 +259,43 @@ pub mod simple_message_notification {
self
}
- // todo!()
- // pub fn dismiss(&mut self, _: &CancelMessageNotification, cx: &mut ViewContext<Self>) {
- // cx.emit(MessageNotificationEvent::Dismiss);
- // }
+ pub fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
+ cx.emit(DismissEvent::Dismiss);
+ }
}
impl Render for MessageNotification {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
- todo!()
+ v_stack()
+ .elevation_3(cx)
+ .p_4()
+ .child(
+ h_stack()
+ .justify_between()
+ .child(div().max_w_80().child(match &self.message {
+ NotificationMessage::Text(text) => Label::new(text.clone()),
+ NotificationMessage::Element(element) => {
+ todo!()
+ }
+ }))
+ .child(
+ div()
+ .id("cancel")
+ .child(IconElement::new(Icon::Close))
+ .cursor_pointer()
+ .on_click(cx.listener(|this, event, cx| this.dismiss(cx))),
+ ),
+ )
+ .children(self.click_message.iter().map(|message| {
+ Button::new(message.clone()).on_click(cx.listener(|this, _, cx| {
+ if let Some(on_click) = this.on_click.as_ref() {
+ (on_click)(cx)
+ };
+ this.dismiss(cx)
+ }))
+ }))
}
}
// todo!()
@@ -359,8 +397,6 @@ pub mod simple_message_notification {
// .into_any()
// }
// }
-
- impl EventEmitter<NotificationEvent> for MessageNotification {}
}
pub trait NotifyResultExt {
@@ -371,6 +407,8 @@ pub trait NotifyResultExt {
workspace: &mut Workspace,
cx: &mut ViewContext<Workspace>,
) -> Option<Self::Ok>;
+
+ fn notify_async_err(self, cx: &mut AsyncWindowContext) -> Option<Self::Ok>;
}
impl<T, E> NotifyResultExt for Result<T, E>
@@ -384,14 +422,23 @@ where
Ok(value) => Some(value),
Err(err) => {
log::error!("TODO {err:?}");
- // todo!()
- // workspace.show_notification(0, cx, |cx| {
- // cx.add_view(|_cx| {
- // simple_message_notification::MessageNotification::new(format!(
- // "Error: {err:?}",
- // ))
- // })
- // });
+ workspace.show_error(&err, cx);
+ None
+ }
+ }
+ }
+
+ fn notify_async_err(self, cx: &mut AsyncWindowContext) -> Option<T> {
+ match self {
+ Ok(value) => Some(value),
+ Err(err) => {
+ log::error!("TODO {err:?}");
+ cx.update(|view, cx| {
+ if let Ok(workspace) = view.downcast::<Workspace>() {
+ workspace.update(cx, |workspace, cx| workspace.show_error(&err, cx))
+ }
+ })
+ .ok();
None
}
}
@@ -683,7 +683,21 @@ impl Workspace {
}),
];
- cx.defer(|this, cx| this.update_window_title(cx));
+ cx.defer(|this, cx| {
+ this.update_window_title(cx);
+ // todo! @nate - these are useful for testing notifications
+ // this.show_error(
+ // &anyhow::anyhow!("what happens if this message is very very very very very long"),
+ // cx,
+ // );
+
+ // this.show_notification(1, cx, |cx| {
+ // cx.build_view(|_cx| {
+ // simple_message_notification::MessageNotification::new(format!("Error:"))
+ // .with_click_message("click here because!")
+ // })
+ // });
+ });
Workspace {
window_self: window_handle,
weak_self: weak_handle.clone(),
@@ -2566,32 +2580,31 @@ impl Workspace {
// }
// }
- // fn render_notifications(
- // &self,
- // theme: &theme::Workspace,
- // cx: &AppContext,
- // ) -> Option<AnyElement<Workspace>> {
- // if self.notifications.is_empty() {
- // None
- // } else {
- // Some(
- // Flex::column()
- // .with_children(self.notifications.iter().map(|(_, _, notification)| {
- // ChildView::new(notification.as_any(), cx)
- // .contained()
- // .with_style(theme.notification)
- // }))
- // .constrained()
- // .with_width(theme.notifications.width)
- // .contained()
- // .with_style(theme.notifications.container)
- // .aligned()
- // .bottom()
- // .right()
- // .into_any(),
- // )
- // }
- // }
+ fn render_notifications(&self, cx: &ViewContext<Self>) -> Option<Div> {
+ if self.notifications.is_empty() {
+ None
+ } else {
+ Some(
+ div()
+ .absolute()
+ .z_index(100)
+ .right_3()
+ .bottom_3()
+ .w_96()
+ .h_full()
+ .flex()
+ .flex_col()
+ .justify_end()
+ .gap_2()
+ .children(self.notifications.iter().map(|(_, _, notification)| {
+ div()
+ .on_any_mouse_down(|_, cx| cx.stop_propagation())
+ .on_any_mouse_up(|_, cx| cx.stop_propagation())
+ .child(notification.to_any())
+ })),
+ )
+ }
+ }
// // RPC handlers
@@ -3653,7 +3666,6 @@ impl Render for Workspace {
.bg(cx.theme().colors().background)
.children(self.titlebar_item.clone())
.child(
- // todo! should this be a component a view?
div()
.id("workspace")
.relative()
@@ -3703,7 +3715,8 @@ impl Render for Workspace {
.overflow_hidden()
.child(self.right_dock.clone()),
),
- ),
+ )
+ .children(self.render_notifications(cx)),
)
.child(self.status_bar.clone())
}