Skip to main content

kas_wgpu/
shaded_theme.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//! Shaded theme
7
8use 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/// A theme using simple shading to give apparent depth to elements
28#[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    /// Construct
41    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    // Type-cast to flat_theme's DrawHandle. Should be equivalent to transmute.
131    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    /// Draw an edit box with optional navigation highlight.
140    /// Return the inner rect.
141    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    /// Draw a grip (for slider, scroll bar)
161    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        // track
318        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        // grip
324        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        // track
330        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        // grip
340        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}