egui_material3/dialog.rs
1use crate::get_global_color;
2use eframe::egui::{self, Color32, Context, Id, Modal, Response, Sense, Stroke, Ui, Vec2};
3
4/// Material Design dialog types following Material Design 3 specifications
5#[derive(Clone, Copy, PartialEq)]
6pub enum DialogType {
7 /// Standard dialog for general purpose use
8 Standard,
9 /// Alert dialog for important notifications requiring acknowledgment
10 Alert,
11 /// Confirmation dialog for confirming actions before proceeding
12 Confirm,
13 /// Form dialog containing input fields and form elements
14 Form,
15}
16
17/// Material Design dialog component following Material Design 3 specifications
18///
19/// Dialogs interrupt users with overlaid content that requires a response.
20/// They appear above all other content and disable all app functionality when shown.
21///
22/// ## Usage Examples
23/// ```rust
24/// # egui::__run_test_ui(|ui| {
25/// let mut dialog_open = false;
26///
27/// // Basic dialog
28/// let dialog = MaterialDialog::new("my_dialog", "Confirm Action", &mut dialog_open)
29/// .content(|ui| {
30/// ui.label("Are you sure you want to proceed?");
31/// })
32/// .action("Cancel", ActionType::Text, || {
33/// // Cancel action
34/// })
35/// .action("Confirm", ActionType::Filled, || {
36/// // Confirm action
37/// });
38///
39/// dialog.show(ui.ctx());
40/// # });
41/// ```
42///
43/// ## Material Design Spec
44/// - Max width: 560dp on large screens
45/// - Corner radius: 28dp
46/// - Elevation: 6dp (24dp shadow)
47/// - Surface color background
48/// - Minimum touch target: 48x48dp for actions
49pub struct MaterialDialog<'a> {
50 /// Unique identifier for the dialog
51 id: Id,
52 /// Dialog title text
53 title: String,
54 /// Mutable reference to dialog open state
55 open: &'a mut bool,
56 /// Type of dialog (affects styling and behavior)
57 dialog_type: DialogType,
58 /// Optional icon to display in dialog header
59 icon: Option<String>,
60 /// Content rendering function called once
61 content: Box<dyn FnOnce(&mut Ui) + 'a>,
62 /// List of action buttons at the bottom of the dialog
63 actions: Vec<DialogAction<'a>>,
64 /// Whether this is a quick/temporary dialog
65 quick: bool,
66 /// Whether to disable focus trapping within the dialog
67 no_focus_trap: bool,
68 /// Maximum width constraint for the dialog
69 max_width: Option<f32>,
70 /// Minimum width constraint for the dialog (default: 280dp)
71 min_width: Option<f32>,
72 /// Maximum height constraint for the dialog
73 max_height: Option<f32>,
74 /// Padding around the title (default: 24dp horizontal, varies vertical)
75 title_padding: Option<[f32; 4]>,
76 /// Padding around the content (default: 24dp horizontal, 16dp top, 24dp bottom)
77 content_padding: Option<[f32; 4]>,
78 /// Padding around the actions area (default: 24dp all sides)
79 actions_padding: Option<[f32; 4]>,
80 /// Padding around individual action buttons
81 button_padding: Option<[f32; 2]>,
82 /// Whether content should be scrollable
83 scrollable: bool,
84 /// Spacing between action buttons (default: 8dp)
85 actions_spacing: f32,
86}
87
88/// Represents an action button in a Material Design dialog
89pub struct DialogAction<'a> {
90 /// Button text label
91 text: String,
92 /// Visual style of the action button
93 action_type: ActionType,
94 /// Whether the action is currently enabled
95 _enabled: bool,
96 /// Callback function executed when action is triggered
97 action: Box<dyn FnOnce() + 'a>,
98}
99
100/// Material Design action button styles for dialogs
101#[derive(Clone, Copy, PartialEq)]
102pub enum ActionType {
103 /// Text button - lowest emphasis, used for secondary actions
104 Text,
105 /// Filled tonal button - medium emphasis, used for secondary actions
106 FilledTonal,
107 /// Filled button - highest emphasis, used for primary actions
108 Filled,
109}
110
111impl<'a> MaterialDialog<'a> {
112 /// Create a new Material Design dialog
113 ///
114 /// ## Parameters
115 /// - `id`: Unique identifier for the dialog (used for egui state)
116 /// - `title`: Title text displayed at the top of the dialog
117 /// - `open`: Mutable reference to boolean controlling dialog visibility
118 ///
119 /// ## Returns
120 /// A new MaterialDialog instance ready for customization
121 pub fn new(id: impl Into<Id>, title: impl Into<String>, open: &'a mut bool) -> Self {
122 Self {
123 id: id.into(),
124 title: title.into(),
125 open,
126 dialog_type: DialogType::Standard,
127 icon: None,
128 content: Box::new(|_| {}),
129 actions: Vec::new(),
130 quick: false,
131 no_focus_trap: false,
132 max_width: None,
133 min_width: Some(280.0),
134 max_height: None,
135 title_padding: None,
136 content_padding: None,
137 actions_padding: None,
138 button_padding: None,
139 scrollable: false,
140 actions_spacing: 8.0,
141 }
142 }
143
144 /// Set the dialog type (affects styling and behavior)
145 ///
146 /// ## Parameters
147 /// - `dialog_type`: The type of dialog to display
148 ///
149 /// ## Returns
150 /// Self for method chaining
151 pub fn dialog_type(mut self, dialog_type: DialogType) -> Self {
152 self.dialog_type = dialog_type;
153 self
154 }
155
156 /// Set an optional icon to display in the dialog header
157 ///
158 /// ## Parameters
159 /// - `icon`: The icon to display (as a string identifier)
160 ///
161 /// ## Returns
162 /// Self for method chaining
163 pub fn icon(mut self, icon: impl Into<String>) -> Self {
164 self.icon = Some(icon.into());
165 self
166 }
167
168 /// Set the content of the dialog
169 ///
170 /// ## Parameters
171 /// - `content`: A closure that renders the content UI
172 ///
173 /// ## Returns
174 /// Self for method chaining
175 pub fn content<F>(mut self, content: F) -> Self
176 where
177 F: FnOnce(&mut Ui) + 'a,
178 {
179 self.content = Box::new(content);
180 self
181 }
182
183 /// Set whether this is a quick/temporary dialog
184 ///
185 /// ## Parameters
186 /// - `quick`: If true, the dialog is considered quick/temporary
187 ///
188 /// ## Returns
189 /// Self for method chaining
190 pub fn quick(mut self, quick: bool) -> Self {
191 self.quick = quick;
192 self
193 }
194
195 /// Set whether to disable focus trapping within the dialog
196 ///
197 /// ## Parameters
198 /// - `no_focus_trap`: If true, focus trapping is disabled
199 ///
200 /// ## Returns
201 /// Self for method chaining
202 pub fn no_focus_trap(mut self, no_focus_trap: bool) -> Self {
203 self.no_focus_trap = no_focus_trap;
204 self
205 }
206
207 /// Set the maximum width constraint for the dialog
208 ///
209 /// ## Parameters
210 /// - `width`: The maximum width in pixels
211 ///
212 /// ## Returns
213 /// Self for method chaining
214 pub fn max_width(mut self, width: f32) -> Self {
215 self.max_width = Some(width);
216 self
217 }
218
219 /// Set the minimum width constraint for the dialog
220 ///
221 /// ## Parameters
222 /// - `width`: The minimum width in pixels (default: 280.0)
223 ///
224 /// ## Returns
225 /// Self for method chaining
226 pub fn min_width(mut self, width: f32) -> Self {
227 self.min_width = Some(width);
228 self
229 }
230
231 /// Set the maximum height constraint for the dialog
232 ///
233 /// ## Parameters
234 /// - `height`: The maximum height in pixels
235 ///
236 /// ## Returns
237 /// Self for method chaining
238 pub fn max_height(mut self, height: f32) -> Self {
239 self.max_height = Some(height);
240 self
241 }
242
243 /// Set custom padding for the title area
244 ///
245 /// ## Parameters
246 /// - `padding`: [left, right, top, bottom] padding in pixels
247 ///
248 /// ## Returns
249 /// Self for method chaining
250 pub fn title_padding(mut self, padding: [f32; 4]) -> Self {
251 self.title_padding = Some(padding);
252 self
253 }
254
255 /// Set custom padding for the content area
256 ///
257 /// ## Parameters
258 /// - `padding`: [left, right, top, bottom] padding in pixels
259 ///
260 /// ## Returns
261 /// Self for method chaining
262 pub fn content_padding(mut self, padding: [f32; 4]) -> Self {
263 self.content_padding = Some(padding);
264 self
265 }
266
267 /// Set custom padding for the actions area
268 ///
269 /// ## Parameters
270 /// - `padding`: [left, right, top, bottom] padding in pixels
271 ///
272 /// ## Returns
273 /// Self for method chaining
274 pub fn actions_padding(mut self, padding: [f32; 4]) -> Self {
275 self.actions_padding = Some(padding);
276 self
277 }
278
279 /// Set custom padding for individual action buttons
280 ///
281 /// ## Parameters
282 /// - `padding`: [horizontal, vertical] padding in pixels
283 ///
284 /// ## Returns
285 /// Self for method chaining
286 pub fn button_padding(mut self, padding: [f32; 2]) -> Self {
287 self.button_padding = Some(padding);
288 self
289 }
290
291 /// Set whether the content should be scrollable
292 ///
293 /// ## Parameters
294 /// - `scrollable`: If true, content will be placed in a ScrollArea
295 ///
296 /// ## Returns
297 /// Self for method chaining
298 pub fn scrollable(mut self, scrollable: bool) -> Self {
299 self.scrollable = scrollable;
300 self
301 }
302
303 /// Set the spacing between action buttons
304 ///
305 /// ## Parameters
306 /// - `spacing`: Spacing in pixels (default: 8.0)
307 ///
308 /// ## Returns
309 /// Self for method chaining
310 pub fn actions_spacing(mut self, spacing: f32) -> Self {
311 self.actions_spacing = spacing;
312 self
313 }
314
315 /// Add a text action button to the dialog
316 ///
317 /// ## Parameters
318 /// - `text`: The text label for the button
319 /// - `action`: A closure that is called when the button is clicked
320 ///
321 /// ## Returns
322 /// Self for method chaining
323 pub fn text_action<F>(mut self, text: impl Into<String>, action: F) -> Self
324 where
325 F: FnOnce() + 'a,
326 {
327 self.actions.push(DialogAction {
328 text: text.into(),
329 action_type: ActionType::Text,
330 _enabled: true,
331 action: Box::new(action),
332 });
333 self
334 }
335
336 /// Add a filled tonal action button to the dialog
337 ///
338 /// ## Parameters
339 /// - `text`: The text label for the button
340 /// - `action`: A closure that is called when the button is clicked
341 ///
342 /// ## Returns
343 /// Self for method chaining
344 pub fn filled_tonal_action<F>(mut self, text: impl Into<String>, action: F) -> Self
345 where
346 F: FnOnce() + 'a,
347 {
348 self.actions.push(DialogAction {
349 text: text.into(),
350 action_type: ActionType::FilledTonal,
351 _enabled: true,
352 action: Box::new(action),
353 });
354 self
355 }
356
357 /// Add a filled action button to the dialog
358 ///
359 /// ## Parameters
360 /// - `text`: The text label for the button
361 /// - `action`: A closure that is called when the button is clicked
362 ///
363 /// ## Returns
364 /// Self for method chaining
365 pub fn filled_action<F>(mut self, text: impl Into<String>, action: F) -> Self
366 where
367 F: FnOnce() + 'a,
368 {
369 self.actions.push(DialogAction {
370 text: text.into(),
371 action_type: ActionType::Filled,
372 _enabled: true,
373 action: Box::new(action),
374 });
375 self
376 }
377
378 /// Backward compatibility methods
379 ///
380 /// These methods exist to support older code that used different naming conventions for actions.
381 /// They are functionally equivalent to the more descriptively named methods introduced later.
382 ///
383 /// ## Parameters
384 /// - `text`: The text label for the button
385 /// - `action`: A closure that is called when the button is clicked
386 ///
387 /// ## Returns
388 /// Self for method chaining
389 pub fn action<F>(self, text: impl Into<String>, action: F) -> Self
390 where
391 F: FnOnce() + 'a,
392 {
393 self.text_action(text, action)
394 }
395
396 /// Backward compatibility method for primary actions
397 ///
398 /// This method is provided for convenience and is functionally equivalent to `filled_action`.
399 ///
400 /// ## Parameters
401 /// - `text`: The text label for the button
402 /// - `action`: A closure that is called when the button is clicked
403 ///
404 /// ## Returns
405 /// Self for method chaining
406 pub fn primary_action<F>(self, text: impl Into<String>, action: F) -> Self
407 where
408 F: FnOnce() + 'a,
409 {
410 self.filled_action(text, action)
411 }
412
413 /// Show the dialog, rendering it in the given context
414 ///
415 /// ## Parameters
416 /// - `ctx`: The egui context used for rendering the dialog
417 ///
418 /// ## Behavior
419 /// - The dialog will be displayed as an overlay, blocking interaction with other windows
420 /// - Clicking outside the dialog or pressing the escape key will close the dialog
421 /// - Action buttons will execute their associated actions when clicked
422 pub fn show(mut self, ctx: &Context) {
423 if !*self.open {
424 return;
425 }
426
427 let mut should_close = false;
428 let mut pending_actions = Vec::new();
429
430 // Extract values we need before moving into closure
431 let default_width: f32 = match self.dialog_type {
432 DialogType::Alert => 280.0,
433 DialogType::Confirm => 320.0,
434 DialogType::Form => 560.0,
435 DialogType::Standard => 400.0,
436 };
437
438 let dialog_min_width = self.min_width.unwrap_or(280.0);
439 let dialog_max_width = self.max_width.unwrap_or(default_width.max(560.0));
440 let dialog_max_height = self.max_height;
441
442 // Calculate reasonable max height based on screen size if not specified
443 let screen_height = ctx.screen_rect().height();
444 let effective_max_height = dialog_max_height.unwrap_or((screen_height * 0.9).min(800.0));
445
446 let title = self.title.clone();
447 let icon = self.icon.clone();
448 let actions = std::mem::take(&mut self.actions);
449 let open_ref = self.open as *mut bool;
450
451 let title_padding = self.title_padding;
452 let content_padding = self.content_padding;
453 let actions_padding = self.actions_padding;
454 let button_padding = self.button_padding;
455 let scrollable = self.scrollable;
456 let actions_spacing = self.actions_spacing;
457
458 // Configure Modal frame with top/bottom margin for proper padding
459 let modal_frame = egui::Frame::default()
460 .inner_margin(egui::vec2(0.0, 24.0))
461 .fill(get_global_color("surfaceContainerHigh"))
462 .rounding(egui::Rounding::same(28))
463 .stroke(Stroke::NONE);
464
465 let modal = Modal::new(self.id)
466 .frame(modal_frame)
467 .show(ctx, |ui| {
468 ui.set_min_width(dialog_min_width);
469 ui.set_max_width(dialog_max_width);
470 // Only set max_height for scrollable dialogs to avoid empty space at bottom
471 if scrollable {
472 ui.set_max_height(effective_max_height);
473 }
474
475 // Material Design colors
476 let surface_container_high = get_global_color("surfaceContainerHigh");
477 let on_surface = get_global_color("onSurface");
478 let on_surface_variant = get_global_color("onSurfaceVariant");
479
480 // Set dialog background
481 ui.style_mut().visuals.window_fill = surface_container_high;
482 ui.style_mut().visuals.panel_fill = surface_container_high;
483 ui.style_mut().visuals.window_stroke = Stroke::NONE;
484
485 // Remove all automatic spacing and margins
486 // ui.spacing_mut().item_spacing.y = 0.0;
487 // ui.spacing_mut().window_margin = egui::Margin::ZERO;
488
489 ui.vertical(|ui| {
490 // ui.spacing_mut().item_spacing.y = 0.0;
491 // Top padding now handled by Modal frame margin
492
493 // Icon (if present) - positioned above headline, centered
494 if let Some(ref icon) = icon {
495 ui.with_layout(egui::Layout::top_down(egui::Align::Center), |ui| {
496 ui.add_space(0.0);
497 // Material icon - centered above title
498 // Use MaterialIcon for proper icon rendering
499 let icon_widget = crate::icon::MaterialIcon::new(crate::material_symbol::material_symbol_text(icon))
500 .size(24.0)
501 .color(on_surface_variant);
502 ui.add(icon_widget);
503 ui.add_space(16.0);
504 });
505 }
506
507 // Headline with custom padding support
508 let [title_left, title_right, title_top, title_bottom] =
509 title_padding.unwrap_or([24.0, 24.0, 0.0, 0.0]);
510
511 ui.horizontal(|ui| {
512 ui.add_space(title_left);
513 // Center title if there's an icon
514 let layout = if icon.is_some() {
515 egui::Layout::centered_and_justified(egui::Direction::LeftToRight)
516 } else {
517 egui::Layout::left_to_right(egui::Align::TOP)
518 };
519 ui.with_layout(layout, |ui| {
520 ui.label(
521 egui::RichText::new(&title)
522 .size(24.0)
523 .color(on_surface)
524 .family(egui::FontFamily::Proportional),
525 );
526 });
527 ui.add_space(title_right);
528 });
529
530 ui.add_space(if title_bottom > 0.0 { title_bottom } else { 16.0 });
531
532 // Content area with optional scrolling and custom padding
533 let [content_left, content_right, content_top, content_bottom] =
534 content_padding.unwrap_or([24.0, 24.0, 0.0, 24.0]);
535
536 if scrollable {
537 // Scrollable content - use fixed width area
538 let scroll_width = ui.available_width() - content_left - content_right;
539 let scroll_height = ui.available_height() - content_bottom;
540
541 ui.horizontal(|ui| {
542 ui.add_space(content_left);
543
544 // Allocate fixed space for scroll area
545 ui.allocate_ui_with_layout(
546 egui::vec2(scroll_width, scroll_height),
547 egui::Layout::top_down(egui::Align::LEFT),
548 |ui| {
549 egui::ScrollArea::vertical()
550 .id_salt("dialog_content_scroll")
551 .auto_shrink([false, false])
552 .show(ui, |ui| {
553 ui.set_width(scroll_width - 20.0); // Account for scrollbar
554 ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Wrap);
555 if content_top > 0.0 {
556 ui.add_space(content_top);
557 }
558 (self.content)(ui);
559 });
560 },
561 );
562
563 ui.add_space(content_right);
564 });
565 } else {
566 // Non-scrollable content - render directly with width constraint
567 let content_width = ui.available_width() - content_left - content_right;
568 ui.horizontal(|ui| {
569 ui.add_space(content_left);
570 ui.vertical(|ui| {
571 ui.set_max_width(content_width);
572 ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Wrap);
573 if content_top > 0.0 {
574 ui.add_space(content_top);
575 }
576 (self.content)(ui);
577 // Don't consume remaining vertical space - let content size naturally
578 });
579 ui.add_space(content_right);
580 });
581 }
582
583 // Actions area with custom padding and spacing
584 if !actions.is_empty() {
585 let [actions_left, actions_right, actions_top, actions_bottom] =
586 actions_padding.unwrap_or([24.0, 24.0, 0.0, 0.0]);
587
588 // Add spacing between content and actions
589 // Use actions_top if specified, otherwise use smaller default spacing
590 let spacing_before_actions = if actions_top > 0.0 {
591 actions_top
592 } else if content_bottom > 0.0 {
593 content_bottom.min(16.0)
594 } else {
595 16.0
596 };
597 ui.add_space(spacing_before_actions);
598
599 ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
600 ui.add_space(actions_right);
601
602 for (index, action) in actions.into_iter().enumerate().rev() {
603 let button_response = Self::draw_action_button_static(ui, &action, button_padding);
604
605 if button_response.clicked() {
606 pending_actions.push((index, action.action));
607 }
608
609 if index > 0 {
610 ui.add_space(actions_spacing);
611 }
612 }
613
614 ui.add_space(actions_left);
615 });
616 // Bottom padding now handled by Modal frame margin
617 }
618 });
619 });
620
621 // Execute pending actions
622 for (_index, action) in pending_actions {
623 action();
624 should_close = true;
625 }
626
627 // Handle modal close events (escape key, click outside, etc.)
628 if modal.should_close() || should_close {
629 unsafe {
630 *open_ref = false;
631 }
632 }
633 }
634
635 fn draw_action_button_static(ui: &mut Ui, action: &DialogAction, button_padding: Option<[f32; 2]>) -> Response {
636 let primary = get_global_color("primary");
637 let on_primary = get_global_color("onPrimary");
638 let secondary_container = get_global_color("secondaryContainer");
639 let on_secondary_container = get_global_color("onSecondaryContainer");
640 let _on_surface_variant = get_global_color("onSurfaceVariant");
641
642 let [btn_h_padding, btn_v_padding] = button_padding.unwrap_or([12.0, 8.0]);
643
644 let text_width = ui.fonts(|fonts| {
645 fonts
646 .layout_no_wrap(action.text.clone(), egui::FontId::default(), Color32::WHITE)
647 .rect
648 .width()
649 });
650
651 let button_width = (text_width + btn_h_padding * 2.0).max(64.0);
652 let button_height = (20.0 + btn_v_padding * 2.0).max(40.0);
653 let desired_size = Vec2::new(button_width, button_height);
654
655 let (rect, response) = ui.allocate_exact_size(desired_size, Sense::click());
656
657 let (bg_color, text_color, _border_color) = match action.action_type {
658 ActionType::Text => {
659 if response.hovered() {
660 (
661 Color32::from_rgba_premultiplied(primary.r(), primary.g(), primary.b(), 20), // 8% opacity state layer
662 primary,
663 Color32::TRANSPARENT,
664 )
665 } else {
666 (Color32::TRANSPARENT, primary, Color32::TRANSPARENT)
667 }
668 }
669 ActionType::FilledTonal => {
670 if response.hovered() {
671 (
672 secondary_container,
673 on_secondary_container,
674 Color32::TRANSPARENT,
675 )
676 } else {
677 (
678 secondary_container,
679 on_secondary_container,
680 Color32::TRANSPARENT,
681 )
682 }
683 }
684 ActionType::Filled => {
685 if response.hovered() {
686 (primary, on_primary, Color32::TRANSPARENT)
687 } else {
688 (primary, on_primary, Color32::TRANSPARENT)
689 }
690 }
691 };
692
693 // Draw button background
694 ui.painter().rect_filled(
695 rect, 20.0, // Full rounded corners
696 bg_color,
697 );
698
699 // Draw state layer for pressed state
700 if response.is_pointer_button_down_on() {
701 let pressed_overlay = Color32::from_rgba_premultiplied(
702 text_color.r(),
703 text_color.g(),
704 text_color.b(),
705 31,
706 ); // 12% opacity
707 ui.painter().rect_filled(rect, 20.0, pressed_overlay);
708 }
709
710 // Draw button text
711 ui.painter().text(
712 rect.center(),
713 egui::Align2::CENTER_CENTER,
714 &action.text,
715 egui::FontId::proportional(14.0),
716 text_color,
717 );
718
719 response
720 }
721
722 fn _draw_action_button(&self, ui: &mut Ui, action: &DialogAction) -> Response {
723 Self::draw_action_button_static(ui, action, self.button_padding)
724 }
725}
726
727// Convenience constructors
728/// Create a standard Material Design dialog
729///
730/// ## Parameters
731/// - `id`: Unique identifier for the dialog (used for egui state)
732/// - `title`: Title text displayed at the top of the dialog
733/// - `open`: Mutable reference to boolean controlling dialog visibility
734///
735/// ## Returns
736/// A new MaterialDialog instance configured as a standard dialog
737pub fn dialog(
738 id: impl Into<egui::Id>,
739 title: impl Into<String>,
740 open: &mut bool,
741) -> MaterialDialog<'_> {
742 MaterialDialog::new(id, title, open)
743}
744
745/// Create an alert dialog
746///
747/// ## Parameters
748/// - `id`: Unique identifier for the dialog (used for egui state)
749/// - `title`: Title text displayed at the top of the dialog
750/// - `open`: Mutable reference to boolean controlling dialog visibility
751///
752/// ## Returns
753/// A new MaterialDialog instance configured as an alert dialog
754pub fn alert_dialog(
755 id: impl Into<egui::Id>,
756 title: impl Into<String>,
757 open: &mut bool,
758) -> MaterialDialog<'_> {
759 MaterialDialog::new(id, title, open).dialog_type(DialogType::Alert)
760}
761
762/// Create a confirmation dialog
763///
764/// ## Parameters
765/// - `id`: Unique identifier for the dialog (used for egui state)
766/// - `title`: Title text displayed at the top of the dialog
767/// - `open`: Mutable reference to boolean controlling dialog visibility
768///
769/// ## Returns
770/// A new MaterialDialog instance configured as a confirmation dialog
771pub fn confirm_dialog(
772 id: impl Into<egui::Id>,
773 title: impl Into<String>,
774 open: &mut bool,
775) -> MaterialDialog<'_> {
776 MaterialDialog::new(id, title, open).dialog_type(DialogType::Confirm)
777}
778
779/// Create a form dialog
780///
781/// ## Parameters
782/// - `id`: Unique identifier for the dialog (used for egui state)
783/// - `title`: Title text displayed at the top of the dialog
784/// - `open`: Mutable reference to boolean controlling dialog visibility
785///
786/// ## Returns
787/// A new MaterialDialog instance configured as a form dialog
788pub fn form_dialog(
789 id: impl Into<egui::Id>,
790 title: impl Into<String>,
791 open: &mut bool,
792) -> MaterialDialog<'_> {
793 MaterialDialog::new(id, title, open).dialog_type(DialogType::Form)
794}