fret_ui_kit/declarative/
collection_semantics.rs1use 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}