Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/src/terminal/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14775,8 +14775,9 @@ impl TerminalView {
ctx: &mut ViewContext<Self>,
) {
let window_id = ctx.window_id();
let object_id = format!("persistent_toast:{}", &text);
ToastStack::handle(ctx).update(ctx, |toast_stack, ctx| {
let toast = DismissibleToast::new(text, flavor);
let toast = DismissibleToast::new(text, flavor).with_object_id(object_id);
toast_stack.add_persistent_toast(toast, window_id, ctx);
});
}
Expand Down
48 changes: 38 additions & 10 deletions app/src/view_components/dismissible_toast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ struct ToastData<A: Action + Clone> {
/// Unique identifier for the toast. Used for finding the toast to dismiss from the
/// stack.
uuid: Uuid,

/// How many times this toast has been added (for duplicate counting).
count: usize,
}
/// This View is a stack of toasts, each of which holds some "main text" on the left, and optionally
/// a hyperlink on the right. They can either be manually dismissed by clicking the X button, or
Expand Down Expand Up @@ -94,6 +97,7 @@ impl<A: Action + Clone> DismissibleToastStack<A> {
dismissible_toast: toast,
abort_handle: Some(abort_handle),
uuid,
count: 1,
});

ctx.notify();
Expand All @@ -107,13 +111,23 @@ impl<A: Action + Clone> DismissibleToastStack<A> {
ctx: &mut ViewContext<Self>,
) {
if let Some(object_id) = &toast.object_id {
self.dismiss_older_toasts(object_id, ctx);
if let Some(existing) = self
.toasts
.iter_mut()
.rev()
.find(|t| t.dismissible_toast.object_id.as_ref() == Some(object_id))
{
existing.count += 1;
ctx.notify();
return;
}
}

self.toasts.push(ToastData {
dismissible_toast: toast,
abort_handle: None,
uuid: Uuid::new_v4(),
count: 1,
});

ctx.notify();
Expand Down Expand Up @@ -178,15 +192,20 @@ impl<A: Action + Clone> View for DismissibleToastStack<A> {
fn render(&self, app: &AppContext) -> Box<dyn Element> {
let mut rendered_toasts =
Flex::column().with_cross_axis_alignment(CrossAxisAlignment::Center);
// For loop over the toasts in reverse order so that the most recent toast is
// rendered first. Pass in the toast's UUID to the render method so that it is
// piped to the dismiss action when the close button is clicked. The handler will
// use this UUID to determine which toast in the stack to close.
for toast in self.toasts.iter().rev() {
let display_text = if toast.count > 1 {
format!("{} (x{})", toast.dismissible_toast.main_text, toast.count)
} else {
toast.dismissible_toast.main_text.clone()
};
rendered_toasts.add_child(
Container::new(toast.dismissible_toast.render(app, toast.uuid))
.with_margin_bottom(5.)
.finish(),
Container::new(
toast
.dismissible_toast
.render_with_text(app, toast.uuid, display_text),
)
.with_margin_bottom(5.)
.finish(),
);
}

Expand Down Expand Up @@ -312,7 +331,7 @@ pub type OnBodyClickCallback<A> = Rc<dyn Fn(&mut ViewContext<DismissibleToastSta
#[derive(Clone)]
pub struct DismissibleToast<A: Action + Clone> {
flavor: ToastFlavor,
main_text: String,
pub(crate) main_text: String,
link: Option<ToastLink<A>>,
close_button_mouse_state: MouseStateHandle,
close_button_hover_state: MouseStateHandle,
Expand Down Expand Up @@ -390,6 +409,15 @@ impl<A: Action + Clone> DismissibleToast<A> {
}

fn render(&self, app: &AppContext, uuid: Uuid) -> Box<dyn Element> {
self.render_with_text(app, uuid, self.main_text.clone())
}

fn render_with_text(
&self,
app: &AppContext,
uuid: Uuid,
display_text: String,
) -> Box<dyn Element> {
let appearance = Appearance::as_ref(app);
let ui_builder = appearance.ui_builder();

Expand All @@ -405,7 +433,7 @@ impl<A: Action + Clone> DismissibleToast<A> {
Shrinkable::new(
1.,
ui_builder
.wrappable_text(self.main_text.clone(), true)
.wrappable_text(display_text, true)
.with_style(UiComponentStyles {
font_size: Some(appearance.ui_font_size() * 1.2),
font_color: Some(self.flavor.text_color(appearance)),
Expand Down
9 changes: 8 additions & 1 deletion app/src/workspace/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22928,8 +22928,15 @@ impl View for Workspace {
.get_active_input_view_handle(app)
.map(|input| app.view(&input).save_position_id());

let toast_stack_max_height = app
.element_position_by_id_at_last_frame(self.window_id, TAB_CONTENT_POSITION_ID)
.map(|rect| (rect.height() - 32.).max(0.))
.unwrap_or(f32::MAX);

stack.add_positioned_overlay_child(
ChildView::new(&self.toast_stack).finish(),
ConstrainedBox::new(Clipped::new(ChildView::new(&self.toast_stack).finish()).finish())
.with_max_height(toast_stack_max_height)
.finish(),
self.global_toast_positioning(),
);

Expand Down