1use crate::overlay::OverlayDismissCallback;
5use crate::widget_id::WidgetId;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
9pub enum ModalPresentation {
10 #[default]
12 Auto,
13 InTree,
15 NativeWindow,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
21pub enum ModalCloseBehavior {
22 ClickOutside,
24 EscapeKey,
26 #[default]
28 EscapeOrClickOutside,
29 Manual,
31}
32
33pub type ModalBuilder = Box<dyn FnOnce(&mut crate::widget_tree::WidgetTree) -> WidgetId>;
35
36pub enum ModalContent {
38 ExistingWidget(WidgetId),
40 Deferred(ModalBuilder),
42}
43
44impl std::fmt::Debug for ModalContent {
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 match self {
47 Self::ExistingWidget(id) => f.debug_tuple("ExistingWidget").field(id).finish(),
48 Self::Deferred(_) => f.write_str("Deferred(..)"),
49 }
50 }
51}
52
53pub struct ModalRequest {
55 pub content: ModalContent,
56 pub presentation: ModalPresentation,
57 pub close_behavior: ModalCloseBehavior,
58 pub title: Option<String>,
59 pub size: Option<(u32, u32)>,
60 pub focus_target: Option<WidgetId>,
69 pub on_dismiss: Option<OverlayDismissCallback>,
74}
75
76impl ModalRequest {
77 pub fn in_tree(content_id: WidgetId) -> Self {
79 Self {
80 content: ModalContent::ExistingWidget(content_id),
81 presentation: ModalPresentation::Auto,
82 close_behavior: ModalCloseBehavior::default(),
83 title: None,
84 size: None,
85 focus_target: None,
86 on_dismiss: None,
87 }
88 }
89
90 pub fn deferred(
92 builder: impl FnOnce(&mut crate::widget_tree::WidgetTree) -> WidgetId + 'static,
93 ) -> Self {
94 Self {
95 content: ModalContent::Deferred(Box::new(builder)),
96 presentation: ModalPresentation::Auto,
97 close_behavior: ModalCloseBehavior::default(),
98 title: None,
99 size: None,
100 focus_target: None,
101 on_dismiss: None,
102 }
103 }
104
105 pub fn presentation(mut self, presentation: ModalPresentation) -> Self {
106 self.presentation = presentation;
107 self
108 }
109
110 pub fn close_behavior(mut self, close_behavior: ModalCloseBehavior) -> Self {
111 self.close_behavior = close_behavior;
112 self
113 }
114
115 pub fn title(mut self, title: impl Into<String>) -> Self {
116 self.title = Some(title.into());
117 self
118 }
119
120 pub fn size(mut self, width: u32, height: u32) -> Self {
121 self.size = Some((width, height));
122 self
123 }
124
125 pub fn on_dismiss(mut self, callback: OverlayDismissCallback) -> Self {
129 self.on_dismiss = Some(callback);
130 self
131 }
132
133 pub fn focus_target(mut self, id: WidgetId) -> Self {
139 self.focus_target = Some(id);
140 self
141 }
142}
143
144impl std::fmt::Debug for ModalRequest {
145 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146 f.debug_struct("ModalRequest")
147 .field("content", &self.content)
148 .field("presentation", &self.presentation)
149 .field("close_behavior", &self.close_behavior)
150 .field("title", &self.title)
151 .field("size", &self.size)
152 .field("focus_target", &self.focus_target)
153 .field(
154 "on_dismiss",
155 &self.on_dismiss.as_ref().map(|_| "<callback>"),
156 )
157 .finish()
158 }
159}
160
161#[derive(Debug)]
163pub struct QueuedModalRequest {
164 pub source_widget: WidgetId,
165 pub request: ModalRequest,
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171 use crate::test_widgets::FillWidget;
172
173 #[test]
174 fn in_tree_request_defaults_to_auto() {
175 let mut tree = crate::WidgetTree::new();
176 let content_id = tree.add(FillWidget::new());
177 let request = ModalRequest::in_tree(content_id);
178 assert_eq!(request.presentation, ModalPresentation::Auto);
179 assert_eq!(
180 request.close_behavior,
181 ModalCloseBehavior::EscapeOrClickOutside
182 );
183 match request.content {
184 ModalContent::ExistingWidget(id) => assert_eq!(id, content_id),
185 ModalContent::Deferred(_) => panic!("expected ExistingWidget content"),
186 }
187 }
188
189 #[test]
190 fn deferred_request_can_override_metadata() {
191 let request =
192 ModalRequest::deferred(|tree| tree.add(crate::test_widgets::FillWidget::new()))
193 .presentation(ModalPresentation::NativeWindow)
194 .close_behavior(ModalCloseBehavior::Manual)
195 .title("Preferences")
196 .size(640, 480);
197
198 assert_eq!(request.presentation, ModalPresentation::NativeWindow);
199 assert_eq!(request.close_behavior, ModalCloseBehavior::Manual);
200 assert_eq!(request.title.as_deref(), Some("Preferences"));
201 assert_eq!(request.size, Some((640, 480)));
202 assert!(matches!(request.content, ModalContent::Deferred(_)));
203 }
204
205 #[test]
206 fn focus_target_defaults_to_none() {
207 let mut tree = crate::WidgetTree::new();
208 let content_id = tree.add(FillWidget::new());
209 let in_tree = ModalRequest::in_tree(content_id);
210 assert!(in_tree.focus_target.is_none());
211
212 let deferred =
213 ModalRequest::deferred(|tree| tree.add(crate::test_widgets::FillWidget::new()));
214 assert!(deferred.focus_target.is_none());
215 }
216
217 #[test]
218 fn focus_target_builder_sets_field() {
219 let mut tree = crate::WidgetTree::new();
220 let content_id = tree.add(FillWidget::new());
221 let target_id = tree.add(FillWidget::new());
222 let request = ModalRequest::in_tree(content_id).focus_target(target_id);
223 assert_eq!(request.focus_target, Some(target_id));
224 }
225}