1use bevy::prelude::*;
4use crate::button::{ButtonBuilder, ButtonSize};
5use crate::styles::{colors, dimensions, ButtonStyle};
6use crate::relationships::BelongsToDialog;
7use super::types::*;
8
9pub struct DialogBuilder {
11 title: String,
12 body: String,
13 dialog_type: DialogType,
14 width: Val,
15 min_width: Val,
16 max_width: Val,
17 height: Val,
18 min_height: Val,
19 max_height: Val,
20 buttons: Vec<DialogButton>,
21 dismissible: bool,
22 z_index: i32,
23}
24
25impl DialogBuilder {
26 pub fn new(dialog_type: DialogType) -> Self {
28 Self {
29 title: String::new(),
30 body: String::new(),
31 dialog_type,
32 width: Val::Px(dimensions::DIALOG_WIDTH_MEDIUM),
33 min_width: Val::Auto,
34 max_width: Val::Auto,
35 height: Val::Auto,
36 min_height: Val::Auto,
37 max_height: Val::Auto,
38 buttons: Vec::new(),
39 dismissible: true,
40 z_index: dimensions::Z_INDEX_MODAL,
41 }
42 }
43
44 pub fn title(mut self, title: impl Into<String>) -> Self {
46 self.title = title.into();
47 self
48 }
49
50 pub fn body(mut self, body: impl Into<String>) -> Self {
52 self.body = body.into();
53 self
54 }
55
56 pub fn width(mut self, width: Val) -> Self {
58 self.width = width;
59 self
60 }
61
62 pub fn min_width(mut self, min_width: Val) -> Self {
64 self.min_width = min_width;
65 self
66 }
67
68 pub fn max_width(mut self, max_width: Val) -> Self {
70 self.max_width = max_width;
71 self
72 }
73
74 pub fn height(mut self, height: Val) -> Self {
76 self.height = height;
77 self
78 }
79
80 pub fn min_height(mut self, min_height: Val) -> Self {
82 self.min_height = min_height;
83 self
84 }
85
86 pub fn max_height(mut self, max_height: Val) -> Self {
88 self.max_height = max_height;
89 self
90 }
91
92 pub fn dismissible(mut self, dismissible: bool) -> Self {
94 self.dismissible = dismissible;
95 self
96 }
97
98 pub fn z_index(mut self, z_index: i32) -> Self {
100 self.z_index = z_index;
101 self
102 }
103
104 pub fn confirm_button(mut self, text: impl Into<String>) -> Self {
106 self.buttons.push(DialogButton {
107 text: text.into(),
108 style: ButtonStyle::Primary,
109 marker: DialogButtonMarker::Confirm,
110 });
111 self
112 }
113
114 pub fn cancel_button(mut self, text: impl Into<String>) -> Self {
116 self.buttons.push(DialogButton {
117 text: text.into(),
118 style: ButtonStyle::Secondary,
119 marker: DialogButtonMarker::Cancel,
120 });
121 self
122 }
123
124 pub fn danger_button(mut self, text: impl Into<String>) -> Self {
126 self.buttons.push(DialogButton {
127 text: text.into(),
128 style: ButtonStyle::Danger,
129 marker: DialogButtonMarker::Confirm,
130 });
131 self
132 }
133
134 pub fn save_button(mut self, text: impl Into<String>) -> Self {
136 self.buttons.push(DialogButton {
137 text: text.into(),
138 style: ButtonStyle::Success,
139 marker: DialogButtonMarker::Save,
140 });
141 self
142 }
143
144 pub fn discard_button(mut self, text: impl Into<String>) -> Self {
146 self.buttons.push(DialogButton {
147 text: text.into(),
148 style: ButtonStyle::Warning,
149 marker: DialogButtonMarker::Discard,
150 });
151 self
152 }
153
154 pub fn ok_button(mut self) -> Self {
156 self.buttons.push(DialogButton {
157 text: "OK".to_string(),
158 style: ButtonStyle::Primary,
159 marker: DialogButtonMarker::Ok,
160 });
161 self
162 }
163
164 pub fn yes_no_buttons(mut self) -> Self {
166 self.buttons.push(DialogButton {
167 text: "Yes".to_string(),
168 style: ButtonStyle::Primary,
169 marker: DialogButtonMarker::Yes,
170 });
171 self.buttons.push(DialogButton {
172 text: "No".to_string(),
173 style: ButtonStyle::Secondary,
174 marker: DialogButtonMarker::No,
175 });
176 self
177 }
178
179 pub fn custom_button(
181 mut self,
182 text: impl Into<String>,
183 style: ButtonStyle,
184 marker: DialogButtonMarker,
185 ) -> Self {
186 self.buttons.push(DialogButton {
187 text: text.into(),
188 style,
189 marker,
190 });
191 self
192 }
193
194 pub fn build(self, commands: &mut Commands) -> Entity {
196 let overlay_entity = commands
198 .spawn((
199 Button, Node {
201 position_type: PositionType::Absolute,
202 width: Val::Percent(100.0),
203 height: Val::Percent(100.0),
204 justify_content: JustifyContent::Center,
205 align_items: AlignItems::Center,
206 ..default()
207 },
208 BackgroundColor(colors::OVERLAY_BACKDROP),
209 DialogOverlay {
210 dialog_type: self.dialog_type,
211 dismissible: self.dismissible,
212 },
213 ZIndex(self.z_index),
214 ))
215 .id();
216
217 match self.dialog_type {
219 DialogType::ExitConfirmation => {
220 commands.entity(overlay_entity).insert(ExitConfirmationDialog);
221 }
222 DialogType::UnsavedChanges => {
223 commands.entity(overlay_entity).insert(UnsavedChangesDialog);
224 }
225 DialogType::Resolution => {
226 commands.entity(overlay_entity).insert(ResolutionDialog);
227 }
228 DialogType::Error => {
229 commands.entity(overlay_entity).insert(ErrorDialog);
230 }
231 DialogType::Info => {
232 commands.entity(overlay_entity).insert(InfoDialog);
233 }
234 DialogType::Warning => {
235 commands.entity(overlay_entity).insert(WarningDialog);
236 }
237 DialogType::Success => {
238 commands.entity(overlay_entity).insert(SuccessDialog);
239 }
240 DialogType::Custom => {}
241 }
242
243 let container_entity = commands
245 .spawn((
246 Node {
247 width: self.width,
248 height: self.height,
249 min_width: self.min_width,
250 min_height: self.min_height,
251 max_width: self.max_width,
252 max_height: self.max_height,
253 padding: UiRect::all(Val::Px(dimensions::PADDING_LARGE)),
254 flex_direction: FlexDirection::Column,
255 align_items: AlignItems::Center,
256 border: UiRect::all(Val::Px(dimensions::BORDER_WIDTH_THIN)),
257 ..default()
258 },
259 BackgroundColor(colors::BACKGROUND_SECONDARY),
260 BorderColor(colors::BORDER_DEFAULT),
261 BorderRadius::all(Val::Px(dimensions::BORDER_RADIUS_LARGE)),
262 DialogContainer {
263 dialog_type: self.dialog_type,
264 },
265 ZIndex(self.z_index + 50),
266 BelongsToDialog(overlay_entity), ))
268 .id();
269
270 commands.entity(container_entity).with_children(|parent| {
271 if !self.title.is_empty() {
273 parent
274 .spawn((
275 Node {
276 width: Val::Percent(100.0),
277 margin: UiRect::bottom(Val::Px(dimensions::SPACING_LARGE)),
278 justify_content: JustifyContent::Center,
279 align_items: AlignItems::Center,
280 ..default()
281 },
282 BackgroundColor(Color::NONE),
283 ))
284 .with_children(|title_parent| {
285 title_parent.spawn((
286 Text::new(self.title.clone()),
287 TextFont {
288 font_size: dimensions::FONT_SIZE_HEADING,
289 ..default()
290 },
291 TextColor(colors::TEXT_PRIMARY),
292 DialogTitle,
293 ));
294 });
295 }
296
297 if !self.body.is_empty() {
299 parent
300 .spawn((
301 Node {
302 width: Val::Percent(100.0),
303 margin: UiRect::bottom(Val::Px(dimensions::SPACING_LARGE)),
304 justify_content: JustifyContent::Center,
305 align_items: AlignItems::Center,
306 ..default()
307 },
308 BackgroundColor(Color::NONE),
309 ))
310 .with_children(|body_parent| {
311 body_parent.spawn((
312 Text::new(self.body.clone()),
313 TextFont {
314 font_size: dimensions::FONT_SIZE_MEDIUM,
315 ..default()
316 },
317 TextColor(colors::TEXT_SECONDARY),
318 DialogBody,
319 ));
320 });
321 }
322
323 if !self.buttons.is_empty() {
325 parent
326 .spawn((
327 Node {
328 width: Val::Percent(100.0),
329 flex_direction: FlexDirection::Row,
330 justify_content: JustifyContent::Center,
331 column_gap: Val::Px(dimensions::SPACING_MEDIUM),
332 ..default()
333 },
334 BackgroundColor(Color::NONE),
335 DialogButtonRow,
336 ))
337 .with_children(|button_row| {
338 for button in self.buttons {
339 let button_entity = ButtonBuilder::new(button.text)
340 .style(button.style)
341 .size(ButtonSize::Medium)
342 .build(button_row);
343
344 match button.marker {
346 DialogButtonMarker::Confirm => {
347 button_row.commands().entity(button_entity).insert(ConfirmButton);
348 }
349 DialogButtonMarker::Cancel => {
350 button_row.commands().entity(button_entity).insert(CancelButton);
351 }
352 DialogButtonMarker::Save => {
353 button_row.commands().entity(button_entity).insert(SaveButton);
354 }
355 DialogButtonMarker::Discard => {
356 button_row.commands().entity(button_entity).insert(DiscardButton);
357 }
358 DialogButtonMarker::Ok => {
359 button_row.commands().entity(button_entity).insert(OkButton);
360 }
361 DialogButtonMarker::Yes => {
362 button_row.commands().entity(button_entity).insert(YesButton);
363 }
364 DialogButtonMarker::No => {
365 button_row.commands().entity(button_entity).insert(NoButton);
366 }
367 DialogButtonMarker::Custom(_) => {
368 }
370 }
371 }
372 });
373 }
374 });
375
376 commands.entity(overlay_entity).add_child(container_entity);
379
380 overlay_entity
381 }
382}
383
384pub mod presets {
386 use super::*;
387
388 pub fn exit_confirmation(commands: &mut Commands) -> Entity {
390 DialogBuilder::new(DialogType::ExitConfirmation)
391 .title("Exit Application")
392 .body("Are you sure you want to exit?")
393 .danger_button("Exit")
394 .cancel_button("Cancel")
395 .build(commands)
396 }
397
398 pub fn unsaved_changes(commands: &mut Commands) -> Entity {
400 DialogBuilder::new(DialogType::UnsavedChanges)
401 .title("Unsaved Changes")
402 .body("You have unsaved changes. What would you like to do?")
403 .save_button("Save")
404 .discard_button("Discard")
405 .cancel_button("Cancel")
406 .build(commands)
407 }
408
409 pub fn error(commands: &mut Commands, message: impl Into<String>) -> Entity {
411 DialogBuilder::new(DialogType::Error)
412 .title("Error")
413 .body(message)
414 .ok_button()
415 .build(commands)
416 }
417
418 pub fn info(commands: &mut Commands, title: impl Into<String>, message: impl Into<String>) -> Entity {
420 DialogBuilder::new(DialogType::Info)
421 .title(title)
422 .body(message)
423 .ok_button()
424 .build(commands)
425 }
426
427 pub fn warning(commands: &mut Commands, message: impl Into<String>) -> Entity {
429 DialogBuilder::new(DialogType::Warning)
430 .title("Warning")
431 .body(message)
432 .ok_button()
433 .build(commands)
434 }
435
436 pub fn success(commands: &mut Commands, message: impl Into<String>) -> Entity {
438 DialogBuilder::new(DialogType::Success)
439 .title("Success")
440 .body(message)
441 .ok_button()
442 .build(commands)
443 }
444
445 pub fn confirm(commands: &mut Commands, title: impl Into<String>, message: impl Into<String>) -> Entity {
447 DialogBuilder::new(DialogType::Custom)
448 .title(title)
449 .body(message)
450 .yes_no_buttons()
451 .build(commands)
452 }
453}