1use gpui::prelude::*;
4use gpui::{div, px, App, ClickEvent, FontWeight, IntoElement, SharedString, Window};
5
6use crate::devtools::Probed;
7use crate::icon::Glyph;
8use crate::input::ClickHandler;
9use crate::style::{surface, ColorValue, Variant};
10use crate::theme::{theme, Size};
11
12#[derive(IntoElement)]
14pub struct Alert {
15 title: Option<SharedString>,
16 message: SharedString,
17 variant: Variant,
18 color: ColorValue,
19 icon: Option<Glyph>,
20 on_close: Option<ClickHandler>,
21}
22
23impl Alert {
24 pub fn new(message: impl Into<SharedString>) -> Self {
25 Alert {
26 title: None,
27 message: message.into(),
28 variant: Variant::Light,
29 color: ColorValue::default(),
30 icon: None,
31 on_close: None,
32 }
33 }
34
35 pub fn title(mut self, title: impl Into<SharedString>) -> Self {
36 self.title = Some(title.into());
37 self
38 }
39
40 pub fn variant(mut self, variant: Variant) -> Self {
41 self.variant = variant;
42 self
43 }
44
45 pub fn color(mut self, color: impl Into<ColorValue>) -> Self {
46 self.color = color.into();
47 self
48 }
49
50 pub fn icon(mut self, icon: impl Into<Glyph>) -> Self {
52 self.icon = Some(icon.into());
53 self
54 }
55
56 pub fn on_close(
57 mut self,
58 handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
59 ) -> Self {
60 self.on_close = Some(Box::new(handler));
61 self
62 }
63}
64
65impl RenderOnce for Alert {
66 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
67 let t = theme(cx);
68 let s = surface(t, self.color, self.variant);
69 let filled = self.variant == Variant::Filled;
70 let body_color = if filled { s.fg } else { t.text().hsla() };
71 let radius = t.radius(Size::Md);
72 let pad = t.spacing(Size::Md);
73
74 let mut content = div().flex().flex_col().gap(px(2.0)).flex_1();
75 if let Some(title) = self.title {
76 content = content.child(
77 div()
78 .font_weight(FontWeight::BOLD)
79 .text_size(px(t.font_size(Size::Sm)))
80 .text_color(s.fg)
81 .child(title),
82 );
83 }
84 content = content.child(
85 div()
86 .text_size(px(t.font_size(Size::Sm)))
87 .text_color(body_color)
88 .child(self.message),
89 );
90
91 let mut row = div()
92 .flex()
93 .items_start()
94 .gap(px(10.0))
95 .p(px(pad))
96 .rounded(px(radius))
97 .bg(s.bg);
98 if let Some(border) = s.border {
99 row = row.border_1().border_color(border);
100 }
101 if let Some(icon) = self.icon {
102 row = row.child(
103 div()
104 .text_size(px(t.font_size(Size::Md)))
105 .text_color(s.fg)
106 .child(icon),
107 );
108 }
109 row = row.child(content);
110 if let Some(handler) = self.on_close {
111 let close_color = if filled { s.fg } else { t.dimmed().hsla() };
112 row = row.child(
113 div()
114 .id("guise-alert-close")
115 .text_color(close_color)
116 .child(SharedString::new_static("\u{00d7}"))
117 .on_click(handler),
118 );
119 }
120 row.probe("Alert").attr("variant", self.variant.label())
121 }
122}