1use super::config::*;
2use super::input_configuration::{ActiveInputConfiguration, InputConfigurationEvent}; use crate::{
4 theme::KonnektorenTheme,
5 ui::{
6 responsive::{ResponsiveFontSize, ResponsiveInfo, ResponsiveSpacing},
7 widgets::{ResponsiveText, ThemedButton},
8 },
9};
10use bevy::prelude::*;
11use bevy_egui::{
12 egui::{self, Widget},
13 EguiContexts,
14};
15
16#[cfg(feature = "settings")]
17use crate::settings::{SettingType, SettingValue};
18
19#[derive(Component)]
21pub struct ActiveSettingsScreen {
22 config: SettingsScreenConfig,
23 navigation_state: SettingsNavigationState,
24}
25
26#[derive(Clone)]
28pub struct SettingsNavigationState {
29 pub current_index: usize,
30 pub max_index: usize,
31 pub enabled: bool,
32}
33
34impl Default for SettingsNavigationState {
35 fn default() -> Self {
36 Self {
37 current_index: 0,
38 max_index: 0,
39 enabled: true,
40 }
41 }
42}
43
44#[allow(clippy::type_complexity)]
46pub fn check_settings_screen_config(
47 mut commands: Commands,
48 query: Query<
49 (Entity, &SettingsScreenConfig),
50 (Without<ActiveSettingsScreen>, Changed<SettingsScreenConfig>),
51 >,
52 existing_settings: Query<Entity, With<ActiveSettingsScreen>>,
53) {
54 for (entity, config) in query.iter() {
55 info!("Setting up settings screen for entity {:?}", entity);
56
57 for existing_entity in existing_settings.iter() {
59 info!(
60 "Cleaning up existing settings screen: {:?}",
61 existing_entity
62 );
63 commands
64 .entity(existing_entity)
65 .remove::<ActiveSettingsScreen>();
66 }
67
68 let mut max_index = 0;
70 for section in &config.sections {
71 for setting in §ion.settings {
72 if let Some(nav_index) = setting.navigation_index {
73 max_index = max_index.max(nav_index);
74 }
75 }
76 }
77 if config.allow_dismissal {
78 max_index += 1; }
80
81 let nav_state = SettingsNavigationState {
82 max_index,
83 ..Default::default()
84 };
85
86 commands.entity(entity).insert(ActiveSettingsScreen {
87 config: config.clone(),
88 navigation_state: nav_state,
89 });
90 }
91}
92
93pub fn update_settings_screen_values(
95 mut settings_events: EventReader<SettingsScreenEvent>,
96 mut active_settings_query: Query<&mut ActiveSettingsScreen>,
97) {
98 for event in settings_events.read() {
99 if let SettingsScreenEvent::ValueChanged {
100 setting_id,
101 value,
102 entity: _,
103 } = event
104 {
105 for mut active_settings in active_settings_query.iter_mut() {
107 let mut updated = false;
108
109 for section in &mut active_settings.config.sections {
110 for setting in &mut section.settings {
111 if setting.id == *setting_id {
112 #[cfg(feature = "settings")]
113 {
114 setting.current_value = value.clone();
115 updated = true;
116 info!("Updated active setting '{}' to {:?}", setting_id, value);
117 }
118 #[cfg(not(feature = "settings"))]
119 {
120 setting.current_value = value.clone();
121 updated = true;
122 info!("Updated active setting '{}' to {:?}", setting_id, value);
123 }
124 break;
125 }
126 }
127 if updated {
128 break;
129 }
130 }
131 }
132 }
133 }
134}
135
136pub fn render_settings_screen_ui(
138 mut contexts: EguiContexts,
139 theme: Res<KonnektorenTheme>,
140 responsive: Res<ResponsiveInfo>,
141 mut query: Query<(Entity, &mut ActiveSettingsScreen)>,
142 mut settings_events: EventWriter<SettingsScreenEvent>,
143 input: Res<ButtonInput<KeyCode>>,
144) {
145 if query.is_empty() {
146 return;
147 }
148
149 if contexts.try_ctx_mut().is_none() {
150 return;
151 }
152
153 let ctx = contexts.ctx_mut();
154
155 if let Some((entity, mut settings)) = query.iter_mut().next() {
157 let should_dismiss = settings.config.allow_dismissal && input.just_pressed(KeyCode::Escape);
159 if should_dismiss {
160 settings_events.write(SettingsScreenEvent::Dismissed { entity });
161 return;
162 }
163
164 let ActiveSettingsScreen {
166 config,
167 navigation_state,
168 } = &mut *settings;
169
170 egui::CentralPanel::default()
171 .frame(egui::Frame::NONE.fill(theme.base_100))
172 .show(ctx, |ui| {
173 render_settings_content(
174 ui,
175 config,
176 navigation_state,
177 &theme,
178 &responsive,
179 entity,
180 &mut settings_events,
181 );
182 });
183 }
184}
185
186fn render_settings_content(
188 ui: &mut egui::Ui,
189 config: &SettingsScreenConfig,
190 nav_state: &mut SettingsNavigationState,
191 theme: &KonnektorenTheme,
192 responsive: &ResponsiveInfo,
193 entity: Entity,
194 settings_events: &mut EventWriter<SettingsScreenEvent>,
195) {
196 ui.vertical_centered(|ui| {
197 let max_width = if responsive.is_mobile() {
198 ui.available_width() * 0.95
199 } else {
200 800.0_f32.min(ui.available_width() * 0.9)
201 };
202
203 ui.set_max_width(max_width);
204
205 ui.add_space(responsive.spacing(ResponsiveSpacing::Large));
207 ResponsiveText::new(&config.title, ResponsiveFontSize::Header, theme.primary)
208 .responsive(responsive)
209 .strong()
210 .ui(ui);
211
212 ui.add_space(responsive.spacing(ResponsiveSpacing::Large));
213
214 let scroll_height = ui.available_height() - 80.0;
216 egui::ScrollArea::vertical()
217 .max_height(scroll_height)
218 .auto_shrink([false; 2])
219 .show(ui, |ui| {
220 if config.mobile_layout || responsive.is_mobile() {
221 render_mobile_settings_layout(
222 ui,
223 config,
224 nav_state,
225 theme,
226 responsive,
227 entity,
228 settings_events,
229 );
230 } else {
231 render_desktop_settings_layout(
232 ui,
233 config,
234 nav_state,
235 theme,
236 responsive,
237 entity,
238 settings_events,
239 );
240 }
241 });
242
243 if config.allow_dismissal {
245 ui.add_space(responsive.spacing(ResponsiveSpacing::Large));
246 let back_button = ThemedButton::new(&config.back_button_text, theme)
247 .responsive(responsive)
248 .width(if responsive.is_mobile() { 200.0 } else { 150.0 });
249
250 if ui.add(back_button).clicked() {
251 settings_events.write(SettingsScreenEvent::Dismissed { entity });
252 }
253 }
254
255 ui.add_space(responsive.spacing(ResponsiveSpacing::Medium));
256 });
257}
258
259#[allow(clippy::too_many_arguments)]
261fn render_mobile_settings_layout(
262 ui: &mut egui::Ui,
263 config: &SettingsScreenConfig,
264 _nav_state: &SettingsNavigationState,
265 theme: &KonnektorenTheme,
266 responsive: &ResponsiveInfo,
267 entity: Entity,
268 settings_events: &mut EventWriter<SettingsScreenEvent>,
269) {
270 let section_spacing = responsive.spacing(ResponsiveSpacing::Large);
271
272 for section in &config.sections {
273 ResponsiveText::new(§ion.title, ResponsiveFontSize::Large, theme.secondary)
275 .responsive(responsive)
276 .strong()
277 .ui(ui);
278
279 ui.add_space(responsive.spacing(ResponsiveSpacing::Medium));
280
281 for setting in §ion.settings {
283 render_mobile_setting_item(ui, setting, theme, responsive, entity, settings_events);
284 ui.add_space(responsive.spacing(ResponsiveSpacing::Medium));
285 }
286
287 ui.add_space(section_spacing);
288 ui.separator();
289 ui.add_space(section_spacing);
290 }
291}
292
293#[allow(clippy::too_many_arguments)]
295fn render_desktop_settings_layout(
296 ui: &mut egui::Ui,
297 config: &SettingsScreenConfig,
298 _nav_state: &SettingsNavigationState,
299 theme: &KonnektorenTheme,
300 responsive: &ResponsiveInfo,
301 entity: Entity,
302 settings_events: &mut EventWriter<SettingsScreenEvent>,
303) {
304 for section in &config.sections {
305 ResponsiveText::new(§ion.title, ResponsiveFontSize::Large, theme.secondary)
307 .responsive(responsive)
308 .strong()
309 .ui(ui);
310
311 ui.add_space(responsive.spacing(ResponsiveSpacing::Medium));
312
313 egui::Grid::new(format!("settings_grid_{}", section.title))
315 .num_columns(2)
316 .spacing([30.0, 15.0])
317 .show(ui, |ui| {
318 for setting in §ion.settings {
319 ResponsiveText::new(
321 &setting.label,
322 ResponsiveFontSize::Medium,
323 theme.base_content,
324 )
325 .responsive(responsive)
326 .ui(ui);
327
328 render_desktop_setting_control(
330 ui,
331 setting,
332 theme,
333 responsive,
334 entity,
335 settings_events,
336 );
337 ui.end_row();
338 }
339 });
340
341 ui.add_space(responsive.spacing(ResponsiveSpacing::Large));
342 }
343}
344
345fn render_mobile_setting_item(
347 ui: &mut egui::Ui,
348 setting: &ScreenSettingsItem,
349 theme: &KonnektorenTheme,
350 responsive: &ResponsiveInfo,
351 entity: Entity,
352 settings_events: &mut EventWriter<SettingsScreenEvent>,
353) {
354 ui.vertical_centered(|ui| {
355 ResponsiveText::new(
356 &setting.label,
357 ResponsiveFontSize::Medium,
358 theme.base_content,
359 )
360 .responsive(responsive)
361 .strong()
362 .ui(ui);
363
364 ui.add_space(responsive.spacing(ResponsiveSpacing::Small));
365
366 render_setting_control(ui, setting, theme, responsive, entity, settings_events);
367 });
368}
369
370fn render_desktop_setting_control(
372 ui: &mut egui::Ui,
373 setting: &ScreenSettingsItem,
374 theme: &KonnektorenTheme,
375 responsive: &ResponsiveInfo,
376 entity: Entity,
377 settings_events: &mut EventWriter<SettingsScreenEvent>,
378) {
379 render_setting_control(ui, setting, theme, responsive, entity, settings_events);
380}
381
382fn render_setting_control(
384 ui: &mut egui::Ui,
385 setting: &ScreenSettingsItem,
386 theme: &KonnektorenTheme,
387 responsive: &ResponsiveInfo,
388 entity: Entity,
389 settings_events: &mut EventWriter<SettingsScreenEvent>,
390) {
391 #[cfg(feature = "settings")]
392 {
393 match &setting.setting_type {
394 crate::settings::SettingType::Toggle => {
395 if let Some(current_value) = setting.current_value.as_bool() {
396 let button_text = if current_value { "ON" } else { "OFF" };
397 let button = ThemedButton::new(button_text, theme).responsive(responsive);
398
399 if ui.add(button).clicked() {
400 settings_events.write(SettingsScreenEvent::ValueChanged {
401 entity,
402 setting_id: setting.id.clone(),
403 value: SettingValue::Bool(!current_value),
404 });
405 }
406 }
407 }
408
409 SettingType::FloatRange { min, max, step } => {
410 if let Some(current_value) = setting.current_value.as_float() {
411 ui.horizontal(|ui| {
412 let dec_button = ThemedButton::new("-", theme)
413 .responsive(responsive)
414 .width(30.0);
415
416 if ui.add(dec_button).clicked() {
417 let new_value = (current_value - step).max(*min);
418 settings_events.write(SettingsScreenEvent::ValueChanged {
419 entity,
420 setting_id: setting.id.clone(),
421 value: SettingValue::Float(new_value),
422 });
423 }
424
425 ui.add_space(responsive.spacing(ResponsiveSpacing::Small));
426
427 let display_text = if setting.id.contains("volume") {
429 format!("{:.0}%", current_value * 100.0)
430 } else {
431 format!("{:.1}", current_value)
432 };
433
434 ResponsiveText::new(
435 &display_text,
436 ResponsiveFontSize::Medium,
437 theme.base_content,
438 )
439 .responsive(responsive)
440 .ui(ui);
441
442 ui.add_space(responsive.spacing(ResponsiveSpacing::Small));
443
444 let inc_button = ThemedButton::new("+", theme)
445 .responsive(responsive)
446 .width(30.0);
447
448 if ui.add(inc_button).clicked() {
449 let new_value = (current_value + step).min(*max);
450 settings_events.write(SettingsScreenEvent::ValueChanged {
451 entity,
452 setting_id: setting.id.clone(),
453 value: SettingValue::Float(new_value),
454 });
455 }
456 });
457 }
458 }
459
460 SettingType::Selection { options } => {
461 if let Some(current_index) = setting.current_value.as_selection() {
462 if responsive.is_mobile() {
463 ui.horizontal(|ui| {
464 let left_button = ThemedButton::new("◀", theme)
465 .responsive(responsive)
466 .width(40.0);
467
468 if ui.add(left_button).clicked() {
469 let new_index = if current_index > 0 {
470 current_index - 1
471 } else {
472 options.len() - 1
473 };
474 settings_events.write(SettingsScreenEvent::ValueChanged {
475 entity,
476 setting_id: setting.id.clone(),
477 value: SettingValue::Selection(new_index),
478 });
479 }
480
481 ui.add_space(responsive.spacing(ResponsiveSpacing::Small));
482
483 let def = String::new();
484 let current_option = options.get(current_index).unwrap_or(&def);
485 ResponsiveText::new(
486 current_option,
487 ResponsiveFontSize::Medium,
488 theme.primary,
489 )
490 .responsive(responsive)
491 .ui(ui);
492
493 ui.add_space(responsive.spacing(ResponsiveSpacing::Small));
494
495 let right_button = ThemedButton::new("▶", theme)
496 .responsive(responsive)
497 .width(40.0);
498
499 if ui.add(right_button).clicked() {
500 let new_index = (current_index + 1) % options.len();
501 settings_events.write(SettingsScreenEvent::ValueChanged {
502 entity,
503 setting_id: setting.id.clone(),
504 value: SettingValue::Selection(new_index),
505 });
506 }
507 });
508 } else {
509 ui.horizontal_wrapped(|ui| {
510 for (index, option) in options.iter().enumerate() {
511 let is_selected = index == current_index;
512 let mut button =
513 ThemedButton::new(option, theme).responsive(responsive);
514
515 if is_selected {
516 button = button.with_style(|btn| {
517 btn.fill(theme.primary)
518 .stroke(egui::Stroke::new(2.0, theme.primary))
519 });
520 }
521
522 if ui.add(button).clicked() && !is_selected {
523 settings_events.write(SettingsScreenEvent::ValueChanged {
524 entity,
525 setting_id: setting.id.clone(),
526 value: SettingValue::Selection(index),
527 });
528 }
529 }
530 });
531 }
532 }
533 }
534
535 SettingType::IntRange { min, max, step } => {
536 if let Some(current_value) = setting.current_value.as_int() {
537 ui.horizontal(|ui| {
538 let dec_button = ThemedButton::new("-", theme)
539 .responsive(responsive)
540 .width(30.0);
541
542 if ui.add(dec_button).clicked() {
543 let new_value = (current_value - step).max(*min);
544 settings_events.write(SettingsScreenEvent::ValueChanged {
545 entity,
546 setting_id: setting.id.clone(),
547 value: SettingValue::Int(new_value),
548 });
549 }
550
551 ui.add_space(responsive.spacing(ResponsiveSpacing::Small));
552
553 ResponsiveText::new(
554 ¤t_value.to_string(),
555 ResponsiveFontSize::Medium,
556 theme.base_content,
557 )
558 .responsive(responsive)
559 .ui(ui);
560
561 ui.add_space(responsive.spacing(ResponsiveSpacing::Small));
562
563 let inc_button = ThemedButton::new("+", theme)
564 .responsive(responsive)
565 .width(30.0);
566
567 if ui.add(inc_button).clicked() {
568 let new_value = (current_value + step).min(*max);
569 settings_events.write(SettingsScreenEvent::ValueChanged {
570 entity,
571 setting_id: setting.id.clone(),
572 value: SettingValue::Int(new_value),
573 });
574 }
575 });
576 }
577 }
578
579 SettingType::Text { max_length } => {
580 if let Some(current_text) = setting.current_value.as_string() {
581 let mut text = current_text.to_string();
582 let text_edit = egui::TextEdit::singleline(&mut text);
583
584 if ui.add(text_edit).changed() {
585 if let Some(max_len) = max_length {
587 text.truncate(*max_len);
588 }
589
590 settings_events.write(SettingsScreenEvent::ValueChanged {
591 entity,
592 setting_id: setting.id.clone(),
593 value: SettingValue::String(text),
594 });
595 }
596 }
597 }
598
599 SettingType::Custom { display_fn, .. } => {
600 let display_text = display_fn(&setting.current_value);
601
602 if setting.id == "configure_players" {
604 let button = ThemedButton::new(&display_text, theme).responsive(responsive);
605
606 if ui.add(button).clicked() {
607 settings_events.write(SettingsScreenEvent::ValueChanged {
608 entity,
609 setting_id: setting.id.clone(),
610 value: setting.current_value.clone(),
611 });
612 }
613 } else {
614 ResponsiveText::new(
616 &display_text,
617 ResponsiveFontSize::Medium,
618 theme.base_content,
619 )
620 .responsive(responsive)
621 .ui(ui);
622 }
623 }
624 }
625 }
626
627 #[cfg(not(feature = "settings"))]
628 {
629 match &setting.setting_type {
630 ScreenOnlySettingType::Toggle => {
631 if let ScreenSettingValue::Bool(current_value) = &setting.current_value {
632 let button_text = if *current_value { "ON" } else { "OFF" };
633 let button = ThemedButton::new(button_text, theme).responsive(responsive);
634
635 if ui.add(button).clicked() {
636 settings_events.write(SettingsScreenEvent::ValueChanged {
637 entity,
638 setting_id: setting.id.clone(),
639 value: ScreenSettingValue::Bool(!current_value),
640 });
641 }
642 }
643 }
644 ScreenOnlySettingType::Custom { display_fn } => {
645 let display_text = display_fn(&setting.current_value);
646
647 if setting.id == "configure_players" {
648 let button = ThemedButton::new(&display_text, theme).responsive(responsive);
649
650 if ui.add(button).clicked() {
651 settings_events.write(SettingsScreenEvent::ValueChanged {
652 entity,
653 setting_id: setting.id.clone(),
654 value: setting.current_value.clone(),
655 });
656 }
657 } else {
658 ResponsiveText::new(
659 &display_text,
660 ResponsiveFontSize::Medium,
661 theme.base_content,
662 )
663 .responsive(responsive)
664 .ui(ui);
665 }
666 }
667 _ => {
668 ui.label("Setting type not implemented for screen-only mode");
669 }
670 }
671 }
672}
673
674pub fn handle_settings_screen_events(
676 mut commands: Commands,
677 mut settings_events: EventReader<SettingsScreenEvent>,
678 mut input_config_events: EventWriter<InputConfigurationEvent>,
679) {
680 for event in settings_events.read() {
681 match event {
682 SettingsScreenEvent::Dismissed { entity } => {
683 info!("Dismissing settings screen for entity {:?}", entity);
684 commands.entity(*entity).remove::<ActiveSettingsScreen>();
685 }
686 SettingsScreenEvent::ValueChanged {
687 entity: _,
688 setting_id,
689 value,
690 } => {
691 info!("Setting '{}' changed to {:?}", setting_id, value);
692
693 if setting_id == "configure_players" {
695 input_config_events.write(InputConfigurationEvent::Open);
696 commands.spawn((
698 Name::new("Input Configuration Screen"),
699 ActiveInputConfiguration {
700 max_players: 4,
701 current_players: 4,
702 },
703 ));
704 }
705 }
706 SettingsScreenEvent::Navigate { direction } => {
707 info!("Navigation event: {:?}", direction);
708 }
709 }
710 }
711}