Skip to main content

fret_ui_kit/declarative/
collection_semantics.rs

1use fret_ui::element::PressableA11y;
2
3pub trait CollectionSemanticsExt {
4    fn with_collection_position(self, index: usize, count: usize) -> Self;
5}
6
7impl CollectionSemanticsExt for PressableA11y {
8    fn with_collection_position(mut self, index: usize, count: usize) -> Self {
9        if count == 0 || index >= count {
10            self.pos_in_set = None;
11            self.set_size = None;
12            return self;
13        }
14
15        let pos_in_set = index.saturating_add(1);
16        self.pos_in_set = u32::try_from(pos_in_set).ok();
17        self.set_size = u32::try_from(count).ok();
18
19        if let (Some(pos), Some(size)) = (self.pos_in_set, self.set_size)
20            && pos > size
21        {
22            self.pos_in_set = None;
23            self.set_size = None;
24        }
25
26        self
27    }
28}
29
30#[cfg(test)]
31mod tests {
32    use super::*;
33    use fret_app::App;
34    use fret_core::{
35        AppWindowId, PathCommand, SvgId, SvgService, TextBlobId, TextConstraints, TextInput,
36        TextMetrics, TextService,
37    };
38    use fret_core::{PathConstraints, PathId, PathMetrics, PathService, PathStyle};
39    use fret_core::{Point, Px, Rect};
40    use fret_ui::element::{ContainerProps, LayoutStyle, Length, PressableProps};
41    use fret_ui::{Theme, ThemeConfig, UiTree};
42
43    #[derive(Default)]
44    struct FakeServices;
45
46    impl TextService for FakeServices {
47        fn prepare(
48            &mut self,
49            _input: &TextInput,
50            _constraints: TextConstraints,
51        ) -> (TextBlobId, TextMetrics) {
52            (
53                TextBlobId::default(),
54                TextMetrics {
55                    size: fret_core::Size::new(Px(0.0), Px(0.0)),
56                    baseline: Px(0.0),
57                },
58            )
59        }
60
61        fn release(&mut self, _blob: TextBlobId) {}
62    }
63
64    impl PathService for FakeServices {
65        fn prepare(
66            &mut self,
67            _commands: &[PathCommand],
68            _style: PathStyle,
69            _constraints: PathConstraints,
70        ) -> (PathId, PathMetrics) {
71            (PathId::default(), PathMetrics::default())
72        }
73
74        fn release(&mut self, _path: PathId) {}
75    }
76
77    impl SvgService for FakeServices {
78        fn register_svg(&mut self, _bytes: &[u8]) -> SvgId {
79            SvgId::default()
80        }
81
82        fn unregister_svg(&mut self, _svg: SvgId) -> bool {
83            true
84        }
85    }
86
87    impl fret_core::MaterialService for FakeServices {
88        fn register_material(
89            &mut self,
90            _desc: fret_core::MaterialDescriptor,
91        ) -> Result<fret_core::MaterialId, fret_core::MaterialRegistrationError> {
92            Err(fret_core::MaterialRegistrationError::Unsupported)
93        }
94
95        fn unregister_material(&mut self, _id: fret_core::MaterialId) -> bool {
96            true
97        }
98    }
99
100    #[test]
101    fn pressable_a11y_can_stamp_collection_position() {
102        let window = AppWindowId::default();
103        let mut app = App::new();
104        let mut ui: UiTree<App> = UiTree::new();
105        ui.set_window(window);
106
107        Theme::with_global_mut(&mut app, |theme| {
108            theme.apply_config(&ThemeConfig {
109                name: "Test".to_string(),
110                ..ThemeConfig::default()
111            });
112        });
113
114        let mut services = FakeServices;
115        let bounds = Rect::new(
116            Point::new(Px(0.0), Px(0.0)),
117            fret_core::Size::new(Px(200.0), Px(80.0)),
118        );
119
120        let root = fret_ui::declarative::render_root(
121            &mut ui,
122            &mut app,
123            &mut services,
124            window,
125            bounds,
126            "test",
127            |cx| {
128                vec![cx.pressable(
129                    PressableProps {
130                        layout: {
131                            let mut layout = LayoutStyle::default();
132                            layout.size.width = Length::Fill;
133                            layout.size.height = Length::Px(Px(32.0));
134                            layout
135                        },
136                        a11y: PressableA11y::default().with_collection_position(56, 1200),
137                        ..Default::default()
138                    },
139                    |cx, _st| vec![cx.container(ContainerProps::default(), |_| Vec::new())],
140                )]
141            },
142        );
143        ui.set_root(root);
144        ui.request_semantics_snapshot();
145        ui.layout_all(&mut app, &mut services, bounds, 1.0);
146
147        let snap = ui.semantics_snapshot().expect("semantics snapshot");
148        let node = snap
149            .nodes
150            .iter()
151            .find(|n| n.role == fret_core::SemanticsRole::Button)
152            .expect("pressable semantics node");
153        assert_eq!(node.pos_in_set, Some(57));
154        assert_eq!(node.set_size, Some(1200));
155    }
156}