cadmus_core/view/settings_editor/
setting_row.rs1use super::super::label::Label;
2use super::super::Align;
3use super::super::{Bus, Event, Hub, Id, RenderQueue, View, ID_FEEDER};
4use super::setting_value::{Kind as ValueKind, SettingValue};
5use crate::context::Context;
6use crate::framebuffer::Framebuffer;
7use crate::geom::Rectangle;
8use crate::settings::Settings;
9use crate::view::settings_editor::ToggleSettings;
10
11pub enum Kind {
12 KeyboardLayout,
13 SleepCover,
14 AutoShare,
15 AutoSuspend,
16 AutoPowerOff,
17 ButtonScheme,
18 Library(usize),
19 LibraryName(usize),
20 LibraryPath(usize),
21 LibraryMode(usize),
22 IntermissionSuspend,
23 IntermissionPowerOff,
24 IntermissionShare,
25 SettingsRetention,
26}
27
28impl Kind {
29 pub fn label(&self, settings: &Settings) -> String {
39 match self {
40 Kind::KeyboardLayout => "Keyboard Layout".to_string(),
41 Kind::SleepCover => "Enable Sleep Cover".to_string(),
42 Kind::AutoShare => "Enable Auto Share".to_string(),
43 Kind::AutoSuspend => "Auto Suspend (minutes)".to_string(),
44 Kind::AutoPowerOff => "Auto Power Off (days)".to_string(),
45 Kind::ButtonScheme => "Button Scheme".to_string(),
46 Kind::Library(index) => settings
47 .libraries
48 .get(*index)
49 .map(|lib| lib.name.clone())
50 .unwrap_or_else(|| "Unknown".to_string()),
51 Kind::LibraryName(_) => "Name".to_string(),
52 Kind::LibraryPath(_) => "Path".to_string(),
53 Kind::LibraryMode(_) => "Mode".to_string(),
54 Kind::IntermissionSuspend => "Suspend Screen".to_string(),
55 Kind::IntermissionPowerOff => "Power Off Screen".to_string(),
56 Kind::IntermissionShare => "Share Screen".to_string(),
57 Kind::SettingsRetention => "Settings Retention".to_string(),
58 }
59 }
60
61 fn value_kind(&self) -> ValueKind {
62 match self {
63 Kind::KeyboardLayout => ValueKind::KeyboardLayout,
64 Kind::SleepCover => ValueKind::Toggle(ToggleSettings::SleepCover),
65 Kind::AutoShare => ValueKind::Toggle(ToggleSettings::AutoShare),
66 Kind::AutoSuspend => ValueKind::AutoSuspend,
67 Kind::AutoPowerOff => ValueKind::AutoPowerOff,
68 Kind::ButtonScheme => ValueKind::Toggle(ToggleSettings::ButtonScheme),
69 Kind::Library(index) => ValueKind::LibraryInfo(*index),
70 Kind::LibraryName(index) => ValueKind::LibraryName(*index),
71 Kind::LibraryPath(index) => ValueKind::LibraryPath(*index),
72 Kind::LibraryMode(index) => ValueKind::LibraryMode(*index),
73 Kind::IntermissionSuspend => ValueKind::IntermissionSuspend,
74 Kind::IntermissionPowerOff => ValueKind::IntermissionPowerOff,
75 Kind::IntermissionShare => ValueKind::IntermissionShare,
76 Kind::SettingsRetention => ValueKind::SettingsRetention,
77 }
78 }
79}
80
81pub struct SettingRow {
94 id: Id,
95 rect: Rectangle,
96 children: Vec<Box<dyn View>>,
97 kind: Kind,
98}
99
100impl SettingRow {
101 pub fn new(
102 kind: Kind,
103 rect: Rectangle,
104 settings: &Settings,
105 fonts: &mut crate::font::Fonts,
106 ) -> SettingRow {
107 let mut children = Vec::new();
108
109 let half_width = rect.width() as i32 / 2;
110 let label_rect = rect![rect.min.x, rect.min.y, rect.min.x + half_width, rect.max.y];
111 let value_rect = rect![rect.min.x + half_width, rect.min.y, rect.max.x, rect.max.y];
112
113 let label_text = kind.label(settings);
114 let label = Label::new(label_rect, label_text, Align::Left(50));
115 children.push(Box::new(label) as Box<dyn View>);
116
117 let setting_value = SettingValue::new(kind.value_kind(), value_rect, settings, fonts);
118 children.push(Box::new(setting_value) as Box<dyn View>);
119
120 SettingRow {
121 id: ID_FEEDER.next(),
122 rect,
123 children,
124 kind,
125 }
126 }
127}
128
129impl View for SettingRow {
130 #[cfg_attr(feature = "otel", tracing::instrument(skip(self, _hub, _bus, rq, _context), fields(event = ?evt), ret(level=tracing::Level::TRACE)))]
131 fn handle_event(
132 &mut self,
133 evt: &Event,
134 _hub: &Hub,
135 _bus: &mut Bus,
136 rq: &mut RenderQueue,
137 _context: &mut Context,
138 ) -> bool {
139 match evt {
140 Event::UpdateLibrary(index, ref library) => match &self.kind {
141 Kind::Library(our_index) => {
142 if index == our_index {
143 if let Some(name_view) = self.children.get_mut(0) {
144 if let Some(name_label) = name_view.as_any_mut().downcast_mut::<Label>()
145 {
146 name_label.update(&library.name, rq);
147 return true;
148 }
149 }
150 }
151 false
152 }
153 _ => false,
154 },
155 _ => false,
156 }
157 }
158
159 #[cfg_attr(feature = "otel", tracing::instrument(skip(self, _fb, _fonts), fields(rect = ?_rect)))]
160 fn render(&self, _fb: &mut dyn Framebuffer, _rect: Rectangle, _fonts: &mut crate::font::Fonts) {
161 }
162
163 fn rect(&self) -> &Rectangle {
164 &self.rect
165 }
166
167 fn rect_mut(&mut self) -> &mut Rectangle {
168 &mut self.rect
169 }
170
171 fn children(&self) -> &Vec<Box<dyn View>> {
172 &self.children
173 }
174
175 fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
176 &mut self.children
177 }
178
179 fn id(&self) -> Id {
180 self.id
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187 use crate::context::test_helpers::create_test_context;
188 use crate::settings::{LibraryMode, LibrarySettings};
189 use std::collections::VecDeque;
190 use std::path::PathBuf;
191 use std::sync::mpsc::channel;
192
193 fn create_test_settings() -> Settings {
194 let mut settings = Settings::default();
195 settings.libraries.clear();
196 settings.libraries.push(LibrarySettings {
197 name: "Test Library 0".to_string(),
198 path: PathBuf::from("/tmp/lib0"),
199 mode: LibraryMode::Filesystem,
200 ..Default::default()
201 });
202 settings.libraries.push(LibrarySettings {
203 name: "Test Library 1".to_string(),
204 path: PathBuf::from("/tmp/lib1"),
205 mode: LibraryMode::Database,
206 ..Default::default()
207 });
208 settings
209 }
210
211 #[test]
212 fn test_update_library_event_updates_matching_row() {
213 let mut context = create_test_context();
214 let settings = create_test_settings();
215 let rect = rect![0, 0, 400, 60];
216
217 let mut row = SettingRow::new(Kind::Library(0), rect, &settings, &mut context.fonts);
218
219 let (hub, _receiver) = channel();
220 let mut bus = VecDeque::new();
221 let mut rq = RenderQueue::new();
222
223 let updated_library = LibrarySettings {
224 name: "Updated Library Name".to_string(),
225 path: PathBuf::from("/tmp/updated"),
226 mode: LibraryMode::Database,
227 ..Default::default()
228 };
229
230 let event = Event::UpdateLibrary(0, Box::new(updated_library.clone()));
231 let handled = row.handle_event(&event, &hub, &mut bus, &mut rq, &mut context);
232
233 assert!(handled);
234 assert!(!rq.is_empty());
235 }
236
237 #[test]
238 fn test_update_library_event_ignores_non_matching() {
239 let mut context = create_test_context();
240 let settings = create_test_settings();
241 let rect = rect![0, 0, 400, 60];
242
243 let mut row = SettingRow::new(Kind::Library(0), rect, &settings, &mut context.fonts);
244
245 let (hub, _receiver) = channel();
246 let mut bus = VecDeque::new();
247 let mut rq = RenderQueue::new();
248
249 let updated_library = LibrarySettings {
250 name: "Updated Library 1".to_string(),
251 path: PathBuf::from("/tmp/lib1_updated"),
252 mode: LibraryMode::Database,
253 ..Default::default()
254 };
255
256 let event = Event::UpdateLibrary(1, Box::new(updated_library));
257 let handled = row.handle_event(&event, &hub, &mut bus, &mut rq, &mut context);
258
259 assert!(!handled);
260 assert!(rq.is_empty());
261 }
262}