Skip to main content

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/// Run-time switching support for pre-loaded themes
19pub struct MultiTheme<DS> {
20    names: HashMap<String, usize>,
21    themes: Vec<DynTheme<DS>>,
22    active: usize,
23}
24
25impl<DS> MultiTheme<DS> {
26    /// Construct an empty `MultiTheme`
27    ///
28    /// **At least one theme must be added before the UI starts.**.
29    #[allow(clippy::new_without_default)]
30    pub fn new() -> MultiTheme<DS> {
31        MultiTheme {
32            names: HashMap::new(),
33            themes: vec![],
34            active: 0,
35        }
36    }
37
38    /// Add a theme
39    ///
40    /// Returns the index of the new theme.
41    pub fn add<S: ToString, T>(&mut self, name: S, theme: T) -> usize
42    where
43        DS: DrawSharedImpl,
44        T: ThemeDst<DS> + 'static,
45    {
46        let index = self.themes.len();
47        self.names.insert(name.to_string(), index);
48        self.themes.push(Box::new(theme));
49        index
50    }
51
52    /// Set the active theme
53    ///
54    /// If this is not called, then the first theme added will be active.
55    ///
56    /// An invalid index will cause the UI to panic on start.
57    pub fn set_active(&mut self, index: usize) {
58        self.active = index;
59    }
60}
61
62impl<DS: DrawSharedImpl> Theme<DS> for MultiTheme<DS> {
63    type Window = Box<dyn Window>;
64    type Draw<'a> = Box<dyn ThemeDraw + 'a>;
65
66    fn init(&mut self, config: &RefCell<Config>) {
67        if self.active >= self.themes.len() {
68            panic!(
69                "MultiTheme: invalid index {} in list of {} themes added",
70                self.active,
71                self.themes.len()
72            );
73        }
74
75        if config.borrow().theme.active_theme.is_empty() {
76            for (name, index) in &self.names {
77                if *index == self.active {
78                    let _ = config.borrow_mut().theme.set_active_theme(name.to_string());
79                    break;
80                }
81            }
82        }
83
84        for theme in &mut self.themes {
85            theme.init(config);
86        }
87    }
88
89    fn new_window(&mut self, config: &WindowConfig) -> Self::Window {
90        // We may switch themes here
91        let theme = &config.theme().active_theme;
92        if let Some(index) = self.names.get(theme).cloned()
93            && index != self.active
94        {
95            self.active = index;
96        }
97
98        self.themes[self.active].new_window(config)
99    }
100
101    fn update_window(&mut self, window: &mut Self::Window, config: &WindowConfig) -> bool {
102        self.themes[self.active].update_window(window, config)
103    }
104
105    fn draw<'a>(
106        &'a self,
107        draw: DrawIface<'a, DS>,
108        ev: &'a mut EventState,
109        window: &'a mut Self::Window,
110    ) -> Box<dyn ThemeDraw + 'a> {
111        self.themes[self.active].draw(draw, ev, window)
112    }
113
114    fn draw_upcast<'a>(
115        _draw: DrawIface<'a, DS>,
116        _ev: &'a mut EventState,
117        _w: &'a mut Self::Window,
118        _cols: &'a ColorsLinear,
119    ) -> Self::Draw<'a> {
120        unimplemented!()
121    }
122
123    fn clear_color(&self) -> color::Rgba {
124        self.themes[self.active].clear_color()
125    }
126}