1use std::cell::RefCell;
9use std::f32;
10use std::ops::Range;
11use std::time::Instant;
12
13use crate::Id;
14use crate::cast::traits::*;
15use crate::config::{Config, WindowConfig};
16use crate::dir::{Direction, Directional};
17use crate::draw::{color::Rgba, *};
18use crate::event::EventState;
19use crate::geom::*;
20use crate::text::{Effect, TextDisplay};
21use crate::theme::dimensions as dim;
22use crate::theme::{Background, FrameStyle, MarkStyle};
23use crate::theme::{ColorsLinear, InputState, Theme};
24use crate::theme::{SelectionStyle, ThemeDraw, ThemeSize};
25
26use super::ColorsSrgb;
27
28#[derive(Clone, Debug)]
33pub struct SimpleTheme {
34 pub cols: ColorsLinear,
35 dims: dim::Parameters,
36}
37
38impl Default for SimpleTheme {
39 fn default() -> Self {
40 Self::new()
41 }
42}
43
44impl SimpleTheme {
45 #[inline]
47 pub fn new() -> Self {
48 SimpleTheme {
49 cols: ColorsSrgb::LIGHT.into(), dims: Default::default(),
51 }
52 }
53}
54
55pub struct DrawHandle<'a, DS: DrawSharedImpl> {
56 pub(crate) draw: DrawIface<'a, DS>,
57 pub(crate) ev: &'a mut EventState,
58 pub(crate) w: &'a mut dim::Window<DS::Draw>,
59 pub(crate) cols: &'a ColorsLinear,
60}
61
62impl<DS: DrawSharedImpl> Theme<DS> for SimpleTheme {
63 type Window = dim::Window<DS::Draw>;
64 type Draw<'a> = DrawHandle<'a, DS>;
65
66 fn init(&mut self, _: &RefCell<Config>) {}
67
68 fn new_window(&mut self, config: &WindowConfig) -> Self::Window {
69 self.cols = config.theme().get_active_scheme().into();
70 dim::Window::new(&self.dims, config)
71 }
72
73 fn update_window(&mut self, w: &mut Self::Window, config: &WindowConfig) -> bool {
74 self.cols = config.theme().get_active_scheme().into();
75 w.update(&self.dims, config)
76 }
77
78 fn draw<'a>(
79 &'a self,
80 draw: DrawIface<'a, DS>,
81 ev: &'a mut EventState,
82 w: &'a mut Self::Window,
83 ) -> Self::Draw<'a> {
84 w.anim.update();
85
86 DrawHandle {
87 draw,
88 ev,
89 w,
90 cols: &self.cols,
91 }
92 }
93
94 fn draw_upcast<'a>(
95 draw: DrawIface<'a, DS>,
96 ev: &'a mut EventState,
97 w: &'a mut Self::Window,
98 cols: &'a ColorsLinear,
99 ) -> Self::Draw<'a> {
100 DrawHandle { draw, ev, w, cols }
101 }
102
103 fn clear_color(&self) -> Rgba {
104 self.cols.background
105 }
106}
107
108impl<'a, DS: DrawSharedImpl> DrawHandle<'a, DS> {
109 pub fn button_frame(
110 &mut self,
111 outer: Quad,
112 col_frame: Rgba,
113 col_bg: Rgba,
114 _: InputState,
115 ) -> Quad {
116 let inner = outer.shrink(self.w.dims.button_frame as f32);
117 #[cfg(debug_assertions)]
118 {
119 if !(inner.a < inner.b) {
120 log::warn!("button_frame: frame too small: {outer:?}");
121 }
122 }
123
124 let bgr = outer.shrink(self.w.dims.button_frame as f32);
125 self.draw.rect(bgr, col_bg);
126
127 self.draw.frame(outer, inner, col_frame);
128 inner
129 }
130
131 pub fn edit_box(&mut self, id: &Id, outer: Quad, bg: Background) {
132 let state = InputState::new_except_depress(self.ev, id);
133 let col_bg = self.cols.from_edit_bg(bg, state);
134 if col_bg != self.cols.background {
135 let inner = outer.shrink(self.w.dims.button_frame as f32);
136 self.draw.rect(inner, col_bg);
137 }
138
139 let inner = outer.shrink(self.w.dims.button_frame as f32);
140 self.draw.frame(outer, inner, self.cols.frame);
141
142 if !state.disabled() && !self.cols.is_dark && (state.nav_focus() || state.under_mouse()) {
143 let mut line = outer;
144 line.a.1 = line.b.1 - self.w.dims.button_frame as f32;
145 let col = if state.nav_focus() {
146 self.cols.nav_focus
147 } else {
148 self.cols.text
149 };
150 self.draw.rect(line, col);
151 }
152 }
153
154 fn draw_mark(&mut self, rect: Rect, style: MarkStyle, col: Rgba) {
155 match style {
156 MarkStyle::Chevron(dir) => {
157 let size = match dir.is_horizontal() {
158 true => Size(self.w.dims.mark / 2, self.w.dims.mark),
159 false => Size(self.w.dims.mark, self.w.dims.mark / 2),
160 };
161 let offset = Offset::conv((rect.size - size) / 2);
162 let q = Quad::conv(Rect::new(rect.pos + offset, size));
163
164 let (p1, p2, p3);
165 if dir.is_horizontal() {
166 let (mut x1, mut x2) = (q.a.0, q.b.0);
167 if dir.is_reversed() {
168 std::mem::swap(&mut x1, &mut x2);
169 }
170 p1 = Vec2(x1, q.a.1);
171 p2 = Vec2(x2, 0.5 * (q.a.1 + q.b.1));
172 p3 = Vec2(x1, q.b.1);
173 } else {
174 let (mut y1, mut y2) = (q.a.1, q.b.1);
175 if dir.is_reversed() {
176 std::mem::swap(&mut y1, &mut y2);
177 }
178 p1 = Vec2(q.a.0, y1);
179 p2 = Vec2(0.5 * (q.a.0 + q.b.0), y2);
180 p3 = Vec2(q.b.0, y1);
181 };
182
183 let f = self.w.dims.diagonal_mark_line;
184 self.draw.line(p1, p2, f, col);
185 self.draw.line(p2, p3, f, col);
186 }
187 MarkStyle::X => {
188 let size = Size::splat(self.w.dims.mark);
189 let offset = Offset::conv((rect.size - size) / 2);
190 let q = Quad::conv(Rect::new(rect.pos + offset, size));
191
192 let f = self.w.dims.diagonal_mark_line;
193 self.draw.line(q.a, q.b, f, col);
194 let c = Vec2(q.a.0, q.b.1);
195 let d = Vec2(q.b.0, q.a.1);
196 self.draw.line(c, d, f, col);
197 }
198 MarkStyle::Plus => {
199 let size = Size::splat(self.w.dims.mark);
200 let offset = Offset::conv((rect.size - size) / 2);
201 let q = Quad::conv(Rect::new(rect.pos + offset, size));
202
203 let f = self.w.dims.mark_line;
204 let mid = q.center();
205 let north = Vec2(mid.0, q.a.1);
206 let south = Vec2(mid.0, q.b.1);
207 let west = Vec2(q.a.0, mid.1);
208 let east = Vec2(q.b.0, mid.1);
209 self.draw.line(north, south, f, col);
210 self.draw.line(west, east, f, col);
211 }
212 MarkStyle::Minus => {
213 let size = Size::splat(self.w.dims.mark);
214 let offset = Offset::conv((rect.size - size) / 2);
215 let q = Quad::conv(Rect::new(rect.pos + offset, size));
216
217 let f = self.w.dims.mark_line;
218 let mid = q.center();
219 let west = Vec2(q.a.0, mid.1);
220 let east = Vec2(q.b.0, mid.1);
221 self.draw.line(west, east, f, col);
222 }
223 }
224 }
225}
226
227impl<'a, DS: DrawSharedImpl> ThemeDraw for DrawHandle<'a, DS> {
228 fn components(&mut self) -> (&dyn ThemeSize, &mut dyn Draw, &mut EventState) {
229 (self.w, &mut self.draw, self.ev)
230 }
231
232 fn colors(&self) -> &ColorsLinear {
233 self.cols
234 }
235
236 fn draw_rounded(&mut self) -> Option<&mut dyn DrawRounded> {
237 None
239 }
240
241 fn new_pass<'b>(
242 &mut self,
243 inner_rect: Rect,
244 offset: Offset,
245 class: PassType,
246 f: Box<dyn FnOnce(&mut dyn ThemeDraw) + 'b>,
247 ) {
248 let draw = self.draw.new_pass(inner_rect, offset, class);
249 let mut handle = DrawHandle {
250 draw,
251 ev: self.ev,
252 w: self.w,
253 cols: self.cols,
254 };
255 f(&mut handle);
256 }
257
258 fn get_clip_rect(&mut self) -> Rect {
259 self.draw.get_clip_rect()
260 }
261
262 fn event_state_overlay(&mut self) {
263 if let Some((coord, used)) = self.ev.mouse_pin() {
264 let center = coord.round().cast_approx();
265 let c = self.cols.accent;
266 if !used {
267 let outer = Quad::from_center(center, self.w.dims.scale * 3.6);
268 self.draw.rect(outer, c);
269 } else {
270 let outer = Quad::from_center(center, self.w.dims.scale * 6.0);
271 let inner = outer.shrink(self.w.dims.scale * 1.2);
272 self.draw.frame(outer, inner, c);
273 }
274 }
275 }
276
277 fn frame(&mut self, id: &Id, rect: Rect, style: FrameStyle, bg: Background) {
278 let outer = Quad::conv(rect);
279 match style {
280 FrameStyle::None => {
281 let state = InputState::new_except_depress(self.ev, id);
282 let col = self.cols.from_bg(bg, state, false);
283 self.draw.rect(outer, col);
284 }
285 FrameStyle::Frame | FrameStyle::Window => {
286 let inner = outer.shrink(self.w.dims.frame as f32);
287 self.draw.frame(outer, inner, self.cols.frame);
288 }
289 FrameStyle::Popup => {
290 let size = self.w.dims.menu_frame as f32;
294 let inner = outer.shrink(size);
295 self.draw.frame(outer, inner, self.cols.frame);
296 self.draw.rect(inner, self.cols.background);
297 }
298 FrameStyle::MenuEntry => {
299 let state = InputState::new_all(self.ev, id);
300 if let Some(col) = self.cols.menu_entry(state) {
301 self.draw.rect(outer, col);
302 }
303 }
304 FrameStyle::NavFocus => {
305 let state = InputState::new_all(self.ev, id);
306 if let Some(col) = self.cols.nav_region(state) {
307 let inner = outer.shrink(self.w.dims.m_inner as f32);
308 self.draw.frame(outer, inner, col);
309 }
310 }
311 FrameStyle::Button | FrameStyle::InvisibleButton | FrameStyle::Tab => {
312 let state = InputState::new_all(self.ev, id);
313 if style == FrameStyle::InvisibleButton && !state.under_mouse() {
314 return;
315 }
316 let outer = Quad::conv(rect);
317
318 let col_bg = self.cols.from_bg(bg, state, false);
319 let col_frame = self.cols.nav_region(state).unwrap_or(self.cols.frame);
320 self.button_frame(outer, col_frame, col_bg, state);
321 }
322 FrameStyle::EditBox => self.edit_box(id, outer, bg),
323 }
324 }
325
326 fn separator(&mut self, rect: Rect) {
327 let outer = Quad::conv(rect);
328 self.draw.rect(outer, self.cols.frame);
329 }
330
331 fn selection(&mut self, rect: Rect, style: SelectionStyle) {
332 let inner = Quad::conv(rect);
333 match style {
334 SelectionStyle::Highlight => {
335 self.draw.rect(inner, self.cols.text_sel_bg);
336 }
337 SelectionStyle::Frame => {
338 let outer = inner.grow(self.w.dims.m_inner.into());
339 let col = self.cols.accent;
341 self.draw.frame(outer, inner, col);
342 }
343 SelectionStyle::Both => {
344 let outer = inner.grow(self.w.dims.m_inner.into());
345 self.draw.rect(outer, self.cols.accent);
346 self.draw.rect(inner, self.cols.text_sel_bg);
347 }
348 }
349 }
350
351 fn text(&mut self, id: &Id, pos: Coord, rect: Rect, text: &TextDisplay, color: Option<Rgba>) {
352 let bb = Quad::conv(rect);
353 let col = color.unwrap_or_else(|| {
354 if self.ev.is_disabled(id) {
355 self.cols.text_disabled
356 } else {
357 self.cols.text
358 }
359 });
360 self.draw.text(pos.cast(), bb, text, col);
361 }
362
363 fn text_effects(
364 &mut self,
365 id: &Id,
366 pos: Coord,
367 rect: Rect,
368 text: &TextDisplay,
369 colors: &[Rgba],
370 effects: &[Effect],
371 ) {
372 let bb = Quad::conv(rect);
373 let col;
374 let mut colors = colors;
375 if colors.is_empty() {
376 col = [if self.ev.is_disabled(id) {
377 self.cols.text_disabled
378 } else {
379 self.cols.text
380 }];
381 colors = &col;
382 }
383 self.draw
384 .text_effects(pos.cast(), bb, text, colors, effects);
385 }
386
387 fn text_selected_range(
388 &mut self,
389 id: &Id,
390 pos: Coord,
391 rect: Rect,
392 text: &TextDisplay,
393 range: Range<usize>,
394 ) {
395 let pos = pos.cast();
396 let bb = Quad::conv(rect);
397 let col = if self.ev.is_disabled(id) {
398 self.cols.text_disabled
399 } else {
400 self.cols.text
401 };
402 let sel_col = self.cols.text_over(self.cols.text_sel_bg);
403
404 text.highlight_range(range.clone(), &mut |p1, p2| {
406 let p1 = Vec2::from(p1);
407 let p2 = Vec2::from(p2);
408 if let Some(quad) = Quad::from_coords(pos + p1, pos + p2).intersection(&bb) {
409 self.draw.rect(quad, self.cols.text_sel_bg);
410 }
411 });
412
413 let effects = [
414 Effect {
415 start: 0,
416 e: 0,
417 flags: Default::default(),
418 },
419 Effect {
420 start: range.start.cast(),
421 e: 1,
422 flags: Default::default(),
423 },
424 Effect {
425 start: range.end.cast(),
426 e: 0,
427 flags: Default::default(),
428 },
429 ];
430 let colors = [col, sel_col];
431 self.draw.text_effects(pos, bb, text, &colors, &effects);
432 }
433
434 fn text_cursor(&mut self, id: &Id, pos: Coord, rect: Rect, text: &TextDisplay, byte: usize) {
435 if self.ev.window_has_focus() && !self.w.anim.text_cursor(self.draw.draw, id, byte) {
436 return;
437 }
438
439 let width = self.w.dims.mark_line;
440 let pos = Vec2::conv(pos);
441 let bb = Quad::conv(rect);
442 let l_half = (0.5 * width).floor();
443
444 let mut col = self.cols.nav_focus;
445 for cursor in text.text_glyph_pos(byte).rev() {
446 let mut p1 = pos + Vec2::from(cursor.pos);
447 let mut p2 = p1;
448 p1.1 -= cursor.ascent;
449 p2.1 -= cursor.descent;
450 p1.0 -= l_half;
451 p2.0 += width - l_half;
452
453 if let Some(quad) = Quad::from_coords(p1, p2).intersection(&bb) {
454 self.draw.rect(quad, col);
455 }
456
457 if cursor.embedding_level() > 0 {
458 let height = width;
460 let quad = if cursor.is_ltr() {
461 Quad::from_coords(Vec2(p2.0, p1.1), Vec2(p2.0 + width, p1.1 + height))
462 } else {
463 Quad::from_coords(Vec2(p1.0 - width, p1.1), Vec2(p1.0, p1.1 + height))
464 };
465 if let Some(quad) = quad.intersection(&bb) {
466 self.draw.rect(quad, col);
467 }
468 }
469 col = col.average();
471 }
472 }
473
474 fn check_box(&mut self, id: &Id, rect: Rect, checked: bool, _: Option<Instant>) {
475 let state = InputState::new_all(self.ev, id);
476 let outer = Quad::conv(rect);
477
478 let col_frame = self.cols.nav_region(state).unwrap_or(self.cols.frame);
479 let col_bg = self.cols.from_edit_bg(Default::default(), state);
480 let inner = self.button_frame(outer, col_frame, col_bg, state);
481
482 if checked {
483 let inner = inner.shrink(self.w.dims.m_inner as f32);
484 let col = self.cols.check_mark_state(state);
485 self.draw.rect(inner, col);
486 }
487 }
488
489 fn radio_box(&mut self, id: &Id, rect: Rect, checked: bool, last_change: Option<Instant>) {
490 self.check_box(id, rect, checked, last_change);
491 }
492
493 fn mark(&mut self, id: &Id, rect: Rect, style: MarkStyle) {
494 let col = if self.ev.is_disabled(id) {
495 self.cols.text_disabled
496 } else {
497 let is_depressed = self.ev.is_depressed(id);
498 if self.ev.is_under_mouse(id) || is_depressed {
499 self.draw.rect(rect.cast(), self.cols.accent_soft);
500 }
501 if is_depressed { self.cols.accent } else { self.cols.text }
502 };
503
504 self.draw_mark(rect, style, col);
505 }
506
507 fn scroll_bar(&mut self, id: &Id, id2: &Id, rect: Rect, h_rect: Rect, _: Direction) {
508 let track = Quad::conv(rect);
509 self.draw.rect(track, self.cols.frame);
510
511 let grip = Quad::conv(h_rect);
512 let state = InputState::new2(self.ev, id, id2);
513 let col = self.cols.accent_soft_state(state);
514 self.draw.rect(grip, col);
515 }
516
517 fn slider(&mut self, id: &Id, id2: &Id, rect: Rect, h_rect: Rect, _: Direction) {
518 let track = Quad::conv(rect);
519 self.draw.rect(track, self.cols.frame);
520
521 let grip = Quad::conv(h_rect);
522 let state = InputState::new2(self.ev, id, id2);
523 let col = self.cols.accent_soft_state(state);
524 self.draw.rect(grip, col);
525 }
526
527 fn progress_bar(&mut self, _: &Id, rect: Rect, dir: Direction, value: f32) {
528 let mut outer = Quad::conv(rect);
529 self.draw.rect(outer, self.cols.frame);
530
531 if dir.is_horizontal() {
532 outer.b.0 = outer.a.0 + value * (outer.b.0 - outer.a.0);
533 } else {
534 outer.b.1 = outer.a.1 + value * (outer.b.1 - outer.a.1);
535 }
536 self.draw.rect(outer, self.cols.accent);
537 }
538
539 fn image(&mut self, id: ImageId, rect: Rect) {
540 self.draw.image(id, rect.cast());
541 }
542}