use gpui::prelude::*;
use gpui::{
deferred, div, px, Context, FontWeight, IntoElement, SharedString, Window,
};
use crate::theme::{theme, ColorName, Size};
struct Toast {
id: usize,
title: Option<SharedString>,
message: SharedString,
color: ColorName,
}
pub struct ToastStack {
toasts: Vec<Toast>,
next_id: usize,
}
impl ToastStack {
pub fn new() -> Self {
ToastStack {
toasts: Vec::new(),
next_id: 0,
}
}
pub fn push(&mut self, message: impl Into<SharedString>, cx: &mut Context<Self>) -> usize {
self.push_toast(None, message.into(), ColorName::Blue, cx)
}
pub fn push_titled(
&mut self,
title: impl Into<SharedString>,
message: impl Into<SharedString>,
color: ColorName,
cx: &mut Context<Self>,
) -> usize {
self.push_toast(Some(title.into()), message.into(), color, cx)
}
fn push_toast(
&mut self,
title: Option<SharedString>,
message: SharedString,
color: ColorName,
cx: &mut Context<Self>,
) -> usize {
let id = self.next_id;
self.next_id += 1;
self.toasts.push(Toast {
id,
title,
message,
color,
});
cx.notify();
id
}
pub fn remove(&mut self, id: usize, cx: &mut Context<Self>) {
self.toasts.retain(|t| t.id != id);
cx.notify();
}
pub fn clear(&mut self, cx: &mut Context<Self>) {
self.toasts.clear();
cx.notify();
}
pub fn len(&self) -> usize {
self.toasts.len()
}
pub fn is_empty(&self) -> bool {
self.toasts.is_empty()
}
}
impl Default for ToastStack {
fn default() -> Self {
ToastStack::new()
}
}
impl Render for ToastStack {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let mut root = div();
if self.toasts.is_empty() {
return root;
}
let t = theme(cx);
let surface = t.surface().hsla();
let border = t.border().hsla();
let text = t.text().hsla();
let dimmed = t.dimmed().hsla();
let radius = t.radius(Size::Md);
let font_sm = t.font_size(Size::Sm);
let mut stack = div()
.absolute()
.top(px(16.0))
.right(px(16.0))
.flex()
.flex_col()
.gap(px(10.0));
for toast in &self.toasts {
let id = toast.id;
let accent = t.color(toast.color, t.primary_shade()).hsla();
let mut content = div().flex().flex_col().gap(px(2.0)).flex_1();
if let Some(title) = toast.title.clone() {
content = content.child(
div()
.font_weight(FontWeight::BOLD)
.text_size(px(font_sm))
.text_color(text)
.child(title),
);
}
content = content.child(
div()
.text_size(px(font_sm))
.text_color(dimmed)
.child(toast.message.clone()),
);
let card = div()
.flex()
.items_start()
.gap(px(12.0))
.w(px(320.0))
.p(px(t.spacing(Size::Md)))
.rounded(px(radius))
.bg(surface)
.border_1()
.border_color(border)
.shadow_md()
.child(div().w(px(4.0)).h(px(38.0)).rounded(px(4.0)).bg(accent))
.child(content)
.child(
div()
.id(("guise-toast-close", id))
.text_color(dimmed)
.hover(move |s| s.text_color(text))
.child(SharedString::new_static("\u{00d7}"))
.on_click(cx.listener(move |this, _ev, _window, cx| this.remove(id, cx))),
);
stack = stack.child(card);
}
root = root.child(deferred(stack));
root
}
}