Skip to main content

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}