Restyle notification close control (#30262)

Follow-up of https://github.com/zed-industries/zed/pull/30015

Merges suppress and close buttons into one, with `shift` changing the
state and showing different tooltips.
Currently, there's no tooltip for notification suppress action, hence
none is displayed in the video:


https://github.com/user-attachments/assets/678c4d76-a86e-4fe9-8d7b-92996470a8a8

Release Notes:

- N/A
This commit is contained in:
Kirill Bulatov 2025-05-08 17:10:30 +03:00 committed by GitHub
parent 93b88a905a
commit 203cb7a7a2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 123 additions and 77 deletions

View file

@ -155,6 +155,7 @@ pub fn notify_if_app_was_updated(cx: &mut App) {
} }
cx.emit(DismissEvent); cx.emit(DismissEvent);
}) })
.show_suppress_button(false)
}) })
}, },
); );

View file

@ -6,8 +6,8 @@ use collections::HashMap;
use db::kvp::KEY_VALUE_STORE; use db::kvp::KEY_VALUE_STORE;
use futures::StreamExt; use futures::StreamExt;
use gpui::{ use gpui::{
AnyElement, App, AsyncWindowContext, Context, CursorStyle, DismissEvent, Element, Entity, AnyElement, App, AsyncWindowContext, ClickEvent, Context, CursorStyle, DismissEvent, Element,
EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, ListAlignment, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, ListAlignment,
ListScrollEvent, ListState, ParentElement, Render, StatefulInteractiveElement, Styled, Task, ListScrollEvent, ListState, ParentElement, Render, StatefulInteractiveElement, Styled, Task,
WeakEntity, Window, actions, div, img, list, px, WeakEntity, Window, actions, div, img, list, px,
}; };
@ -22,7 +22,6 @@ use ui::{
Avatar, Button, Icon, IconButton, IconName, Label, Tab, Tooltip, h_flex, prelude::*, v_flex, Avatar, Button, Icon, IconButton, IconName, Label, Tab, Tooltip, h_flex, prelude::*, v_flex,
}; };
use util::{ResultExt, TryFutureExt}; use util::{ResultExt, TryFutureExt};
use workspace::SuppressNotification;
use workspace::notifications::{ use workspace::notifications::{
Notification as WorkspaceNotification, NotificationId, SuppressEvent, Notification as WorkspaceNotification, NotificationId, SuppressEvent,
}; };
@ -812,32 +811,50 @@ impl NotificationToast {
} }
impl Render for NotificationToast { impl Render for NotificationToast {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let user = self.actor.clone(); let user = self.actor.clone();
let suppress = window.modifiers().shift;
let (close_id, close_icon) = if suppress {
("suppress", IconName::Minimize)
} else {
("close", IconName::Close)
};
h_flex() h_flex()
.id("notification_panel_toast") .id("notification_panel_toast")
.elevation_3(cx) .elevation_3(cx)
.p_2() .p_2()
.gap_2() .justify_between()
.children(user.map(|user| Avatar::new(user.avatar_uri.clone()))) .children(user.map(|user| Avatar::new(user.avatar_uri.clone())))
.child(Label::new(self.text.clone())) .child(Label::new(self.text.clone()))
.on_modifiers_changed(cx.listener(|_, _, _, cx| cx.notify()))
.child( .child(
IconButton::new("close", IconName::Close) IconButton::new(close_id, close_icon)
.tooltip(|window, cx| Tooltip::for_action("Close", &menu::Cancel, window, cx)) .tooltip(move |window, cx| {
.on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))), if suppress {
) Tooltip::for_action(
.child( "Suppress.\nClose with click.",
IconButton::new("suppress", IconName::SquareMinus) &workspace::SuppressNotification,
.tooltip(|window, cx| { window,
Tooltip::for_action( cx,
"Do not show until restart", )
&SuppressNotification, } else {
window, Tooltip::for_action(
cx, "Close.\nSuppress with shift-click",
) &menu::Cancel,
window,
cx,
)
}
}) })
.on_click(cx.listener(|_, _, _, cx| cx.emit(SuppressEvent))), .on_click(cx.listener(move |_, _: &ClickEvent, _, cx| {
if suppress {
cx.emit(SuppressEvent);
} else {
cx.emit(DismissEvent);
}
})),
) )
.on_click(cx.listener(|this, _, window, cx| { .on_click(cx.listener(|this, _, window, cx| {
this.focus_notification_panel(window, cx); this.focus_notification_panel(window, cx);

View file

@ -1,7 +1,8 @@
use crate::{SuppressNotification, Toast, Workspace}; use crate::{SuppressNotification, Toast, Workspace};
use gpui::{ use gpui::{
AnyView, App, AppContext as _, AsyncWindowContext, ClipboardItem, Context, DismissEvent, AnyView, App, AppContext as _, AsyncWindowContext, ClickEvent, ClipboardItem, Context,
Entity, EventEmitter, FocusHandle, Focusable, PromptLevel, Render, ScrollHandle, Task, svg, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, PromptLevel, Render, ScrollHandle,
Task, svg,
}; };
use parking_lot::Mutex; use parking_lot::Mutex;
use std::ops::Deref; use std::ops::Deref;
@ -263,6 +264,13 @@ impl Render for LanguageServerPrompt {
PromptLevel::Critical => (IconName::XCircle, Color::Error), PromptLevel::Critical => (IconName::XCircle, Color::Error),
}; };
let suppress = window.modifiers().shift;
let (close_id, close_icon) = if suppress {
("suppress", IconName::Minimize)
} else {
("close", IconName::Close)
};
div() div()
.id("language_server_prompt_notification") .id("language_server_prompt_notification")
.group("language_server_prompt_notification") .group("language_server_prompt_notification")
@ -272,6 +280,7 @@ impl Render for LanguageServerPrompt {
.elevation_3(cx) .elevation_3(cx)
.overflow_y_scroll() .overflow_y_scroll()
.track_scroll(&self.scroll_handle) .track_scroll(&self.scroll_handle)
.on_modifiers_changed(cx.listener(|_, _, _, cx| cx.notify()))
.child( .child(
v_flex() v_flex()
.p_3() .p_3()
@ -289,20 +298,6 @@ impl Render for LanguageServerPrompt {
.child( .child(
h_flex() h_flex()
.gap_2() .gap_2()
.child(
IconButton::new("suppress", IconName::SquareMinus)
.tooltip(|window, cx| {
Tooltip::for_action(
"Do not show until restart",
&SuppressNotification,
window,
cx,
)
})
.on_click(
cx.listener(|_, _, _, cx| cx.emit(SuppressEvent)),
),
)
.child( .child(
IconButton::new("copy", IconName::Copy) IconButton::new("copy", IconName::Copy)
.on_click({ .on_click({
@ -316,18 +311,33 @@ impl Render for LanguageServerPrompt {
.tooltip(Tooltip::text("Copy Description")), .tooltip(Tooltip::text("Copy Description")),
) )
.child( .child(
IconButton::new("close", IconName::Close) IconButton::new(close_id, close_icon)
.tooltip(|window, cx| { .tooltip(move |window, cx| {
Tooltip::for_action( if suppress {
"Close", Tooltip::for_action(
&menu::Cancel, "Suppress.\nClose with click.",
window, &SuppressNotification,
cx, window,
) cx,
)
} else {
Tooltip::for_action(
"Close.\nSuppress with shift-click.",
&menu::Cancel,
window,
cx,
)
}
}) })
.on_click(cx.listener(|_, _, _, cx| { .on_click(cx.listener(
cx.emit(gpui::DismissEvent) move |_, _: &ClickEvent, _, cx| {
})), if suppress {
cx.emit(SuppressEvent);
} else {
cx.emit(DismissEvent);
}
},
)),
), ),
), ),
) )
@ -416,9 +426,8 @@ impl Render for ErrorMessagePrompt {
}), }),
) )
.child( .child(
ui::IconButton::new("close", ui::IconName::Close).on_click( ui::IconButton::new("close", ui::IconName::Close)
cx.listener(|_, _, _, cx| cx.emit(gpui::DismissEvent)), .on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))),
),
), ),
) )
.child( .child(
@ -456,8 +465,8 @@ pub mod simple_message_notification {
use std::sync::Arc; use std::sync::Arc;
use gpui::{ use gpui::{
AnyElement, DismissEvent, EventEmitter, FocusHandle, Focusable, ParentElement, Render, AnyElement, ClickEvent, DismissEvent, EventEmitter, FocusHandle, Focusable, ParentElement,
SharedString, Styled, div, Render, SharedString, Styled, div,
}; };
use ui::{Tooltip, prelude::*}; use ui::{Tooltip, prelude::*};
@ -637,6 +646,14 @@ pub mod simple_message_notification {
impl Render for MessageNotification { impl Render for MessageNotification {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let show_suppress_button = self.show_suppress_button;
let suppress = show_suppress_button && window.modifiers().shift;
let (close_id, close_icon) = if suppress {
("suppress", IconName::Minimize)
} else {
("close", IconName::Close)
};
v_flex() v_flex()
.occlude() .occlude()
.p_3() .p_3()
@ -655,42 +672,43 @@ pub mod simple_message_notification {
}) })
.child(div().max_w_96().child((self.build_content)(window, cx))), .child(div().max_w_96().child((self.build_content)(window, cx))),
) )
.child( .when(self.show_close_button, |this| {
h_flex() this.on_modifiers_changed(cx.listener(|_, _, _, cx| cx.notify()))
.gap_2() .child(
.when(self.show_suppress_button, |this| { IconButton::new(close_id, close_icon)
this.child( .tooltip(move |window, cx| {
IconButton::new("suppress", IconName::SquareMinus) if suppress {
.tooltip(|window, cx| {
Tooltip::for_action( Tooltip::for_action(
"Do not show until restart", "Suppress.\nClose with click.",
&SuppressNotification, &SuppressNotification,
window, window,
cx, cx,
) )
}) } else if show_suppress_button {
.on_click(cx.listener(|_, _, _, cx| {
cx.emit(SuppressEvent);
})),
)
})
.when(self.show_close_button, |this| {
this.child(
IconButton::new("close", IconName::Close)
.tooltip(|window, cx| {
Tooltip::for_action( Tooltip::for_action(
"Close", "Close.\nSuppress with shift-click.",
&menu::Cancel, &menu::Cancel,
window, window,
cx, cx,
) )
}) } else {
.on_click( Tooltip::for_action(
cx.listener(|this, _, _, cx| this.dismiss(cx)), "Close.",
), &menu::Cancel,
) window,
}), cx,
), )
}
})
.on_click(cx.listener(move |_, _: &ClickEvent, _, cx| {
if suppress {
cx.emit(SuppressEvent);
} else {
cx.emit(DismissEvent);
}
})),
)
}),
) )
.child( .child(
h_flex() h_flex()

View file

@ -5729,6 +5729,11 @@ impl Render for Workspace {
let theme = cx.theme().clone(); let theme = cx.theme().clone();
let colors = theme.colors(); let colors = theme.colors();
let notification_entities = self
.notifications
.iter()
.map(|(_, notification)| notification.entity_id())
.collect::<Vec<_>>();
client_side_decorations( client_side_decorations(
self.actions(div(), window, cx) self.actions(div(), window, cx)
@ -5744,6 +5749,11 @@ impl Render for Workspace {
.text_color(colors.text) .text_color(colors.text)
.overflow_hidden() .overflow_hidden()
.children(self.titlebar_item.clone()) .children(self.titlebar_item.clone())
.on_modifiers_changed(move |_, _, cx| {
for &id in &notification_entities {
cx.notify(id);
}
})
.child( .child(
div() div()
.size_full() .size_full()