kas_core/config/
config.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4//     https://www.apache.org/licenses/LICENSE-2.0
5
6//! Top-level configuration struct
7
8use super::{EventConfig, EventConfigMsg, EventWindowConfig};
9use super::{FontConfig, FontConfigMsg, ThemeConfig, ThemeConfigMsg};
10use crate::Action;
11use crate::config::Shortcuts;
12#[cfg(feature = "serde")]
13use serde::{Deserialize, Serialize};
14use std::cell::{Ref, RefCell};
15use std::rc::Rc;
16
17/// A message which may be used to update [`Config`]
18#[derive(Clone, Debug)]
19#[non_exhaustive]
20pub enum ConfigMsg {
21    Event(EventConfigMsg),
22    Font(FontConfigMsg),
23    Theme(ThemeConfigMsg),
24}
25
26/// Base configuration
27///
28/// This is serializable (using `feature = "serde"`) with the following fields:
29///
30/// > `event`: [`EventConfig`] \
31/// > `font`: [`FontConfig`] \
32/// > `shortcuts`: [`Shortcuts`] \
33/// > `theme`: [`ThemeConfig`]
34///
35/// For descriptions of configuration effects, see [`WindowConfig`] methods.
36#[derive(Debug, PartialEq)]
37#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
38pub struct Config {
39    #[cfg_attr(feature = "serde", serde(default))]
40    pub event: EventConfig,
41
42    #[cfg_attr(feature = "serde", serde(default))]
43    pub font: FontConfig,
44
45    #[cfg_attr(feature = "serde", serde(default = "Shortcuts::platform_defaults"))]
46    pub shortcuts: Shortcuts,
47
48    #[cfg_attr(feature = "serde", serde(default))]
49    pub theme: ThemeConfig,
50
51    #[cfg_attr(feature = "serde", serde(skip))]
52    is_dirty: bool,
53}
54
55impl Default for Config {
56    fn default() -> Self {
57        Config {
58            event: EventConfig::default(),
59            font: Default::default(),
60            shortcuts: Shortcuts::platform_defaults(),
61            theme: Default::default(),
62            is_dirty: false,
63        }
64    }
65}
66
67impl Config {
68    /// Call on startup
69    pub(crate) fn init(&mut self) {
70        self.font.init();
71    }
72
73    /// Has the config ever been updated?
74    #[inline]
75    pub fn is_dirty(&self) -> bool {
76        self.is_dirty
77    }
78
79    pub(crate) fn write_if_dirty(&mut self, writer: &mut dyn FnMut(&Self)) {
80        if self.is_dirty {
81            writer(self);
82            self.is_dirty = false;
83        }
84    }
85}
86
87/// Configuration, adapted for the application and window scale
88#[derive(Clone, Debug)]
89pub struct WindowConfig {
90    pub(super) config: Rc<RefCell<Config>>,
91    pub(super) scale_factor: f32,
92    pub(super) kinetic_decay_sub: f32,
93    pub(super) scroll_dist: f32,
94    pub(super) pan_dist_thresh: f32,
95    pub(crate) alt_bypass: bool,
96    /// Whether navigation focus is enabled for this application window
97    pub(crate) nav_focus: bool,
98}
99
100impl WindowConfig {
101    /// Construct
102    ///
103    /// It is required to call [`Self::update`] before usage.
104    pub(crate) fn new(config: Rc<RefCell<Config>>) -> Self {
105        WindowConfig {
106            config,
107            scale_factor: f32::NAN,
108            kinetic_decay_sub: f32::NAN,
109            scroll_dist: f32::NAN,
110            pan_dist_thresh: f32::NAN,
111            alt_bypass: false,
112            nav_focus: true,
113        }
114    }
115
116    /// Update window-specific/cached values
117    pub(crate) fn update(&mut self, scale_factor: f32) {
118        let base = self.config.borrow();
119        self.scale_factor = scale_factor;
120        self.kinetic_decay_sub = base.event.kinetic_decay_sub * scale_factor;
121        let dpem = base.font.size() * scale_factor;
122        self.scroll_dist = base.event.scroll_dist_em * dpem;
123        self.pan_dist_thresh = base.event.pan_dist_thresh * scale_factor;
124    }
125
126    /// Access base (unscaled) [`Config`]
127    pub fn base(&self) -> Ref<'_, Config> {
128        self.config.borrow()
129    }
130
131    /// Get a clone of the base (unscaled) [`Config`]
132    pub fn clone_base(&self) -> Rc<RefCell<Config>> {
133        self.config.clone()
134    }
135
136    /// Update the base config
137    ///
138    /// Since it is not known which parts of the configuration are updated, all
139    /// configuration-update [`Action`]s must be performed.
140    ///
141    /// NOTE: adjusting font settings from a running app is not currently
142    /// supported, excepting font size.
143    ///
144    /// NOTE: it is assumed that widget state is not affected by config except
145    /// (a) state affected by a widget update (e.g. the `EventConfig` widget)
146    /// and (b) widget size may be affected by font size.
147    pub fn update_base<F: FnOnce(&mut Config)>(&self, f: F) -> Action {
148        if let Ok(mut c) = self.config.try_borrow_mut() {
149            c.is_dirty = true;
150
151            let font_size = c.font.size();
152            f(&mut c);
153
154            let mut action = Action::EVENT_CONFIG | Action::THEME_UPDATE;
155            if c.font.size() != font_size {
156                action |= Action::RESIZE;
157            }
158            action
159        } else {
160            Action::empty()
161        }
162    }
163
164    /// Access event config
165    pub fn event(&self) -> EventWindowConfig<'_> {
166        EventWindowConfig(self)
167    }
168
169    /// Update event configuration
170    pub fn update_event<F: FnOnce(&mut EventConfig) -> Action>(&self, f: F) -> Action {
171        if let Ok(mut c) = self.config.try_borrow_mut() {
172            c.is_dirty = true;
173            f(&mut c.event)
174        } else {
175            Action::empty()
176        }
177    }
178
179    /// Access font config
180    pub fn font(&self) -> Ref<'_, FontConfig> {
181        Ref::map(self.config.borrow(), |c| &c.font)
182    }
183
184    /// Set standard font size
185    ///
186    /// Units: logical (unscaled) pixels per Em.
187    ///
188    /// To convert to Points, multiply by three quarters.
189    ///
190    /// NOTE: this is currently the only supported run-time update to font configuration.
191    pub fn set_font_size(&self, pt_size: f32) -> Action {
192        if let Ok(mut c) = self.config.try_borrow_mut() {
193            c.is_dirty = true;
194            c.font.set_size(pt_size)
195        } else {
196            Action::empty()
197        }
198    }
199
200    /// Access shortcut config
201    pub fn shortcuts(&self) -> Ref<'_, Shortcuts> {
202        Ref::map(self.config.borrow(), |c| &c.shortcuts)
203    }
204
205    /// Access theme config
206    pub fn theme(&self) -> Ref<'_, ThemeConfig> {
207        Ref::map(self.config.borrow(), |c| &c.theme)
208    }
209
210    /// Update theme configuration
211    pub fn update_theme<F: FnOnce(&mut ThemeConfig) -> Action>(&self, f: F) -> Action {
212        if let Ok(mut c) = self.config.try_borrow_mut() {
213            c.is_dirty = true;
214
215            f(&mut c.theme)
216        } else {
217            Action::empty()
218        }
219    }
220
221    /// Adjust shortcuts
222    pub fn update_shortcuts<F: FnOnce(&mut Shortcuts)>(&self, f: F) -> Action {
223        if let Ok(mut c) = self.config.try_borrow_mut() {
224            c.is_dirty = true;
225
226            f(&mut c.shortcuts);
227        }
228
229        Action::empty()
230    }
231
232    /// Scale factor
233    pub fn scale_factor(&self) -> f32 {
234        debug_assert!(self.scale_factor.is_finite());
235        self.scale_factor
236    }
237
238    /// Update event configuration via a [`ConfigMsg`]
239    pub fn change_config(&self, msg: ConfigMsg) -> Action {
240        match msg {
241            ConfigMsg::Event(msg) => self.update_event(|ev| ev.change_config(msg)),
242            ConfigMsg::Font(FontConfigMsg::Size(size)) => self.set_font_size(size),
243            ConfigMsg::Theme(msg) => self.update_theme(|theme| theme.change_config(msg)),
244        }
245    }
246}