1use std::cell::RefCell;
9use std::f32;
10use std::ops::Range;
11use std::time::Instant;
12
13use crate::{DrawShaded, DrawShadedImpl};
14use kas::Id;
15use kas::cast::traits::*;
16use kas::config::{Config, WindowConfig};
17use kas::dir::{Direction, Directional};
18use kas::draw::{color::Rgba, *};
19use kas::event::EventState;
20use kas::geom::*;
21use kas::text::TextDisplay;
22use kas::theme::dimensions as dim;
23use kas::theme::{Background, ThemeDraw, ThemeSize};
24use kas::theme::{ColorsLinear, FlatTheme, InputState, SimpleTheme, Theme};
25use kas::theme::{FrameStyle, MarkStyle};
26
27#[derive(Clone, Debug)]
29pub struct ShadedTheme {
30 base: SimpleTheme,
31}
32
33impl Default for ShadedTheme {
34 fn default() -> Self {
35 Self::new()
36 }
37}
38
39impl ShadedTheme {
40 pub fn new() -> Self {
42 let base = SimpleTheme::new();
43 ShadedTheme { base }
44 }
45}
46
47fn dimensions() -> dim::Parameters {
48 dim::Parameters {
49 m_large: 5.0,
50 m_text: (3.4, 0.8),
51 frame: 5.0,
52 button_frame: 2.0,
53 button_inner: 3.0,
54 check_box: 24.0,
55 mark: 9.0,
56 grip_len: 8.0,
57 shadow_size: Vec2::splat(6.0),
58 shadow_rel_offset: Vec2::ZERO,
59 ..Default::default()
60 }
61}
62
63const NORMS_FRAME: (f32, f32) = (-0.7, 0.7);
64const NORMS_TRACK: (f32, f32) = (0.0, -0.7);
65const NORMS_SUNK: (f32, f32) = (-0.7, 0.0);
66const NORMS_RAISED: (f32, f32) = (0.0, 0.7);
67
68pub struct DrawHandle<'a, DS: DrawSharedImpl> {
69 draw: DrawIface<'a, DS>,
70 ev: &'a mut EventState,
71 w: &'a mut dim::Window<DS::Draw>,
72 cols: &'a ColorsLinear,
73}
74
75impl<DS: DrawSharedImpl> Theme<DS> for ShadedTheme
76where
77 DS::Draw: DrawRoundedImpl + DrawShadedImpl,
78{
79 type Window = dim::Window<DS::Draw>;
80 type Draw<'a> = DrawHandle<'a, DS>;
81
82 fn init(&mut self, config: &RefCell<Config>) {
83 <SimpleTheme as Theme<DS>>::init(&mut self.base, config)
84 }
85
86 fn new_window(&mut self, config: &WindowConfig) -> Self::Window {
87 self.base.cols = config.theme().get_active_scheme().into();
88 dim::Window::new(&dimensions(), config)
89 }
90
91 fn update_window(&mut self, w: &mut Self::Window, config: &WindowConfig) -> bool {
92 self.base.cols = config.theme().get_active_scheme().into();
93 w.update(&dimensions(), config)
94 }
95
96 fn draw<'a>(
97 &'a self,
98 draw: DrawIface<'a, DS>,
99 ev: &'a mut EventState,
100 w: &'a mut Self::Window,
101 ) -> Self::Draw<'a> {
102 w.anim.update();
103
104 DrawHandle {
105 draw,
106 ev,
107 w,
108 cols: &self.base.cols,
109 }
110 }
111
112 fn draw_upcast<'a>(
113 draw: DrawIface<'a, DS>,
114 ev: &'a mut EventState,
115 w: &'a mut Self::Window,
116 cols: &'a ColorsLinear,
117 ) -> Self::Draw<'a> {
118 DrawHandle { draw, ev, w, cols }
119 }
120
121 fn clear_color(&self) -> Rgba {
122 <SimpleTheme as Theme<DS>>::clear_color(&self.base)
123 }
124}
125
126impl<'a, DS: DrawSharedImpl> DrawHandle<'a, DS>
127where
128 DS::Draw: DrawRoundedImpl + DrawShadedImpl,
129{
130 fn as_flat<'b, 'c>(&'b mut self) -> <FlatTheme as Theme<DS>>::Draw<'c>
132 where
133 'a: 'c,
134 'b: 'c,
135 {
136 FlatTheme::draw_upcast(self.draw.re(), self.ev, self.w, self.cols)
137 }
138
139 fn draw_edit_box(&mut self, outer: Rect, bg_col: Rgba, nav_focus: bool) -> Quad {
142 let outer = Quad::conv(outer);
143 let inner = outer.shrink(self.w.dims.frame as f32);
144 #[cfg(debug_assertions)]
145 {
146 if !(inner.a < inner.b) {
147 log::warn!("draw_edit_box: frame too small: {outer:?}");
148 }
149 }
150
151 let outer_col = self.cols.background;
152 let inner_col = if nav_focus { self.cols.accent_soft } else { outer_col };
153 self.draw
154 .shaded_square_frame(outer, inner, NORMS_SUNK, outer_col, inner_col);
155
156 self.draw.rect(inner, bg_col);
157 inner
158 }
159
160 fn draw_grip(&mut self, rect: Rect, state: InputState) {
162 let outer = Quad::conv(rect);
163 let thickness = outer.size().min_comp() / 2.0;
164 let inner = outer.shrink(thickness);
165 let col = self.cols.accent_soft_state(state);
166 self.draw
167 .shaded_round_frame(outer, inner, NORMS_RAISED, col);
168
169 if let Some(col) = self.cols.nav_region(state) {
170 let outer = outer.shrink(thickness / 4.0);
171 self.draw.rounded_frame(outer, inner, 0.6, col);
172 }
173 }
174}
175
176#[kas::extends(ThemeDraw using self.as_flat())]
177impl<'a, DS: DrawSharedImpl> ThemeDraw for DrawHandle<'a, DS>
178where
179 DS::Draw: DrawRoundedImpl + DrawShadedImpl,
180{
181 fn components(&mut self) -> (&dyn ThemeSize, &mut dyn Draw, &mut EventState) {
182 (self.w, &mut self.draw, self.ev)
183 }
184
185 fn colors(&self) -> &ColorsLinear {
186 self.cols
187 }
188
189 fn draw_rounded(&mut self) -> Option<&mut dyn DrawRounded> {
190 Some(&mut self.draw)
191 }
192
193 fn new_pass<'b>(
194 &mut self,
195 inner_rect: Rect,
196 offset: Offset,
197 class: PassType,
198 f: Box<dyn FnOnce(&mut dyn ThemeDraw) + 'b>,
199 ) {
200 let mut shadow = Default::default();
201 let mut outer_rect = inner_rect;
202 if class == PassType::Overlay {
203 shadow = Quad::conv(inner_rect);
204 shadow.a += self.w.dims.shadow_a;
205 shadow.b += self.w.dims.shadow_b;
206 let a = Coord::conv_floor(shadow.a);
207 let b = Coord::conv_ceil(shadow.b);
208 outer_rect = Rect::new(a, (b - a).cast());
209 }
210 let mut draw = self.draw.new_pass(outer_rect, offset, class);
211
212 if class == PassType::Overlay {
213 shadow += offset.cast();
214 let inner = Quad::conv(inner_rect + offset);
215 draw.rounded_frame_2col(shadow, inner, Rgba::BLACK, Rgba::TRANSPARENT);
216 }
217
218 let mut handle = DrawHandle {
219 draw,
220 ev: self.ev,
221 w: self.w,
222 cols: self.cols,
223 };
224 f(&mut handle);
225 }
226
227 fn frame(&mut self, id: &Id, rect: Rect, style: FrameStyle, bg: Background) {
228 match style {
229 FrameStyle::Frame => {
230 let outer = Quad::conv(rect);
231 let inner = outer.shrink(self.w.dims.frame as f32);
232 let col = self.cols.background;
233 self.draw.shaded_round_frame(outer, inner, NORMS_FRAME, col);
234 }
235 FrameStyle::Popup => {
236 let outer = Quad::conv(rect);
237 self.draw.rect(outer, self.cols.background);
238 }
239 FrameStyle::MenuEntry => {
240 let state = InputState::new_all(self.ev, id);
241 if let Some(col) = self.cols.menu_entry(state) {
242 let outer = Quad::conv(rect);
243 self.draw.rect(outer, col);
244 }
245 }
246 FrameStyle::Button | FrameStyle::InvisibleButton => {
247 let state = InputState::new_all(self.ev, id);
248 if style == FrameStyle::InvisibleButton && !state.under_mouse() {
249 return;
250 }
251 let outer = Quad::conv(rect);
252 let frame = self.w.dims.button_frame as f32 + self.w.dims.button_inner as f32;
253 let inner = outer.shrink(frame);
254 let col_bg = self.cols.from_bg(bg, state, true);
255
256 self.draw
257 .shaded_round_frame(outer, inner, NORMS_RAISED, col_bg);
258 self.draw.rect(inner, col_bg);
259
260 if let Some(col) = self.cols.nav_region(state) {
261 let outer = outer.shrink(self.w.dims.m_inner as f32);
262 self.draw.rounded_frame(outer, inner, 0.6, col);
263 }
264 }
265 FrameStyle::EditBox => {
266 let state = InputState::new_except_depress(self.ev, id);
267 let bg_col = self.cols.from_edit_bg(bg, state);
268 self.draw_edit_box(rect, bg_col, state.nav_focus());
269 }
270 FrameStyle::Window => {
271 let outer = Quad::conv(rect);
272 let inner = outer.shrink(self.w.dims.frame_window as f32);
273 let col = self.cols.background;
274 self.draw
275 .shaded_round_frame(outer, inner, NORMS_RAISED, col);
276 self.draw.rect(inner, col);
277 }
278 style => self.as_flat().frame(id, rect, style, bg),
279 }
280 }
281
282 fn separator(&mut self, rect: Rect) {
283 let outer = Quad::conv(rect);
284 let inner = outer.shrink(outer.size().min_comp() / 2.0);
285 let col = self.cols.background;
286 self.draw.shaded_round_frame(outer, inner, NORMS_TRACK, col);
287 }
288
289 fn check_box(&mut self, id: &Id, rect: Rect, checked: bool, last_change: Option<Instant>) {
290 let state = InputState::new_all(self.ev, id);
291 let bg_col = self.cols.from_edit_bg(Default::default(), state);
292 let inner = self.draw_edit_box(rect, bg_col, state.nav_focus());
293
294 self.as_flat()
295 .check_mark(inner, state, checked, last_change);
296 }
297
298 fn radio_box(&mut self, id: &Id, rect: Rect, checked: bool, last_change: Option<Instant>) {
299 let state = InputState::new_all(self.ev, id);
300 let anim_fade = 1.0 - self.w.anim.fade_bool(self.draw.draw, checked, last_change);
301
302 let bg_col = self.cols.from_edit_bg(Default::default(), state);
303
304 let inner = self
305 .draw_edit_box(rect, bg_col, state.nav_focus())
306 .shrink(self.w.dims.m_inner as f32);
307
308 if anim_fade < 1.0 {
309 let v = inner.size() * (anim_fade / 2.0);
310 let inner = Quad::from_coords(inner.a + v, inner.b - v);
311 let col = self.cols.check_mark_state(state);
312 self.draw.shaded_circle(inner, NORMS_RAISED, col);
313 }
314 }
315
316 fn scroll_bar(&mut self, id: &Id, id2: &Id, rect: Rect, h_rect: Rect, _: Direction) {
317 let outer = Quad::conv(rect);
319 let inner = outer.shrink(outer.size().min_comp() / 2.0);
320 let col = self.cols.background;
321 self.draw.shaded_round_frame(outer, inner, NORMS_TRACK, col);
322
323 let state = InputState::new2(self.ev, id, id2);
325 self.draw_grip(h_rect, state);
326 }
327
328 fn slider(&mut self, id: &Id, id2: &Id, rect: Rect, h_rect: Rect, dir: Direction) {
329 let mut outer = Quad::conv(rect);
331 outer = match dir.is_horizontal() {
332 true => outer.shrink_vec(Vec2(0.0, outer.size().1 * (3.0 / 8.0))),
333 false => outer.shrink_vec(Vec2(outer.size().0 * (3.0 / 8.0), 0.0)),
334 };
335 let inner = outer.shrink(outer.size().min_comp() / 2.0);
336 let col = self.cols.background;
337 self.draw.shaded_round_frame(outer, inner, NORMS_TRACK, col);
338
339 let state = InputState::new2(self.ev, id, id2);
341 self.draw_grip(h_rect, state);
342 }
343
344 fn progress_bar(&mut self, _: &Id, rect: Rect, dir: Direction, value: f32) {
345 let mut outer = Quad::conv(rect);
346 let inner = outer.shrink(outer.size().min_comp() / 2.0);
347 let col = self.cols.frame;
348 self.draw.shaded_round_frame(outer, inner, NORMS_TRACK, col);
349
350 if dir.is_horizontal() {
351 outer.b.0 = outer.a.0 + value * (outer.b.0 - outer.a.0);
352 } else {
353 outer.b.1 = outer.a.1 + value * (outer.b.1 - outer.a.1);
354 }
355 let thickness = outer.size().min_comp() / 2.0;
356 let inner = outer.shrink(thickness);
357 let col = self.cols.accent_soft;
358 self.draw
359 .shaded_round_frame(outer, inner, NORMS_RAISED, col);
360 }
361}