Skip to main content

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}