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