teksilo_settings/ext.rs
1// SPDX-License-Identifier: MPL-2.0
2// SPDX-FileCopyrightText: 2026 FernTech
3
4//! Extension traits exposing settings services on `BuildContext` and
5//! `EventContext`.
6//!
7//! `teksilo-settings` cannot live below `teksilo-core` (it depends on
8//! `teksilo-core` for `Signal`, `ObserverHandle`, etc.), so the
9//! convenience accessors `ctx.settings()` / `ctx.window_state()` /
10//! `ctx.mru::<T>()` ship as an extension trait that apps `use`
11//! explicitly:
12//!
13//! ```ignore
14//! use teksilo_settings::SettingsExt;
15//!
16//! // inside any handler / build method:
17//! let store = ctx.settings();
18//! let recents = ctx.mru::<RecentProject>();
19//! ```
20//!
21//! Each accessor wraps the existing `app_state::<T>()` lookup. The
22//! mandatory accessors panic with a clear message if the service has
23//! not been registered; the `try_*` variants return `Option`.
24//!
25//! Window-geometry persistence is **not** an extension method: when a
26//! `WindowStateService` is registered via `TeksiloAppBuilder::settings`,
27//! every `WindowConfig` carrying an `id(...)` is automatically
28//! restored on creation and recorded on every change by `teksilo-app`'s
29//! window manager. No widget-side wiring needed.
30
31use teksilo_core::BuildContext;
32use teksilo_core::widget::EventContext;
33
34use crate::mru::{MruEntry, MruList};
35use crate::store::SettingsStore;
36use crate::window_state::WindowStateService;
37
38/// Convenience accessors for settings services attached to the app's
39/// `app_state` registry.
40pub trait SettingsExt {
41 /// The K/V settings store. Panics if `TeksiloAppBuilder::settings(...)`
42 /// was not called.
43 fn settings(&self) -> &SettingsStore {
44 self.try_settings().unwrap_or_else(|| {
45 panic!(
46 "SettingsExt::settings(): no SettingsStore registered. \
47 Call TeksiloAppBuilder::settings(SettingsBundle::new()) at startup."
48 )
49 })
50 }
51
52 /// The window-state service. Panics if not registered.
53 fn window_state(&self) -> &WindowStateService {
54 self.try_window_state().unwrap_or_else(|| {
55 panic!(
56 "SettingsExt::window_state(): no WindowStateService registered. \
57 Call .with_window_state(true) on the SettingsBundle."
58 )
59 })
60 }
61
62 /// An app-defined MRU list. Panics if no `MruList<T>` was
63 /// registered for that exact `T` via
64 /// `TeksiloAppBuilder::app_state(mru_handle.clone())`.
65 fn mru<T: MruEntry>(&self) -> &MruList<T> {
66 self.try_mru::<T>().unwrap_or_else(|| {
67 panic!(
68 "SettingsExt::mru::<{}>(): no MruList registered. \
69 Construct one with MruList::open(...) and register via \
70 TeksiloAppBuilder::app_state(mru_handle.clone()).",
71 std::any::type_name::<T>(),
72 )
73 })
74 }
75
76 /// Returns the K/V settings store, or `None` if not registered.
77 fn try_settings(&self) -> Option<&SettingsStore>;
78 /// Returns the window-state service, or `None` if not registered.
79 fn try_window_state(&self) -> Option<&WindowStateService>;
80 /// Returns the MRU list for `T`, or `None` if no `MruList<T>` was registered.
81 fn try_mru<T: MruEntry>(&self) -> Option<&MruList<T>>;
82}
83
84impl<'a> SettingsExt for BuildContext<'a> {
85 fn try_settings(&self) -> Option<&SettingsStore> {
86 self.app_state::<SettingsStore>()
87 }
88 fn try_window_state(&self) -> Option<&WindowStateService> {
89 self.app_state::<WindowStateService>()
90 }
91 fn try_mru<T: MruEntry>(&self) -> Option<&MruList<T>> {
92 self.app_state::<MruList<T>>()
93 }
94}
95
96impl<'a> SettingsExt for EventContext<'a> {
97 fn try_settings(&self) -> Option<&SettingsStore> {
98 self.app_state::<SettingsStore>()
99 }
100 fn try_window_state(&self) -> Option<&WindowStateService> {
101 self.app_state::<WindowStateService>()
102 }
103 fn try_mru<T: MruEntry>(&self) -> Option<&MruList<T>> {
104 self.app_state::<MruList<T>>()
105 }
106}