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