kas_core/config/
theme.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//! Theme configuration
7
8use crate::Action;
9use crate::theme::ColorsSrgb;
10use std::collections::BTreeMap;
11use std::time::Duration;
12
13/// A message which may be used to update [`ThemeConfig`]
14#[derive(Clone, Debug)]
15#[non_exhaustive]
16pub enum ThemeConfigMsg {
17    /// Changes the active theme (reliant on `MultiTheme` to do the work)
18    SetActiveTheme(String),
19    /// Changes the active colour scheme (only if this already exists)
20    SetActiveScheme(String),
21    /// Adds or updates a scheme. Does not change the active scheme.
22    AddScheme(String, ColorsSrgb),
23    /// Removes a scheme
24    RemoveScheme(String),
25    /// Set the fade duration (ms)
26    FadeDurationMs(u32),
27}
28
29/// Event handling configuration
30#[derive(Clone, Debug, PartialEq)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32pub struct ThemeConfig {
33    /// The theme to use (used by `MultiTheme`)
34    #[cfg_attr(feature = "serde", serde(default))]
35    pub active_theme: String,
36
37    /// The colour scheme to use
38    #[cfg_attr(feature = "serde", serde(default = "defaults::default_scheme"))]
39    pub active_scheme: String,
40
41    /// All colour schemes
42    /// TODO: possibly we should not save default schemes and merge when
43    /// loading (perhaps via a `PartialConfig` type).
44    #[cfg_attr(feature = "serde", serde(default = "defaults::color_schemes"))]
45    pub color_schemes: BTreeMap<String, ColorsSrgb>,
46
47    /// Text cursor blink rate: delay between switching states
48    #[cfg_attr(feature = "serde", serde(default = "defaults::cursor_blink_rate_ms"))]
49    pub cursor_blink_rate_ms: u32,
50
51    /// Transition duration used in animations
52    #[cfg_attr(feature = "serde", serde(default = "defaults::transition_fade_ms"))]
53    pub transition_fade_ms: u32,
54}
55
56impl Default for ThemeConfig {
57    fn default() -> Self {
58        ThemeConfig {
59            active_theme: "".to_string(),
60            active_scheme: defaults::default_scheme(),
61            color_schemes: defaults::color_schemes(),
62            cursor_blink_rate_ms: defaults::cursor_blink_rate_ms(),
63            transition_fade_ms: defaults::transition_fade_ms(),
64        }
65    }
66}
67
68impl ThemeConfig {
69    pub(super) fn change_config(&mut self, msg: ThemeConfigMsg) -> Action {
70        match msg {
71            ThemeConfigMsg::SetActiveTheme(theme) => self.set_active_theme(theme),
72            ThemeConfigMsg::SetActiveScheme(scheme) => self.set_active_scheme(scheme),
73            ThemeConfigMsg::AddScheme(scheme, colors) => self.add_scheme(scheme, colors),
74            ThemeConfigMsg::RemoveScheme(scheme) => self.remove_scheme(&scheme),
75            ThemeConfigMsg::FadeDurationMs(dur) => {
76                self.transition_fade_ms = dur;
77                Action::empty()
78            }
79        }
80    }
81}
82
83impl ThemeConfig {
84    /// Set the active theme (by name)
85    ///
86    /// Only does anything if `MultiTheme` (or another multiplexer) is in use
87    /// and knows this theme.
88    pub fn set_active_theme(&mut self, theme: impl ToString) -> Action {
89        let theme = theme.to_string();
90        if self.active_theme == theme {
91            Action::empty()
92        } else {
93            self.active_theme = theme;
94            Action::THEME_SWITCH
95        }
96    }
97
98    /// Active colour scheme (name)
99    ///
100    /// An empty string will resolve the default colour scheme.
101    #[inline]
102    pub fn active_scheme(&self) -> &str {
103        &self.active_scheme
104    }
105
106    /// Set the active colour scheme (by name)
107    ///
108    /// Does nothing if the named scheme is not found.
109    pub fn set_active_scheme(&mut self, scheme: impl ToString) -> Action {
110        let scheme = scheme.to_string();
111        if self.color_schemes.keys().any(|k| *k == scheme) {
112            self.active_scheme = scheme.to_string();
113            Action::THEME_UPDATE
114        } else {
115            Action::empty()
116        }
117    }
118
119    /// Iterate over all colour schemes
120    #[inline]
121    pub fn color_schemes(&self) -> impl Iterator<Item = (&str, &ColorsSrgb)> {
122        self.color_schemes.iter().map(|(s, t)| (s.as_str(), t))
123    }
124
125    /// Get a colour scheme by name
126    #[inline]
127    pub fn get_color_scheme(&self, name: &str) -> Option<&ColorsSrgb> {
128        self.color_schemes.get(name)
129    }
130
131    /// Get the active colour scheme
132    #[inline]
133    pub fn get_active_scheme(&self) -> &ColorsSrgb {
134        self.color_schemes
135            .get(&self.active_scheme)
136            .unwrap_or(&ColorsSrgb::LIGHT)
137    }
138
139    /// Add or update a colour scheme
140    pub fn add_scheme(&mut self, scheme: impl ToString, colors: ColorsSrgb) -> Action {
141        self.color_schemes.insert(scheme.to_string(), colors);
142        Action::empty()
143    }
144
145    /// Remove a colour scheme
146    pub fn remove_scheme(&mut self, scheme: &str) -> Action {
147        self.color_schemes.remove(scheme);
148        if scheme == self.active_scheme {
149            Action::THEME_UPDATE
150        } else {
151            Action::empty()
152        }
153    }
154
155    /// Get the cursor blink rate (delay)
156    #[inline]
157    pub fn cursor_blink_rate(&self) -> Duration {
158        Duration::from_millis(self.cursor_blink_rate_ms as u64)
159    }
160
161    /// Get the fade duration used in transition animations
162    #[inline]
163    pub fn transition_fade_duration(&self) -> Duration {
164        Duration::from_millis(self.transition_fade_ms as u64)
165    }
166}
167
168mod defaults {
169    use super::*;
170
171    #[cfg(not(feature = "dark-light"))]
172    pub fn default_scheme() -> String {
173        "light".to_string()
174    }
175
176    #[cfg(feature = "dark-light")]
177    pub fn default_scheme() -> String {
178        use dark_light::Mode;
179        match dark_light::detect() {
180            Ok(Mode::Dark) => "dark".to_string(),
181            _ => "light".to_string(),
182        }
183    }
184
185    pub fn color_schemes() -> BTreeMap<String, ColorsSrgb> {
186        let mut schemes = BTreeMap::new();
187        schemes.insert("light".to_string(), ColorsSrgb::LIGHT);
188        schemes.insert("dark".to_string(), ColorsSrgb::DARK);
189        schemes.insert("blue".to_string(), ColorsSrgb::BLUE);
190        schemes
191    }
192
193    pub fn cursor_blink_rate_ms() -> u32 {
194        600
195    }
196
197    pub fn transition_fade_ms() -> u32 {
198        150
199    }
200}