1use crate::model::component_model::ComponentModel;
12use crate::model::components_model::SurfaceComponentsModel;
13use crate::protocol::common_types::ChildList;
14
15pub const FOCUSABLE_TYPES: &[&str] = &[
17 "Button",
18 "TextField",
19 "CheckBox",
20 "Slider",
21 "ChoicePicker",
22 "DateTimeInput",
23 "AudioPlayer",
27];
28
29pub struct FocusManager {
31 pub focusable_ids: Vec<String>,
33 current_index: usize,
35}
36
37impl Default for FocusManager {
38 fn default() -> Self {
39 Self::new()
40 }
41}
42
43impl FocusManager {
44 pub fn new() -> Self {
46 Self {
47 focusable_ids: Vec::new(),
48 current_index: 0,
49 }
50 }
51
52 pub fn rebuild_from_components(&mut self, components: &SurfaceComponentsModel) {
58 let previously_focused = self.focused_id().map(|s| s.to_string());
59
60 self.focusable_ids.clear();
61 self.current_index = 0;
62
63 let child_ids = collect_all_child_ids(components);
65 let all = components.all();
66
67 let mut roots: Vec<&ComponentModel> = all
68 .values()
69 .filter(|c| !child_ids.contains(c.id.as_str()))
70 .collect();
71 roots.sort_by(|a, b| a.id.cmp(&b.id));
73
74 for root in &roots {
75 self.collect_focusable_depth_first(root, components);
76 }
77
78 if let Some(ref prev_id) = previously_focused {
80 if let Some(idx) = self.focusable_ids.iter().position(|id| id == prev_id) {
81 self.current_index = idx;
82 }
83 }
84 }
85
86 pub fn focus_next(&mut self) {
88 if self.focusable_ids.is_empty() {
89 return;
90 }
91 self.current_index = (self.current_index + 1) % self.focusable_ids.len();
92 }
93
94 pub fn focus_prev(&mut self) {
96 if self.focusable_ids.is_empty() {
97 return;
98 }
99 if self.current_index == 0 {
100 self.current_index = self.focusable_ids.len() - 1;
101 } else {
102 self.current_index -= 1;
103 }
104 }
105
106 pub fn is_focused(&self, id: &str) -> bool {
108 self.focusable_ids
109 .get(self.current_index)
110 .is_some_and(|focused| focused == id)
111 }
112
113 pub fn focused_id(&self) -> Option<&str> {
115 self.focusable_ids.get(self.current_index).map(|s| s.as_str())
116 }
117
118 pub fn reset(&mut self) {
120 self.focusable_ids.clear();
121 self.current_index = 0;
122 }
123
124 fn collect_focusable_depth_first(
130 &mut self,
131 component: &ComponentModel,
132 components: &SurfaceComponentsModel,
133 ) {
134 if FOCUSABLE_TYPES.contains(&component.component_type.as_str()) {
135 self.focusable_ids.push(component.id.clone());
136 }
137
138 if let Some(child_ids) = component.children() {
140 match child_ids {
141 ChildList::Static(ids) => {
142 for cid in &ids {
143 if let Some(child) = components.get(cid) {
144 self.collect_focusable_depth_first(child, components);
145 }
146 }
147 }
148 ChildList::Template { .. } => {
149 }
153 }
154 }
155
156 if let Some(single_id) = component.child() {
158 if let Some(child) = components.get(&single_id) {
159 self.collect_focusable_depth_first(child, components);
160 }
161 }
162 }
163}
164
165fn collect_all_child_ids(components: &SurfaceComponentsModel) -> std::collections::HashSet<String> {
167 let mut ids = std::collections::HashSet::new();
168 for component in components.all().values() {
169 if let Some(ChildList::Static(children)) = component.children() {
170 for cid in children {
171 ids.insert(cid.clone());
172 }
173 }
174 if let Some(single_id) = component.child() {
175 ids.insert(single_id);
176 }
177 }
178 ids
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184 use serde_json::json;
185
186 fn make_component(id: &str, component_type: &str) -> ComponentModel {
187 ComponentModel::from_json(&json!({
188 "id": id,
189 "component": component_type,
190 }))
191 .unwrap()
192 }
193
194 fn make_container(id: &str, component_type: &str, child_ids: &[&str]) -> ComponentModel {
195 ComponentModel::from_json(&json!({
196 "id": id,
197 "component": component_type,
198 "children": child_ids,
199 }))
200 .unwrap()
201 }
202
203 #[test]
204 fn collects_focusable_in_dfs_order() {
205 let mut surface = SurfaceComponentsModel::new();
206 surface.upsert(make_container("col", "Column", &["btn1", "row"]));
208 surface.upsert(make_component("btn1", "Button"));
209 surface.upsert(make_container("row", "Row", &["tf1", "btn2"]));
210 surface.upsert(make_component("tf1", "TextField"));
211 surface.upsert(make_component("btn2", "Button"));
212
213 let mut fm = FocusManager::new();
214 fm.rebuild_from_components(&surface);
215
216 assert_eq!(fm.focusable_ids, vec!["btn1", "tf1", "btn2"]);
217 }
218
219 #[test]
220 fn focus_cycles_forward() {
221 let mut fm = FocusManager::new();
222 fm.focusable_ids = vec!["a".into(), "b".into(), "c".into()];
223
224 assert_eq!(fm.focused_id(), Some("a"));
225 fm.focus_next();
226 assert_eq!(fm.focused_id(), Some("b"));
227 fm.focus_next();
228 assert_eq!(fm.focused_id(), Some("c"));
229 fm.focus_next();
230 assert_eq!(fm.focused_id(), Some("a")); }
232
233 #[test]
234 fn focus_cycles_backward() {
235 let mut fm = FocusManager::new();
236 fm.focusable_ids = vec!["a".into(), "b".into(), "c".into()];
237
238 fm.focus_prev();
239 assert_eq!(fm.focused_id(), Some("c")); fm.focus_prev();
241 assert_eq!(fm.focused_id(), Some("b"));
242 }
243
244 #[test]
245 fn is_focused_checks_current() {
246 let mut fm = FocusManager::new();
247 fm.focusable_ids = vec!["a".into(), "b".into()];
248
249 assert!(fm.is_focused("a"));
250 assert!(!fm.is_focused("b"));
251 fm.focus_next();
252 assert!(!fm.is_focused("a"));
253 assert!(fm.is_focused("b"));
254 }
255
256 #[test]
257 fn reset_clears_everything() {
258 let mut fm = FocusManager::new();
259 fm.focusable_ids = vec!["a".into()];
260 fm.focus_next();
261 fm.reset();
262 assert!(fm.focused_id().is_none());
263 assert!(fm.focusable_ids.is_empty());
264 }
265
266 #[test]
267 fn empty_surface_yields_no_focus() {
268 let surface = SurfaceComponentsModel::new();
269 let mut fm = FocusManager::new();
270 fm.rebuild_from_components(&surface);
271 assert!(fm.focused_id().is_none());
272 fm.focus_next(); assert!(fm.focused_id().is_none());
274 }
275
276 #[test]
277 fn rebuild_preserves_focus_if_still_present() {
278 let mut surface = SurfaceComponentsModel::new();
279 surface.upsert(make_container("col", "Column", &["btn1", "btn2"]));
280 surface.upsert(make_component("btn1", "Button"));
281 surface.upsert(make_component("btn2", "Button"));
282
283 let mut fm = FocusManager::new();
284 fm.rebuild_from_components(&surface);
285 fm.focus_next(); assert_eq!(fm.focused_id(), Some("btn2"));
287
288 fm.rebuild_from_components(&surface);
290 assert_eq!(fm.focused_id(), Some("btn2"));
291 }
292
293 #[test]
294 fn non_focusable_types_are_skipped() {
295 let mut surface = SurfaceComponentsModel::new();
296 surface.upsert(make_component("txt", "Text"));
297 surface.upsert(make_component("btn", "Button"));
298
299 let mut fm = FocusManager::new();
300 fm.rebuild_from_components(&surface);
301 assert_eq!(fm.focusable_ids, vec!["btn"]);
302 }
303}