egui_dialogs/
dialog_details.rs

1//! Define the `Dialog` trait which can be implemented to customize dialogs
2//! and `DialogDetails` struct which can be used to show dialogs.
3
4use std::any::Any;
5
6use egui::{Color32, Id, WidgetText};
7
8use crate::*;
9
10/// Represents a dialog.
11/// Implement this trait to customize dialogs.
12///
13/// # Example
14/// ```
15/// use egui_dialogs::{dialog_window, Dialog, DialogContext};
16///
17/// // custom dialog for name confirmation
18/// pub struct NameConfirmDialog {
19///   name: String,
20/// }
21///
22/// impl NameConfirmDialog {
23///   pub fn new(name: String) -> Self {
24///     Self { name }
25///   }
26/// }
27///
28/// // implement dialog logic
29/// impl Dialog<String> for NameConfirmDialog {
30///   fn show(&mut self, ctx: &egui::Context, dctx: &DialogContext) -> Option<String> {
31///     // return None if the user hasn't replied
32///     let mut res = None;
33///
34///     // draw the dialog
35///     dialog_window(ctx, dctx, "Confirm name")
36///       .show(ctx, |ui| {
37///         ui.label("Your name: ");
38///         ui.text_edit_singleline(&mut self.name);
39///         if ui.button("Done").clicked() {
40///           // set the reply and end the dialog
41///           res = Some(self.name.clone());
42///         }
43///       });
44///
45///     res
46///   }
47/// }
48/// ```
49pub trait Dialog<Reply> {
50    /// Customized dialog rendering and response handling process.
51    fn show(&mut self, ctx: &egui::Context, dctx: &DialogContext) -> Option<Reply>;
52}
53
54/// Details of a dialog to be shown and replied.
55/// Used to build and show dialogs.
56///
57/// # Example
58/// ```
59/// use egui_dialogs::{DialogDetails, StandardReply};
60///
61/// # use egui_dialogs::Dialogs;
62/// #
63/// # pub struct MyApp<'a> {
64/// #     // ... your other app states
65/// #     dialogs: Dialogs<'a>,
66/// # }
67/// #
68/// # impl MyApp<'_> {
69/// #     // ... your other app logic
70/// #
71/// #     pub fn update(&mut self, ctx: &egui::Context) {
72/// #         self.dialogs.show(ctx);
73/// #
74/// #         // ... your other rendering logic
75/// // show a confirm dialog
76/// // in your update function
77/// DialogDetails::confirm("Confirm", "Are you sure you want to do this?")
78///     .on_reply(|res| {
79///         if res == StandardReply::Yes {
80///             println!("User confirmed!");
81///         }
82///     })
83///    .show(&mut self.dialogs);
84/// #     }
85/// # }
86/// ```
87pub struct DialogDetails<'a, Reply>
88where
89    Reply: 'a + Any,
90{
91    pub(crate) dialog: Box<dyn Dialog<Reply> + 'a>,
92    pub(crate) mask: Option<Color32>,
93    pub(crate) id: Option<Id>,
94}
95
96impl<'a, Reply> DialogDetails<'a, Reply>
97where
98    Reply: 'a + Any,
99{
100    #[inline]
101    /// Create a `DialogDetails` struct with the specified dialog.
102    pub fn new(dialog: impl Dialog<Reply> + 'a) -> Self {
103        Self::new_dyn(Box::new(dialog))
104    }
105
106    pub fn new_dyn(dialog: Box<dyn Dialog<Reply> + 'a>) -> Self {
107        Self {
108            dialog,
109            mask: Some(Color32::from_black_alpha(0x80)),
110            id: None,
111        }
112    }
113
114    #[inline]
115    /// Return a new `DialogDetails` struct with the specified reply handler
116    /// and a reply type mapped by the handler.
117    pub fn on_reply<R: Any>(self, handler: impl FnOnce(Reply) -> R + 'a) -> DialogDetails<'a, R> {
118        self.on_reply_dyn(Box::new(handler))
119    }
120
121    #[inline]
122    /// dynamic version of [`Self::on_reply`]
123    pub fn on_reply_dyn<R: Any>(
124        self,
125        handler: Box<dyn FnOnce(Reply) -> R + 'a>,
126    ) -> DialogDetails<'a, R> {
127        struct MappedDialog<'m, From, To> {
128            dialog: Box<dyn Dialog<From> + 'm>,
129            mapper: Option<Box<dyn FnOnce(From) -> To + 'm>>,
130        }
131
132        impl<'m, From, To> Dialog<To> for MappedDialog<'m, From, To> {
133            fn show(&mut self, ctx: &egui::Context, dctx: &DialogContext) -> Option<To> {
134                self.dialog
135                    .show(ctx, dctx)
136                    .and_then(|from| self.mapper.take().map(|mapper| (mapper)(from)))
137            }
138        }
139
140        DialogDetails {
141            dialog: Box::new(MappedDialog {
142                dialog: self.dialog,
143                mapper: Some(handler),
144            }),
145            mask: self.mask,
146            id: self.id,
147        }
148    }
149
150    #[inline]
151    /// Set whether to show a mask over the background.
152    /// The mask will intercept all user interactions with the background.
153    pub fn with_mask(mut self, mask: Option<Color32>) -> Self {
154        self.mask = mask;
155        self
156    }
157
158    #[inline]
159    /// Check if a mask is set and return it if there is.
160    pub fn mask(&self) -> Option<Color32> {
161        self.mask
162    }
163
164    #[inline]
165    /// Set the id of the dialog. Used for identify different dialogs
166    /// with a AbstractDialog trait object.
167    pub fn with_id(mut self, id: impl Into<Id>) -> Self {
168        self.id = Some(id.into());
169        self
170    }
171
172    #[inline]
173    /// Check if an id is set and return it if there is.
174    pub fn id(&self) -> Option<Id> {
175        self.id
176    }
177
178    #[inline]
179    /// Show the dialog.
180    pub fn show(self, dialogs: &mut Dialogs<'a>) {
181        dialogs.add(self);
182    }
183
184    #[inline]
185    /// Show thre dialog if it is not already open.
186    pub fn show_if_absent(self, dialogs: &mut Dialogs<'a>) {
187        dialogs.add_if_absent(self);
188    }
189}
190
191/// Alias for `DialogDetails<StandardReply>`
192pub type StandardDialogDetails<'a> = DialogDetails<'a, StandardReply>;
193
194impl StandardDialogDetails<'_> {
195    #[inline]
196    /// Create a `DialogDetails` struct with an info dialog.
197    pub fn info(title: impl Into<WidgetText>, message: impl Into<WidgetText>) -> Self {
198        StandardDialogDetails::new(StandardDialog::info(title, message))
199    }
200
201    #[inline]
202    /// Create a `DialogDetails` struct with a success dialog.
203    pub fn success(title: impl Into<WidgetText>, message: impl Into<WidgetText>) -> Self {
204        StandardDialogDetails::new(StandardDialog::success(title, message))
205    }
206
207    #[inline]
208    /// Create a `DialogDetails` struct with a confirm dialog.
209    pub fn confirm(title: impl Into<WidgetText>, message: impl Into<WidgetText>) -> Self {
210        StandardDialogDetails::new(StandardDialog::confirm(title, message))
211    }
212
213    #[inline]
214    /// Create a `DialogDetails` struct with a warning dialog.
215    pub fn warning(title: impl Into<WidgetText>, message: impl Into<WidgetText>) -> Self {
216        StandardDialogDetails::new(StandardDialog::warning(title, message))
217    }
218
219    #[inline]
220    /// Create a `DialogDetails` struct with an error dialog.
221    pub fn error(title: impl Into<WidgetText>, message: impl Into<WidgetText>) -> Self {
222        StandardDialogDetails::new(StandardDialog::error(title, message))
223    }
224}
225
226impl<'a> StandardDialogDetails<'a> {
227    #[inline]
228    /// Invoke handler when the dialog is accepted.
229    pub fn on_accepted(self, handler: impl FnOnce() + 'a) -> Self {
230        self.on_reply(|reply| {
231            if reply.accepted() {
232                (handler)();
233            }
234            reply
235        })
236    }
237
238    #[inline]
239    /// Invoke handler when the dialog is rejected.
240    pub fn on_rejected(self, handler: impl FnOnce() + 'a) -> Self {
241        self.on_reply(|reply| {
242            if reply.rejected() {
243                (handler)();
244            }
245            reply
246        })
247    }
248
249    #[inline]
250    /// Map to a DialogDetails with a new reply type using the handler
251    /// which receives a boolean indicating whether the dialog was accepted.
252    pub fn map_accepted<R: Any>(
253        self,
254        handler: impl FnOnce(bool) -> R + 'a,
255    ) -> DialogDetails<'a, R> {
256        self.on_reply(|reply| (handler)(reply.accepted()))
257    }
258
259    #[inline]
260    /// Map to a DialogDetails with a new reply type using the handler
261    /// which receives a boolean indicating whether the dialog was rejected.
262    pub fn map_rejected<R: Any>(
263        self,
264        handler: impl FnOnce(bool) -> R + 'a,
265    ) -> DialogDetails<'a, R> {
266        self.on_reply(|reply| (handler)(reply.rejected()))
267    }
268
269    #[inline]
270    /// Convert to a DialogDetails with boolean reply type
271    /// indicating whether the dialog was accepted.
272    pub fn into_accepted(self) -> DialogDetails<'a, bool> {
273        self.on_reply(StandardReply::accepted)
274    }
275
276    #[inline]
277    /// Convert to a DialogDetails with boolean reply type
278    /// indicating whether the dialog was rejected.
279    pub fn into_rejected(self) -> DialogDetails<'a, bool> {
280        self.on_reply(StandardReply::rejected)
281    }
282
283    #[inline]
284    /// Convert to a DialogDetails with a new reply type
285    /// by matching the accepted and rejected replies.
286    pub fn match_accepted<R: Any>(self, accepted: R, rejected: R) -> DialogDetails<'a, R> {
287        self.on_reply(|reply| if reply.accepted() { accepted } else { rejected })
288    }
289}