1use 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
18pub struct MultiTheme<DS> {
20 names: HashMap<String, usize>,
21 themes: Vec<DynTheme<DS>>,
22 active: usize,
23}
24
25pub struct MultiThemeBuilder<DS> {
29 names: HashMap<String, usize>,
30 themes: Vec<DynTheme<DS>>,
31}
32
33impl<DS> MultiTheme<DS> {
34 pub fn builder() -> MultiThemeBuilder<DS> {
36 MultiThemeBuilder {
37 names: HashMap::new(),
38 themes: vec![],
39 }
40 }
41}
42
43impl<DS> MultiThemeBuilder<DS> {
44 #[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 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 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 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 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 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 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}