Skip to main content

gpui_base/
alert_dialog.rs

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/// Wrapper that dispatches the alert dialog's confirm action.
141#[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
186/// Alert-dialog specialization of the Base modal host.
187pub 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}