cranpose_ui/modifier/
toggleable.rs1use super::{inspector_metadata, Modifier, SemanticsConfiguration};
2use cranpose_foundation::SemanticsWidgetRole;
3use std::rc::Rc;
4
5impl Modifier {
6 pub fn toggleable(
33 self,
34 value: bool,
35 description: Option<String>,
36 role: Option<SemanticsWidgetRole>,
37 on_value_change: impl Fn(bool) + 'static,
38 ) -> Self {
39 let on_value_change = Rc::new(on_value_change);
40 let toggled = value;
41 let modifier = Modifier::empty()
42 .clickable(move |_point| on_value_change(!toggled))
43 .with_inspector_metadata(inspector_metadata("toggleable", move |info| {
44 info.add_property("value", if toggled { "true" } else { "false" });
45 info.add_property("onValueChange", "provided");
46 }))
47 .then(
48 Modifier::empty().semantics(move |config: &mut SemanticsConfiguration| {
49 config.is_clickable = true;
50 config.toggled = Some(toggled);
51 if let Some(description) = &description {
52 config.content_description = Some(description.clone());
53 }
54 if let Some(role) = role {
55 config.role = Some(role);
56 }
57 }),
58 );
59 self.then(modifier)
60 }
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66 use crate::modifier::{collect_semantics_from_modifier, collect_slices_from_modifier, Point};
67 use cranpose_foundation::{PointerButton, PointerButtons, PointerEvent, PointerEventKind};
68 use std::cell::Cell;
69
70 fn tap(modifier: &Modifier) {
73 let slices = collect_slices_from_modifier(modifier);
74 let handlers = slices.pointer_inputs();
75 assert_eq!(handlers.len(), 1, "toggleable takes pointer input once");
76 let at = Point { x: 4.0, y: 4.0 };
77 for kind in [PointerEventKind::Down, PointerEventKind::Up] {
78 let mut event = PointerEvent::new(kind, at, at);
79 event.buttons = PointerButtons::new().with(PointerButton::Primary);
80 handlers[0](event);
81 }
82 }
83
84 #[test]
85 fn a_click_reports_the_new_value_not_the_old_one() {
86 let _app_context = crate::render_state::app_context_test_scope();
87 for start in [false, true] {
88 let seen: Rc<Cell<Option<bool>>> = Rc::new(Cell::new(None));
89 let sink = seen.clone();
90 let modifier =
91 Modifier::empty().toggleable(start, None, None, move |next| sink.set(Some(next)));
92 tap(&modifier);
93 assert_eq!(
94 seen.get(),
95 Some(!start),
96 "a toggle hands over the value it is moving to"
97 );
98 }
99 }
100
101 #[test]
102 fn a_toggleable_row_reads_as_clickable_and_carries_its_description() {
103 let modifier =
104 Modifier::empty().toggleable(true, Some("Haptics, on".to_string()), None, |_| {});
105 let semantics = collect_semantics_from_modifier(&modifier)
106 .expect("a toggleable row publishes semantics");
107 assert!(semantics.is_clickable);
108 assert_eq!(
109 semantics.content_description.as_deref(),
110 Some("Haptics, on")
111 );
112 assert_eq!(semantics.toggled, Some(true));
115 assert_eq!(semantics.role, None);
118 }
119
120 #[test]
121 fn a_role_reaches_the_semantics_so_a_reader_can_say_what_the_control_is() {
122 let modifier = Modifier::empty().toggleable(
123 false,
124 Some("Haptics, off".to_string()),
125 Some(SemanticsWidgetRole::Switch),
126 |_| {},
127 );
128 let semantics = collect_semantics_from_modifier(&modifier).expect("semantics");
129 assert_eq!(semantics.role, Some(SemanticsWidgetRole::Switch));
130 assert!(semantics.is_clickable);
132 assert_eq!(semantics.toggled, Some(false));
133 assert_eq!(
134 semantics.content_description.as_deref(),
135 Some("Haptics, off")
136 );
137 }
138}