Skip to main content

glassy_ui/
alert_dialog.rs

1use std::rc::Rc;
2
3use gpui::{prelude::*, App, FocusHandle, RenderOnce, SharedString, Window};
4
5use crate::{
6    Button, ButtonVariant, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader,
7    DialogTitle,
8};
9
10type AlertDialogHandler = Rc<dyn Fn(&mut Window, &mut App) + 'static>;
11
12struct AlertDialogState {
13    cancel_focus: FocusHandle,
14    confirm_focus: FocusHandle,
15}
16
17/// Controlled destructive confirmation with safe focus and a locked scrim.
18#[derive(IntoElement)]
19pub struct AlertDialog {
20    id: SharedString,
21    open: bool,
22    title: SharedString,
23    description: SharedString,
24    cancel_label: SharedString,
25    confirm_label: SharedString,
26    on_cancel: Option<AlertDialogHandler>,
27    on_confirm: Option<AlertDialogHandler>,
28}
29
30impl AlertDialog {
31    pub fn new(
32        id: impl Into<SharedString>,
33        title: impl Into<SharedString>,
34        description: impl Into<SharedString>,
35    ) -> Self {
36        Self {
37            id: id.into(),
38            open: false,
39            title: title.into(),
40            description: description.into(),
41            cancel_label: SharedString::from("Cancel"),
42            confirm_label: SharedString::from("Continue"),
43            on_cancel: None,
44            on_confirm: None,
45        }
46    }
47
48    pub fn open(mut self, open: bool) -> Self {
49        self.open = open;
50        self
51    }
52
53    pub fn cancel_label(mut self, label: impl Into<SharedString>) -> Self {
54        self.cancel_label = label.into();
55        self
56    }
57
58    pub fn confirm_label(mut self, label: impl Into<SharedString>) -> Self {
59        self.confirm_label = label.into();
60        self
61    }
62
63    pub fn on_cancel(mut self, listener: impl Fn(&mut Window, &mut App) + 'static) -> Self {
64        self.on_cancel = Some(Rc::new(listener));
65        self
66    }
67
68    pub fn on_confirm(mut self, listener: impl Fn(&mut Window, &mut App) + 'static) -> Self {
69        self.on_confirm = Some(Rc::new(listener));
70        self
71    }
72}
73
74impl RenderOnce for AlertDialog {
75    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
76        let state_id = SharedString::from(format!("{}-actions", self.id));
77        let state = window.use_keyed_state(state_id, cx, |_, cx| AlertDialogState {
78            cancel_focus: cx.focus_handle().tab_stop(true),
79            confirm_focus: cx.focus_handle().tab_stop(true),
80        });
81        let cancel_focus = state.read(cx).cancel_focus.clone();
82        let confirm_focus = state.read(cx).confirm_focus.clone();
83        let cancel_click = self.on_cancel.clone();
84        let confirm_click = self.on_confirm.clone();
85        let escape_cancel = self.on_cancel;
86        let cancel_id = SharedString::from(format!("{}-cancel", self.id));
87        let confirm_id = SharedString::from(format!("{}-confirm", self.id));
88
89        Dialog::new(self.id)
90            .open(self.open)
91            .dismiss_on_scrim(false)
92            .initial_focus(cancel_focus.clone())
93            .focus_cycle([cancel_focus.clone(), confirm_focus.clone()])
94            .on_dismiss(move |window, cx| {
95                if let Some(on_cancel) = escape_cancel.as_ref() {
96                    on_cancel(window, cx);
97                }
98            })
99            .child(
100                DialogContent::new()
101                    .child(
102                        DialogHeader::new()
103                            .child(DialogTitle::new(self.title))
104                            .child(DialogDescription::new(self.description)),
105                    )
106                    .child(
107                        DialogFooter::new()
108                            .child(
109                                Button::new(cancel_id, self.cancel_label)
110                                    .variant(ButtonVariant::Ghost)
111                                    .focus_handle(cancel_focus)
112                                    .on_click(move |_, window, cx| {
113                                        if let Some(on_cancel) = cancel_click.as_ref() {
114                                            on_cancel(window, cx);
115                                        }
116                                    }),
117                            )
118                            .child(
119                                Button::new(confirm_id, self.confirm_label)
120                                    .variant(ButtonVariant::Destructive)
121                                    .focus_handle(confirm_focus)
122                                    .on_click(move |_, window, cx| {
123                                        if let Some(on_confirm) = confirm_click.as_ref() {
124                                            on_confirm(window, cx);
125                                        }
126                                    }),
127                            ),
128                    ),
129            )
130    }
131}