cranpose_ui/modifier/
toggleable.rs1use super::{inspector_metadata, Modifier, SemanticsConfiguration};
2use std::rc::Rc;
3
4impl Modifier {
5 pub fn toggleable(
22 self,
23 value: bool,
24 description: Option<String>,
25 on_value_change: impl Fn(bool) + 'static,
26 ) -> Self {
27 let on_value_change = Rc::new(on_value_change);
28 let toggled = value;
29 let modifier = Modifier::empty()
30 .clickable(move |_point| on_value_change(!toggled))
31 .with_inspector_metadata(inspector_metadata("toggleable", move |info| {
32 info.add_property("value", if toggled { "true" } else { "false" });
33 info.add_property("onValueChange", "provided");
34 }))
35 .then(
36 Modifier::empty().semantics(move |config: &mut SemanticsConfiguration| {
37 config.is_clickable = true;
38 config.toggled = Some(toggled);
39 if let Some(description) = &description {
40 config.content_description = Some(description.clone());
41 }
42 }),
43 );
44 self.then(modifier)
45 }
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51 use crate::modifier::{collect_semantics_from_modifier, collect_slices_from_modifier, Point};
52 use cranpose_foundation::{PointerButton, PointerButtons, PointerEvent, PointerEventKind};
53 use std::cell::Cell;
54
55 fn tap(modifier: &Modifier) {
58 let slices = collect_slices_from_modifier(modifier);
59 let handlers = slices.pointer_inputs();
60 assert_eq!(handlers.len(), 1, "toggleable takes pointer input once");
61 let at = Point { x: 4.0, y: 4.0 };
62 for kind in [PointerEventKind::Down, PointerEventKind::Up] {
63 let mut event = PointerEvent::new(kind, at, at);
64 event.buttons = PointerButtons::new().with(PointerButton::Primary);
65 handlers[0](event);
66 }
67 }
68
69 #[test]
70 fn a_click_reports_the_new_value_not_the_old_one() {
71 let _app_context = crate::render_state::app_context_test_scope();
72 for start in [false, true] {
73 let seen: Rc<Cell<Option<bool>>> = Rc::new(Cell::new(None));
74 let sink = seen.clone();
75 let modifier =
76 Modifier::empty().toggleable(start, None, move |next| sink.set(Some(next)));
77 tap(&modifier);
78 assert_eq!(
79 seen.get(),
80 Some(!start),
81 "a toggle hands over the value it is moving to"
82 );
83 }
84 }
85
86 #[test]
87 fn a_toggleable_row_reads_as_clickable_and_carries_its_description() {
88 let modifier = Modifier::empty().toggleable(true, Some("Haptics, on".to_string()), |_| {});
89 let semantics = collect_semantics_from_modifier(&modifier)
90 .expect("a toggleable row publishes semantics");
91 assert!(semantics.is_clickable);
92 assert_eq!(
93 semantics.content_description.as_deref(),
94 Some("Haptics, on")
95 );
96 assert_eq!(semantics.toggled, Some(true));
99 assert_eq!(semantics.role, None);
102 }
103}