Skip to main content

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