cranpose_ui/modifier/
focus_ring.rs1use std::{cell::Cell, rc::Rc};
5
6use cranpose_ui_graphics::{Brush, Color, DrawScope, Rect, Size, Stroke};
7
8use super::Modifier;
9use crate::focus_dispatch::keyboard_focus_visible;
10
11pub const FOCUS_RING_COLOR: Color = Color::from_rgba_u8(0, 95, 204, 255);
13pub const FOCUS_RING_INNER_COLOR: Color = Color::from_rgba_u8(255, 255, 255, 255);
15const OUTER_WIDTH: f32 = 2.0;
16const INNER_WIDTH: f32 = 1.0;
17
18pub fn focus_ring_lines(size: Size) -> [(Rect, f32); 2] {
22 [
23 (inset(size, OUTER_WIDTH / 2.0), OUTER_WIDTH),
24 (inset(size, OUTER_WIDTH + INNER_WIDTH / 2.0), INNER_WIDTH),
25 ]
26}
27
28fn inset(size: Size, by: f32) -> Rect {
29 Rect {
30 x: by,
31 y: by,
32 width: (size.width - 2.0 * by).max(0.0),
33 height: (size.height - 2.0 * by).max(0.0),
34 }
35}
36
37fn draw_focus_ring(scope: &mut dyn DrawScope) {
38 let [(outer, outer_width), (inner, inner_width)] = focus_ring_lines(scope.size());
39 scope.draw_rect_at_stroked(
40 outer,
41 Brush::solid(FOCUS_RING_COLOR),
42 Stroke::new(outer_width),
43 );
44 scope.draw_rect_at_stroked(
45 inner,
46 Brush::solid(FOCUS_RING_INNER_COLOR),
47 Stroke::new(inner_width),
48 );
49}
50
51impl Modifier {
52 pub fn focusable(self) -> Self {
60 let focused = Rc::new(Cell::new(false));
61 let seen = Rc::clone(&focused);
62 self.on_focus_changed(move |state| {
63 seen.set(state.is_focused());
64 crate::request_render_invalidation();
65 })
66 .draw_with_content(move |scope| {
67 scope.draw_content();
68 if focused.get() && keyboard_focus_visible() {
69 draw_focus_ring(scope);
70 }
71 })
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 #[test]
80 fn the_ring_stays_inside_the_control() {
81 let [(outer, outer_width), (inner, inner_width)] = focus_ring_lines(Size {
82 width: 48.0,
83 height: 20.0,
84 });
85 assert_eq!((outer_width, inner_width), (2.0, 1.0));
86 assert_eq!(
87 (outer.x, outer.y, outer.width, outer.height),
88 (1.0, 1.0, 46.0, 18.0)
89 );
90 assert_eq!(
91 (inner.x, inner.y, inner.width, inner.height),
92 (2.5, 2.5, 43.0, 15.0)
93 );
94 }
95
96 #[test]
97 fn a_ring_around_nothing_has_no_width() {
98 let [(outer, _), (inner, _)] = focus_ring_lines(Size {
99 width: 1.0,
100 height: 1.0,
101 });
102 assert_eq!(
103 (outer.width, outer.height, inner.width, inner.height),
104 (0.0, 0.0, 0.0, 0.0)
105 );
106 }
107}