konnektoren_bevy/screens/settings/
ui.rs

1use super::config::*;
2use super::input_configuration::{ActiveInputConfiguration, InputConfigurationEvent}; // Add this import
3use 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/// Component marking an active settings screen
20#[derive(Component)]
21pub struct ActiveSettingsScreen {
22    config: SettingsScreenConfig,
23    navigation_state: SettingsNavigationState,
24}
25
26/// Navigation state for settings
27#[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/// System to check for new settings configurations
45#[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        // Clean up any existing settings screens first
58        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        // Calculate max navigation index
69        let mut max_index = 0;
70        for section in &config.sections {
71            for setting in &section.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; // For back button
79        }
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
93/// System to handle settings value changes and update the active screen
94pub 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            // Update all active settings screens (there should only be one)
106            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
136/// System to render settings UI
137pub 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    // Only render the first (most recent) settings screen
156    if let Some((entity, mut settings)) = query.iter_mut().next() {
157        // Check for escape key dismissal
158        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        // Destructure to get separate borrows
165        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
186/// Render main settings content
187fn 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        // Header
206        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        // Main content scrollable area
215        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        // Back button
244        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/// Render mobile-style settings layout
260#[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        // Section header
274        ResponsiveText::new(&section.title, ResponsiveFontSize::Large, theme.secondary)
275            .responsive(responsive)
276            .strong()
277            .ui(ui);
278
279        ui.add_space(responsive.spacing(ResponsiveSpacing::Medium));
280
281        // Section settings
282        for setting in &section.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/// Render desktop-style settings layout
294#[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        // Section header
306        ResponsiveText::new(&section.title, ResponsiveFontSize::Large, theme.secondary)
307            .responsive(responsive)
308            .strong()
309            .ui(ui);
310
311        ui.add_space(responsive.spacing(ResponsiveSpacing::Medium));
312
313        // Grid layout for desktop
314        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 &section.settings {
319                    // Label column
320                    ResponsiveText::new(
321                        &setting.label,
322                        ResponsiveFontSize::Medium,
323                        theme.base_content,
324                    )
325                    .responsive(responsive)
326                    .ui(ui);
327
328                    // Control column
329                    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
345/// Render mobile setting item (vertical layout)
346fn 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
370/// Render desktop setting control (horizontal layout)
371fn 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
382/// Render individual setting control
383fn 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                        // Display current value as percentage for volume controls
428                        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                            &current_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                        // Apply max_length if specified
586                        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                // Check if this is a button-like custom setting (like "Configure Players")
603                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                    // For other custom types, just display the text
615                    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
674/// System to handle settings events
675pub 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                // Handle input configuration button
694                if setting_id == "configure_players" {
695                    input_config_events.write(InputConfigurationEvent::Open);
696                    // Spawn the input configuration screen directly
697                    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}