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}