cadmus_core/view/settings_editor/kinds/mod.rs
1//! Trait and supporting types for defining individual settings.
2//!
3//! Each setting is a small struct that implements [`SettingKind`]. The trait
4//! encapsulates everything a [`SettingRow`](crate::view::settings_editor::SettingRow)
5//! needs to know: the row label, the current display value, which widget to
6//! render (`ActionLabel`, `Toggle`, or `SubMenu`), and which event to fire on tap.
7//!
8//! [`SettingIdentity`] is the single, deduplicated identity enum used by
9//! [`SettingsEvent::UpdateValue`](crate::view::settings_editor::SettingsEvent) to
10//! target the correct [`SettingValue`](crate::view::settings_editor::SettingValue) view.
11
12pub mod dictionary;
13pub mod general;
14pub mod identity;
15pub mod import;
16pub mod intermission;
17pub mod library;
18pub mod reader;
19pub mod telemetry;
20
21pub use identity::SettingIdentity;
22
23use crate::geom::Rectangle;
24use crate::settings::Settings;
25use crate::view::{Bus, EntryId, EntryKind, Event, ViewId};
26
27/// Identifies which boolean setting a toggle widget controls.
28///
29/// Used in [`ToggleEvent::Setting`](crate::view::ToggleEvent) so that
30/// [`CategoryEditor`](crate::view::settings_editor::CategoryEditor) can dispatch
31/// to the correct toggle handler without coupling to UI view IDs.
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum ToggleSettings {
34 /// Sleep cover enable/disable setting
35 SleepCover,
36 /// Auto-share enable/disable setting
37 AutoShare,
38 /// Auto time sync enable/disable setting
39 AutoTime,
40 /// Button scheme selection (natural or inverted)
41 ButtonScheme,
42 /// Logging enabled setting
43 LoggingEnabled,
44 /// Sync metadata enable/disable setting
45 ImportSyncMetadata,
46 /// Kernel logging enabled setting (test + kobo builds only)
47 #[cfg(all(feature = "test", feature = "kobo"))]
48 EnableKernLog,
49 /// D-Bus logging enabled setting (test + kobo builds only)
50 #[cfg(all(feature = "test", feature = "kobo"))]
51 EnableDbusLog,
52}
53
54/// Describes how the value side of a setting row should be rendered.
55///
56/// Each variant is fully self-contained: it carries everything needed to build
57/// the widget, including the tap event or sub-menu entries.
58#[derive(Debug)]
59pub enum WidgetKind {
60 /// No interactive widget; the value is shown as static text only.
61 None,
62 /// A tappable label that opens a free-form editor (e.g. a text input dialog).
63 ///
64 /// The inner event is fired when the label is tapped.
65 ActionLabel(Event),
66 /// A two-state toggle switch.
67 Toggle {
68 /// Label shown on the left (the "on" side).
69 left_label: String,
70 /// Label shown on the right (the "off" side).
71 right_label: String,
72 /// Whether the toggle is currently in the left/enabled state.
73 enabled: bool,
74 /// Event fired when the toggle is tapped.
75 tap_event: Event,
76 },
77 /// A tappable label that opens a sub-menu with the given entries.
78 ///
79 /// The entries (e.g. radio buttons) are stored here so that the widget is
80 /// fully self-contained.
81 SubMenu(Vec<EntryKind>),
82}
83
84/// All data needed to build and update the value side of a setting row.
85pub struct SettingData {
86 /// Text representation of the current value (shown in the widget).
87 pub value: String,
88 /// Which widget type to render, including all tap/event data for that widget.
89 pub widget: WidgetKind,
90}
91
92/// A self-contained description of a single setting.
93///
94/// Implementing this trait is sufficient to add a new setting to the editor.
95pub trait SettingKind {
96 /// Unique identity used to route [`SettingsEvent::UpdateValue`](crate::view::settings_editor::SettingsEvent::UpdateValue) to the
97 /// correct [`SettingValue`](crate::view::settings_editor::SettingValue) view.
98 fn identity(&self) -> SettingIdentity;
99
100 /// Human-readable label shown on the left side of the setting row.
101 ///
102 /// `settings` is provided for dynamic labels (e.g. library names).
103 fn label(&self, settings: &Settings) -> String;
104
105 /// Fetch the current display value and widget configuration from `settings`.
106 fn fetch(&self, settings: &Settings) -> SettingData;
107
108 /// Handle an incoming event that may apply a change to this setting.
109 ///
110 /// Mutates `settings` if the event is relevant and returns:
111 /// - `Some(display_string)` as the first element when the event changes this
112 /// setting's display value, or `None` if the event does not apply.
113 /// - `true` as the second element when the event has been fully consumed and
114 /// should stop propagating; `false` to allow further handlers to see it.
115 ///
116 /// `bus` is available for settings that need to propagate side-effects.
117 fn handle(
118 &self,
119 _evt: &Event,
120 _settings: &mut Settings,
121 _bus: &mut Bus,
122 ) -> (Option<String>, bool) {
123 (None, false)
124 }
125
126 /// Returns this setting as an [`InputSettingKind`] if it supports text input.
127 ///
128 /// [`InputSettingKind`] implementors override this to return `Some(self)`.
129 /// All other settings inherit the default `None`.
130 fn as_input_kind(&self) -> Option<&dyn InputSettingKind> {
131 None
132 }
133
134 /// Returns the [`EntryId`] that triggers opening a file chooser for this setting.
135 ///
136 /// Default `None`. Implement on settings that offer a "Custom Image..." option
137 /// (currently the three intermission kinds).
138 fn file_chooser_entry_id(&self) -> Option<EntryId> {
139 None
140 }
141
142 /// The event that should be emitted if the settings is held.
143 fn hold_event(&self, _rect: Rectangle) -> Option<Event> {
144 None
145 }
146
147 /// Whether a submenu should remain open after this setting handles a selection.
148 fn keep_menu_open(&self) -> bool {
149 false
150 }
151}
152
153impl<T: SettingKind + ?Sized> SettingKind for &T {
154 fn identity(&self) -> SettingIdentity {
155 (**self).identity()
156 }
157
158 fn label(&self, settings: &Settings) -> String {
159 (**self).label(settings)
160 }
161
162 fn fetch(&self, settings: &Settings) -> SettingData {
163 (**self).fetch(settings)
164 }
165
166 fn handle(
167 &self,
168 evt: &Event,
169 settings: &mut Settings,
170 bus: &mut Bus,
171 ) -> (Option<String>, bool) {
172 (**self).handle(evt, settings, bus)
173 }
174
175 fn as_input_kind(&self) -> Option<&dyn InputSettingKind> {
176 (**self).as_input_kind()
177 }
178
179 fn file_chooser_entry_id(&self) -> Option<EntryId> {
180 (**self).file_chooser_entry_id()
181 }
182
183 fn hold_event(&self, rect: Rectangle) -> Option<Event> {
184 (**self).hold_event(rect)
185 }
186
187 fn keep_menu_open(&self) -> bool {
188 (**self).keep_menu_open()
189 }
190}
191
192impl<T: SettingKind + ?Sized> SettingKind for Box<T> {
193 fn identity(&self) -> SettingIdentity {
194 (**self).identity()
195 }
196
197 fn label(&self, settings: &Settings) -> String {
198 (**self).label(settings)
199 }
200
201 fn fetch(&self, settings: &Settings) -> SettingData {
202 (**self).fetch(settings)
203 }
204
205 fn handle(
206 &self,
207 evt: &Event,
208 settings: &mut Settings,
209 bus: &mut Bus,
210 ) -> (Option<String>, bool) {
211 (**self).handle(evt, settings, bus)
212 }
213
214 fn as_input_kind(&self) -> Option<&dyn InputSettingKind> {
215 (**self).as_input_kind()
216 }
217
218 fn file_chooser_entry_id(&self) -> Option<EntryId> {
219 (**self).file_chooser_entry_id()
220 }
221
222 fn hold_event(&self, rect: Rectangle) -> Option<Event> {
223 (**self).hold_event(rect)
224 }
225
226 fn keep_menu_open(&self) -> bool {
227 (**self).keep_menu_open()
228 }
229}
230
231/// Extended trait for settings that accept free-form text input via a [`NamedInput`] overlay.
232///
233/// [`NamedInput`]: crate::view::named_input::NamedInput
234pub trait InputSettingKind: SettingKind {
235 /// The [`ViewId`] used by this setting's [`NamedInput`] and its submit event.
236 ///
237 /// [`NamedInput`]: crate::view::named_input::NamedInput
238 fn submit_view_id(&self) -> ViewId;
239
240 /// The [`EntryId`] event that opens this setting's input dialog when tapped.
241 fn open_entry_id(&self) -> EntryId;
242
243 /// Label shown inside the [`NamedInput`] dialog.
244 ///
245 /// [`NamedInput`]: crate::view::named_input::NamedInput
246 fn input_label(&self) -> String;
247
248 /// Maximum number of characters the input accepts.
249 fn input_max_chars(&self) -> usize;
250
251 /// The current value as a string to pre-populate the input field.
252 fn current_text(&self, settings: &Settings) -> String;
253
254 /// Parse `text` from the input, mutate `settings`, and return the display string.
255 fn apply_text(&self, text: &str, settings: &mut Settings) -> String;
256}