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