1use super::super::action_label::ActionLabel;
2use super::super::{Align, Bus, Event, Hub, ID_FEEDER, Id, RenderQueue, View, ViewId};
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(entry_id) = evt
200 && 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 if let Event::FileChooserClosed(_) = evt {
233 if self.active_file_chooser {
234 if let (Some(display), _) = self.kind.handle(evt, &mut context.settings, bus) {
235 self.update(display, &context.settings, rq);
236 }
237 return false;
238 }
239 return false;
240 }
241
242 if let Event::Close(ViewId::FileChooser) = evt {
243 if self.active_file_chooser {
244 if let Some(idx) = locate_by_id(self, ViewId::FileChooser) {
245 self.children.remove(idx);
246 }
247 self.active_file_chooser = false;
248 #[cfg(test)]
249 {
250 self._temp_dir = None;
251 }
252 rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
253 return false;
254 }
255 }
256
257 if let (Some(display), handled) = self.kind.handle(evt, &mut context.settings, bus) {
258 self.update(display, &context.settings, rq);
259 if !self.kind.keep_menu_open() {
260 bus.push_back(Event::Close(ViewId::SettingsValueMenu));
261 }
262 return handled;
263 }
264
265 if let Some(input_kind) = self.kind.as_input_kind() {
266 let view_id = input_kind.submit_view_id();
267 let open_entry = input_kind.open_entry_id();
268
269 if let Event::Select(id) = evt
270 && *id == open_entry
271 && self.active_input.is_none()
272 {
273 bus.push_back(Event::OpenNamedInput {
274 view_id,
275 label: input_kind.input_label(),
276 max_chars: input_kind.input_max_chars(),
277 initial_text: input_kind.current_text(&context.settings),
278 });
279 self.active_input = Some(view_id);
280 return true;
281 }
282
283 if let Event::Submit(submitted_id, text) = evt
284 && Some(*submitted_id) == self.active_input
285 {
286 let display = self
287 .kind
288 .as_input_kind()
289 .unwrap()
290 .apply_text(text, &mut context.settings);
291 self.active_input = None;
292 self.update(display, &context.settings, rq);
293 return true;
294 }
295 }
296
297 if let Event::Settings(SettingsEvent::UpdateValue { kind, value }) = evt {
298 if self.kind.identity() == *kind {
299 self.update(value.clone(), &context.settings, rq);
300 return true;
301 }
302 }
303
304 false
305 }
306
307 #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, _fb, _fonts), fields(rect = ?_rect)))]
308 fn render(&self, _fb: &mut dyn Framebuffer, _rect: Rectangle, _fonts: &mut crate::font::Fonts) {
309 }
310
311 fn rect(&self) -> &Rectangle {
312 &self.rect
313 }
314
315 fn rect_mut(&mut self) -> &mut Rectangle {
316 &mut self.rect
317 }
318
319 fn children(&self) -> &Vec<Box<dyn View>> {
320 &self.children
321 }
322
323 fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
324 &mut self.children
325 }
326
327 fn id(&self) -> Id {
328 self.id
329 }
330}
331
332#[cfg(test)]
333mod tests {
334 use super::*;
335 use crate::context::test_helpers::create_test_context;
336 use crate::gesture::GestureEvent;
337 use crate::settings::Settings;
338 use crate::view::settings_editor::kinds::general::{
339 AutoPowerOff, AutoSuspend, KeyboardLayout, SettingsRetention,
340 };
341 use crate::view::settings_editor::kinds::import::AllowedKindsSetting;
342 use crate::view::settings_editor::kinds::intermission::{
343 IntermissionPowerOff, IntermissionShare, IntermissionSuspend,
344 };
345 use crate::view::settings_editor::kinds::library::{LibraryInfo, LibraryName, LibraryPath};
346 use crate::view::settings_editor::kinds::telemetry::LogLevel;
347 use crate::view::{EntryId, RenderQueue};
348 use std::collections::VecDeque;
349 use std::path::PathBuf;
350 use std::sync::mpsc::channel;
351
352 #[test]
353 fn test_file_chooser_closed_updates_all_intermission_values() {
354 let mut context = create_test_context();
355 let settings = Settings::default();
356 let rect = rect![0, 0, 200, 50];
357
358 let mut suspend_value =
359 SettingValue::new(&IntermissionSuspend, rect, &settings, &mut context.fonts);
360 let mut power_off_value =
361 SettingValue::new(&IntermissionPowerOff, rect, &settings, &mut context.fonts);
362 let mut share_value =
363 SettingValue::new(&IntermissionShare, rect, &settings, &mut context.fonts);
364
365 let (hub, _receiver) = channel();
366 let mut bus = VecDeque::new();
367 let mut rq = RenderQueue::new();
368
369 let initial_suspend = suspend_value.value().clone();
370 let initial_power_off = power_off_value.value().clone();
371 let initial_share = share_value.value().clone();
372
373 let event = Event::FileChooserClosed(None);
374
375 suspend_value.handle_event(&event, &hub, &mut bus, &mut rq, &mut context);
376 power_off_value.handle_event(&event, &hub, &mut bus, &mut rq, &mut context);
377 share_value.handle_event(&event, &hub, &mut bus, &mut rq, &mut context);
378
379 assert_eq!(suspend_value.value(), initial_suspend);
380 assert_eq!(power_off_value.value(), initial_power_off);
381 assert_eq!(share_value.value(), initial_share);
382 }
383
384 #[test]
385 fn test_intermission_values_update_via_update_value_event() {
386 let mut context = create_test_context();
387 let settings = Settings::default();
388 let rect = rect![0, 0, 200, 50];
389
390 let mut suspend_value =
391 SettingValue::new(&IntermissionSuspend, rect, &settings, &mut context.fonts);
392 let mut power_off_value =
393 SettingValue::new(&IntermissionPowerOff, rect, &settings, &mut context.fonts);
394 let mut share_value =
395 SettingValue::new(&IntermissionShare, rect, &settings, &mut context.fonts);
396
397 let (hub, _receiver) = channel();
398 let mut bus = VecDeque::new();
399 let mut rq = RenderQueue::new();
400
401 let handled_suspend = suspend_value.handle_event(
402 &Event::Settings(SettingsEvent::UpdateValue {
403 kind: SettingIdentity::IntermissionSuspend,
404 value: "suspend_image.png".to_string(),
405 }),
406 &hub,
407 &mut bus,
408 &mut rq,
409 &mut context,
410 );
411 let handled_power_off = power_off_value.handle_event(
412 &Event::Settings(SettingsEvent::UpdateValue {
413 kind: SettingIdentity::IntermissionPowerOff,
414 value: "poweroff_image.png".to_string(),
415 }),
416 &hub,
417 &mut bus,
418 &mut rq,
419 &mut context,
420 );
421 let handled_share = share_value.handle_event(
422 &Event::Settings(SettingsEvent::UpdateValue {
423 kind: SettingIdentity::IntermissionShare,
424 value: "share_image.png".to_string(),
425 }),
426 &hub,
427 &mut bus,
428 &mut rq,
429 &mut context,
430 );
431
432 assert!(handled_suspend);
433 assert!(handled_power_off);
434 assert!(handled_share);
435 assert_eq!(suspend_value.value(), "suspend_image.png");
436 assert_eq!(power_off_value.value(), "poweroff_image.png");
437 assert_eq!(share_value.value(), "share_image.png");
438 }
439
440 #[test]
441 fn test_keyboard_layout_select_updates_value() {
442 let mut context = create_test_context();
443 let settings = Settings {
444 keyboard_layout: "English".to_string(),
445 ..Default::default()
446 };
447 let rect = rect![0, 0, 200, 50];
448
449 let mut value = SettingValue::new(&KeyboardLayout, rect, &settings, &mut context.fonts);
450 let mut rq = RenderQueue::new();
451
452 let update_event = Event::Settings(SettingsEvent::UpdateValue {
453 kind: SettingIdentity::KeyboardLayout,
454 value: "French".to_string(),
455 });
456 let (hub, _receiver) = channel();
457 let mut bus = VecDeque::new();
458 value.handle_event(&update_event, &hub, &mut bus, &mut rq, &mut context);
459
460 assert_eq!(value.value(), "French");
461 assert!(!rq.is_empty());
462 }
463
464 #[test]
465 fn test_auto_suspend_submit_updates_value() {
466 let mut context = create_test_context();
467 let settings = Settings::default();
468 let rect = rect![0, 0, 200, 50];
469
470 let mut value = SettingValue::new(&AutoSuspend, rect, &settings, &mut context.fonts);
471 let mut rq = RenderQueue::new();
472 let (hub, _receiver) = channel();
473 let mut bus = VecDeque::new();
474
475 let update_event = Event::Settings(SettingsEvent::UpdateValue {
476 kind: SettingIdentity::AutoSuspend,
477 value: "15.0".to_string(),
478 });
479 value.handle_event(&update_event, &hub, &mut bus, &mut rq, &mut context);
480
481 assert_eq!(value.value(), "15.0");
482 assert!(!rq.is_empty());
483 }
484
485 #[test]
486 fn test_auto_power_off_submit_updates_value() {
487 let mut context = create_test_context();
488 let settings = Settings::default();
489 let rect = rect![0, 0, 200, 50];
490
491 let mut value = SettingValue::new(&AutoPowerOff, rect, &settings, &mut context.fonts);
492 let mut rq = RenderQueue::new();
493 let (hub, _receiver) = channel();
494 let mut bus = VecDeque::new();
495
496 let update_event = Event::Settings(SettingsEvent::UpdateValue {
497 kind: SettingIdentity::AutoPowerOff,
498 value: "7.0".to_string(),
499 });
500 value.handle_event(&update_event, &hub, &mut bus, &mut rq, &mut context);
501
502 assert_eq!(value.value(), "7.0");
503 assert!(!rq.is_empty());
504 }
505
506 #[test]
507 fn test_library_name_submit_updates_value() {
508 use crate::settings::LibrarySettings;
509 let mut settings = Settings::default();
510 settings.libraries.push(LibrarySettings {
511 name: "Old Name".to_string(),
512 path: PathBuf::from("/tmp"),
513 ..Default::default()
514 });
515 let rect = rect![0, 0, 200, 50];
516
517 let mut context = create_test_context();
518 let mut value = SettingValue::new(LibraryName(0), rect, &settings, &mut context.fonts);
519 let mut rq = RenderQueue::new();
520 let (hub, _receiver) = channel();
521 let mut bus = VecDeque::new();
522
523 let update_event = Event::Settings(SettingsEvent::UpdateValue {
524 kind: SettingIdentity::LibraryName(0),
525 value: "New Name".to_string(),
526 });
527 value.handle_event(&update_event, &hub, &mut bus, &mut rq, &mut context);
528
529 assert_eq!(value.value(), "New Name");
530 assert!(!rq.is_empty());
531 }
532
533 #[test]
534 fn test_library_path_file_chooser_closed_updates_value() {
535 use crate::settings::LibrarySettings;
536 let mut settings = Settings::default();
537 settings.libraries.push(LibrarySettings {
538 name: "Test Library".to_string(),
539 path: PathBuf::from("/tmp"),
540 ..Default::default()
541 });
542 let rect = rect![0, 0, 200, 50];
543
544 let mut context = create_test_context();
545 let mut value = SettingValue::new(LibraryPath(0), rect, &settings, &mut context.fonts);
546 let mut rq = RenderQueue::new();
547 let (hub, _receiver) = channel();
548 let mut bus = VecDeque::new();
549
550 let new_path = PathBuf::from("/mnt/onboard/new_library");
551 let update_event = Event::Settings(SettingsEvent::UpdateValue {
552 kind: SettingIdentity::LibraryPath(0),
553 value: new_path.display().to_string(),
554 });
555 value.handle_event(&update_event, &hub, &mut bus, &mut rq, &mut context);
556
557 assert_eq!(value.value(), new_path.display().to_string());
558 assert!(!rq.is_empty());
559 }
560
561 #[test]
562 fn test_tap_gesture_on_library_info_emits_edit_event() {
563 use crate::settings::LibrarySettings;
564 let mut settings = Settings::default();
565 settings.libraries.push(LibrarySettings {
566 name: "Test Library".to_string(),
567 path: PathBuf::from("/tmp"),
568 ..Default::default()
569 });
570 let rect = rect![0, 0, 200, 50];
571
572 let mut context = create_test_context();
573 let value = SettingValue::new(LibraryInfo(0), rect, &settings, &mut context.fonts);
574 let (hub, _receiver) = channel();
575 let mut bus = VecDeque::new();
576 let mut rq = RenderQueue::new();
577
578 let point = crate::geom::Point::new(100, 25);
579 let event = Event::Gesture(GestureEvent::Tap(point));
580
581 let mut boxed: Box<dyn View> = Box::new(value);
582 crate::view::handle_event(
583 boxed.as_mut(),
584 &event,
585 &hub,
586 &mut bus,
587 &mut rq,
588 &mut context,
589 );
590
591 assert_eq!(bus.len(), 1);
592 if let Some(Event::EditLibrary(index)) = bus.pop_front() {
593 assert_eq!(index, 0);
594 } else {
595 panic!("Expected EditLibrary event");
596 }
597 }
598
599 #[test]
600 fn test_update_value_event_updates_library_name_display() {
601 use crate::settings::LibrarySettings;
602 let mut context = create_test_context();
603 context.settings.libraries.clear();
604 context.settings.libraries.push(LibrarySettings {
605 name: "Old Name".to_string(),
606 path: PathBuf::from("/tmp"),
607 ..Default::default()
608 });
609 let rect = rect![0, 0, 200, 50];
610
611 let mut value =
612 SettingValue::new(LibraryName(0), rect, &context.settings, &mut context.fonts);
613 let (hub, _receiver) = channel();
614 let mut bus = VecDeque::new();
615 let mut rq = RenderQueue::new();
616
617 assert_eq!(value.value(), "Old Name");
618
619 let update_event = Event::Settings(SettingsEvent::UpdateValue {
620 kind: SettingIdentity::LibraryName(0),
621 value: "New Name".to_string(),
622 });
623 let handled = value.handle_event(&update_event, &hub, &mut bus, &mut rq, &mut context);
624
625 assert!(
626 handled,
627 "UpdateValue event should be handled when kind matches"
628 );
629 assert_eq!(value.value(), "New Name");
630 }
631
632 #[test]
633 fn test_update_value_event_updates_library_path_display() {
634 use crate::settings::LibrarySettings;
635 let mut context = create_test_context();
636 context.settings.libraries.clear();
637 context.settings.libraries.push(LibrarySettings {
638 name: "Test Library".to_string(),
639 path: PathBuf::from("/old/path"),
640 ..Default::default()
641 });
642 let rect = rect![0, 0, 200, 50];
643
644 let mut value =
645 SettingValue::new(LibraryPath(0), rect, &context.settings, &mut context.fonts);
646 let (hub, _receiver) = channel();
647 let mut bus = VecDeque::new();
648 let mut rq = RenderQueue::new();
649
650 assert_eq!(value.value(), "/old/path");
651
652 let update_event = Event::Settings(SettingsEvent::UpdateValue {
653 kind: SettingIdentity::LibraryPath(0),
654 value: "/new/path".to_string(),
655 });
656 let handled = value.handle_event(&update_event, &hub, &mut bus, &mut rq, &mut context);
657
658 assert!(
659 handled,
660 "UpdateValue event should be handled when kind matches"
661 );
662 assert_eq!(value.value(), "/new/path");
663 }
664
665 #[test]
666 fn test_update_value_event_ignores_wrong_kind() {
667 use crate::settings::LibrarySettings;
668 let mut context = create_test_context();
669 context.settings.libraries.clear();
670 context.settings.libraries.push(LibrarySettings {
671 name: "Test Library".to_string(),
672 path: PathBuf::from("/tmp"),
673 ..Default::default()
674 });
675 let rect = rect![0, 0, 200, 50];
676
677 let mut value =
678 SettingValue::new(LibraryName(0), rect, &context.settings, &mut context.fonts);
679 let (hub, _receiver) = channel();
680 let mut bus = VecDeque::new();
681 let mut rq = RenderQueue::new();
682
683 assert_eq!(value.value(), "Test Library");
684
685 let update_event = Event::Settings(SettingsEvent::UpdateValue {
686 kind: SettingIdentity::LibraryPath(0),
687 value: "Some Path".to_string(),
688 });
689 let handled = value.handle_event(&update_event, &hub, &mut bus, &mut rq, &mut context);
690
691 assert!(
692 !handled,
693 "UpdateValue event should not be handled when kind does not match"
694 );
695 assert_eq!(
696 value.value(),
697 "Test Library",
698 "Value should not change when kind mismatches"
699 );
700 }
701
702 #[test]
703 fn test_update_value_event_ignores_wrong_index() {
704 use crate::settings::LibrarySettings;
705 let mut context = create_test_context();
706 context.settings.libraries.clear();
707 context.settings.libraries.push(LibrarySettings {
708 name: "Library 0".to_string(),
709 path: PathBuf::from("/path0"),
710 ..Default::default()
711 });
712 context.settings.libraries.push(LibrarySettings {
713 name: "Library 1".to_string(),
714 path: PathBuf::from("/path1"),
715 ..Default::default()
716 });
717 let rect = rect![0, 0, 200, 50];
718
719 let mut value =
720 SettingValue::new(LibraryName(0), rect, &context.settings, &mut context.fonts);
721 let (hub, _receiver) = channel();
722 let mut bus = VecDeque::new();
723 let mut rq = RenderQueue::new();
724
725 assert_eq!(value.value(), "Library 0");
726
727 let update_event = Event::Settings(SettingsEvent::UpdateValue {
728 kind: SettingIdentity::LibraryName(1),
729 value: "Updated Library 1".to_string(),
730 });
731 let handled = value.handle_event(&update_event, &hub, &mut bus, &mut rq, &mut context);
732
733 assert!(
734 !handled,
735 "UpdateValue event should not be handled when index does not match"
736 );
737 assert_eq!(
738 value.value(),
739 "Library 0",
740 "Value should not change when index mismatches"
741 );
742 }
743
744 #[test]
745 fn test_update_value_event_updates_auto_suspend() {
746 let rect = rect![0, 0, 200, 50];
747 let mut context = create_test_context();
748 let mut value =
749 SettingValue::new(&AutoSuspend, rect, &context.settings, &mut context.fonts);
750 let (hub, _receiver) = channel();
751 let mut bus = VecDeque::new();
752 let mut rq = RenderQueue::new();
753
754 assert_eq!(value.value(), "30.0");
755
756 let update_event = Event::Settings(SettingsEvent::UpdateValue {
757 kind: SettingIdentity::AutoSuspend,
758 value: "60.0".to_string(),
759 });
760 let handled = value.handle_event(&update_event, &hub, &mut bus, &mut rq, &mut context);
761
762 assert!(
763 handled,
764 "UpdateValue event should be handled when kind matches"
765 );
766 assert_eq!(value.value(), "60.0");
767 }
768
769 #[test]
770 fn test_update_value_event_updates_auto_power_off() {
771 let rect = rect![0, 0, 200, 50];
772 let mut context = create_test_context();
773 let mut value =
774 SettingValue::new(&AutoPowerOff, rect, &context.settings, &mut context.fonts);
775 let (hub, _receiver) = channel();
776 let mut bus = VecDeque::new();
777 let mut rq = RenderQueue::new();
778
779 assert_eq!(value.value(), "3.0");
780
781 let update_event = Event::Settings(SettingsEvent::UpdateValue {
782 kind: SettingIdentity::AutoPowerOff,
783 value: "60.0".to_string(),
784 });
785 let handled = value.handle_event(&update_event, &hub, &mut bus, &mut rq, &mut context);
786
787 assert!(
788 handled,
789 "UpdateValue event should be handled when kind matches"
790 );
791 assert_eq!(value.value(), "60.0");
792 }
793
794 #[test]
795 fn test_update_value_event_updates_settings_retention() {
796 let rect = rect![0, 0, 200, 50];
797 let mut context = create_test_context();
798 let mut value = SettingValue::new(
799 &SettingsRetention,
800 rect,
801 &context.settings,
802 &mut context.fonts,
803 );
804 let (hub, _receiver) = channel();
805 let mut bus = VecDeque::new();
806 let mut rq = RenderQueue::new();
807
808 assert_eq!(value.value(), "3");
809
810 let update_event = Event::Settings(SettingsEvent::UpdateValue {
811 kind: SettingIdentity::SettingsRetention,
812 value: "5".to_string(),
813 });
814 let handled = value.handle_event(&update_event, &hub, &mut bus, &mut rq, &mut context);
815
816 assert!(
817 handled,
818 "UpdateValue event should be handled when kind matches"
819 );
820 assert_eq!(value.value(), "5");
821 }
822
823 #[test]
824 fn test_update_value_event_regenerates_log_level_radio_buttons() {
825 let rect = rect![0, 0, 200, 50];
826 let mut context = create_test_context();
827 context.settings.logging.level = "INFO".to_string();
828
829 let mut value = SettingValue::new(&LogLevel, rect, &context.settings, &mut context.fonts);
830 let (hub, _receiver) = channel();
831 let mut bus = VecDeque::new();
832 let mut rq = RenderQueue::new();
833
834 let initial_entries = value.entries.clone();
835 assert_eq!(initial_entries.len(), 5);
836
837 let info_entry = initial_entries.iter().find(|e| {
838 if let EntryKind::RadioButton(label, _, _) = e {
839 label == "INFO"
840 } else {
841 false
842 }
843 });
844 assert!(
845 matches!(info_entry, Some(EntryKind::RadioButton(_, _, true))),
846 "INFO should be initially checked"
847 );
848
849 context.settings.logging.level = "DEBUG".to_string();
850 let update_event = Event::Settings(SettingsEvent::UpdateValue {
851 kind: SettingIdentity::LogLevel,
852 value: "DEBUG".to_string(),
853 });
854 let handled = value.handle_event(&update_event, &hub, &mut bus, &mut rq, &mut context);
855
856 assert!(
857 handled,
858 "UpdateValue event should be handled when kind matches"
859 );
860 assert_eq!(value.value(), "DEBUG");
861 }
862
863 #[test]
864 fn test_keep_open_submenu_does_not_queue_menu_close() {
865 use crate::settings::FileExtension;
866
867 let rect = rect![0, 0, 200, 50];
868 let mut context = create_test_context();
869 let mut value = SettingValue::new(
870 &AllowedKindsSetting,
871 rect,
872 &context.settings,
873 &mut context.fonts,
874 );
875 let (hub, _receiver) = channel();
876 let mut bus = VecDeque::new();
877 let mut rq = RenderQueue::new();
878
879 let handled = value.handle_event(
880 &Event::Select(EntryId::ToggleAllowedKind(FileExtension::Cbr)),
881 &hub,
882 &mut bus,
883 &mut rq,
884 &mut context,
885 );
886
887 assert!(handled);
888 assert!(
889 context
890 .settings
891 .import
892 .allowed_kinds
893 .contains(&FileExtension::Cbr)
894 );
895 assert!(
896 !bus.iter()
897 .any(|evt| matches!(evt, Event::Close(ViewId::SettingsValueMenu))),
898 "submenu should remain open for multi-select settings"
899 );
900 }
901}