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