Skip to main content

cranpose_ui/modifier/
selectable.rs

1use std::rc::Rc;
2
3use cranpose_foundation::SemanticsWidgetRole;
4
5use super::{Modifier, SemanticsConfiguration, inspector_metadata};
6
7impl Modifier {
8    /// Makes the component one choice among several, of which one is picked
9    /// at a time: a tab, a radio row, an entry in a segmented control.
10    ///
11    /// This is Compose's `Modifier.selectable(selected, enabled, role,
12    /// onClick)`. A screen reader reads the role, whether the choice is
13    /// picked, and offers the click; a person who cannot see the screen hears
14    /// "Receipts, tab, selected" rather than a bare word.
15    ///
16    /// `role` is what the reader announces the control **as**; a tab row
17    /// passes `Some(SemanticsWidgetRole::Tab)`, a radio list
18    /// `Some(SemanticsWidgetRole::RadioButton)`. The picked state is published
19    /// either way, as Compose's `selected` semantics.
20    pub fn selectable(
21        self,
22        selected: bool,
23        role: Option<SemanticsWidgetRole>,
24        on_click: impl Fn() + 'static,
25    ) -> Self {
26        let on_click = Rc::new(on_click);
27        let modifier = Modifier::empty()
28            .clickable(move |_point| on_click())
29            .with_inspector_metadata(inspector_metadata("selectable", move |info| {
30                info.add_property("selected", if selected { "true" } else { "false" });
31                info.add_property("onClick", "provided");
32            }))
33            .then(Modifier::empty().semantics(selectable_semantics(selected, role)));
34        self.then(modifier)
35    }
36}
37
38fn selectable_semantics(
39    selected: bool,
40    role: Option<SemanticsWidgetRole>,
41) -> impl Fn(&mut SemanticsConfiguration) {
42    move |config| {
43        config.is_clickable = true;
44        config.selected = Some(selected);
45        if let Some(role) = role {
46            config.role = Some(role);
47        }
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn a_picked_tab_says_so_and_takes_a_click() {
57        let mut config = SemanticsConfiguration::default();
58
59        selectable_semantics(true, Some(SemanticsWidgetRole::Tab))(&mut config);
60
61        assert_eq!(config.selected, Some(true));
62        assert_eq!(config.role, Some(SemanticsWidgetRole::Tab));
63        assert!(config.is_clickable);
64    }
65
66    #[test]
67    fn a_choice_with_no_role_still_says_whether_it_is_picked() {
68        let mut config = SemanticsConfiguration::default();
69
70        selectable_semantics(false, None)(&mut config);
71
72        assert_eq!(config.selected, Some(false));
73        assert_eq!(config.role, None);
74    }
75}