Skip to main content

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        let hold_event = kind.hold_event(rect);
40
41        let label_text = kind.label(settings);
42        let identity = kind.identity();
43
44        let label =
45            Label::new(label_rect, label_text, Align::Left(50)).hold_event(hold_event.clone());
46        children.push(Box::new(label) as Box<dyn View>);
47
48        let setting_value =
49            SettingValue::new(kind, value_rect, settings, fonts).hold_event(hold_event);
50        children.push(Box::new(setting_value) as Box<dyn View>);
51
52        SettingRow {
53            id: ID_FEEDER.next(),
54            rect,
55            children,
56            identity,
57        }
58    }
59}
60
61impl View for SettingRow {
62    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, _hub, _bus, rq, _context), fields(event = ?evt), ret(level=tracing::Level::TRACE)))]
63    fn handle_event(
64        &mut self,
65        evt: &Event,
66        _hub: &Hub,
67        _bus: &mut Bus,
68        rq: &mut RenderQueue,
69        _context: &mut Context,
70    ) -> bool {
71        match evt {
72            Event::UpdateLibrary(index, ref library) => {
73                if let SettingIdentity::LibraryInfo(our_index) = self.identity {
74                    if *index == our_index {
75                        if let Some(name_view) = self.children.get_mut(0) {
76                            if let Some(name_label) = name_view.as_any_mut().downcast_mut::<Label>()
77                            {
78                                name_label.update(&library.name, rq);
79                                return true;
80                            }
81                        }
82                    }
83                }
84                false
85            }
86            _ => false,
87        }
88    }
89
90    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, _fb, _fonts), fields(rect = ?_rect)))]
91    fn render(&self, _fb: &mut dyn Framebuffer, _rect: Rectangle, _fonts: &mut crate::font::Fonts) {
92    }
93
94    fn rect(&self) -> &Rectangle {
95        &self.rect
96    }
97
98    fn rect_mut(&mut self) -> &mut Rectangle {
99        &mut self.rect
100    }
101
102    fn children(&self) -> &Vec<Box<dyn View>> {
103        &self.children
104    }
105
106    fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
107        &mut self.children
108    }
109
110    fn id(&self) -> Id {
111        self.id
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118    use crate::context::test_helpers::create_test_context;
119    use crate::gesture::GestureEvent;
120    use crate::settings::LibrarySettings;
121    use crate::view::settings_editor::kinds::library::LibraryInfo;
122    use std::collections::VecDeque;
123    use std::path::PathBuf;
124    use std::sync::mpsc::channel;
125
126    fn create_test_settings() -> Settings {
127        let mut settings = Settings::default();
128        settings.libraries.clear();
129        settings.libraries.push(LibrarySettings {
130            name: "Test Library 0".to_string(),
131            path: PathBuf::from("/tmp/lib0"),
132            ..Default::default()
133        });
134        settings.libraries.push(LibrarySettings {
135            name: "Test Library 1".to_string(),
136            path: PathBuf::from("/tmp/lib1"),
137            ..Default::default()
138        });
139        settings
140    }
141
142    #[test]
143    fn test_update_library_event_updates_matching_row() {
144        let mut context = create_test_context();
145        let settings = create_test_settings();
146        let rect = rect![0, 0, 400, 60];
147
148        let mut row = SettingRow::new(LibraryInfo(0), rect, &settings, &mut context.fonts);
149
150        let (hub, _receiver) = channel();
151        let mut bus = VecDeque::new();
152        let mut rq = RenderQueue::new();
153
154        let updated_library = LibrarySettings {
155            name: "Updated Library Name".to_string(),
156            path: PathBuf::from("/tmp/updated"),
157            ..Default::default()
158        };
159
160        let event = Event::UpdateLibrary(0, Box::new(updated_library));
161        let handled = row.handle_event(&event, &hub, &mut bus, &mut rq, &mut context);
162
163        assert!(handled);
164        assert!(!rq.is_empty());
165    }
166
167    #[test]
168    fn test_update_library_event_ignores_non_matching() {
169        let mut context = create_test_context();
170        let settings = create_test_settings();
171        let rect = rect![0, 0, 400, 60];
172
173        let mut row = SettingRow::new(LibraryInfo(0), rect, &settings, &mut context.fonts);
174
175        let (hub, _receiver) = channel();
176        let mut bus = VecDeque::new();
177        let mut rq = RenderQueue::new();
178
179        let updated_library = LibrarySettings {
180            name: "Updated Library 1".to_string(),
181            path: PathBuf::from("/tmp/lib1_updated"),
182            ..Default::default()
183        };
184
185        let event = Event::UpdateLibrary(1, Box::new(updated_library));
186        let handled = row.handle_event(&event, &hub, &mut bus, &mut rq, &mut context);
187
188        assert!(!handled);
189        assert!(rq.is_empty());
190    }
191
192    #[test]
193    fn test_hold_finger_short_outside_label_rect_is_not_handled() {
194        let mut context = create_test_context();
195        let settings = create_test_settings();
196        let rect = rect![0, 0, 400, 60];
197
198        let mut row: Box<dyn View> = Box::new(SettingRow::new(
199            LibraryInfo(0),
200            rect,
201            &settings,
202            &mut context.fonts,
203        ));
204
205        let (hub, _receiver) = channel();
206        let mut bus = VecDeque::new();
207        let mut rq = RenderQueue::new();
208
209        // Point outside the row entirely
210        let point = crate::geom::Point::new(500, 100);
211        let event = Event::Gesture(GestureEvent::HoldFingerShort(point, 0));
212
213        crate::view::handle_event(row.as_mut(), &event, &hub, &mut bus, &mut rq, &mut context);
214
215        assert!(
216            bus.is_empty(),
217            "No event should be pushed for out-of-bounds hold"
218        );
219    }
220}