1use gpui::prelude::*;
6use gpui::*;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub enum AlertVariant {
11 #[default]
13 Info,
14 Success,
16 Warning,
18 Error,
20}
21
22impl AlertVariant {
23 fn colors(&self) -> (Rgba, Rgba, Rgba) {
24 match self {
26 AlertVariant::Info => (rgb(0x1a2a3a), rgb(0x007acc), rgb(0x007acc)),
27 AlertVariant::Success => (rgb(0x1a3a1a), rgb(0x2da44e), rgb(0x2da44e)),
28 AlertVariant::Warning => (rgb(0x3a3a1a), rgb(0xd29922), rgb(0xd29922)),
29 AlertVariant::Error => (rgb(0x3a1a1a), rgb(0xcc3333), rgb(0xcc3333)),
30 }
31 }
32
33 fn icon(&self) -> &'static str {
34 match self {
35 AlertVariant::Info => "ℹ",
36 AlertVariant::Success => "✓",
37 AlertVariant::Warning => "⚠",
38 AlertVariant::Error => "✕",
39 }
40 }
41}
42
43pub struct Alert {
45 id: ElementId,
46 title: Option<SharedString>,
47 message: SharedString,
48 variant: AlertVariant,
49 closeable: bool,
50 icon: Option<SharedString>,
51 on_close: Option<Box<dyn Fn(&mut Window, &mut App) + 'static>>,
52}
53
54impl Alert {
55 pub fn new(id: impl Into<ElementId>, message: impl Into<SharedString>) -> Self {
57 Self {
58 id: id.into(),
59 title: None,
60 message: message.into(),
61 variant: AlertVariant::default(),
62 closeable: false,
63 icon: None,
64 on_close: None,
65 }
66 }
67
68 pub fn title(mut self, title: impl Into<SharedString>) -> Self {
70 self.title = Some(title.into());
71 self
72 }
73
74 pub fn variant(mut self, variant: AlertVariant) -> Self {
76 self.variant = variant;
77 self
78 }
79
80 pub fn closeable(mut self, closeable: bool) -> Self {
82 self.closeable = closeable;
83 self
84 }
85
86 pub fn icon(mut self, icon: impl Into<SharedString>) -> Self {
88 self.icon = Some(icon.into());
89 self
90 }
91
92 pub fn on_close(mut self, handler: impl Fn(&mut Window, &mut App) + 'static) -> Self {
94 self.on_close = Some(Box::new(handler));
95 self
96 }
97
98 pub fn build(self) -> Stateful<Div> {
100 let (bg, border, icon_color) = self.variant.colors();
101 let default_icon = self.variant.icon();
102
103 let mut alert = div()
104 .id(self.id)
105 .flex()
106 .items_start()
107 .gap_3()
108 .p_4()
109 .bg(bg)
110 .border_1()
111 .border_color(border)
112 .rounded_lg();
113
114 let icon = self.icon.unwrap_or_else(|| default_icon.into());
116 alert = alert.child(div().text_lg().text_color(icon_color).child(icon));
117
118 let mut content = div().flex_1().flex().flex_col().gap_1();
120
121 if let Some(title) = self.title {
122 content = content.child(
123 div()
124 .text_sm()
125 .font_weight(FontWeight::SEMIBOLD)
126 .text_color(rgb(0xffffff))
127 .child(title),
128 );
129 }
130
131 content = content.child(
132 div()
133 .text_sm()
134 .text_color(rgb(0xcccccc))
135 .child(self.message),
136 );
137
138 alert = alert.child(content);
139
140 if self.closeable {
142 let mut close_btn = div()
143 .id("alert-close")
144 .text_sm()
145 .text_color(rgb(0x888888))
146 .cursor_pointer()
147 .hover(|s| s.text_color(rgb(0xffffff)));
148
149 if let Some(handler) = self.on_close {
150 let handler_ptr: *const dyn Fn(&mut Window, &mut App) = handler.as_ref();
151 close_btn =
152 close_btn.on_mouse_up(MouseButton::Left, move |_event, window, cx| unsafe {
153 (*handler_ptr)(window, cx);
154 });
155 std::mem::forget(handler);
156 }
157
158 alert = alert.child(close_btn.child("×"));
159 }
160
161 alert
162 }
163}
164
165impl IntoElement for Alert {
166 type Element = Stateful<Div>;
167
168 fn into_element(self) -> Self::Element {
169 self.build()
170 }
171}
172
173pub struct InlineAlert {
175 message: SharedString,
176 variant: AlertVariant,
177}
178
179impl InlineAlert {
180 pub fn new(message: impl Into<SharedString>) -> Self {
182 Self {
183 message: message.into(),
184 variant: AlertVariant::default(),
185 }
186 }
187
188 pub fn variant(mut self, variant: AlertVariant) -> Self {
190 self.variant = variant;
191 self
192 }
193
194 pub fn build(self) -> Div {
196 let (_, _border, icon_color) = self.variant.colors();
197 let icon = self.variant.icon();
198
199 div()
200 .flex()
201 .items_center()
202 .gap_2()
203 .text_sm()
204 .text_color(icon_color)
205 .child(div().child(icon))
206 .child(self.message)
207 }
208}
209
210impl IntoElement for InlineAlert {
211 type Element = Div;
212
213 fn into_element(self) -> Self::Element {
214 self.build()
215 }
216}