1use crate::tree::{WidgetId, WidgetTree};
20
21#[derive(Debug, Default, Clone)]
23pub struct FocusManager {
24 focused: Option<WidgetId>,
25 trap: Option<WidgetId>,
27}
28
29impl FocusManager {
30 pub fn new() -> Self {
32 Self::default()
33 }
34
35 pub fn focused(&self) -> Option<WidgetId> {
37 self.focused
38 }
39
40 pub fn trap(&self) -> Option<WidgetId> {
42 self.trap
43 }
44
45 pub fn focusable_set(&self, tree: &WidgetTree) -> Vec<WidgetId> {
49 let order = tree.focus_order();
50 match self.trap {
51 None => order,
52 Some(trap) => order
53 .into_iter()
54 .filter(|&id| id == trap || tree.is_descendant(id, trap))
55 .collect(),
56 }
57 }
58
59 pub fn focus(&mut self, tree: &WidgetTree, id: WidgetId) -> bool {
62 if self.focusable_set(tree).contains(&id) {
63 self.focused = Some(id);
64 true
65 } else {
66 false
67 }
68 }
69
70 pub fn blur(&mut self) {
72 self.focused = None;
73 }
74
75 pub fn push_trap(&mut self, tree: &WidgetTree, trap: WidgetId) -> Option<WidgetId> {
79 self.trap = Some(trap);
80 let inside = self.focusable_set(tree);
81 let still_valid = self.focused.map(|f| inside.contains(&f)).unwrap_or(false);
82 if !still_valid {
83 self.focused = inside.first().copied();
84 }
85 self.focused
86 }
87
88 pub fn pop_trap(&mut self) {
90 self.trap = None;
91 }
92
93 pub fn focus_next(&mut self, tree: &WidgetTree) -> Option<WidgetId> {
96 self.step(tree, true)
97 }
98
99 pub fn focus_prev(&mut self, tree: &WidgetTree) -> Option<WidgetId> {
101 self.step(tree, false)
102 }
103
104 fn step(&mut self, tree: &WidgetTree, forward: bool) -> Option<WidgetId> {
105 let order = self.focusable_set(tree);
106 if order.is_empty() {
107 self.focused = None;
108 return None;
109 }
110 let next = match self
111 .focused
112 .and_then(|f| order.iter().position(|&id| id == f))
113 {
114 Some(idx) => {
115 let n = order.len();
116 if forward {
117 (idx + 1) % n
118 } else {
119 (idx + n - 1) % n
120 }
121 }
122 None => {
125 if forward {
126 0
127 } else {
128 order.len() - 1
129 }
130 }
131 };
132 self.focused = order.get(next).copied();
133 self.focused
134 }
135
136 pub fn autofocus(
142 &mut self,
143 tree: &WidgetTree,
144 is_autofocus: impl Fn(WidgetId) -> bool,
145 ) -> Option<WidgetId> {
146 let target = self
147 .focusable_set(tree)
148 .into_iter()
149 .find(|&id| is_autofocus(id));
150 if let Some(id) = target {
151 self.focused = Some(id);
152 }
153 self.focused
154 }
155
156 pub fn reconcile(&mut self, tree: &WidgetTree) -> bool {
160 if let Some(f) = self.focused {
161 if !self.focusable_set(tree).contains(&f) {
162 self.focused = None;
163 return true;
164 }
165 }
166 false
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173 use crate::geometry::Rect;
174
175 fn focus_tree() -> (WidgetTree, [WidgetId; 5]) {
177 let mut t = WidgetTree::new(Rect::ZERO);
178 let a = t.insert(WidgetId::ROOT, Rect::ZERO).expect("root");
179 let b = t.insert(WidgetId::ROOT, Rect::ZERO).expect("root");
180 let modal = t.insert(WidgetId::ROOT, Rect::ZERO).expect("root");
181 let m1 = t.insert(modal, Rect::ZERO).expect("modal");
182 let m2 = t.insert(modal, Rect::ZERO).expect("modal");
183 for id in [a, b, m1, m2] {
184 if let Some(n) = t.get_mut(id) {
185 n.focusable = true;
186 }
187 }
188 (t, [a, b, modal, m1, m2])
190 }
191
192 #[test]
193 fn tab_cycles_with_wraparound() {
194 let (tree, [a, b, _modal, m1, m2]) = focus_tree();
195 let mut fm = FocusManager::new();
196 assert_eq!(fm.focus_next(&tree), Some(a));
198 assert_eq!(fm.focus_next(&tree), Some(b));
199 assert_eq!(fm.focus_next(&tree), Some(m1));
200 assert_eq!(fm.focus_next(&tree), Some(m2));
201 assert_eq!(fm.focus_next(&tree), Some(a));
203 }
204
205 #[test]
206 fn shift_tab_goes_backward_and_wraps() {
207 let (tree, [a, _b, _modal, _m1, m2]) = focus_tree();
208 let mut fm = FocusManager::new();
209 assert_eq!(fm.focus_prev(&tree), Some(m2));
211 fm.focus(&tree, a);
212 assert_eq!(fm.focus_prev(&tree), Some(m2));
214 }
215
216 #[test]
217 fn focus_trap_confines_tabbing() {
218 let (tree, [a, _b, modal, m1, m2]) = focus_tree();
219 let mut fm = FocusManager::new();
220 fm.focus(&tree, a);
221 let landed = fm.push_trap(&tree, modal);
223 assert_eq!(landed, Some(m1));
224 assert_eq!(fm.focus_next(&tree), Some(m2));
226 assert_eq!(fm.focus_next(&tree), Some(m1)); assert!(!fm.focus(&tree, a));
229 fm.pop_trap();
231 assert!(fm.focus(&tree, a));
232 }
233
234 #[test]
235 fn autofocus_picks_first_match() {
236 let (tree, [_a, b, _modal, _m1, _m2]) = focus_tree();
237 let mut fm = FocusManager::new();
238 let focused = fm.autofocus(&tree, |id| id == b);
239 assert_eq!(focused, Some(b));
240 }
241
242 #[test]
243 fn reconcile_drops_removed_focus() {
244 let (mut tree, [a, _b, _modal, _m1, _m2]) = focus_tree();
245 let mut fm = FocusManager::new();
246 fm.focus(&tree, a);
247 assert_eq!(fm.focused(), Some(a));
248 tree.remove(a);
249 assert!(fm.reconcile(&tree));
250 assert_eq!(fm.focused(), None);
251 }
252
253 #[test]
254 fn blur_clears_focus() {
255 let (tree, [a, ..]) = focus_tree();
256 let mut fm = FocusManager::new();
257 fm.focus(&tree, a);
258 fm.blur();
259 assert_eq!(fm.focused(), None);
260 }
261}