kas_theme/
multi.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//! Wrapper around mutliple themes, supporting run-time switching
7
8use std::collections::HashMap;
9
10use crate::{Config, Theme, ThemeDst, Window};
11use kas::draw::{color, DrawIface, DrawSharedImpl, SharedState};
12use kas::event::EventState;
13use kas::theme::{ThemeControl, ThemeDraw};
14use kas::TkAction;
15
16type DynTheme<DS> = Box<dyn ThemeDst<DS>>;
17
18/// Wrapper around mutliple themes, supporting run-time switching
19pub struct MultiTheme<DS> {
20    names: HashMap<String, usize>,
21    themes: Vec<DynTheme<DS>>,
22    active: usize,
23}
24
25/// Builder for [`MultiTheme`]
26///
27/// Construct via [`MultiTheme::builder`].
28pub struct MultiThemeBuilder<DS> {
29    names: HashMap<String, usize>,
30    themes: Vec<DynTheme<DS>>,
31}
32
33impl<DS> MultiTheme<DS> {
34    /// Construct with builder pattern
35    pub fn builder() -> MultiThemeBuilder<DS> {
36        MultiThemeBuilder {
37            names: HashMap::new(),
38            themes: vec![],
39        }
40    }
41}
42
43impl<DS> MultiThemeBuilder<DS> {
44    /// Add a theme
45    #[must_use]
46    pub fn add<S: ToString, T>(mut self, name: S, theme: T) -> Self
47    where
48        DS: DrawSharedImpl,
49        T: ThemeDst<DS> + 'static,
50    {
51        let index = self.themes.len();
52        self.names.insert(name.to_string(), index);
53        self.themes.push(Box::new(theme));
54        self
55    }
56
57    /// Build
58    ///
59    /// Returns `None` if no themes were added.
60    pub fn try_build(self) -> Option<MultiTheme<DS>> {
61        if self.themes.is_empty() {
62            return None;
63        }
64        Some(MultiTheme {
65            names: self.names,
66            themes: self.themes,
67            active: 0,
68        })
69    }
70
71    /// Build
72    ///
73    /// Panics if no themes were added.
74    pub fn build(self) -> MultiTheme<DS> {
75        self.try_build()
76            .unwrap_or_else(|| panic!("MultiThemeBuilder: no themes added"))
77    }
78}
79
80impl<DS: DrawSharedImpl> Theme<DS> for MultiTheme<DS> {
81    type Config = Config;
82    type Window = Box<dyn Window>;
83
84    type Draw<'a> = Box<dyn ThemeDraw + 'a>;
85
86    fn config(&self) -> std::borrow::Cow<Self::Config> {
87        let boxed_config = self.themes[self.active].config();
88        // TODO: write each sub-theme's config instead of this stupid cast!
89        let config: Config = boxed_config
90            .as_ref()
91            .downcast_ref::<Config>()
92            .unwrap()
93            .clone();
94        std::borrow::Cow::Owned(config)
95    }
96
97    fn apply_config(&mut self, config: &Self::Config) -> TkAction {
98        let mut action = TkAction::empty();
99        for theme in &mut self.themes {
100            action |= theme.apply_config(config);
101        }
102        action
103    }
104
105    fn init(&mut self, shared: &mut SharedState<DS>) {
106        for theme in &mut self.themes {
107            theme.init(shared);
108        }
109    }
110
111    fn new_window(&self, dpi_factor: f32) -> Self::Window {
112        self.themes[self.active].new_window(dpi_factor)
113    }
114
115    fn update_window(&self, window: &mut Self::Window, dpi_factor: f32) {
116        self.themes[self.active].update_window(window, dpi_factor);
117    }
118
119    fn draw<'a>(
120        &'a self,
121        draw: DrawIface<'a, DS>,
122        ev: &'a mut EventState,
123        window: &'a mut Self::Window,
124    ) -> Box<dyn ThemeDraw + 'a> {
125        self.themes[self.active].draw(draw, ev, window)
126    }
127
128    fn clear_color(&self) -> color::Rgba {
129        self.themes[self.active].clear_color()
130    }
131}
132
133impl<DS> ThemeControl for MultiTheme<DS> {
134    fn set_font_size(&mut self, size: f32) -> TkAction {
135        // Slightly inefficient, but sufficient: update both
136        // (Otherwise we would have to call set_scheme in set_theme too.)
137        let mut action = TkAction::empty();
138        for theme in &mut self.themes {
139            action = action.max(theme.set_font_size(size));
140        }
141        action
142    }
143
144    fn set_scheme(&mut self, scheme: &str) -> TkAction {
145        // Slightly inefficient, but sufficient: update all
146        // (Otherwise we would have to call set_scheme in set_theme too.)
147        let mut action = TkAction::empty();
148        for theme in &mut self.themes {
149            action = action.max(theme.set_scheme(scheme));
150        }
151        action
152    }
153
154    fn list_schemes(&self) -> Vec<&str> {
155        // We list only schemes of the active theme. Probably all themes should
156        // have the same schemes anyway.
157        self.themes[self.active].list_schemes()
158    }
159
160    fn set_theme(&mut self, theme: &str) -> TkAction {
161        if let Some(index) = self.names.get(theme).cloned() {
162            if index != self.active {
163                self.active = index;
164                return TkAction::RESIZE | TkAction::THEME_UPDATE;
165            }
166        }
167        TkAction::empty()
168    }
169}