kas_core/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 super::{ColorsLinear, Theme, ThemeDst, Window};
9use crate::config::{Config, WindowConfig};
10use crate::draw::{DrawIface, DrawSharedImpl, color};
11use crate::event::EventState;
12use crate::theme::ThemeDraw;
13use std::cell::RefCell;
14use std::collections::HashMap;
15
16type DynTheme<DS> = Box<dyn ThemeDst<DS>>;
17
18/// Wrapper around multiple 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 Window = Box<dyn Window>;
82    type Draw<'a> = Box<dyn ThemeDraw + 'a>;
83
84    fn init(&mut self, config: &RefCell<Config>) {
85        if config.borrow().theme.active_theme.is_empty() {
86            for (name, index) in &self.names {
87                if *index == self.active {
88                    let _ = config.borrow_mut().theme.set_active_theme(name.to_string());
89                    break;
90                }
91            }
92        }
93
94        for theme in &mut self.themes {
95            theme.init(config);
96        }
97    }
98
99    fn new_window(&mut self, config: &WindowConfig) -> Self::Window {
100        // We may switch themes here
101        let theme = &config.theme().active_theme;
102        if let Some(index) = self.names.get(theme).cloned()
103            && index != self.active
104        {
105            self.active = index;
106        }
107
108        self.themes[self.active].new_window(config)
109    }
110
111    fn update_window(&mut self, window: &mut Self::Window, config: &WindowConfig) {
112        self.themes[self.active].update_window(window, config);
113    }
114
115    fn draw<'a>(
116        &'a self,
117        draw: DrawIface<'a, DS>,
118        ev: &'a mut EventState,
119        window: &'a mut Self::Window,
120    ) -> Box<dyn ThemeDraw + 'a> {
121        self.themes[self.active].draw(draw, ev, window)
122    }
123
124    fn draw_upcast<'a>(
125        _draw: DrawIface<'a, DS>,
126        _ev: &'a mut EventState,
127        _w: &'a mut Self::Window,
128        _cols: &'a ColorsLinear,
129    ) -> Self::Draw<'a> {
130        unimplemented!()
131    }
132
133    fn clear_color(&self) -> color::Rgba {
134        self.themes[self.active].clear_color()
135    }
136}