1use crate::TestSupportExt as _;
2use std::{
3 cell::{Cell, RefCell},
4 rc::Rc,
5};
6
7use gpui::{
8 AnyElement, App, ClickEvent, FocusHandle, InteractiveElement as _, IntoElement, KeyBinding,
9 MouseButton, ParentElement, Pixels, RenderOnce, Role, StatefulInteractiveElement as _,
10 StyleRefinement, Styled, Window, anchored, deferred, div, point, prelude::FluentBuilder as _,
11 px,
12};
13use smallvec::SmallVec;
14
15use crate::actions::{Cancel, Confirm};
16use crate::{FocusTrapElement as _, StyledExt as _};
17
18const CONTEXT: &str = "Dialog";
19type Decision = Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) -> bool>;
20type Closed = Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>;
21type CloseRequest = Rc<dyn Fn(bool, &mut Window, &mut App)>;
22type OpenRequest = Rc<dyn Fn(&mut Window, &mut App)>;
23type OpenChange = Rc<dyn Fn(bool, DialogChangeReason, &mut Window, &mut App)>;
24
25#[derive(Clone, Copy, Debug, Eq, PartialEq)]
26pub enum DialogChangeReason {
27 TriggerPress,
28 BackdropPress,
29 Cancel,
30 Confirm,
31 Imperative,
32}
33
34#[derive(Clone)]
35pub struct DialogHandle {
36 open: Rc<Cell<bool>>,
37 on_open_change: Rc<RefCell<Option<OpenChange>>>,
38}
39
40impl DialogHandle {
41 pub fn new(open: bool) -> Self {
42 Self {
43 open: Rc::new(Cell::new(open)),
44 on_open_change: Rc::new(RefCell::new(None)),
45 }
46 }
47 pub fn is_open(&self) -> bool {
48 self.open.get()
49 }
50 pub fn open(&self, window: &mut Window, cx: &mut App) {
51 self.set_open(true, DialogChangeReason::Imperative, window, cx);
52 }
53 pub fn close(&self, window: &mut Window, cx: &mut App) {
54 self.set_open(false, DialogChangeReason::Imperative, window, cx);
55 }
56 pub(crate) fn set_open(
57 &self,
58 open: bool,
59 reason: DialogChangeReason,
60 window: &mut Window,
61 cx: &mut App,
62 ) {
63 if self.open.replace(open) == open {
64 return;
65 }
66 let callback = self.on_open_change.borrow().clone();
67 if let Some(callback) = callback {
68 callback(open, reason, window, cx);
69 }
70 window.refresh();
71 }
72}
73
74fn request_open_change(
75 handle: &Option<DialogHandle>,
76 callback: &Option<OpenChange>,
77 open: bool,
78 reason: DialogChangeReason,
79 window: &mut Window,
80 cx: &mut App,
81) {
82 if let Some(handle) = handle {
83 handle.set_open(open, reason, window, cx);
84 } else if let Some(callback) = callback {
85 callback(open, reason, window, cx);
86 }
87}
88
89pub fn init(cx: &mut App) {
90 cx.bind_keys([
91 KeyBinding::new("escape", Cancel, Some(CONTEXT)),
92 KeyBinding::new("enter", Confirm { secondary: false }, Some(CONTEXT)),
93 ]);
94}
95
96impl Dialog {
97 pub fn on_ok(
98 mut self,
99 handler: impl Fn(&ClickEvent, &mut Window, &mut App) -> bool + 'static,
100 ) -> Self {
101 self.on_ok = Rc::new(handler);
102 self
103 }
104
105 pub fn on_cancel(
106 mut self,
107 handler: impl Fn(&ClickEvent, &mut Window, &mut App) -> bool + 'static,
108 ) -> Self {
109 self.on_cancel = Rc::new(handler);
110 self
111 }
112
113 pub fn on_close(
114 mut self,
115 handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
116 ) -> Self {
117 self.on_close = Rc::new(handler);
118 self
119 }
120}
121
122#[derive(IntoElement)]
124pub struct Dialog {
125 style: StyleRefinement,
126 focus: FocusHandle,
127 role: Role,
128 layer: usize,
129 keyboard: bool,
130 overlay_closable: bool,
131 topmost: bool,
132 dismiss_below_y: Pixels,
133 backdrop: Option<AnyElement>,
134 popup: Option<AnyElement>,
135 children: SmallVec<[AnyElement; 2]>,
136 on_ok: Decision,
137 on_cancel: Decision,
138 on_close: Closed,
139 request_close: CloseRequest,
140 handle: Option<DialogHandle>,
141 open: bool,
142 on_open_change: Option<OpenChange>,
143}
144
145#[derive(IntoElement)]
147pub struct DialogTrigger {
148 trigger: AnyElement,
149 open: OpenRequest,
150 handle: Option<DialogHandle>,
151}
152
153impl DialogTrigger {
154 pub fn new(trigger: impl IntoElement) -> Self {
155 Self {
156 trigger: trigger.into_any_element(),
157 open: Rc::new(|_, _| {}),
158 handle: None,
159 }
160 }
161 pub fn handle(mut self, handle: DialogHandle) -> Self {
162 self.handle = Some(handle);
163 self
164 }
165
166 pub fn on_open(mut self, open: impl Fn(&mut Window, &mut App) + 'static) -> Self {
167 self.open = Rc::new(open);
168 self
169 }
170}
171
172impl RenderOnce for DialogTrigger {
173 fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
174 div()
175 .on_mouse_down(MouseButton::Left, move |_, window, cx| {
176 if let Some(handle) = self.handle.as_ref() {
177 handle.set_open(true, DialogChangeReason::TriggerPress, window, cx);
178 }
179 (self.open)(window, cx);
180 cx.stop_propagation();
181 })
182 .child(self.trigger)
183 }
184}
185
186macro_rules! dialog_part {
187 ($(#[$meta:meta])* $name:ident, $id:literal) => {
188 $(#[$meta])*
189 #[derive(IntoElement)]
190 pub struct $name {
191 style: StyleRefinement,
192 children: SmallVec<[AnyElement; 2]>,
193 }
194
195 impl $name {
196 pub fn new() -> Self {
197 Self {
198 style: StyleRefinement::default(),
199 children: SmallVec::new(),
200 }
201 }
202 }
203
204 impl Default for $name {
205 fn default() -> Self {
206 Self::new()
207 }
208 }
209
210 impl Styled for $name {
211 fn style(&mut self) -> &mut StyleRefinement {
212 &mut self.style
213 }
214 }
215
216 impl ParentElement for $name {
217 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
218 self.children.extend(elements);
219 }
220 }
221
222 impl RenderOnce for $name {
223 fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
224 div()
225 .id($id)
226 .children(self.children)
227 .refine_style(&self.style)
228 }
229 }
230 };
231}
232
233dialog_part!(
234 DialogBackdrop,
236 "dialog-backdrop"
237);
238
239dialog_part!(
240 DialogPopup,
242 "dialog-popup"
243);
244
245#[derive(IntoElement)]
247pub struct DialogTitle {
248 base: gpui::Div,
249 style: StyleRefinement,
250 children: SmallVec<[AnyElement; 2]>,
251}
252
253impl DialogTitle {
254 pub fn new() -> Self {
255 Self {
256 base: div(),
257 style: StyleRefinement::default(),
258 children: SmallVec::new(),
259 }
260 }
261}
262
263impl Default for DialogTitle {
264 fn default() -> Self {
265 Self::new()
266 }
267}
268impl Styled for DialogTitle {
269 fn style(&mut self) -> &mut StyleRefinement {
270 &mut self.style
271 }
272}
273impl ParentElement for DialogTitle {
274 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
275 self.children.extend(elements);
276 }
277}
278impl RenderOnce for DialogTitle {
279 fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
280 self.base
281 .id("dialog-title")
282 .children(self.children)
283 .refine_style(&self.style)
284 }
285}
286
287#[derive(IntoElement)]
289pub struct DialogDescription {
290 base: gpui::Div,
291 style: StyleRefinement,
292 children: SmallVec<[AnyElement; 2]>,
293}
294
295impl DialogDescription {
296 pub fn new() -> Self {
297 Self {
298 base: div(),
299 style: StyleRefinement::default(),
300 children: SmallVec::new(),
301 }
302 }
303}
304
305impl Default for DialogDescription {
306 fn default() -> Self {
307 Self::new()
308 }
309}
310impl Styled for DialogDescription {
311 fn style(&mut self) -> &mut StyleRefinement {
312 &mut self.style
313 }
314}
315impl ParentElement for DialogDescription {
316 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
317 self.children.extend(elements);
318 }
319}
320impl RenderOnce for DialogDescription {
321 fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
322 self.base
323 .id("dialog-description")
324 .children(self.children)
325 .refine_style(&self.style)
326 }
327}
328
329#[derive(IntoElement)]
331pub struct DialogClose {
332 style: StyleRefinement,
333 children: SmallVec<[AnyElement; 1]>,
334 trigger: Option<AnyElement>,
335 anchor: Rc<RefCell<Option<FocusHandle>>>,
338}
339
340impl DialogClose {
341 pub fn new() -> Self {
342 Self {
343 style: StyleRefinement::default(),
344 children: SmallVec::new(),
345 trigger: None,
346 anchor: Rc::default(),
347 }
348 }
349
350 pub fn trigger<E: IntoElement>(mut self, build: impl FnOnce(crate::Button) -> E) -> Self {
356 let anchor = self.anchor.clone();
357 let button = crate::Button::new("close")
358 .accessibility_label("Close")
359 .on_click(move |_, window, cx| Self::activate(&anchor, window, cx));
360 self.trigger = Some(build(button).into_any_element());
361 self
362 }
363
364 fn activate(anchor: &RefCell<Option<FocusHandle>>, window: &mut Window, cx: &mut App) {
369 let anchor = anchor.borrow().clone();
372 match anchor {
373 Some(anchor) => anchor.dispatch_action(&Cancel, window, cx),
374 None => window.dispatch_action(Box::new(Cancel), cx),
375 }
376 }
377}
378impl Default for DialogClose {
379 fn default() -> Self {
380 Self::new()
381 }
382}
383impl ParentElement for DialogClose {
384 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
385 self.children.extend(elements);
386 }
387}
388impl Styled for DialogClose {
389 fn style(&mut self) -> &mut StyleRefinement {
390 &mut self.style
391 }
392}
393impl RenderOnce for DialogClose {
394 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
395 let anchor = window
396 .use_keyed_state("dialog-close-anchor", cx, |_, cx| cx.focus_handle())
397 .read(cx)
398 .clone();
399 *self.anchor.borrow_mut() = Some(anchor.clone());
400 let cell = self.anchor;
401 div()
402 .id("dialog-close")
403 .child(div().absolute().size_0().track_focus(&anchor))
407 .when(self.trigger.is_none(), |this| {
408 this.on_click(move |_, window, cx| Self::activate(&cell, window, cx))
409 })
410 .children(self.trigger)
411 .children(self.children)
412 .refine_style(&self.style)
413 }
414}
415
416impl Dialog {
417 pub fn new(cx: &mut App) -> Self {
418 Self {
419 style: StyleRefinement::default(),
420 focus: cx.focus_handle(),
421 role: Role::Dialog,
422 layer: 0,
423 keyboard: true,
424 overlay_closable: true,
425 topmost: true,
426 dismiss_below_y: px(0.),
427 backdrop: None,
428 popup: None,
429 children: SmallVec::new(),
430 on_ok: Rc::new(|_, _, _| true),
431 on_cancel: Rc::new(|_, _, _| true),
432 on_close: Rc::new(|_, _, _| {}),
433 request_close: Rc::new(|_, _, _| {}),
434 handle: None,
435 open: true,
436 on_open_change: None,
437 }
438 }
439 pub fn open(mut self, open: bool) -> Self {
440 self.open = open;
441 self
442 }
443 pub fn handle(mut self, handle: DialogHandle) -> Self {
444 if let Some(callback) = self.on_open_change.as_ref() {
445 *handle.on_open_change.borrow_mut() = Some(callback.clone());
446 }
447 self.handle = Some(handle);
448 self
449 }
450 pub fn on_open_change(
451 mut self,
452 handler: impl Fn(bool, DialogChangeReason, &mut Window, &mut App) + 'static,
453 ) -> Self {
454 let handler: OpenChange = Rc::new(handler);
455 if let Some(handle) = self.handle.as_ref() {
456 *handle.on_open_change.borrow_mut() = Some(handler.clone());
457 }
458 self.on_open_change = Some(handler);
459 self
460 }
461
462 pub fn backdrop(mut self, element: impl IntoElement) -> Self {
463 self.backdrop = Some(element.into_any_element());
464 self
465 }
466 pub fn popup(mut self, element: impl IntoElement) -> Self {
467 self.popup = Some(element.into_any_element());
468 self
469 }
470 pub fn close_on_escape(mut self, value: bool) -> Self {
471 self.keyboard = value;
472 self
473 }
474 pub fn close_on_backdrop_press(mut self, value: bool) -> Self {
475 self.overlay_closable = value;
476 self
477 }
478 pub fn dismiss_below_y(mut self, value: Pixels) -> Self {
479 self.dismiss_below_y = value;
480 self
481 }
482 pub(crate) fn role(mut self, role: Role) -> Self {
483 self.role = role;
484 self
485 }
486 #[doc(hidden)]
487 pub fn layer(mut self, index: usize, topmost: bool) -> Self {
488 self.layer = index;
489 self.topmost = topmost;
490 self
491 }
492 #[doc(hidden)]
493 pub fn focus_handle(mut self, value: FocusHandle) -> Self {
494 self.focus = value;
495 self
496 }
497 #[doc(hidden)]
498 pub fn request_close(
499 mut self,
500 handler: impl Fn(bool, &mut Window, &mut App) + 'static,
501 ) -> Self {
502 self.request_close = Rc::new(handler);
503 self
504 }
505}
506
507impl Styled for Dialog {
508 fn style(&mut self) -> &mut StyleRefinement {
509 &mut self.style
510 }
511}
512impl ParentElement for Dialog {
513 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
514 self.children.extend(elements);
515 }
516}
517
518impl RenderOnce for Dialog {
519 fn render(self, window: &mut Window, _: &mut App) -> impl IntoElement {
520 let open = self
521 .handle
522 .as_ref()
523 .map_or(self.open, DialogHandle::is_open);
524 if !open {
525 return div().into_any_element();
526 }
527 let request_close = self.request_close;
528 let cancel = self.on_cancel.clone();
529 let confirm = self.on_ok.clone();
530 let closed = self.on_close.clone();
531 let overlay_closable = self.overlay_closable && self.topmost;
532 let dismiss_below_y = self.dismiss_below_y;
533 let escape_handle = self.handle.clone();
534 let confirm_handle = self.handle.clone();
535 let backdrop_handle = self.handle.clone();
536 let escape_change = self.on_open_change.clone();
537 let confirm_change = self.on_open_change.clone();
538 let backdrop_change = self.on_open_change.clone();
539 let viewport = window.viewport_size();
540
541 deferred(
542 anchored().position(point(px(0.), px(0.))).child(
543 div()
544 .id(("dialog-host", self.layer))
545 .test_support()
546 .absolute()
547 .top_0()
548 .left_0()
549 .w(viewport.width)
550 .h(viewport.height)
551 .role(self.role)
552 .track_focus(&self.focus)
553 .focus_trap(format!("dialog-{}", self.layer), &self.focus)
554 .when(self.keyboard, |this| this.key_context(CONTEXT))
555 .map(|this| {
556 let request_cancel = request_close.clone();
557 let request_confirm = request_close.clone();
558 let closed_cancel = closed.clone();
559 this.on_action(move |_: &Cancel, window, cx| {
560 let event = ClickEvent::default();
561 if cancel(&event, window, cx) {
562 request_open_change(
563 &escape_handle,
564 &escape_change,
565 false,
566 DialogChangeReason::Cancel,
567 window,
568 cx,
569 );
570 request_cancel(false, window, cx);
571 closed_cancel(&event, window, cx);
572 }
573 })
574 .on_action(move |_: &Confirm, window, cx| {
575 let event = ClickEvent::default();
576 if confirm(&event, window, cx) {
577 request_open_change(
578 &confirm_handle,
579 &confirm_change,
580 false,
581 DialogChangeReason::Confirm,
582 window,
583 cx,
584 );
585 request_confirm(true, window, cx);
586 closed(&event, window, cx);
587 }
588 })
589 })
590 .when_some(self.backdrop, |this, backdrop| {
591 let cancel = self.on_cancel.clone();
592 let closed = self.on_close.clone();
593 let request_close = request_close.clone();
594 this.child(
595 div()
596 .absolute()
599 .inset_0()
600 .on_any_mouse_down(move |event, window, cx| {
601 if event.position.y < dismiss_below_y {
602 return;
603 }
604 let button = event.button;
605 cx.stop_propagation();
606 let event = ClickEvent::default();
607 if button == MouseButton::Left
608 && overlay_closable
609 && cancel(&event, window, cx)
610 {
611 request_open_change(
612 &backdrop_handle,
613 &backdrop_change,
614 false,
615 DialogChangeReason::BackdropPress,
616 window,
617 cx,
618 );
619 request_close(false, window, cx);
620 closed(&event, window, cx);
621 }
622 })
623 .child(backdrop),
624 )
625 })
626 .children(self.popup)
627 .children(self.children)
628 .refine_style(&self.style),
629 ),
630 )
631 .with_priority(10 + self.layer)
632 .into_any_element()
633 }
634}
635
636#[cfg(test)]
637mod tests {
638 use super::*;
639 use gpui::{Context, Render, point};
640 use std::{cell::RefCell, rc::Rc};
641
642 #[gpui::test]
643 fn close_trigger_supplies_accessible_button(cx: &mut gpui::TestAppContext) {
644 use gpui::{Element as _, accesskit, canvas};
645 use std::sync::{Arc, Mutex};
646
647 struct Probe(Arc<Mutex<Option<accesskit::Node>>>);
648 impl Render for Probe {
649 fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
650 let captured = self.0.clone();
651 canvas(
652 move |_, window, cx| {
653 DialogClose::new().trigger(|button| {
654 let element = button.render(window, cx).into_element();
655 let mut node = accesskit::Node::new(element.a11y_role().unwrap());
656 element.write_a11y_info(&mut node);
657 *captured.lock().unwrap() = Some(node);
658 element
659 });
660 },
661 |_, _, _, _| {},
662 )
663 }
664 }
665
666 let captured = Arc::new(Mutex::new(None));
667 let result = captured.clone();
668 let (_, cx) = cx.add_window_view(move |_, _| Probe(captured));
669 cx.update(|window, cx| window.draw(cx).clear(cx));
670 let node = result.lock().unwrap().take().unwrap();
671 assert_eq!(node.role(), Role::Button);
672 assert_eq!(node.label(), Some("Close"));
673 assert!(node.supports_action(accesskit::Action::Click));
674 }
675
676 #[gpui::test]
677 fn close_trigger_activates_once_and_respects_cancel_veto(cx: &mut gpui::TestAppContext) {
678 use gpui::{KeyDownEvent, KeyUpEvent, Keystroke};
679
680 struct Harness {
681 focus: FocusHandle,
682 button_focus: FocusHandle,
683 handle: DialogHandle,
684 attempts: Rc<Cell<usize>>,
685 }
686 impl Render for Harness {
687 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
688 let attempts = self.attempts.clone();
689 let button_focus = self.button_focus.clone();
690 Dialog::new(cx)
691 .handle(self.handle.clone())
692 .focus_handle(self.focus.clone())
693 .on_cancel(move |_, _, _| {
694 attempts.set(attempts.get() + 1);
695 attempts.get() > 1
696 })
697 .popup(
698 DialogClose::new().trigger(move |button| {
699 button.size(px(100.)).track_focus(&button_focus)
700 }),
701 )
702 }
703 }
704
705 cx.update(crate::init);
706 let handle = DialogHandle::new(true);
707 let attempts = Rc::new(Cell::new(0));
708 let (view, cx) = cx.add_window_view({
709 let handle = handle.clone();
710 let attempts = attempts.clone();
711 move |_, cx| Harness {
712 focus: cx.focus_handle(),
713 button_focus: cx.focus_handle(),
714 handle,
715 attempts,
716 }
717 });
718 cx.update(|window, cx| {
719 view.read(cx).focus.clone().focus(window, cx);
720 window.draw(cx).clear(cx);
721 });
722 cx.simulate_click(point(px(20.), px(20.)), Default::default());
723 cx.run_until_parked();
724 assert_eq!(attempts.get(), 1);
725 assert!(handle.is_open(), "on_cancel can veto pointer dismissal");
726
727 cx.update(|window, cx| {
728 view.read(cx).button_focus.clone().focus(window, cx);
729 window.draw(cx).clear(cx);
730 });
731 let keystroke = Keystroke::parse("space").unwrap();
732 cx.simulate_event(KeyDownEvent {
733 keystroke: keystroke.clone(),
734 is_held: false,
735 prefer_character_input: false,
736 });
737 cx.simulate_event(KeyUpEvent { keystroke });
738 cx.run_until_parked();
739 assert_eq!(attempts.get(), 2);
740 assert!(!handle.is_open(), "Space uses the same cancel decision");
741 }
742
743 struct TriggerHarness {
744 handle: DialogHandle,
745 }
746 impl Render for TriggerHarness {
747 fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
748 DialogTrigger::new(div().size(px(100.))).handle(self.handle.clone())
749 }
750 }
751
752 #[gpui::test]
753 fn trigger_opens_shared_handle_and_reports_reason(cx: &mut gpui::TestAppContext) {
754 let handle = DialogHandle::new(false);
755 let changes = Rc::new(RefCell::new(Vec::new()));
756 *handle.on_open_change.borrow_mut() = Some({
757 let changes = changes.clone();
758 Rc::new(move |open, reason, _, _| changes.borrow_mut().push((open, reason)))
759 });
760 let (_, cx) = cx.add_window_view({
761 let handle = handle.clone();
762 move |_, _| TriggerHarness { handle }
763 });
764 cx.update(|window, cx| window.draw(cx).clear(cx));
765 cx.simulate_click(point(px(20.), px(20.)), Default::default());
766
767 assert!(handle.is_open());
768 assert_eq!(
769 &*changes.borrow(),
770 &[(true, DialogChangeReason::TriggerPress)]
771 );
772 }
773
774 #[gpui::test]
778 fn the_backdrop_fills_the_host(cx: &mut gpui::TestAppContext) {
779 use gpui::{Bounds, canvas};
780 use std::cell::Cell;
781
782 struct Harness {
783 focus: FocusHandle,
784 bounds: Rc<Cell<Bounds<Pixels>>>,
785 }
786 impl Render for Harness {
787 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
788 let bounds = self.bounds.clone();
789 Dialog::new(cx)
790 .open(true)
791 .focus_handle(self.focus.clone())
792 .backdrop(
793 canvas(
794 move |bounds_of_backdrop, _, _| bounds.set(bounds_of_backdrop),
795 |_, _, _, _| {},
796 )
797 .absolute()
798 .size_full(),
799 )
800 .popup(div().size(px(100.)))
801 }
802 }
803
804 cx.update(crate::init);
805 let bounds = Rc::new(Cell::new(Bounds::default()));
806 let (_, cx) = cx.add_window_view({
807 let bounds = bounds.clone();
808 move |_, cx| Harness {
809 focus: cx.focus_handle(),
810 bounds,
811 }
812 });
813 let viewport = cx.update(|window, cx| {
814 let viewport = window.viewport_size();
815 window.draw(cx).clear(cx);
816 viewport
817 });
818
819 assert_eq!(
820 bounds.get().size,
821 viewport,
822 "a zero-sized backdrop paints no overlay behind the dialog"
823 );
824 }
825}