Skip to main content

fui/controls/internal/
switch_indicator_presenter.rs

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}