konnektoren_bevy/screens/settings/
config.rs

1#[cfg(feature = "settings")]
2use crate::settings::{Setting, SettingType, SettingValue};
3use bevy::prelude::*;
4
5/// Configuration for screen-based settings (not component-based)
6#[derive(Component, Clone)]
7pub struct SettingsScreenConfig {
8    pub title: String,
9    pub sections: Vec<SettingsSection>,
10    pub allow_dismissal: bool,
11    pub back_button_text: String,
12    pub navigation_enabled: bool,
13    pub mobile_layout: bool,
14}
15
16impl Default for SettingsScreenConfig {
17    fn default() -> Self {
18        Self {
19            title: "Settings".to_string(),
20            sections: vec![],
21            allow_dismissal: true,
22            back_button_text: "Back".to_string(),
23            navigation_enabled: true,
24            mobile_layout: false,
25        }
26    }
27}
28
29impl SettingsScreenConfig {
30    pub fn new(title: impl Into<String>) -> Self {
31        Self {
32            title: title.into(),
33            ..Default::default()
34        }
35    }
36
37    pub fn with_sections(mut self, sections: Vec<SettingsSection>) -> Self {
38        self.sections = sections;
39        self
40    }
41
42    pub fn add_section(mut self, section: SettingsSection) -> Self {
43        self.sections.push(section);
44        self
45    }
46
47    pub fn with_back_button_text(mut self, text: impl Into<String>) -> Self {
48        self.back_button_text = text.into();
49        self
50    }
51
52    pub fn with_navigation(mut self, enabled: bool) -> Self {
53        self.navigation_enabled = enabled;
54        self
55    }
56
57    pub fn mobile_layout(mut self, mobile: bool) -> Self {
58        self.mobile_layout = mobile;
59        self
60    }
61
62    pub fn no_dismissal(mut self) -> Self {
63        self.allow_dismissal = false;
64        self
65    }
66}
67
68/// A section in the settings screen
69#[derive(Clone)]
70pub struct SettingsSection {
71    pub title: String,
72    pub settings: Vec<ScreenSettingsItem>,
73}
74
75impl SettingsSection {
76    pub fn new(title: impl Into<String>) -> Self {
77        Self {
78            title: title.into(),
79            settings: vec![],
80        }
81    }
82
83    pub fn with_settings(mut self, settings: Vec<ScreenSettingsItem>) -> Self {
84        self.settings = settings;
85        self
86    }
87
88    pub fn add_setting(mut self, setting: ScreenSettingsItem) -> Self {
89        self.settings.push(setting);
90        self
91    }
92}
93
94/// Individual setting item for screen-based settings
95/// This wraps the core SettingType with screen-specific data and current values
96#[derive(Clone)]
97pub struct ScreenSettingsItem {
98    pub id: String,
99    pub label: String,
100    #[cfg(feature = "settings")]
101    pub setting_type: SettingType,
102    #[cfg(not(feature = "settings"))]
103    pub setting_type: ScreenOnlySettingType,
104    #[cfg(feature = "settings")]
105    pub current_value: SettingValue,
106    #[cfg(not(feature = "settings"))]
107    pub current_value: ScreenSettingValue,
108    pub navigation_index: Option<usize>,
109}
110
111/// Fallback setting type when core settings feature is disabled
112#[cfg(not(feature = "settings"))]
113#[derive(Clone)]
114pub enum ScreenOnlySettingType {
115    Toggle,
116    FloatRange {
117        min: f32,
118        max: f32,
119        step: f32,
120    },
121    IntRange {
122        min: i32,
123        max: i32,
124        step: i32,
125    },
126    Selection {
127        options: Vec<String>,
128    },
129    Text {
130        max_length: Option<usize>,
131    },
132    Custom {
133        display_fn: fn(&ScreenSettingValue) -> String,
134    },
135}
136
137/// Setting value types for screen-based settings (when core settings not available)
138#[cfg(not(feature = "settings"))]
139#[derive(Debug, Clone)]
140pub enum ScreenSettingValue {
141    Bool(bool),
142    Float(f32),
143    Int(i32),
144    String(String),
145    Selection(usize),
146}
147
148impl ScreenSettingsItem {
149    #[cfg(feature = "settings")]
150    pub fn new(
151        id: impl Into<String>,
152        label: impl Into<String>,
153        setting_type: SettingType,
154        current_value: SettingValue,
155    ) -> Self {
156        Self {
157            id: id.into(),
158            label: label.into(),
159            setting_type,
160            current_value,
161            navigation_index: None,
162        }
163    }
164
165    #[cfg(not(feature = "settings"))]
166    pub fn new(
167        id: impl Into<String>,
168        label: impl Into<String>,
169        setting_type: ScreenOnlySettingType,
170        current_value: ScreenSettingValue,
171    ) -> Self {
172        Self {
173            id: id.into(),
174            label: label.into(),
175            setting_type,
176            current_value,
177            navigation_index: None,
178        }
179    }
180
181    pub fn with_navigation_index(mut self, index: usize) -> Self {
182        self.navigation_index = Some(index);
183        self
184    }
185
186    /// Update the current value
187    #[cfg(feature = "settings")]
188    pub fn with_value(mut self, value: SettingValue) -> Self {
189        self.current_value = value;
190        self
191    }
192
193    #[cfg(not(feature = "settings"))]
194    pub fn with_value(mut self, value: ScreenSettingValue) -> Self {
195        self.current_value = value;
196        self
197    }
198}
199
200/// Conversion from core Setting to ScreenSettingsItem
201#[cfg(feature = "settings")]
202impl From<&Setting> for ScreenSettingsItem {
203    fn from(setting: &Setting) -> Self {
204        Self {
205            id: setting.id.clone(),
206            label: setting.label.clone(),
207            setting_type: setting.setting_type.clone(),
208            current_value: setting.value.clone(),
209            navigation_index: setting.tab_index,
210        }
211    }
212}
213
214#[cfg(feature = "settings")]
215impl ScreenSettingsItem {
216    /// Create from a core Setting component
217    pub fn from_setting(setting: &Setting) -> Self {
218        setting.into()
219    }
220
221    /// Helper functions to create common setting types
222    pub fn toggle(id: impl Into<String>, label: impl Into<String>, current_value: bool) -> Self {
223        Self::new(
224            id,
225            label,
226            SettingType::Toggle,
227            SettingValue::Bool(current_value),
228        )
229    }
230
231    pub fn slider(
232        id: impl Into<String>,
233        label: impl Into<String>,
234        current_value: f32,
235        min: f32,
236        max: f32,
237        step: f32,
238    ) -> Self {
239        Self::new(
240            id,
241            label,
242            SettingType::FloatRange { min, max, step },
243            SettingValue::Float(current_value),
244        )
245    }
246
247    pub fn int_slider(
248        id: impl Into<String>,
249        label: impl Into<String>,
250        current_value: i32,
251        min: i32,
252        max: i32,
253        step: i32,
254    ) -> Self {
255        Self::new(
256            id,
257            label,
258            SettingType::IntRange { min, max, step },
259            SettingValue::Int(current_value),
260        )
261    }
262
263    pub fn selection(
264        id: impl Into<String>,
265        label: impl Into<String>,
266        options: Vec<String>,
267        current_index: usize,
268    ) -> Self {
269        Self::new(
270            id,
271            label,
272            SettingType::Selection { options },
273            SettingValue::Selection(current_index),
274        )
275    }
276
277    pub fn text(
278        id: impl Into<String>,
279        label: impl Into<String>,
280        current_text: String,
281        max_length: Option<usize>,
282    ) -> Self {
283        Self::new(
284            id,
285            label,
286            SettingType::Text { max_length },
287            SettingValue::String(current_text),
288        )
289    }
290
291    /// Create a custom setting type - ADD THIS METHOD
292    pub fn custom(
293        id: impl Into<String>,
294        label: impl Into<String>,
295        current_value: SettingValue,
296        setting_type: SettingType,
297    ) -> Self {
298        Self::new(id, label, setting_type, current_value)
299    }
300}
301
302/// Events for settings screen interactions
303#[derive(Event)]
304pub enum SettingsScreenEvent {
305    /// A setting value changed
306    ValueChanged {
307        entity: Entity,
308        setting_id: String,
309        #[cfg(feature = "settings")]
310        value: SettingValue,
311        #[cfg(not(feature = "settings"))]
312        value: ScreenSettingValue,
313    },
314    /// Settings screen dismissed
315    Dismissed { entity: Entity },
316    /// Navigation event
317    Navigate { direction: NavigationDirection },
318}
319
320/// Navigation directions
321#[derive(Debug, Clone)]
322pub enum NavigationDirection {
323    Up,
324    Down,
325    Left,
326    Right,
327    Select,
328}
329
330// Pre-built sections using core types
331impl SettingsSection {
332    /// Create an audio settings section with common audio controls
333    #[cfg(feature = "settings")]
334    pub fn audio_section() -> Self {
335        Self::new("Audio Settings")
336            .add_setting(ScreenSettingsItem::slider(
337                "master_volume",
338                "Master Volume",
339                1.0,
340                0.0,
341                1.0,
342                0.1,
343            ))
344            .add_setting(ScreenSettingsItem::slider(
345                "music_volume",
346                "Music Volume",
347                0.8,
348                0.0,
349                1.0,
350                0.1,
351            ))
352            .add_setting(ScreenSettingsItem::slider(
353                "sfx_volume",
354                "Sound Effects",
355                1.0,
356                0.0,
357                1.0,
358                0.1,
359            ))
360            .add_setting(ScreenSettingsItem::toggle(
361                "audio_enabled",
362                "Enable Audio",
363                true,
364            ))
365    }
366
367    #[cfg(not(feature = "settings"))]
368    pub fn audio_section() -> Self {
369        Self::new("Audio Settings")
370            .add_setting(ScreenSettingsItem::new(
371                "master_volume",
372                "Master Volume",
373                ScreenOnlySettingType::FloatRange {
374                    min: 0.0,
375                    max: 1.0,
376                    step: 0.1,
377                },
378                ScreenSettingValue::Float(1.0),
379            ))
380            .add_setting(ScreenSettingsItem::new(
381                "audio_enabled",
382                "Enable Audio",
383                ScreenOnlySettingType::Toggle,
384                ScreenSettingValue::Bool(true),
385            ))
386    }
387
388    #[cfg(feature = "input")]
389    pub fn input_section() -> Self {
390        Self::new("Input Settings").add_setting(ScreenSettingsItem::custom(
391            "configure_players",
392            "Configure Players",
393            #[cfg(feature = "settings")]
394            SettingValue::String("Configure".to_string()),
395            #[cfg(not(feature = "settings"))]
396            ScreenSettingValue::String("Configure".to_string()),
397            #[cfg(feature = "settings")]
398            SettingType::Custom {
399                validator: |_| true,
400                display_fn: |_| "Configure Input Devices →".to_string(),
401            },
402            #[cfg(not(feature = "settings"))]
403            ScreenOnlySettingType::Custom {
404                display_fn: |_| "Configure Input Devices →".to_string(),
405            },
406        ))
407    }
408
409    #[cfg(feature = "settings")]
410    pub fn graphics_section() -> Self {
411        Self::new("Graphics Settings")
412            .add_setting(ScreenSettingsItem::selection(
413                "resolution",
414                "Resolution",
415                vec![
416                    "1920x1080".to_string(),
417                    "1680x1050".to_string(),
418                    "1440x900".to_string(),
419                    "1280x720".to_string(),
420                ],
421                0,
422            ))
423            .add_setting(ScreenSettingsItem::toggle(
424                "fullscreen",
425                "Fullscreen",
426                false,
427            ))
428            .add_setting(ScreenSettingsItem::toggle("vsync", "V-Sync", true))
429    }
430
431    #[cfg(feature = "settings")]
432    pub fn gameplay_section() -> Self {
433        Self::new("Gameplay Settings")
434            .add_setting(ScreenSettingsItem::selection(
435                "difficulty",
436                "Difficulty",
437                vec![
438                    "Easy".to_string(),
439                    "Normal".to_string(),
440                    "Hard".to_string(),
441                    "Expert".to_string(),
442                ],
443                1,
444            ))
445            .add_setting(ScreenSettingsItem::toggle("auto_save", "Auto Save", true))
446    }
447
448    /// Create from component-based settings
449    #[cfg(feature = "settings")]
450    pub fn from_settings(title: impl Into<String>, settings: &[&Setting]) -> Self {
451        let mut section = Self::new(title);
452        for setting in settings {
453            section = section.add_setting(ScreenSettingsItem::from_setting(setting));
454        }
455        section
456    }
457}
458
459// Pre-built configurations
460impl SettingsScreenConfig {
461    /// Create a complete game settings configuration with common sections
462    #[cfg(all(feature = "settings", feature = "input"))]
463    pub fn game_settings_with_input(title: impl Into<String>) -> Self {
464        Self::new(title)
465            .add_section(SettingsSection::audio_section())
466            .add_section(SettingsSection::graphics_section())
467            .add_section(SettingsSection::input_section())
468            .add_section(SettingsSection::gameplay_section())
469    }
470
471    /// Create a complete game settings configuration with common sections
472    #[cfg(feature = "settings")]
473    pub fn game_settings(title: impl Into<String>) -> Self {
474        Self::new(title)
475            .add_section(SettingsSection::audio_section())
476            .add_section(SettingsSection::graphics_section())
477            .add_section(SettingsSection::gameplay_section())
478    }
479
480    /// Create minimal audio-only settings
481    pub fn audio_only(title: impl Into<String>) -> Self {
482        Self::new(title).add_section(SettingsSection::audio_section())
483    }
484
485    /// Create settings focused on educational games
486    #[cfg(feature = "settings")]
487    pub fn educational_game(title: impl Into<String>) -> Self {
488        let learning_section = SettingsSection::new("Learning Settings")
489            .add_setting(ScreenSettingsItem::toggle(
490                "hints_enabled",
491                "Show Hints",
492                true,
493            ))
494            .add_setting(ScreenSettingsItem::selection(
495                "feedback_level",
496                "Feedback Level",
497                vec![
498                    "Minimal".to_string(),
499                    "Standard".to_string(),
500                    "Detailed".to_string(),
501                ],
502                1,
503            ))
504            .add_setting(ScreenSettingsItem::toggle(
505                "progress_tracking",
506                "Track Progress",
507                true,
508            ));
509
510        Self::new(title)
511            .add_section(SettingsSection::audio_section())
512            .add_section(learning_section)
513            .add_section(SettingsSection::gameplay_section())
514    }
515
516    /// Create from component-based settings grouped by category
517    #[cfg(feature = "settings")]
518    pub fn from_component_settings(title: impl Into<String>, settings: &[&Setting]) -> Self {
519        use std::collections::HashMap;
520
521        let mut categories: HashMap<String, Vec<&Setting>> = HashMap::new();
522
523        for setting in settings {
524            let category = setting
525                .category
526                .clone()
527                .unwrap_or_else(|| "General".to_string());
528            categories.entry(category).or_default().push(setting);
529        }
530
531        let mut config = Self::new(title);
532        for (category_name, category_settings) in categories {
533            let section = SettingsSection::from_settings(category_name, &category_settings);
534            config = config.add_section(section);
535        }
536
537        config
538    }
539}