cadmus_core/view/settings_editor/
setting_row.rs

1use super::super::label::Label;
2use super::super::Align;
3use super::super::{Bus, Event, Hub, Id, RenderQueue, View, ID_FEEDER};
4use super::kinds::{SettingIdentity, SettingKind};
5use super::setting_value::SettingValue;
6use crate::context::Context;
7use crate::framebuffer::Framebuffer;
8use crate::geom::Rectangle;
9use crate::settings::Settings;
10
11/// A row in the settings UI that displays a setting label and its corresponding value.
12///
13/// `SettingRow` is a composite view that contains two child views:
14/// - A `Label` displaying the human-readable name of the setting
15/// - A `SettingValue` displaying the current value and allowing modifications
16///
17/// The row is completely driven by a [`SettingKind`] implementation — no match arms
18/// are needed here.
19pub struct SettingRow {
20    id: Id,
21    rect: Rectangle,
22    children: Vec<Box<dyn View>>,
23    /// Kept for the `UpdateLibrary` special case that relabels library rows.
24    identity: SettingIdentity,
25}
26
27impl SettingRow {
28    pub fn new(
29        kind: impl SettingKind + 'static,
30        rect: Rectangle,
31        settings: &Settings,
32        fonts: &mut crate::font::Fonts,
33    ) -> SettingRow {
34        let mut children = Vec::new();
35
36        let half_width = rect.width() as i32 / 2;
37        let label_rect = rect![rect.min.x, rect.min.y, rect.min.x + half_width, rect.max.y];
38        let value_rect = rect![rect.min.x + half_width, rect.min.y, rect.max.x, rect.max.y];
39
40        let label_text = kind.label(settings);
41        let identity = kind.identity();
42        let label = Label::new(label_rect, label_text, Align::Left(50));
43        children.push(Box::new(label) as Box<dyn View>);
44
45        let setting_value = SettingValue::new(kind, value_rect, settings, fonts);
46        children.push(Box::new(setting_value) as Box<dyn View>);
47
48        SettingRow {
49            id: ID_FEEDER.next(),
50            rect,
51            children,
52            identity,
53        }
54    }
55}
56
57impl View for SettingRow {
58    #[cfg_attr(feature = "otel", tracing::instrument(skip(self, _hub, _bus, rq, _context), fields(event = ?evt), ret(level=tracing::Level::TRACE)))]
59    fn handle_event(
60        &mut self,
61        evt: &Event,
62        _hub: &Hub,
63        _bus: &mut Bus,
64        rq: &mut RenderQueue,
65        _context: &mut Context,
66    ) -> bool {
67        match evt {
68            Event::UpdateLibrary(index, ref library) => {
69                if let SettingIdentity::LibraryInfo(our_index) = self.identity {
70                    if *index == our_index {
71                        if let Some(name_view) = self.children.get_mut(0) {
72                            if let Some(name_label) = name_view.as_any_mut().downcast_mut::<Label>()
73                            {
74                                name_label.update(&library.name, rq);
75                                return true;
76                            }
77                        }
78                    }
79                }
80                false
81            }
82            _ => false,
83        }
84    }
85
86    #[cfg_attr(feature = "otel", tracing::instrument(skip(self, _fb, _fonts), fields(rect = ?_rect)))]
87    fn render(&self, _fb: &mut dyn Framebuffer, _rect: Rectangle, _fonts: &mut crate::font::Fonts) {
88    }
89
90    fn rect(&self) -> &Rectangle {
91        &self.rect
92    }
93
94    fn rect_mut(&mut self) -> &mut Rectangle {
95        &mut self.rect
96    }
97
98    fn children(&self) -> &Vec<Box<dyn View>> {
99        &self.children
100    }
101
102    fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
103        &mut self.children
104    }
105
106    fn id(&self) -> Id {
107        self.id
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114    use crate::context::test_helpers::create_test_context;
115    use crate::settings::LibrarySettings;
116    use crate::view::settings_editor::kinds::library::LibraryInfo;
117    use std::collections::VecDeque;
118    use std::path::PathBuf;
119    use std::sync::mpsc::channel;
120
121    fn create_test_settings() -> Settings {
122        let mut settings = Settings::default();
123        settings.libraries.clear();
124        settings.libraries.push(LibrarySettings {
125            name: "Test Library 0".to_string(),
126            path: PathBuf::from("/tmp/lib0"),
127            ..Default::default()
128        });
129        settings.libraries.push(LibrarySettings {
130            name: "Test Library 1".to_string(),
131            path: PathBuf::from("/tmp/lib1"),
132            ..Default::default()
133        });
134        settings
135    }
136
137    #[test]
138    fn test_update_library_event_updates_matching_row() {
139        let mut context = create_test_context();
140        let settings = create_test_settings();
141        let rect = rect![0, 0, 400, 60];
142
143        let mut row = SettingRow::new(LibraryInfo(0), rect, &settings, &mut context.fonts);
144
145        let (hub, _receiver) = channel();
146        let mut bus = VecDeque::new();
147        let mut rq = RenderQueue::new();
148
149        let updated_library = LibrarySettings {
150            name: "Updated Library Name".to_string(),
151            path: PathBuf::from("/tmp/updated"),
152            ..Default::default()
153        };
154
155        let event = Event::UpdateLibrary(0, Box::new(updated_library));
156        let handled = row.handle_event(&event, &hub, &mut bus, &mut rq, &mut context);
157
158        assert!(handled);
159        assert!(!rq.is_empty());
160    }
161
162    #[test]
163    fn test_update_library_event_ignores_non_matching() {
164        let mut context = create_test_context();
165        let settings = create_test_settings();
166        let rect = rect![0, 0, 400, 60];
167
168        let mut row = SettingRow::new(LibraryInfo(0), rect, &settings, &mut context.fonts);
169
170        let (hub, _receiver) = channel();
171        let mut bus = VecDeque::new();
172        let mut rq = RenderQueue::new();
173
174        let updated_library = LibrarySettings {
175            name: "Updated Library 1".to_string(),
176            path: PathBuf::from("/tmp/lib1_updated"),
177            ..Default::default()
178        };
179
180        let event = Event::UpdateLibrary(1, Box::new(updated_library));
181        let handled = row.handle_event(&event, &hub, &mut bus, &mut rq, &mut context);
182
183        assert!(!handled);
184        assert!(rq.is_empty());
185    }
186}