1use bevy::prelude::*;
4use crate::button::{ButtonBuilder, ButtonSize};
5use crate::styles::{colors, dimensions, ButtonStyle};
6use crate::relationships::BelongsToDialog;
7use super::types::*;
8use std::collections::HashMap;
9use std::cell::RefCell;
10use std::rc::Rc;
11
12pub struct DialogBuilder {
14 title: String,
15 body: String,
16 dialog_type: DialogType,
17 width: Val,
18 min_width: Val,
19 max_width: Val,
20 height: Val,
21 min_height: Val,
22 max_height: Val,
23 buttons: Vec<DialogButton>,
24 dismissible: bool,
25 z_index: i32,
26}
27
28impl DialogBuilder {
29 pub fn new(dialog_type: DialogType) -> Self {
31 Self {
32 title: String::new(),
33 body: String::new(),
34 dialog_type,
35 width: Val::Px(dimensions::DIALOG_WIDTH_MEDIUM),
36 min_width: Val::Auto,
37 max_width: Val::Auto,
38 height: Val::Auto,
39 min_height: Val::Auto,
40 max_height: Val::Auto,
41 buttons: Vec::new(),
42 dismissible: true,
43 z_index: dimensions::Z_INDEX_MODAL,
44 }
45 }
46
47 pub fn title(mut self, title: impl Into<String>) -> Self {
49 self.title = title.into();
50 self
51 }
52
53 pub fn body(mut self, body: impl Into<String>) -> Self {
55 self.body = body.into();
56 self
57 }
58
59 pub fn width(mut self, width: Val) -> Self {
61 self.width = width;
62 self
63 }
64
65 pub fn min_width(mut self, min_width: Val) -> Self {
67 self.min_width = min_width;
68 self
69 }
70
71 pub fn max_width(mut self, max_width: Val) -> Self {
73 self.max_width = max_width;
74 self
75 }
76
77 pub fn height(mut self, height: Val) -> Self {
79 self.height = height;
80 self
81 }
82
83 pub fn min_height(mut self, min_height: Val) -> Self {
85 self.min_height = min_height;
86 self
87 }
88
89 pub fn max_height(mut self, max_height: Val) -> Self {
91 self.max_height = max_height;
92 self
93 }
94
95 pub fn dismissible(mut self, dismissible: bool) -> Self {
97 self.dismissible = dismissible;
98 self
99 }
100
101 pub fn z_index(mut self, z_index: i32) -> Self {
103 self.z_index = z_index;
104 self
105 }
106
107 pub fn confirm_button(mut self, text: impl Into<String>) -> Self {
109 self.buttons.push(DialogButton {
110 text: text.into(),
111 style: ButtonStyle::Primary,
112 marker: DialogButtonMarker::Confirm,
113 });
114 self
115 }
116
117 pub fn cancel_button(mut self, text: impl Into<String>) -> Self {
119 self.buttons.push(DialogButton {
120 text: text.into(),
121 style: ButtonStyle::Secondary,
122 marker: DialogButtonMarker::Cancel,
123 });
124 self
125 }
126
127 pub fn danger_button(mut self, text: impl Into<String>) -> Self {
129 self.buttons.push(DialogButton {
130 text: text.into(),
131 style: ButtonStyle::Danger,
132 marker: DialogButtonMarker::Confirm,
133 });
134 self
135 }
136
137 pub fn save_button(mut self, text: impl Into<String>) -> Self {
139 self.buttons.push(DialogButton {
140 text: text.into(),
141 style: ButtonStyle::Success,
142 marker: DialogButtonMarker::Save,
143 });
144 self
145 }
146
147 pub fn discard_button(mut self, text: impl Into<String>) -> Self {
149 self.buttons.push(DialogButton {
150 text: text.into(),
151 style: ButtonStyle::Warning,
152 marker: DialogButtonMarker::Discard,
153 });
154 self
155 }
156
157 pub fn ok_button(mut self) -> Self {
159 self.buttons.push(DialogButton {
160 text: "OK".to_string(),
161 style: ButtonStyle::Primary,
162 marker: DialogButtonMarker::Ok,
163 });
164 self
165 }
166
167 pub fn yes_no_buttons(mut self) -> Self {
169 self.buttons.push(DialogButton {
170 text: "Yes".to_string(),
171 style: ButtonStyle::Primary,
172 marker: DialogButtonMarker::Yes,
173 });
174 self.buttons.push(DialogButton {
175 text: "No".to_string(),
176 style: ButtonStyle::Secondary,
177 marker: DialogButtonMarker::No,
178 });
179 self
180 }
181
182 pub fn custom_button(
184 mut self,
185 text: impl Into<String>,
186 style: ButtonStyle,
187 marker: DialogButtonMarker,
188 ) -> Self {
189 self.buttons.push(DialogButton {
190 text: text.into(),
191 style,
192 marker,
193 });
194 self
195 }
196
197 pub fn build_and_mark<M: Component>(
208 self,
209 commands: &mut Commands,
210 button_marker: DialogButtonMarker,
211 component: M,
212 ) -> Entity {
213 let (dialog, buttons) = self.build_with_buttons(commands);
214 if let Some(button_entity) = buttons.get(&button_marker) {
215 commands.entity(*button_entity).insert(component);
216 }
217 dialog
218 }
219
220
221 pub fn build_with_buttons(self, commands: &mut Commands) -> (Entity, HashMap<DialogButtonMarker, Entity>) {
240 self.build_internal(commands, true)
241 }
242
243 pub fn build(self, commands: &mut Commands) -> Entity {
245 let (entity, _) = self.build_internal(commands, false);
246 entity
247 }
248
249 fn build_internal(self, commands: &mut Commands, return_buttons: bool) -> (Entity, HashMap<DialogButtonMarker, Entity>) {
251 let overlay_entity = commands
253 .spawn((
254 Button, Node {
256 position_type: PositionType::Absolute,
257 width: Val::Percent(100.0),
258 height: Val::Percent(100.0),
259 justify_content: JustifyContent::Center,
260 align_items: AlignItems::Center,
261 ..default()
262 },
263 BackgroundColor(colors::OVERLAY_BACKDROP),
264 DialogOverlay {
265 dialog_type: self.dialog_type,
266 dismissible: self.dismissible,
267 },
268 ZIndex(self.z_index),
269 ))
270 .id();
271
272 match self.dialog_type {
274 DialogType::ExitConfirmation => {
275 commands.entity(overlay_entity).insert(ExitConfirmationDialog);
276 }
277 DialogType::UnsavedChanges => {
278 commands.entity(overlay_entity).insert(UnsavedChangesDialog);
279 }
280 DialogType::Resolution => {
281 commands.entity(overlay_entity).insert(ResolutionDialog);
282 }
283 DialogType::Error => {
284 commands.entity(overlay_entity).insert(ErrorDialog);
285 }
286 DialogType::Info => {
287 commands.entity(overlay_entity).insert(InfoDialog);
288 }
289 DialogType::Warning => {
290 commands.entity(overlay_entity).insert(WarningDialog);
291 }
292 DialogType::Success => {
293 commands.entity(overlay_entity).insert(SuccessDialog);
294 }
295 DialogType::Custom => {}
296 }
297
298 let button_entities = Rc::new(RefCell::new(HashMap::new()));
300 let button_entities_clone = button_entities.clone();
301
302 let container_entity = commands
304 .spawn((
305 Node {
306 width: self.width,
307 height: self.height,
308 min_width: self.min_width,
309 min_height: self.min_height,
310 max_width: self.max_width,
311 max_height: self.max_height,
312 padding: UiRect::all(Val::Px(dimensions::PADDING_LARGE)),
313 flex_direction: FlexDirection::Column,
314 align_items: AlignItems::Center,
315 border: UiRect::all(Val::Px(dimensions::BORDER_WIDTH_THIN)),
316 ..default()
317 },
318 BackgroundColor(colors::BACKGROUND_SECONDARY),
319 BorderColor(colors::BORDER_DEFAULT),
320 BorderRadius::all(Val::Px(dimensions::BORDER_RADIUS_LARGE)),
321 DialogContainer {
322 dialog_type: self.dialog_type,
323 },
324 ZIndex(self.z_index + 50),
325 BelongsToDialog(overlay_entity), ))
327 .id();
328
329 commands.entity(container_entity).with_children(|parent| {
330 if !self.title.is_empty() {
332 parent
333 .spawn((
334 Node {
335 width: Val::Percent(100.0),
336 margin: UiRect::bottom(Val::Px(dimensions::SPACING_LARGE)),
337 justify_content: JustifyContent::Center,
338 align_items: AlignItems::Center,
339 ..default()
340 },
341 BackgroundColor(Color::NONE),
342 ))
343 .with_children(|title_parent| {
344 title_parent.spawn((
345 Text::new(self.title.clone()),
346 TextFont {
347 font_size: dimensions::FONT_SIZE_HEADING,
348 ..default()
349 },
350 TextColor(colors::TEXT_PRIMARY),
351 DialogTitle,
352 ));
353 });
354 }
355
356 if !self.body.is_empty() {
358 parent
359 .spawn((
360 Node {
361 width: Val::Percent(100.0),
362 margin: UiRect::bottom(Val::Px(dimensions::SPACING_LARGE)),
363 justify_content: JustifyContent::Center,
364 align_items: AlignItems::Center,
365 ..default()
366 },
367 BackgroundColor(Color::NONE),
368 ))
369 .with_children(|body_parent| {
370 body_parent.spawn((
371 Text::new(self.body.clone()),
372 TextFont {
373 font_size: dimensions::FONT_SIZE_MEDIUM,
374 ..default()
375 },
376 TextColor(colors::TEXT_SECONDARY),
377 DialogBody,
378 ));
379 });
380 }
381
382 if !self.buttons.is_empty() {
384 parent
385 .spawn((
386 Node {
387 width: Val::Percent(100.0),
388 flex_direction: FlexDirection::Row,
389 justify_content: JustifyContent::Center,
390 column_gap: Val::Px(dimensions::SPACING_MEDIUM),
391 ..default()
392 },
393 BackgroundColor(Color::NONE),
394 DialogButtonRow,
395 ))
396 .with_children(|button_row| {
397 for button in self.buttons {
398 let button_entity = ButtonBuilder::new(button.text)
399 .style(button.style)
400 .size(ButtonSize::Medium)
401 .build(button_row);
402
403 if return_buttons {
405 button_entities_clone.borrow_mut().insert(button.marker.clone(), button_entity);
406 }
407
408 match &button.marker {
410 DialogButtonMarker::Confirm => {
411 button_row.commands().entity(button_entity).insert(ConfirmButton);
412 }
413 DialogButtonMarker::Cancel => {
414 button_row.commands().entity(button_entity).insert(CancelButton);
415 }
416 DialogButtonMarker::Save => {
417 button_row.commands().entity(button_entity).insert(SaveButton);
418 }
419 DialogButtonMarker::Discard => {
420 button_row.commands().entity(button_entity).insert(DiscardButton);
421 }
422 DialogButtonMarker::Ok => {
423 button_row.commands().entity(button_entity).insert(OkButton);
424 }
425 DialogButtonMarker::Yes => {
426 button_row.commands().entity(button_entity).insert(YesButton);
427 }
428 DialogButtonMarker::No => {
429 button_row.commands().entity(button_entity).insert(NoButton);
430 }
431 DialogButtonMarker::Custom(_) => {
432 }
434 }
435 }
436 });
437 }
438 });
439
440 commands.entity(overlay_entity).add_child(container_entity);
443
444 let final_button_entities = Rc::try_unwrap(button_entities)
446 .map(|refcell| refcell.into_inner())
447 .unwrap_or_else(|rc| rc.borrow().clone());
448
449 (overlay_entity, final_button_entities)
450 }
451}
452
453pub mod presets {
455 use super::*;
456
457 pub fn exit_confirmation(commands: &mut Commands) -> Entity {
459 DialogBuilder::new(DialogType::ExitConfirmation)
460 .title("Exit Application")
461 .body("Are you sure you want to exit?")
462 .danger_button("Exit")
463 .cancel_button("Cancel")
464 .build(commands)
465 }
466
467 pub fn unsaved_changes(commands: &mut Commands) -> Entity {
469 DialogBuilder::new(DialogType::UnsavedChanges)
470 .title("Unsaved Changes")
471 .body("You have unsaved changes. What would you like to do?")
472 .save_button("Save")
473 .discard_button("Discard")
474 .cancel_button("Cancel")
475 .build(commands)
476 }
477
478 pub fn error(commands: &mut Commands, message: impl Into<String>) -> Entity {
480 DialogBuilder::new(DialogType::Error)
481 .title("Error")
482 .body(message)
483 .ok_button()
484 .build(commands)
485 }
486
487 pub fn info(commands: &mut Commands, title: impl Into<String>, message: impl Into<String>) -> Entity {
489 DialogBuilder::new(DialogType::Info)
490 .title(title)
491 .body(message)
492 .ok_button()
493 .build(commands)
494 }
495
496 pub fn warning(commands: &mut Commands, message: impl Into<String>) -> Entity {
498 DialogBuilder::new(DialogType::Warning)
499 .title("Warning")
500 .body(message)
501 .ok_button()
502 .build(commands)
503 }
504
505 pub fn success(commands: &mut Commands, message: impl Into<String>) -> Entity {
507 DialogBuilder::new(DialogType::Success)
508 .title("Success")
509 .body(message)
510 .ok_button()
511 .build(commands)
512 }
513
514 pub fn confirm(commands: &mut Commands, title: impl Into<String>, message: impl Into<String>) -> Entity {
516 DialogBuilder::new(DialogType::Custom)
517 .title(title)
518 .body(message)
519 .yes_no_buttons()
520 .build(commands)
521 }
522}