1use gpui::{
2 App, ClickEvent, FocusHandle, InteractiveElement as _, IntoElement, MouseButton, ParentElement,
3 Pixels, RenderOnce, Role, StatefulInteractiveElement as _, StyleRefinement, Styled, Window,
4 div,
5};
6use smallvec::SmallVec;
7
8use crate::StyledExt as _;
9use crate::{Dialog, DialogChangeReason, DialogHandle};
10
11macro_rules! alert_part {
12 ($name:ident, $id:literal) => {
13 #[derive(IntoElement)]
14 pub struct $name {
15 style: StyleRefinement,
16 children: SmallVec<[gpui::AnyElement; 2]>,
17 }
18 impl $name {
19 pub fn new() -> Self {
20 Self {
21 style: StyleRefinement::default(),
22 children: SmallVec::new(),
23 }
24 }
25 }
26 impl Default for $name {
27 fn default() -> Self {
28 Self::new()
29 }
30 }
31 impl ParentElement for $name {
32 fn extend(&mut self, elements: impl IntoIterator<Item = gpui::AnyElement>) {
33 self.children.extend(elements);
34 }
35 }
36 impl Styled for $name {
37 fn style(&mut self) -> &mut StyleRefinement {
38 &mut self.style
39 }
40 }
41 impl RenderOnce for $name {
42 fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
43 div()
44 .id($id)
45 .children(self.children)
46 .refine_style(&self.style)
47 }
48 }
49 };
50}
51
52alert_part!(AlertDialogBackdrop, "alert-dialog-backdrop");
53alert_part!(AlertDialogPopup, "alert-dialog-popup");
54alert_part!(AlertDialogTitle, "alert-dialog-title");
55alert_part!(AlertDialogDescription, "alert-dialog-description");
56
57#[derive(IntoElement)]
58pub struct AlertDialogTrigger {
59 trigger: gpui::AnyElement,
60 open: std::rc::Rc<dyn Fn(&mut Window, &mut App)>,
61 handle: Option<DialogHandle>,
62}
63impl AlertDialogTrigger {
64 pub fn new(trigger: impl IntoElement) -> Self {
65 Self {
66 trigger: trigger.into_any_element(),
67 open: std::rc::Rc::new(|_, _| {}),
68 handle: None,
69 }
70 }
71 pub fn on_open(mut self, open: impl Fn(&mut Window, &mut App) + 'static) -> Self {
72 self.open = std::rc::Rc::new(open);
73 self
74 }
75 pub fn handle(mut self, handle: DialogHandle) -> Self {
76 self.handle = Some(handle);
77 self
78 }
79}
80impl RenderOnce for AlertDialogTrigger {
81 fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
82 div()
83 .on_mouse_down(MouseButton::Left, move |_, window, cx| {
84 if let Some(handle) = self.handle.as_ref() {
85 handle.set_open(true, DialogChangeReason::TriggerPress, window, cx);
86 }
87 (self.open)(window, cx);
88 cx.stop_propagation();
89 })
90 .child(self.trigger)
91 }
92}
93
94macro_rules! alert_close_part {
95 ($name:ident, $id:literal) => {
96 #[derive(IntoElement)]
97 pub struct $name {
98 style: StyleRefinement,
99 children: SmallVec<[gpui::AnyElement; 1]>,
100 }
101 impl $name {
102 pub fn new() -> Self {
103 Self {
104 style: StyleRefinement::default(),
105 children: SmallVec::new(),
106 }
107 }
108 }
109 impl Default for $name {
110 fn default() -> Self {
111 Self::new()
112 }
113 }
114 impl ParentElement for $name {
115 fn extend(&mut self, elements: impl IntoIterator<Item = gpui::AnyElement>) {
116 self.children.extend(elements);
117 }
118 }
119 impl Styled for $name {
120 fn style(&mut self) -> &mut StyleRefinement {
121 &mut self.style
122 }
123 }
124 impl RenderOnce for $name {
125 fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
126 div()
127 .id($id)
128 .on_click(|_, window, cx| {
129 window.dispatch_action(Box::new(crate::actions::Cancel), cx)
130 })
131 .children(self.children)
132 .refine_style(&self.style)
133 }
134 }
135 };
136}
137alert_close_part!(AlertDialogClose, "alert-dialog-close");
138alert_close_part!(AlertDialogCancel, "alert-dialog-cancel");
139
140#[derive(IntoElement)]
142pub struct AlertDialogAction {
143 style: StyleRefinement,
144 children: SmallVec<[gpui::AnyElement; 1]>,
145}
146
147impl AlertDialogAction {
148 pub fn new() -> Self {
149 Self {
150 style: StyleRefinement::default(),
151 children: SmallVec::new(),
152 }
153 }
154}
155
156impl Default for AlertDialogAction {
157 fn default() -> Self {
158 Self::new()
159 }
160}
161
162impl ParentElement for AlertDialogAction {
163 fn extend(&mut self, elements: impl IntoIterator<Item = gpui::AnyElement>) {
164 self.children.extend(elements);
165 }
166}
167
168impl Styled for AlertDialogAction {
169 fn style(&mut self) -> &mut StyleRefinement {
170 &mut self.style
171 }
172}
173
174impl RenderOnce for AlertDialogAction {
175 fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
176 div()
177 .id("alert-dialog-action")
178 .on_click(|_, window, cx| {
179 window.dispatch_action(Box::new(crate::actions::Confirm { secondary: false }), cx)
180 })
181 .children(self.children)
182 .refine_style(&self.style)
183 }
184}
185
186pub struct AlertDialog(Dialog);
188
189impl AlertDialog {
190 pub fn new(cx: &mut App) -> Self {
191 Self(
192 Dialog::new(cx)
193 .role(Role::AlertDialog)
194 .close_on_backdrop_press(false),
195 )
196 }
197 pub fn open(mut self, open: bool) -> Self {
198 self.0 = self.0.open(open);
199 self
200 }
201 pub fn handle(mut self, handle: DialogHandle) -> Self {
202 self.0 = self.0.handle(handle);
203 self
204 }
205 pub fn on_open_change(
206 mut self,
207 handler: impl Fn(bool, DialogChangeReason, &mut Window, &mut App) + 'static,
208 ) -> Self {
209 self.0 = self.0.on_open_change(handler);
210 self
211 }
212 pub fn backdrop(mut self, element: impl IntoElement) -> Self {
213 self.0 = self.0.backdrop(element);
214 self
215 }
216 pub fn popup(mut self, element: impl IntoElement) -> Self {
217 self.0 = self.0.popup(element);
218 self
219 }
220 pub fn close_on_escape(mut self, value: bool) -> Self {
221 self.0 = self.0.close_on_escape(value);
222 self
223 }
224 pub fn dismiss_below_y(mut self, value: Pixels) -> Self {
225 self.0 = self.0.dismiss_below_y(value);
226 self
227 }
228 pub fn on_ok(
229 mut self,
230 handler: impl Fn(&ClickEvent, &mut Window, &mut App) -> bool + 'static,
231 ) -> Self {
232 self.0 = self.0.on_ok(handler);
233 self
234 }
235 pub fn on_cancel(
236 mut self,
237 handler: impl Fn(&ClickEvent, &mut Window, &mut App) -> bool + 'static,
238 ) -> Self {
239 self.0 = self.0.on_cancel(handler);
240 self
241 }
242 pub fn on_close(
243 mut self,
244 handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
245 ) -> Self {
246 self.0 = self.0.on_close(handler);
247 self
248 }
249 #[doc(hidden)]
250 pub fn layer(mut self, index: usize, topmost: bool) -> Self {
251 self.0 = self.0.layer(index, topmost);
252 self
253 }
254 #[doc(hidden)]
255 pub fn focus_handle(mut self, value: FocusHandle) -> Self {
256 self.0 = self.0.focus_handle(value);
257 self
258 }
259 #[doc(hidden)]
260 pub fn request_close(
261 mut self,
262 handler: impl Fn(bool, &mut Window, &mut App) + 'static,
263 ) -> Self {
264 self.0 = self.0.request_close(handler);
265 self
266 }
267}
268
269impl ParentElement for AlertDialog {
270 fn extend(&mut self, elements: impl IntoIterator<Item = gpui::AnyElement>) {
271 self.0.extend(elements);
272 }
273}
274impl IntoElement for AlertDialog {
275 type Element = <Dialog as IntoElement>::Element;
276 fn into_element(self) -> Self::Element {
277 self.0.into_element()
278 }
279}
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284 use gpui::{Context, Render, div, point, px};
285 use std::{cell::Cell, rc::Rc};
286
287 struct Harness {
288 close_requested: Rc<Cell<bool>>,
289 }
290
291 impl Render for Harness {
292 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
293 let close_requested = self.close_requested.clone();
294 AlertDialog::new(cx)
295 .request_close(move |_, _, _| close_requested.set(true))
296 .backdrop(div().size(px(200.)))
297 }
298 }
299
300 #[gpui::test]
301 fn backdrop_is_not_closable_by_default(cx: &mut gpui::TestAppContext) {
302 cx.update(crate::init);
303 let close_requested = Rc::new(Cell::new(false));
304 let (_, cx) = cx.add_window_view({
305 let close_requested = close_requested.clone();
306 move |_, _| Harness { close_requested }
307 });
308 cx.update(|window, cx| window.draw(cx).clear(cx));
309
310 cx.simulate_click(point(px(20.), px(20.)), Default::default());
311
312 assert!(!close_requested.get());
313 }
314}