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