fui/controls/internal/
checkbox_indicator_presenter.rs1use super::pressable_indicator_presenter::{
2 PressableIndicatorMetrics, PressableIndicatorPresenter, PressableIndicatorVisualState,
3};
4use crate::controls::{LabeledControlColors, LabeledControlSizing};
5use crate::ffi::{AlignItems, JustifyContent, SemanticCheckedState, Unit};
6use crate::node::{flex_box, svg, BoxStyleSurface, FlexBox, SvgNode};
7use crate::theme::Theme;
8use std::rc::Rc;
9
10const CHECKBOX_CHECK_SVG_URL: &str = "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 14 14'><path d='M2.25 7.15 5.35 10.25 11.75 3.85' fill='none' stroke='%23000000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/></svg>";
11
12#[derive(Clone, Copy, Debug, PartialEq)]
13pub struct CheckboxIndicatorMetrics {
14 pub indicator_size: f32,
15 pub corner_radius: f32,
16 pub check_mark_size: f32,
17}
18
19impl CheckboxIndicatorMetrics {
20 pub const fn new(indicator_size: f32, corner_radius: f32, check_mark_size: f32) -> Self {
21 Self {
22 indicator_size,
23 corner_radius,
24 check_mark_size,
25 }
26 }
27}
28
29const DEFAULT_CHECKBOX_METRICS: CheckboxIndicatorMetrics =
30 CheckboxIndicatorMetrics::new(20.0, 4.0, 16.0);
31
32pub fn resolve_checkbox_metrics(sizing: Option<LabeledControlSizing>) -> CheckboxIndicatorMetrics {
33 let Some(sizing) = sizing else {
34 return DEFAULT_CHECKBOX_METRICS;
35 };
36 if !sizing.has_indicator_size() {
37 return DEFAULT_CHECKBOX_METRICS;
38 }
39 let indicator_size = sizing.indicator_size_px();
40 CheckboxIndicatorMetrics::new(indicator_size, indicator_size * 0.2, indicator_size * 0.8)
41}
42
43#[derive(Clone, Copy, Debug, PartialEq, Eq)]
44pub struct CheckboxIndicatorVisualState {
45 pub checked_state: SemanticCheckedState,
46 pub hovered: bool,
47 pub pressed: bool,
48 pub focused: bool,
49 pub enabled: bool,
50}
51
52impl CheckboxIndicatorVisualState {
53 pub fn new(
54 checked_state: SemanticCheckedState,
55 hovered: bool,
56 pressed: bool,
57 focused: bool,
58 enabled: bool,
59 ) -> Self {
60 Self {
61 checked_state,
62 hovered,
63 pressed,
64 focused,
65 enabled,
66 }
67 }
68
69 pub fn pressable_state(&self) -> PressableIndicatorVisualState {
70 PressableIndicatorVisualState {
71 hovered: self.hovered,
72 pressed: self.pressed,
73 focused: self.focused,
74 enabled: self.enabled,
75 }
76 }
77}
78
79pub trait CheckboxIndicatorPresenter: PressableIndicatorPresenter {
80 fn apply(
81 &self,
82 theme: Theme,
83 state: CheckboxIndicatorVisualState,
84 colors: Option<LabeledControlColors>,
85 );
86}
87
88pub trait CheckboxIndicatorTemplate {
89 fn create(&self, sizing: Option<LabeledControlSizing>) -> Rc<dyn CheckboxIndicatorPresenter>;
90}
91
92#[derive(Clone)]
93pub struct DefaultCheckboxIndicatorPresenter {
94 root: FlexBox,
95 metrics: CheckboxIndicatorMetrics,
96 mark_node: SvgNode,
97}
98
99impl DefaultCheckboxIndicatorPresenter {
100 pub fn new(metrics: CheckboxIndicatorMetrics) -> Self {
101 let root = flex_box();
102 root.width(metrics.indicator_size, Unit::Pixel)
103 .height(metrics.indicator_size, Unit::Pixel)
104 .align_items(AlignItems::Center)
105 .justify_content(JustifyContent::Center);
106 let mark_host = flex_box();
107 mark_host
108 .fill_size()
109 .align_items(AlignItems::Center)
110 .justify_content(JustifyContent::Center);
111 let mark_node = svg(0);
112 mark_node
113 .width(metrics.check_mark_size, Unit::Pixel)
114 .height(metrics.check_mark_size, Unit::Pixel);
115 mark_host.child(&mark_node);
116 root.child(&mark_host);
117 Self {
118 root,
119 metrics,
120 mark_node,
121 }
122 }
123}
124
125impl PressableIndicatorPresenter for DefaultCheckboxIndicatorPresenter {
126 fn root(&self) -> FlexBox {
127 self.root.clone()
128 }
129
130 fn metrics(&self) -> PressableIndicatorMetrics {
131 PressableIndicatorMetrics::new(self.metrics.indicator_size, self.metrics.indicator_size)
132 }
133}
134
135impl CheckboxIndicatorPresenter for DefaultCheckboxIndicatorPresenter {
136 fn apply(
137 &self,
138 theme: Theme,
139 state: CheckboxIndicatorVisualState,
140 colors: Option<LabeledControlColors>,
141 ) {
142 let metrics = self.metrics;
143 let accent = colors
144 .filter(|colors| colors.has_accent())
145 .map(|colors| colors.accent_color())
146 .unwrap_or_else(|| {
147 if state.pressed {
148 theme.colors.accent_pressed
149 } else if state.hovered {
150 theme.colors.accent_hovered
151 } else {
152 theme.colors.accent
153 }
154 });
155 let mut background = colors
156 .filter(|colors| colors.has_background())
157 .map(|colors| colors.background_color())
158 .unwrap_or(theme.colors.surface);
159 let mut border_color = colors
160 .filter(|colors| colors.has_border())
161 .map(|colors| colors.border_color())
162 .unwrap_or(theme.colors.border);
163 let mut mark_visible = false;
164 let mut mark_color = theme.colors.text_primary;
165 if state.checked_state == SemanticCheckedState::True
166 || state.checked_state == SemanticCheckedState::Mixed
167 {
168 background = accent;
169 border_color = background;
170 mark_visible = state.checked_state == SemanticCheckedState::True;
171 mark_color = theme.colors.text_on_accent;
172 } else if state.hovered && colors.is_none_or(|colors| !colors.has_background()) {
173 background = theme.colors.background;
174 }
175 self.root
176 .corner_radius(metrics.corner_radius)
177 .border(1.0, border_color)
178 .bg_color(background);
179 if mark_visible {
180 self.mark_node.source(CHECKBOX_CHECK_SVG_URL);
181 } else {
182 self.mark_node.clear_source();
183 }
184 self.mark_node
185 .width(metrics.check_mark_size, Unit::Pixel)
186 .height(metrics.check_mark_size, Unit::Pixel)
187 .opacity(if mark_visible { 1.0 } else { 0.0 })
188 .tint(mark_color);
189 }
190}
191
192#[derive(Clone, Copy, Debug, Default)]
193pub struct DefaultCheckboxIndicatorTemplate;
194
195impl CheckboxIndicatorTemplate for DefaultCheckboxIndicatorTemplate {
196 fn create(&self, sizing: Option<LabeledControlSizing>) -> Rc<dyn CheckboxIndicatorPresenter> {
197 create_default_checkbox_indicator_presenter(sizing)
198 }
199}
200
201pub const DEFAULT_CHECKBOX_INDICATOR_TEMPLATE: DefaultCheckboxIndicatorTemplate =
202 DefaultCheckboxIndicatorTemplate;
203
204pub fn create_default_checkbox_indicator_presenter(
205 sizing: Option<LabeledControlSizing>,
206) -> Rc<dyn CheckboxIndicatorPresenter> {
207 Rc::new(DefaultCheckboxIndicatorPresenter::new(
208 resolve_checkbox_metrics(sizing),
209 ))
210}