1use std::f32;
9use std::ops::Range;
10use std::time::Instant;
11
12use crate::{dim, ColorsLinear, Config, InputState, SimpleTheme, Theme};
13use kas::cast::traits::*;
14use kas::dir::{Direction, Directional};
15use kas::draw::{color::Rgba, *};
16use kas::event::EventState;
17use kas::geom::*;
18use kas::text::{TextApi, TextDisplay};
19use kas::theme::{Background, FrameStyle, MarkStyle, TextClass};
20use kas::theme::{ThemeControl, ThemeDraw, ThemeSize};
21use kas::{TkAction, WidgetId};
22
23const BG_SHRINK_FACTOR: f32 = 1.0 - std::f32::consts::FRAC_1_SQRT_2;
26
27const SHADOW_HOVER: f32 = 1.1;
29const SHADOW_POPUP: f32 = 1.2;
31
32#[derive(Clone, Debug)]
34pub struct FlatTheme {
35 base: SimpleTheme,
36}
37
38impl Default for FlatTheme {
39 fn default() -> Self {
40 Self::new()
41 }
42}
43
44impl FlatTheme {
45 #[inline]
47 pub fn new() -> Self {
48 let base = SimpleTheme::new();
49 FlatTheme { base }
50 }
51
52 #[inline]
56 #[must_use]
57 pub fn with_font_size(mut self, pt_size: f32) -> Self {
58 self.base.config.set_font_size(pt_size);
59 self
60 }
61
62 #[inline]
66 #[must_use]
67 pub fn with_colours(mut self, scheme: &str) -> Self {
68 self.base.config.set_active_scheme(scheme);
69 if let Some(scheme) = self.base.config.get_color_scheme(scheme) {
70 self.base.cols = scheme.into();
71 }
72 self
73 }
74}
75
76fn dimensions() -> dim::Parameters {
77 dim::Parameters {
78 button_frame: 2.4,
80 button_inner: 0.0,
81 slider_size: Vec2(24.0, 18.0),
82 shadow_size: Vec2(4.0, 4.0),
83 shadow_rel_offset: Vec2(0.2, 0.3),
84 ..Default::default()
85 }
86}
87
88pub struct DrawHandle<'a, DS: DrawSharedImpl> {
89 pub(crate) draw: DrawIface<'a, DS>,
90 pub(crate) ev: &'a mut EventState,
91 pub(crate) w: &'a mut dim::Window<DS::Draw>,
92 pub(crate) cols: &'a ColorsLinear,
93}
94
95impl<DS: DrawSharedImpl> Theme<DS> for FlatTheme
96where
97 DS::Draw: DrawRoundedImpl,
98{
99 type Config = Config;
100 type Window = dim::Window<DS::Draw>;
101
102 type Draw<'a> = DrawHandle<'a, DS>;
103
104 fn config(&self) -> std::borrow::Cow<Self::Config> {
105 <SimpleTheme as Theme<DS>>::config(&self.base)
106 }
107
108 fn apply_config(&mut self, config: &Self::Config) -> TkAction {
109 <SimpleTheme as Theme<DS>>::apply_config(&mut self.base, config)
110 }
111
112 fn init(&mut self, shared: &mut SharedState<DS>) {
113 <SimpleTheme as Theme<DS>>::init(&mut self.base, shared)
114 }
115
116 fn new_window(&self, dpi_factor: f32) -> Self::Window {
117 let fonts = self.base.fonts.as_ref().unwrap().clone();
118 dim::Window::new(&dimensions(), &self.base.config, dpi_factor, fonts)
119 }
120
121 fn update_window(&self, w: &mut Self::Window, dpi_factor: f32) {
122 w.update(&dimensions(), &self.base.config, dpi_factor);
123 }
124
125 fn draw<'a>(
126 &'a self,
127 draw: DrawIface<'a, DS>,
128 ev: &'a mut EventState,
129 w: &'a mut Self::Window,
130 ) -> Self::Draw<'a> {
131 w.anim.update();
132
133 DrawHandle {
134 draw,
135 ev,
136 w,
137 cols: &self.base.cols,
138 }
139 }
140
141 fn clear_color(&self) -> Rgba {
142 self.base.cols.background
143 }
144}
145
146impl ThemeControl for FlatTheme {
147 fn set_font_size(&mut self, pt_size: f32) -> TkAction {
148 self.base.set_font_size(pt_size)
149 }
150
151 fn list_schemes(&self) -> Vec<&str> {
152 self.base.list_schemes()
153 }
154
155 fn set_scheme(&mut self, name: &str) -> TkAction {
156 self.base.set_scheme(name)
157 }
158}
159
160impl<'a, DS: DrawSharedImpl> DrawHandle<'a, DS>
161where
162 DS::Draw: DrawRoundedImpl,
163{
164 fn as_simple<'b, 'c>(&'b mut self) -> super::simple_theme::DrawHandle<'c, DS>
166 where
167 'a: 'c,
168 'b: 'c,
169 {
170 super::simple_theme::DrawHandle {
171 draw: self.draw.re(),
172 ev: self.ev,
173 w: self.w,
174 cols: self.cols,
175 }
176 }
177
178 pub fn button_frame(
179 &mut self,
180 outer: Quad,
181 col_frame: Rgba,
182 col_bg: Rgba,
183 state: InputState,
184 ) -> Quad {
185 let inner = outer.shrink(self.w.dims.button_frame as f32);
186 #[cfg(debug_assertions)]
187 {
188 if !inner.a.lt(inner.b) {
189 log::warn!("button_frame: frame too small: {outer:?}");
190 }
191 }
192
193 if !(self.cols.is_dark || state.disabled() || state.depress()) {
194 let (mut a, mut b) = (self.w.dims.shadow_a, self.w.dims.shadow_b);
195 if state.hover() {
196 a = a * SHADOW_HOVER;
197 b = b * SHADOW_HOVER;
198 }
199 let shadow_outer = Quad::from_coords(a + inner.a, b + inner.b);
200 let col1 = if self.cols.is_dark { col_frame } else { Rgba::BLACK };
201 let mut col2 = col1;
202 col2.a = 0.0;
203 self.draw
204 .rounded_frame_2col(shadow_outer, inner, col1, col2);
205 }
206
207 let bgr = outer.shrink(self.w.dims.button_frame as f32 * BG_SHRINK_FACTOR);
208 self.draw.rect(bgr, col_bg);
209
210 self.draw
211 .rounded_frame(outer, inner, BG_SHRINK_FACTOR, col_frame);
212 inner
213 }
214
215 pub fn edit_box(&mut self, id: &WidgetId, outer: Quad, bg: Background) {
216 let state = InputState::new_except_depress(self.ev, id);
217 let col_bg = self.cols.from_edit_bg(bg, state);
218 if col_bg != self.cols.background {
219 let inner = outer.shrink(self.w.dims.button_frame as f32 * BG_SHRINK_FACTOR);
220 self.draw.rect(inner, col_bg);
221 }
222
223 let inner = outer.shrink(self.w.dims.button_frame as f32);
224 self.draw
225 .rounded_frame(outer, inner, BG_SHRINK_FACTOR, self.cols.frame);
226
227 if !state.disabled() && !self.cols.is_dark && (state.nav_focus() || state.hover()) {
228 let r = 0.5 * self.w.dims.button_frame as f32;
229 let y = outer.b.1 - r;
230 let a = Vec2(outer.a.0 + r, y);
231 let b = Vec2(outer.b.0 - r, y);
232 let col = if state.nav_focus() {
233 self.cols.nav_focus
234 } else {
235 self.cols.text
236 };
237
238 const F: f32 = 0.6;
239 let (sa, sb) = (self.w.dims.shadow_a * F, self.w.dims.shadow_b * F);
240 let outer = Quad::from_coords(a + sa, b + sb);
241 let inner = Quad::from_coords(a, b);
242 let col1 = if self.cols.is_dark { col } else { Rgba::BLACK };
243 let mut col2 = col1;
244 col2.a = 0.0;
245 self.draw.rounded_frame_2col(outer, inner, col1, col2);
246
247 self.draw.rounded_line(a, b, r, col);
248 }
249 }
250
251 pub fn check_mark(
252 &mut self,
253 inner: Quad,
254 state: InputState,
255 checked: bool,
256 last_change: Option<Instant>,
257 ) {
258 let anim_fade = 1.0 - self.w.anim.fade_bool(self.draw.draw, checked, last_change);
259 if anim_fade < 1.0 {
260 let inner = inner.shrink(self.w.dims.m_inner as f32);
261 let v = inner.size() * (anim_fade / 2.0);
262 let inner = Quad::from_coords(inner.a + v, inner.b - v);
263 let col = self.cols.check_mark_state(state);
264 let f = self.w.dims.mark_line;
265 if inner.size().min_comp() >= 2.0 * f {
266 let inner = inner.shrink(f);
267 let size = inner.size();
268 let vstep = size.1 * 0.125;
269 let a = Vec2(inner.a.0, inner.b.1 - 3.0 * vstep);
270 let b = Vec2(inner.a.0 + size.0 * 0.25, inner.b.1 - vstep);
271 let c = Vec2(inner.b.0, inner.a.1 + vstep);
272 self.draw.rounded_line(a, b, f, col);
273 self.draw.rounded_line(b, c, f, col);
274 } else {
275 self.draw.rect(inner, col);
276 }
277 }
278 }
279}
280
281#[kas::extends(ThemeDraw, base=self.as_simple())]
282impl<'a, DS: DrawSharedImpl> ThemeDraw for DrawHandle<'a, DS>
283where
284 DS::Draw: DrawRoundedImpl,
285{
286 fn components(&mut self) -> (&dyn ThemeSize, &mut dyn Draw, &mut EventState) {
287 (self.w, &mut self.draw, self.ev)
288 }
289
290 fn new_pass<'b>(
291 &mut self,
292 inner_rect: Rect,
293 offset: Offset,
294 class: PassType,
295 f: Box<dyn FnOnce(&mut dyn ThemeDraw) + 'b>,
296 ) {
297 let mut shadow = Default::default();
298 let mut outer_rect = inner_rect;
299 if class == PassType::Overlay {
300 shadow = Quad::conv(inner_rect);
301 shadow.a += self.w.dims.shadow_a * SHADOW_POPUP;
302 shadow.b += self.w.dims.shadow_b * SHADOW_POPUP;
303 let a = Coord::conv_floor(shadow.a);
304 let b = Coord::conv_ceil(shadow.b);
305 outer_rect = Rect::new(a, (b - a).cast());
306 }
307 let mut draw = self.draw.new_pass(outer_rect, offset, class);
308
309 if class == PassType::Overlay {
310 shadow += offset.cast();
311 let inner = Quad::conv(inner_rect + offset).shrink(self.w.dims.menu_frame as f32);
312 draw.rounded_frame_2col(shadow, inner, Rgba::BLACK, Rgba::TRANSPARENT);
313 }
314
315 let mut handle = DrawHandle {
316 draw,
317 ev: self.ev,
318 w: self.w,
319 cols: self.cols,
320 };
321 f(&mut handle);
322 }
323
324 fn frame(&mut self, id: &WidgetId, rect: Rect, style: FrameStyle, bg: Background) {
325 let outer = Quad::conv(rect);
326 match style {
327 FrameStyle::Frame => {
328 let inner = outer.shrink(self.w.dims.frame as f32);
329 self.draw
330 .rounded_frame(outer, inner, BG_SHRINK_FACTOR, self.cols.frame);
331 }
332 FrameStyle::Popup => {
333 let size = self.w.dims.menu_frame as f32;
337 let inner = outer.shrink(size);
338 self.draw
339 .rounded_frame(outer, inner, BG_SHRINK_FACTOR, self.cols.frame);
340 let inner = outer.shrink(size * BG_SHRINK_FACTOR);
341 self.draw.rect(inner, self.cols.background);
342 }
343 FrameStyle::MenuEntry => {
344 let state = InputState::new_all(self.ev, id);
345 if let Some(col) = self.cols.menu_entry(state) {
346 let size = self.w.dims.menu_frame as f32;
347 let inner = outer.shrink(size);
348 self.draw.rounded_frame(outer, inner, BG_SHRINK_FACTOR, col);
349 let inner = outer.shrink(size * BG_SHRINK_FACTOR);
350 self.draw.rect(inner, col);
351 }
352 }
353 FrameStyle::NavFocus => {
354 let state = InputState::new_all(self.ev, id);
355 if let Some(col) = self.cols.nav_region(state) {
356 let inner = outer.shrink(self.w.dims.m_inner as f32);
357 self.draw.rounded_frame(outer, inner, 0.0, col);
358 }
359 }
360 FrameStyle::Button => {
361 let state = InputState::new_all(self.ev, id);
362 let outer = Quad::conv(rect);
363
364 let col_bg = self.cols.from_bg(bg, state, false);
365 let col_frame = self.cols.nav_region(state).unwrap_or(self.cols.frame);
366 self.button_frame(outer, col_frame, col_bg, state);
367 }
368 FrameStyle::EditBox => self.edit_box(id, outer, bg),
369 }
370 }
371
372 fn check_box(
373 &mut self,
374 id: &WidgetId,
375 rect: Rect,
376 checked: bool,
377 last_change: Option<Instant>,
378 ) {
379 let state = InputState::new_all(self.ev, id);
380 let outer = Quad::conv(rect);
381
382 let col_frame = self.cols.nav_region(state).unwrap_or(self.cols.frame);
383 let col_bg = self.cols.from_edit_bg(Default::default(), state);
384 let inner = self.button_frame(outer, col_frame, col_bg, state);
385
386 self.check_mark(inner, state, checked, last_change);
387 }
388
389 fn radio_box(
390 &mut self,
391 id: &WidgetId,
392 rect: Rect,
393 checked: bool,
394 last_change: Option<Instant>,
395 ) {
396 let anim_fade = 1.0 - self.w.anim.fade_bool(self.draw.draw, checked, last_change);
397
398 let state = InputState::new_all(self.ev, id);
399 let outer = Quad::conv(rect);
400 let col = self.cols.nav_region(state).unwrap_or(self.cols.frame);
401
402 if !(self.cols.is_dark || state.disabled() || state.depress()) {
403 let (mut a, mut b) = (self.w.dims.shadow_a, self.w.dims.shadow_b);
404 let mut mult = 0.65;
405 if state.hover() {
406 mult *= SHADOW_HOVER;
407 }
408 a = a * mult;
409 b = b * mult;
410 let shadow_outer = Quad::from_coords(a + outer.a, b + outer.b);
411 let col1 = if self.cols.is_dark { col } else { Rgba::BLACK };
412 let mut col2 = col1;
413 col2.a = 0.0;
414 self.draw.circle_2col(shadow_outer, col1, col2);
415 }
416
417 let col_bg = self.cols.from_edit_bg(Default::default(), state);
418 self.draw.circle(outer, 0.0, col_bg);
419
420 const F: f32 = 2.0 * (1.0 - BG_SHRINK_FACTOR); let r = 1.0 - F * self.w.dims.button_frame as f32 / rect.size.0 as f32;
422 self.draw.circle(outer, r, col);
423
424 if anim_fade < 1.0 {
425 let r = self.w.dims.button_frame + self.w.dims.m_inner as i32;
426 let inner = outer.shrink(r as f32);
427 let v = inner.size() * (anim_fade / 2.0);
428 let inner = Quad::from_coords(inner.a + v, inner.b - v);
429 let col = self.cols.check_mark_state(state);
430 self.draw.circle(inner, 0.0, col);
431 }
432 }
433
434 fn scroll_bar(
435 &mut self,
436 id: &WidgetId,
437 id2: &WidgetId,
438 rect: Rect,
439 h_rect: Rect,
440 _: Direction,
441 ) {
442 let outer = Quad::conv(rect);
444 let inner = outer.shrink(outer.size().min_comp() / 2.0);
445 let mut col = self.cols.frame;
446 col.a = 0.5; self.draw.rounded_frame(outer, inner, 0.0, col);
448
449 let outer = Quad::conv(h_rect);
451 let r = outer.size().min_comp() * 0.125;
452 let outer = outer.shrink(r);
453 let inner = outer.shrink(3.0 * r);
454 let state = InputState::new2(self.ev, id, id2);
455 let col = self.cols.accent_soft_state(state);
456 self.draw.rounded_frame(outer, inner, 0.0, col);
457 }
458
459 fn slider(&mut self, id: &WidgetId, id2: &WidgetId, rect: Rect, h_rect: Rect, dir: Direction) {
460 let state = InputState::new2(self.ev, id, id2);
461
462 let mut outer = Quad::conv(rect);
464 let mid = Vec2::conv(h_rect.pos + h_rect.size / 2);
465 let (mut first, mut second);
466 if dir.is_horizontal() {
467 outer = outer.shrink_vec(Vec2(0.0, outer.size().1 * (1.0 / 3.0)));
468 first = Quad::from_coords(outer.a, Vec2(mid.0, outer.b.1));
469 second = Quad::from_coords(Vec2(mid.0, outer.a.1), outer.b);
470 } else {
471 outer = outer.shrink_vec(Vec2(outer.size().0 * (1.0 / 3.0), 0.0));
472 first = Quad::from_coords(outer.a, Vec2(outer.b.0, mid.1));
473 second = Quad::from_coords(Vec2(outer.a.0, mid.1), outer.b);
474 };
475 if dir.is_reversed() {
476 std::mem::swap(&mut first, &mut second);
477 }
478
479 let inner = first.shrink(first.size().min_comp() / 2.0);
480 self.draw.rounded_frame(first, inner, 0.0, self.cols.accent);
481 let inner = second.shrink(second.size().min_comp() / 2.0);
482 self.draw
483 .rounded_frame(second, inner, 1.0 / 3.0, self.cols.frame);
484
485 let size = Size::splat(h_rect.size.0.min(h_rect.size.1));
487 let offset = Offset::conv((h_rect.size - size) / 2);
488 let outer = Quad::conv(Rect::new(h_rect.pos + offset, size));
489
490 let col = if state.nav_focus() && !state.disabled() {
491 self.cols.accent_soft
492 } else {
493 self.cols.background
494 };
495 let col = ColorsLinear::adjust_for_state(col, state);
496
497 if !self.cols.is_dark && !state.contains(InputState::DISABLED | InputState::DEPRESS) {
498 let (mut a, mut b) = (self.w.dims.shadow_a, self.w.dims.shadow_b);
499 let mut mult = 0.6;
500 if state.hover() {
501 mult *= SHADOW_HOVER;
502 }
503 a = a * mult;
504 b = b * mult;
505 let shadow_outer = Quad::from_coords(a + outer.a, b + outer.b);
506 let col1 = if self.cols.is_dark { col } else { Rgba::BLACK };
507 let mut col2 = col1;
508 col2.a = 0.0;
509 self.draw.circle_2col(shadow_outer, col1, col2);
510 }
511
512 self.draw.circle(outer, 0.0, col);
513 let col = self.cols.nav_region(state).unwrap_or(self.cols.frame);
514 self.draw.circle(outer, 14.0 / 16.0, col);
515 }
516
517 fn progress_bar(&mut self, _: &WidgetId, rect: Rect, dir: Direction, value: f32) {
518 let mut outer = Quad::conv(rect);
519 let inner = outer.shrink(outer.size().min_comp() / 2.0);
520 self.draw.rounded_frame(outer, inner, 0.75, self.cols.frame);
521
522 if dir.is_horizontal() {
523 outer.b.0 = outer.a.0 + value * (outer.b.0 - outer.a.0);
524 } else {
525 outer.b.1 = outer.a.1 + value * (outer.b.1 - outer.a.1);
526 }
527 let inner = outer.shrink(outer.size().min_comp() / 2.0);
528 self.draw.rounded_frame(outer, inner, 0.0, self.cols.accent);
529 }
530}