fret_ui_kit/primitives/
toggle.rs1use std::sync::Arc;
12
13use fret_core::SemanticsRole;
14use fret_runtime::Model;
15use fret_ui::element::{AnyElement, PressableA11y, PressableProps, PressableState};
16use fret_ui::{ElementContext, UiHost};
17
18use crate::declarative::ModelWatchExt;
19use crate::declarative::action_hooks::ActionHooksExt as _;
20use crate::{IntoUiElement, collect_children};
21
22pub fn toggle_a11y(label: Option<Arc<str>>, pressed: bool) -> PressableA11y {
27 PressableA11y {
28 role: Some(SemanticsRole::Button),
29 label,
30 pressed_state: Some(if pressed {
31 fret_core::SemanticsPressedState::True
32 } else {
33 fret_core::SemanticsPressedState::False
34 }),
35 ..Default::default()
36 }
37}
38
39pub fn toggle_use_model<H: UiHost>(
42 cx: &mut ElementContext<'_, H>,
43 controlled: Option<Model<bool>>,
44 default_pressed: impl FnOnce() -> bool,
45) -> crate::primitives::controllable_state::ControllableModel<bool> {
46 crate::primitives::controllable_state::use_controllable_model(cx, controlled, default_pressed)
47}
48
49#[derive(Debug, Clone, Default)]
56pub struct ToggleRoot {
57 pressed: Option<Model<bool>>,
58 default_pressed: bool,
59 disabled: bool,
60 a11y_label: Option<Arc<str>>,
61}
62
63impl ToggleRoot {
64 pub fn new() -> Self {
65 Self::default()
66 }
67
68 pub fn pressed(mut self, pressed: Option<Model<bool>>) -> Self {
70 self.pressed = pressed;
71 self
72 }
73
74 pub fn default_pressed(mut self, default_pressed: bool) -> Self {
76 self.default_pressed = default_pressed;
77 self
78 }
79
80 pub fn disabled(mut self, disabled: bool) -> Self {
81 self.disabled = disabled;
82 self
83 }
84
85 pub fn a11y_label(mut self, label: impl Into<Arc<str>>) -> Self {
86 self.a11y_label = Some(label.into());
87 self
88 }
89
90 pub fn new_controllable<H: UiHost>(
97 cx: &mut ElementContext<'_, H>,
98 pressed: Option<Model<bool>>,
99 default_pressed: impl FnOnce() -> bool,
100 ) -> Self {
101 let model = toggle_use_model(cx, pressed, default_pressed).model();
102 Self::new().pressed(Some(model))
103 }
104
105 pub fn use_pressed_model<H: UiHost>(
107 &self,
108 cx: &mut ElementContext<'_, H>,
109 ) -> crate::primitives::controllable_state::ControllableModel<bool> {
110 toggle_use_model(cx, self.pressed.clone(), || self.default_pressed)
111 }
112
113 pub fn pressed_model<H: UiHost>(&self, cx: &mut ElementContext<'_, H>) -> Model<bool> {
114 self.use_pressed_model(cx).model()
115 }
116
117 pub fn is_pressed<H: UiHost>(&self, cx: &mut ElementContext<'_, H>) -> bool {
119 let model = self.pressed_model(cx);
120 cx.watch_model(&model).copied_or_default()
121 }
122
123 #[track_caller]
129 pub fn into_element<H: UiHost, I, T>(
130 self,
131 cx: &mut ElementContext<'_, H>,
132 mut props: PressableProps,
133 f: impl FnOnce(&mut ElementContext<'_, H>, PressableState, bool) -> I,
134 ) -> AnyElement
135 where
136 I: IntoIterator<Item = T>,
137 T: IntoUiElement<H>,
138 {
139 let model = self.pressed_model(cx);
140 let disabled = self.disabled;
141 let label = self.a11y_label.clone();
142
143 cx.pressable_with_id_props(move |cx, st, _id| {
144 if !disabled {
145 cx.pressable_toggle_bool(&model);
146 }
147
148 let pressed = cx.watch_model(&model).copied_or_default();
149 props.enabled = props.enabled && !disabled;
150 props.a11y = toggle_a11y(label.clone(), pressed);
151
152 let items = f(cx, st, pressed);
153 (props, collect_children(cx, items))
154 })
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161
162 use std::cell::Cell;
163
164 use fret_app::App;
165 use fret_core::{AppWindowId, Px, Rect};
166
167 fn bounds() -> Rect {
168 Rect::new(
169 fret_core::Point::new(Px(0.0), Px(0.0)),
170 fret_core::Size::new(Px(200.0), Px(120.0)),
171 )
172 }
173
174 #[test]
175 fn toggle_root_prefers_controlled_model_and_does_not_call_default() {
176 let window = AppWindowId::default();
177 let mut app = App::new();
178 let b = bounds();
179
180 let controlled = app.models_mut().insert(true);
181 let called = Cell::new(0);
182
183 fret_ui::elements::with_element_cx(&mut app, window, b, "test", |cx| {
184 let root = ToggleRoot::new_controllable(cx, Some(controlled.clone()), || {
185 called.set(called.get() + 1);
186 false
187 });
188 assert_eq!(root.pressed_model(cx), controlled);
189 });
190
191 assert_eq!(called.get(), 0);
192 }
193}