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
13pub use super::kinds::ToggleSettings;
15
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub enum SettingsEvent {
18 UpdateValue {
24 kind: SettingIdentity,
26 value: String,
28 },
29}
30
31pub struct SettingValue {
38 id: Id,
40 rect: Rectangle,
42 children: Vec<Box<dyn View>>,
44 kind: Box<dyn SettingKind>,
48 #[cfg(test)]
50 pub entries: Vec<EntryKind>,
51 #[cfg(not(test))]
52 entries: Vec<EntryKind>,
53 active_input: Option<ViewId>,
55 active_file_chooser: bool,
57 #[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 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 #[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}