cadmus_core/view/settings_editor/
setting_value.rs

1use super::super::action_label::ActionLabel;
2use super::super::{Align, Bus, Event, Hub, Id, RenderQueue, View, ViewId, ID_FEEDER};
3use super::kinds::{SettingIdentity, SettingKind, WidgetKind};
4use crate::context::Context;
5use crate::framebuffer::{Framebuffer, UpdateMode};
6use crate::geom::Rectangle;
7use crate::settings::Settings;
8use crate::view::common::locate_by_id;
9use crate::view::file_chooser::{FileChooser, SelectionMode};
10use crate::view::toggle::Toggle;
11use crate::view::{EntryKind, RenderData};
12
13/// Re-exported for use in `ToggleEvent::Setting` and `CategoryEditor`.
14pub use super::kinds::ToggleSettings;
15
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub enum SettingsEvent {
18    /// Updates a SettingValue view by its identity with a new value.
19    ///
20    /// Each SettingValue checks if the identity matches its own, and updates
21    /// itself if there is a match. This allows targeted updates without needing
22    /// to know the specific view ID.
23    UpdateValue {
24        /// The identity of the SettingValue to update (matches against self.identity)
25        kind: SettingIdentity,
26        /// The new value to display
27        value: String,
28    },
29}
30
31/// Represents a single setting value display in the settings UI.
32///
33/// This struct manages the display and interaction of a setting value and its
34/// associated UI widget (an [`ActionLabel`], a sub-menu label, or a [`Toggle`]).
35/// It acts as a View that handles events via the [`SettingKind`] trait, updating the
36/// displayed text and sub-menu checked state when the underlying setting changes.
37pub struct SettingValue {
38    /// Unique identifier for this setting value view
39    id: Id,
40    /// The rectangular area occupied by this view
41    rect: Rectangle,
42    /// Child views — a single ActionLabel or Toggle widget, plus any active NamedInput overlay
43    children: Vec<Box<dyn View>>,
44    /// Retained so that SubMenu entries can be rebuilt with updated checked
45    /// state each time UpdateValue is received, and to provide identity for
46    /// routing [`SettingsEvent::UpdateValue`] without a separate field.
47    kind: Box<dyn SettingKind>,
48    /// Current SubMenu entries, exposed for test inspection.
49    #[cfg(test)]
50    pub entries: Vec<EntryKind>,
51    #[cfg(not(test))]
52    entries: Vec<EntryKind>,
53    /// Tracks the ViewId of an active NamedInput child, if one is open.
54    active_input: Option<ViewId>,
55    /// Whether a FileChooser overlay is currently open as a child.
56    active_file_chooser: bool,
57    /// Keeps the temporary directory alive for the duration of the FileChooser session.
58    /// Without this, the directory would be deleted before FileChooser can use it.
59    #[cfg(test)]
60    _temp_dir: Option<tempfile::TempDir>,
61}
62
63impl std::fmt::Debug for SettingValue {
64    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65        f.debug_struct("SettingValue")
66            .field("id", &self.id)
67            .field("identity", &self.kind.identity())
68            .field("rect", &self.rect)
69            .finish_non_exhaustive()
70    }
71}
72
73impl SettingValue {
74    /// Creates a new `SettingValue` for the given `kind`.
75    ///
76    /// `kind` accepts any owned value that implements [`SettingKind`], including references
77    /// (`&T`), boxes (`Box<T>`), and concrete types directly. The value is erased into a
78    /// `Box<dyn SettingKind>` internally, so the caller does not need to box it beforehand.
79    pub fn new(
80        kind: impl SettingKind + 'static,
81        rect: Rectangle,
82        settings: &Settings,
83        fonts: &mut crate::font::Fonts,
84    ) -> SettingValue {
85        let kind: Box<dyn SettingKind> = Box::new(kind);
86        let data = kind.fetch(settings);
87
88        let entries = if let WidgetKind::SubMenu(ref e) = data.widget {
89            e.clone()
90        } else {
91            Vec::new()
92        };
93
94        let mut setting_value = SettingValue {
95            id: ID_FEEDER.next(),
96            rect,
97            children: vec![],
98            kind,
99            entries,
100            active_input: None,
101            active_file_chooser: false,
102            #[cfg(test)]
103            _temp_dir: None,
104        };
105
106        setting_value.children =
107            vec![setting_value.build_child_view(data.value, data.widget, fonts)];
108
109        setting_value
110    }
111
112    fn build_child_view(
113        &self,
114        value: String,
115        widget: WidgetKind,
116        fonts: &mut crate::font::Fonts,
117    ) -> Box<dyn View> {
118        match widget {
119            WidgetKind::Toggle {
120                left_label,
121                right_label,
122                enabled,
123                tap_event,
124            } => Box::new(Toggle::new(
125                self.rect,
126                &left_label,
127                &right_label,
128                enabled,
129                tap_event,
130                fonts,
131                Align::Right(10),
132            )),
133            WidgetKind::ActionLabel(tap_event) => Box::new(
134                ActionLabel::new(self.rect, value, Align::Right(10)).event(Some(tap_event)),
135            ),
136            WidgetKind::SubMenu(entries) => {
137                let event = Some(Event::SubMenu(self.rect, entries));
138                Box::new(ActionLabel::new(self.rect, value, Align::Right(10)).event(event))
139            }
140        }
141    }
142
143    pub fn update(&mut self, value: String, settings: &Settings, rq: &mut RenderQueue) {
144        if let Some(action_label) = self.children[0].downcast_mut::<ActionLabel>() {
145            action_label.update(&value, rq);
146
147            if let WidgetKind::SubMenu(entries) = self.kind.fetch(settings).widget {
148                self.entries = entries.clone();
149                action_label.set_event(Some(Event::SubMenu(self.rect, entries)));
150            }
151        }
152    }
153
154    pub fn value(&self) -> String {
155        if let Some(action_label) = self.children[0].downcast_ref::<ActionLabel>() {
156            action_label.value()
157        } else {
158            String::new()
159        }
160    }
161}
162
163impl View for SettingValue {
164    /// Handles events in three passes.
165    ///
166    /// 1. Delegates to [`SettingKind::handle`] for direct mutation events such as
167    ///    submenu selections and toggle taps. When the kind handles the event it
168    ///    returns the updated display string, which is applied immediately.
169    ///
170    /// 2. For [`InputSettingKind`](crate::view::settings_editor::InputSettingKind) settings, opens a [`NamedInput`] overlay on the
171    ///    matching [`EntryId`](crate::view::EntryId) tap, applies the submitted text on [`Event::Submit`],
172    ///    and closes the overlay on [`Event::Close`] or after submission.
173    ///
174    /// 3. Falls back to [`SettingsEvent::UpdateValue`] routing so that
175    ///    `LibraryEditor` and other callers can push targeted display updates
176    ///    without going through the event bus.
177    ///
178    /// [`NamedInput`]: crate::view::named_input::NamedInput
179    #[cfg_attr(feature = "otel", tracing::instrument(skip(self, hub, bus, rq, context), fields(event = ?evt), ret(level=tracing::Level::TRACE)))]
180    fn handle_event(
181        &mut self,
182        evt: &Event,
183        hub: &Hub,
184        bus: &mut Bus,
185        rq: &mut RenderQueue,
186        context: &mut Context,
187    ) -> bool {
188        if let Event::Select(ref entry_id) = evt {
189            if Some(entry_id) == self.kind.file_chooser_entry_id().as_ref()
190                && !self.active_file_chooser
191            {
192                #[cfg(not(test))]
193                let initial_path = std::path::PathBuf::from("/mnt/onboard");
194                #[cfg(test)]
195                let initial_path = {
196                    let temp_dir = tempfile::tempdir().expect("failed to create temp dir for test");
197                    let path = temp_dir.path().to_path_buf();
198                    self._temp_dir = Some(temp_dir);
199                    path
200                };
201
202                let file_chooser = FileChooser::new(
203                    rect!(
204                        0,
205                        0,
206                        context.display.dims.0 as i32,
207                        context.display.dims.1 as i32
208                    ),
209                    initial_path,
210                    SelectionMode::File,
211                    hub,
212                    rq,
213                    context,
214                );
215                rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
216                self.children.push(Box::new(file_chooser));
217                self.active_file_chooser = true;
218                return true;
219            }
220        }
221
222        if let Event::FileChooserClosed(_) = evt {
223            if self.active_file_chooser {
224                if let Some(display) = self.kind.handle(evt, &mut context.settings, bus) {
225                    self.update(display, &context.settings, rq);
226                }
227                return false;
228            }
229            return false;
230        }
231
232        if let Event::Close(ViewId::FileChooser) = evt {
233            if self.active_file_chooser {
234                if let Some(idx) = locate_by_id(self, ViewId::FileChooser) {
235                    self.children.remove(idx);
236                }
237                self.active_file_chooser = false;
238                #[cfg(test)]
239                {
240                    self._temp_dir = None;
241                }
242                rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
243                return false;
244            }
245        }
246
247        if let Some(display) = self.kind.handle(evt, &mut context.settings, bus) {
248            self.update(display, &context.settings, rq);
249            bus.push_back(Event::Close(ViewId::SettingsValueMenu));
250            return true;
251        }
252
253        if let Some(input_kind) = self.kind.as_input_kind() {
254            let view_id = input_kind.submit_view_id();
255            let open_entry = input_kind.open_entry_id();
256
257            if let Event::Select(ref id) = evt {
258                if *id == open_entry && self.active_input.is_none() {
259                    bus.push_back(Event::OpenNamedInput {
260                        view_id,
261                        label: input_kind.input_label(),
262                        max_chars: input_kind.input_max_chars(),
263                        initial_text: input_kind.current_text(&context.settings),
264                    });
265                    self.active_input = Some(view_id);
266                    return true;
267                }
268            }
269
270            if let Event::Submit(submitted_id, ref text) = evt {
271                if Some(*submitted_id) == self.active_input {
272                    let display = self
273                        .kind
274                        .as_input_kind()
275                        .unwrap()
276                        .apply_text(text, &mut context.settings);
277                    self.active_input = None;
278                    self.update(display, &context.settings, rq);
279                    return true;
280                }
281            }
282        }
283
284        if let Event::Settings(SettingsEvent::UpdateValue { kind, value }) = evt {
285            if self.kind.identity() == *kind {
286                self.update(value.clone(), &context.settings, rq);
287                return true;
288            }
289        }
290
291        false
292    }
293
294    #[cfg_attr(feature = "otel", tracing::instrument(skip(self, _fb, _fonts), fields(rect = ?_rect)))]
295    fn render(&self, _fb: &mut dyn Framebuffer, _rect: Rectangle, _fonts: &mut crate::font::Fonts) {
296    }
297
298    fn rect(&self) -> &Rectangle {
299        &self.rect
300    }
301
302    fn rect_mut(&mut self) -> &mut Rectangle {
303        &mut self.rect
304    }
305
306    fn children(&self) -> &Vec<Box<dyn View>> {
307        &self.children
308    }
309
310    fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
311        &mut self.children
312    }
313
314    fn id(&self) -> Id {
315        self.id
316    }
317}
318
319#[cfg(test)]
320mod tests {
321    use super::*;
322    use crate::context::test_helpers::create_test_context;
323    use crate::gesture::GestureEvent;
324    use crate::settings::Settings;
325    use crate::view::settings_editor::kinds::general::{
326        AutoPowerOff, AutoSuspend, KeyboardLayout, SettingsRetention,
327    };
328    use crate::view::settings_editor::kinds::intermission::{
329        IntermissionPowerOff, IntermissionShare, IntermissionSuspend,
330    };
331    use crate::view::settings_editor::kinds::library::{LibraryInfo, LibraryName, LibraryPath};
332    use crate::view::settings_editor::kinds::telemetry::LogLevel;
333    use crate::view::RenderQueue;
334    use std::collections::VecDeque;
335    use std::path::PathBuf;
336    use std::sync::mpsc::channel;
337
338    #[test]
339    fn test_file_chooser_closed_updates_all_intermission_values() {
340        let mut context = create_test_context();
341        let settings = Settings::default();
342        let rect = rect![0, 0, 200, 50];
343
344        let mut suspend_value =
345            SettingValue::new(&IntermissionSuspend, rect, &settings, &mut context.fonts);
346        let mut power_off_value =
347            SettingValue::new(&IntermissionPowerOff, rect, &settings, &mut context.fonts);
348        let mut share_value =
349            SettingValue::new(&IntermissionShare, rect, &settings, &mut context.fonts);
350
351        let (hub, _receiver) = channel();
352        let mut bus = VecDeque::new();
353        let mut rq = RenderQueue::new();
354
355        let initial_suspend = suspend_value.value().clone();
356        let initial_power_off = power_off_value.value().clone();
357        let initial_share = share_value.value().clone();
358
359        let event = Event::FileChooserClosed(None);
360
361        suspend_value.handle_event(&event, &hub, &mut bus, &mut rq, &mut context);
362        power_off_value.handle_event(&event, &hub, &mut bus, &mut rq, &mut context);
363        share_value.handle_event(&event, &hub, &mut bus, &mut rq, &mut context);
364
365        assert_eq!(suspend_value.value(), initial_suspend);
366        assert_eq!(power_off_value.value(), initial_power_off);
367        assert_eq!(share_value.value(), initial_share);
368    }
369
370    #[test]
371    fn test_intermission_values_update_via_update_value_event() {
372        let mut context = create_test_context();
373        let settings = Settings::default();
374        let rect = rect![0, 0, 200, 50];
375
376        let mut suspend_value =
377            SettingValue::new(&IntermissionSuspend, rect, &settings, &mut context.fonts);
378        let mut power_off_value =
379            SettingValue::new(&IntermissionPowerOff, rect, &settings, &mut context.fonts);
380        let mut share_value =
381            SettingValue::new(&IntermissionShare, rect, &settings, &mut context.fonts);
382
383        let (hub, _receiver) = channel();
384        let mut bus = VecDeque::new();
385        let mut rq = RenderQueue::new();
386
387        let handled_suspend = suspend_value.handle_event(
388            &Event::Settings(SettingsEvent::UpdateValue {
389                kind: SettingIdentity::IntermissionSuspend,
390                value: "suspend_image.png".to_string(),
391            }),
392            &hub,
393            &mut bus,
394            &mut rq,
395            &mut context,
396        );
397        let handled_power_off = power_off_value.handle_event(
398            &Event::Settings(SettingsEvent::UpdateValue {
399                kind: SettingIdentity::IntermissionPowerOff,
400                value: "poweroff_image.png".to_string(),
401            }),
402            &hub,
403            &mut bus,
404            &mut rq,
405            &mut context,
406        );
407        let handled_share = share_value.handle_event(
408            &Event::Settings(SettingsEvent::UpdateValue {
409                kind: SettingIdentity::IntermissionShare,
410                value: "share_image.png".to_string(),
411            }),
412            &hub,
413            &mut bus,
414            &mut rq,
415            &mut context,
416        );
417
418        assert!(handled_suspend);
419        assert!(handled_power_off);
420        assert!(handled_share);
421        assert_eq!(suspend_value.value(), "suspend_image.png");
422        assert_eq!(power_off_value.value(), "poweroff_image.png");
423        assert_eq!(share_value.value(), "share_image.png");
424    }
425
426    #[test]
427    fn test_keyboard_layout_select_updates_value() {
428        let mut context = create_test_context();
429        let settings = Settings {
430            keyboard_layout: "English".to_string(),
431            ..Default::default()
432        };
433        let rect = rect![0, 0, 200, 50];
434
435        let mut value = SettingValue::new(&KeyboardLayout, rect, &settings, &mut context.fonts);
436        let mut rq = RenderQueue::new();
437
438        let update_event = Event::Settings(SettingsEvent::UpdateValue {
439            kind: SettingIdentity::KeyboardLayout,
440            value: "French".to_string(),
441        });
442        let (hub, _receiver) = channel();
443        let mut bus = VecDeque::new();
444        value.handle_event(&update_event, &hub, &mut bus, &mut rq, &mut context);
445
446        assert_eq!(value.value(), "French");
447        assert!(!rq.is_empty());
448    }
449
450    #[test]
451    fn test_auto_suspend_submit_updates_value() {
452        let mut context = create_test_context();
453        let settings = Settings::default();
454        let rect = rect![0, 0, 200, 50];
455
456        let mut value = SettingValue::new(&AutoSuspend, rect, &settings, &mut context.fonts);
457        let mut rq = RenderQueue::new();
458        let (hub, _receiver) = channel();
459        let mut bus = VecDeque::new();
460
461        let update_event = Event::Settings(SettingsEvent::UpdateValue {
462            kind: SettingIdentity::AutoSuspend,
463            value: "15.0".to_string(),
464        });
465        value.handle_event(&update_event, &hub, &mut bus, &mut rq, &mut context);
466
467        assert_eq!(value.value(), "15.0");
468        assert!(!rq.is_empty());
469    }
470
471    #[test]
472    fn test_auto_power_off_submit_updates_value() {
473        let mut context = create_test_context();
474        let settings = Settings::default();
475        let rect = rect![0, 0, 200, 50];
476
477        let mut value = SettingValue::new(&AutoPowerOff, rect, &settings, &mut context.fonts);
478        let mut rq = RenderQueue::new();
479        let (hub, _receiver) = channel();
480        let mut bus = VecDeque::new();
481
482        let update_event = Event::Settings(SettingsEvent::UpdateValue {
483            kind: SettingIdentity::AutoPowerOff,
484            value: "7.0".to_string(),
485        });
486        value.handle_event(&update_event, &hub, &mut bus, &mut rq, &mut context);
487
488        assert_eq!(value.value(), "7.0");
489        assert!(!rq.is_empty());
490    }
491
492    #[test]
493    fn test_library_name_submit_updates_value() {
494        use crate::settings::LibrarySettings;
495        let mut settings = Settings::default();
496        settings.libraries.push(LibrarySettings {
497            name: "Old Name".to_string(),
498            path: PathBuf::from("/tmp"),
499            ..Default::default()
500        });
501        let rect = rect![0, 0, 200, 50];
502
503        let mut context = create_test_context();
504        let mut value = SettingValue::new(LibraryName(0), rect, &settings, &mut context.fonts);
505        let mut rq = RenderQueue::new();
506        let (hub, _receiver) = channel();
507        let mut bus = VecDeque::new();
508
509        let update_event = Event::Settings(SettingsEvent::UpdateValue {
510            kind: SettingIdentity::LibraryName(0),
511            value: "New Name".to_string(),
512        });
513        value.handle_event(&update_event, &hub, &mut bus, &mut rq, &mut context);
514
515        assert_eq!(value.value(), "New Name");
516        assert!(!rq.is_empty());
517    }
518
519    #[test]
520    fn test_library_path_file_chooser_closed_updates_value() {
521        use crate::settings::LibrarySettings;
522        let mut settings = Settings::default();
523        settings.libraries.push(LibrarySettings {
524            name: "Test Library".to_string(),
525            path: PathBuf::from("/tmp"),
526            ..Default::default()
527        });
528        let rect = rect![0, 0, 200, 50];
529
530        let mut context = create_test_context();
531        let mut value = SettingValue::new(LibraryPath(0), rect, &settings, &mut context.fonts);
532        let mut rq = RenderQueue::new();
533        let (hub, _receiver) = channel();
534        let mut bus = VecDeque::new();
535
536        let new_path = PathBuf::from("/mnt/onboard/new_library");
537        let update_event = Event::Settings(SettingsEvent::UpdateValue {
538            kind: SettingIdentity::LibraryPath(0),
539            value: new_path.display().to_string(),
540        });
541        value.handle_event(&update_event, &hub, &mut bus, &mut rq, &mut context);
542
543        assert_eq!(value.value(), new_path.display().to_string());
544        assert!(!rq.is_empty());
545    }
546
547    #[test]
548    fn test_tap_gesture_on_library_info_emits_edit_event() {
549        use crate::settings::LibrarySettings;
550        let mut settings = Settings::default();
551        settings.libraries.push(LibrarySettings {
552            name: "Test Library".to_string(),
553            path: PathBuf::from("/tmp"),
554            ..Default::default()
555        });
556        let rect = rect![0, 0, 200, 50];
557
558        let mut context = create_test_context();
559        let value = SettingValue::new(LibraryInfo(0), rect, &settings, &mut context.fonts);
560        let (hub, _receiver) = channel();
561        let mut bus = VecDeque::new();
562        let mut rq = RenderQueue::new();
563
564        let point = crate::geom::Point::new(100, 25);
565        let event = Event::Gesture(GestureEvent::Tap(point));
566
567        let mut boxed: Box<dyn View> = Box::new(value);
568        crate::view::handle_event(
569            boxed.as_mut(),
570            &event,
571            &hub,
572            &mut bus,
573            &mut rq,
574            &mut context,
575        );
576
577        assert_eq!(bus.len(), 1);
578        if let Some(Event::EditLibrary(index)) = bus.pop_front() {
579            assert_eq!(index, 0);
580        } else {
581            panic!("Expected EditLibrary event");
582        }
583    }
584
585    #[test]
586    fn test_update_value_event_updates_library_name_display() {
587        use crate::settings::LibrarySettings;
588        let mut context = create_test_context();
589        context.settings.libraries.clear();
590        context.settings.libraries.push(LibrarySettings {
591            name: "Old Name".to_string(),
592            path: PathBuf::from("/tmp"),
593            ..Default::default()
594        });
595        let rect = rect![0, 0, 200, 50];
596
597        let mut value =
598            SettingValue::new(LibraryName(0), rect, &context.settings, &mut context.fonts);
599        let (hub, _receiver) = channel();
600        let mut bus = VecDeque::new();
601        let mut rq = RenderQueue::new();
602
603        assert_eq!(value.value(), "Old Name");
604
605        let update_event = Event::Settings(SettingsEvent::UpdateValue {
606            kind: SettingIdentity::LibraryName(0),
607            value: "New Name".to_string(),
608        });
609        let handled = value.handle_event(&update_event, &hub, &mut bus, &mut rq, &mut context);
610
611        assert!(
612            handled,
613            "UpdateValue event should be handled when kind matches"
614        );
615        assert_eq!(value.value(), "New Name");
616    }
617
618    #[test]
619    fn test_update_value_event_updates_library_path_display() {
620        use crate::settings::LibrarySettings;
621        let mut context = create_test_context();
622        context.settings.libraries.clear();
623        context.settings.libraries.push(LibrarySettings {
624            name: "Test Library".to_string(),
625            path: PathBuf::from("/old/path"),
626            ..Default::default()
627        });
628        let rect = rect![0, 0, 200, 50];
629
630        let mut value =
631            SettingValue::new(LibraryPath(0), rect, &context.settings, &mut context.fonts);
632        let (hub, _receiver) = channel();
633        let mut bus = VecDeque::new();
634        let mut rq = RenderQueue::new();
635
636        assert_eq!(value.value(), "/old/path");
637
638        let update_event = Event::Settings(SettingsEvent::UpdateValue {
639            kind: SettingIdentity::LibraryPath(0),
640            value: "/new/path".to_string(),
641        });
642        let handled = value.handle_event(&update_event, &hub, &mut bus, &mut rq, &mut context);
643
644        assert!(
645            handled,
646            "UpdateValue event should be handled when kind matches"
647        );
648        assert_eq!(value.value(), "/new/path");
649    }
650
651    #[test]
652    fn test_update_value_event_ignores_wrong_kind() {
653        use crate::settings::LibrarySettings;
654        let mut context = create_test_context();
655        context.settings.libraries.clear();
656        context.settings.libraries.push(LibrarySettings {
657            name: "Test Library".to_string(),
658            path: PathBuf::from("/tmp"),
659            ..Default::default()
660        });
661        let rect = rect![0, 0, 200, 50];
662
663        let mut value =
664            SettingValue::new(LibraryName(0), rect, &context.settings, &mut context.fonts);
665        let (hub, _receiver) = channel();
666        let mut bus = VecDeque::new();
667        let mut rq = RenderQueue::new();
668
669        assert_eq!(value.value(), "Test Library");
670
671        let update_event = Event::Settings(SettingsEvent::UpdateValue {
672            kind: SettingIdentity::LibraryPath(0),
673            value: "Some Path".to_string(),
674        });
675        let handled = value.handle_event(&update_event, &hub, &mut bus, &mut rq, &mut context);
676
677        assert!(
678            !handled,
679            "UpdateValue event should not be handled when kind does not match"
680        );
681        assert_eq!(
682            value.value(),
683            "Test Library",
684            "Value should not change when kind mismatches"
685        );
686    }
687
688    #[test]
689    fn test_update_value_event_ignores_wrong_index() {
690        use crate::settings::LibrarySettings;
691        let mut context = create_test_context();
692        context.settings.libraries.clear();
693        context.settings.libraries.push(LibrarySettings {
694            name: "Library 0".to_string(),
695            path: PathBuf::from("/path0"),
696            ..Default::default()
697        });
698        context.settings.libraries.push(LibrarySettings {
699            name: "Library 1".to_string(),
700            path: PathBuf::from("/path1"),
701            ..Default::default()
702        });
703        let rect = rect![0, 0, 200, 50];
704
705        let mut value =
706            SettingValue::new(LibraryName(0), rect, &context.settings, &mut context.fonts);
707        let (hub, _receiver) = channel();
708        let mut bus = VecDeque::new();
709        let mut rq = RenderQueue::new();
710
711        assert_eq!(value.value(), "Library 0");
712
713        let update_event = Event::Settings(SettingsEvent::UpdateValue {
714            kind: SettingIdentity::LibraryName(1),
715            value: "Updated Library 1".to_string(),
716        });
717        let handled = value.handle_event(&update_event, &hub, &mut bus, &mut rq, &mut context);
718
719        assert!(
720            !handled,
721            "UpdateValue event should not be handled when index does not match"
722        );
723        assert_eq!(
724            value.value(),
725            "Library 0",
726            "Value should not change when index mismatches"
727        );
728    }
729
730    #[test]
731    fn test_update_value_event_updates_auto_suspend() {
732        let rect = rect![0, 0, 200, 50];
733        let mut context = create_test_context();
734        let mut value =
735            SettingValue::new(&AutoSuspend, rect, &context.settings, &mut context.fonts);
736        let (hub, _receiver) = channel();
737        let mut bus = VecDeque::new();
738        let mut rq = RenderQueue::new();
739
740        assert_eq!(value.value(), "30.0");
741
742        let update_event = Event::Settings(SettingsEvent::UpdateValue {
743            kind: SettingIdentity::AutoSuspend,
744            value: "60.0".to_string(),
745        });
746        let handled = value.handle_event(&update_event, &hub, &mut bus, &mut rq, &mut context);
747
748        assert!(
749            handled,
750            "UpdateValue event should be handled when kind matches"
751        );
752        assert_eq!(value.value(), "60.0");
753    }
754
755    #[test]
756    fn test_update_value_event_updates_auto_power_off() {
757        let rect = rect![0, 0, 200, 50];
758        let mut context = create_test_context();
759        let mut value =
760            SettingValue::new(&AutoPowerOff, rect, &context.settings, &mut context.fonts);
761        let (hub, _receiver) = channel();
762        let mut bus = VecDeque::new();
763        let mut rq = RenderQueue::new();
764
765        assert_eq!(value.value(), "3.0");
766
767        let update_event = Event::Settings(SettingsEvent::UpdateValue {
768            kind: SettingIdentity::AutoPowerOff,
769            value: "60.0".to_string(),
770        });
771        let handled = value.handle_event(&update_event, &hub, &mut bus, &mut rq, &mut context);
772
773        assert!(
774            handled,
775            "UpdateValue event should be handled when kind matches"
776        );
777        assert_eq!(value.value(), "60.0");
778    }
779
780    #[test]
781    fn test_update_value_event_updates_settings_retention() {
782        let rect = rect![0, 0, 200, 50];
783        let mut context = create_test_context();
784        let mut value = SettingValue::new(
785            &SettingsRetention,
786            rect,
787            &context.settings,
788            &mut context.fonts,
789        );
790        let (hub, _receiver) = channel();
791        let mut bus = VecDeque::new();
792        let mut rq = RenderQueue::new();
793
794        assert_eq!(value.value(), "3");
795
796        let update_event = Event::Settings(SettingsEvent::UpdateValue {
797            kind: SettingIdentity::SettingsRetention,
798            value: "5".to_string(),
799        });
800        let handled = value.handle_event(&update_event, &hub, &mut bus, &mut rq, &mut context);
801
802        assert!(
803            handled,
804            "UpdateValue event should be handled when kind matches"
805        );
806        assert_eq!(value.value(), "5");
807    }
808
809    #[test]
810    fn test_update_value_event_regenerates_log_level_radio_buttons() {
811        let rect = rect![0, 0, 200, 50];
812        let mut context = create_test_context();
813        context.settings.logging.level = "INFO".to_string();
814
815        let mut value = SettingValue::new(&LogLevel, rect, &context.settings, &mut context.fonts);
816        let (hub, _receiver) = channel();
817        let mut bus = VecDeque::new();
818        let mut rq = RenderQueue::new();
819
820        let initial_entries = value.entries.clone();
821        assert_eq!(initial_entries.len(), 5);
822
823        let info_entry = initial_entries.iter().find(|e| {
824            if let EntryKind::RadioButton(label, _, _) = e {
825                label == "INFO"
826            } else {
827                false
828            }
829        });
830        assert!(
831            matches!(info_entry, Some(EntryKind::RadioButton(_, _, true))),
832            "INFO should be initially checked"
833        );
834
835        context.settings.logging.level = "DEBUG".to_string();
836        let update_event = Event::Settings(SettingsEvent::UpdateValue {
837            kind: SettingIdentity::LogLevel,
838            value: "DEBUG".to_string(),
839        });
840        let handled = value.handle_event(&update_event, &hub, &mut bus, &mut rq, &mut context);
841
842        assert!(
843            handled,
844            "UpdateValue event should be handled when kind matches"
845        );
846        assert_eq!(value.value(), "DEBUG");
847    }
848}