1use super::pressable_indicator_presenter::{
2 PressableIndicatorMetrics, PressableIndicatorPresenter, PressableIndicatorVisualState,
3};
4use crate::controls::{LabeledControlColors, LabeledControlSizing};
5use crate::ffi::{AlignItems, PositionType, Unit};
6use crate::node::{flex_box, FlexBox};
7use crate::theme::Theme;
8use std::rc::Rc;
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub struct SwitchIndicatorVisualState {
12 pub checked: bool,
13 pub hovered: bool,
14 pub pressed: bool,
15 pub focused: bool,
16 pub enabled: bool,
17}
18
19impl SwitchIndicatorVisualState {
20 pub fn new(checked: bool, hovered: bool, pressed: bool, focused: bool, enabled: bool) -> Self {
21 Self {
22 checked,
23 hovered,
24 pressed,
25 focused,
26 enabled,
27 }
28 }
29
30 pub fn pressable_state(&self) -> PressableIndicatorVisualState {
31 PressableIndicatorVisualState {
32 hovered: self.hovered,
33 pressed: self.pressed,
34 focused: self.focused,
35 enabled: self.enabled,
36 }
37 }
38}
39
40pub trait SwitchIndicatorPresenter: PressableIndicatorPresenter {
41 fn apply(
42 &self,
43 theme: Theme,
44 state: SwitchIndicatorVisualState,
45 colors: Option<LabeledControlColors>,
46 );
47}
48
49pub trait SwitchIndicatorTemplate {
50 fn create(&self, sizing: Option<LabeledControlSizing>) -> Rc<dyn SwitchIndicatorPresenter>;
51}
52
53#[derive(Clone, Copy, Debug, PartialEq)]
54pub struct SwitchIndicatorMetrics {
55 pub track_width: f32,
56 pub track_height: f32,
57 pub thumb_size: f32,
58 pub thumb_x: f32,
59 pub thumb_checked_x: f32,
60 pub thumb_y: f32,
61}
62
63impl SwitchIndicatorMetrics {
64 pub const fn new(
65 track_width: f32,
66 track_height: f32,
67 thumb_size: f32,
68 thumb_x: f32,
69 thumb_checked_x: f32,
70 thumb_y: f32,
71 ) -> Self {
72 Self {
73 track_width,
74 track_height,
75 thumb_size,
76 thumb_x,
77 thumb_checked_x,
78 thumb_y,
79 }
80 }
81}
82
83const DEFAULT_SWITCH_METRICS: SwitchIndicatorMetrics =
84 SwitchIndicatorMetrics::new(44.0, 26.0, 20.0, 3.0, 21.0, 2.0);
85
86pub fn resolve_switch_metrics(sizing: Option<LabeledControlSizing>) -> SwitchIndicatorMetrics {
87 let Some(sizing) = sizing else {
88 return DEFAULT_SWITCH_METRICS;
89 };
90 if !sizing.has_indicator_size() {
91 return DEFAULT_SWITCH_METRICS;
92 }
93 let scale = sizing.indicator_size_px() / DEFAULT_SWITCH_METRICS.track_height;
94 let track_width = DEFAULT_SWITCH_METRICS.track_width * scale;
95 let track_height = DEFAULT_SWITCH_METRICS.track_height * scale;
96 let thumb_size = DEFAULT_SWITCH_METRICS.thumb_size * scale;
97 let thumb_x = DEFAULT_SWITCH_METRICS.thumb_x * scale;
98 let thumb_y = DEFAULT_SWITCH_METRICS.thumb_y * scale;
99 SwitchIndicatorMetrics::new(
100 track_width,
101 track_height,
102 thumb_size,
103 thumb_x,
104 track_width - thumb_size - thumb_x,
105 thumb_y,
106 )
107}
108
109#[derive(Clone)]
110pub struct DefaultSwitchIndicatorPresenter {
111 root: FlexBox,
112 metrics: SwitchIndicatorMetrics,
113 thumb_node: FlexBox,
114}
115
116impl DefaultSwitchIndicatorPresenter {
117 pub fn new(metrics: SwitchIndicatorMetrics) -> Self {
118 let root = flex_box();
119 root.width(metrics.track_width, Unit::Pixel)
120 .height(metrics.track_height, Unit::Pixel)
121 .clip_to_bounds(true);
122 let thumb_node = flex_box();
123 thumb_node
124 .position_type(PositionType::Absolute)
125 .position(metrics.thumb_x, metrics.thumb_y)
126 .width(metrics.thumb_size, Unit::Pixel)
127 .height(metrics.thumb_size, Unit::Pixel);
128 root.align_items(AlignItems::Center).child(&thumb_node);
129 Self {
130 root,
131 metrics,
132 thumb_node,
133 }
134 }
135}
136
137impl PressableIndicatorPresenter for DefaultSwitchIndicatorPresenter {
138 fn root(&self) -> FlexBox {
139 self.root.clone()
140 }
141
142 fn metrics(&self) -> PressableIndicatorMetrics {
143 PressableIndicatorMetrics::new(self.metrics.track_width, self.metrics.track_height)
144 }
145}
146
147impl SwitchIndicatorPresenter for DefaultSwitchIndicatorPresenter {
148 fn apply(
149 &self,
150 theme: Theme,
151 state: SwitchIndicatorVisualState,
152 colors: Option<LabeledControlColors>,
153 ) {
154 let accent = colors
155 .filter(|colors| colors.has_accent())
156 .map(|colors| colors.accent_color())
157 .unwrap_or_else(|| {
158 if state.pressed {
159 theme.colors.accent_pressed
160 } else if state.hovered {
161 theme.colors.accent_hovered
162 } else {
163 theme.colors.accent
164 }
165 });
166 let track_color = if state.checked {
167 accent
168 } else {
169 colors
170 .filter(|colors| colors.has_background())
171 .map(|colors| colors.background_color())
172 .unwrap_or_else(|| {
173 if state.hovered {
174 theme.colors.background
175 } else {
176 theme.colors.surface
177 }
178 })
179 };
180 let border_color = colors
181 .filter(|colors| colors.has_border())
182 .map(|colors| colors.border_color())
183 .unwrap_or_else(|| {
184 if state.checked {
185 track_color
186 } else {
187 theme.colors.border
188 }
189 });
190 let metrics = self.metrics;
191 self.root
192 .corner_radius(metrics.track_height * 0.5)
193 .border(1.0, border_color)
194 .bg_color(track_color);
195 self.thumb_node
196 .position(
197 if state.checked {
198 metrics.thumb_checked_x
199 } else {
200 metrics.thumb_x
201 },
202 metrics.thumb_y,
203 )
204 .width(metrics.thumb_size, Unit::Pixel)
205 .height(metrics.thumb_size, Unit::Pixel)
206 .corner_radius(metrics.thumb_size * 0.5)
207 .bg_color(
208 colors
209 .filter(|colors| colors.has_background())
210 .map(|colors| colors.background_color())
211 .unwrap_or(theme.colors.surface),
212 )
213 .border(1.0, border_color);
214 }
215}
216
217#[derive(Clone, Copy, Debug, Default)]
218pub struct DefaultSwitchIndicatorTemplate;
219
220impl SwitchIndicatorTemplate for DefaultSwitchIndicatorTemplate {
221 fn create(&self, sizing: Option<LabeledControlSizing>) -> Rc<dyn SwitchIndicatorPresenter> {
222 create_default_switch_indicator_presenter(sizing)
223 }
224}
225
226pub const DEFAULT_SWITCH_INDICATOR_TEMPLATE: DefaultSwitchIndicatorTemplate =
227 DefaultSwitchIndicatorTemplate;
228
229pub fn create_default_switch_indicator_presenter(
230 sizing: Option<LabeledControlSizing>,
231) -> Rc<dyn SwitchIndicatorPresenter> {
232 Rc::new(DefaultSwitchIndicatorPresenter::new(
233 resolve_switch_metrics(sizing),
234 ))
235}