1use crate::focus::{FocusIntent, FocusQuery, FocusTarget};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
13#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
14pub enum FocusWrap {
15 #[default]
18 Clamp,
19 Wrap,
22}
23
24impl FocusWrap {
25 pub fn step(self, index: usize, len: usize, forward: bool) -> usize {
32 match (self, forward) {
33 (FocusWrap::Wrap, true) => (index + 1) % len,
34 (FocusWrap::Wrap, false) => (index + len - 1) % len,
35 (FocusWrap::Clamp, true) => (index + 1).min(len - 1),
36 (FocusWrap::Clamp, false) => index.saturating_sub(1),
37 }
38 }
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
49pub enum OverlayFocus<O = (), M = ()> {
50 Simple(O),
51 Modal { data: M, index: usize, count: usize },
52}
53
54#[derive(Debug, Clone, PartialEq, Eq)]
55struct EnteredSection {
56 section_id: usize,
57 item_index: usize,
58 item_count: usize,
59}
60
61#[derive(Debug, Clone, PartialEq, Eq)]
62pub struct FocusManager<O = (), M = ()> {
63 targets: Vec<FocusTarget<O>>,
64 index: usize,
65 overlay: Option<OverlayFocus<O, M>>,
66 entered_section: Option<EnteredSection>,
67 section_items: Vec<(usize, usize)>,
71 wrap: FocusWrap,
72}
73
74impl<O, M> Default for FocusManager<O, M> {
75 fn default() -> Self {
76 Self::new()
77 }
78}
79
80pub trait FocusController<O = (), M = ()> {
81 fn apply_focus_intent(&mut self, intent: FocusIntent<O, M>);
82}
83
84impl<O, M> FocusManager<O, M> {
86 pub fn new() -> Self {
87 Self {
88 targets: Vec::new(),
89 index: 0,
90 overlay: None,
91 entered_section: None,
92 section_items: Vec::new(),
93 wrap: FocusWrap::Clamp,
94 }
95 }
96
97 pub fn focus_wrap(&self) -> FocusWrap {
99 self.wrap
100 }
101
102 pub fn set_focus_wrap(&mut self, wrap: FocusWrap) {
104 self.wrap = wrap;
105 }
106
107 pub fn targets(&self) -> &[FocusTarget<O>] {
108 &self.targets
109 }
110
111 pub fn overlay(&self) -> Option<&OverlayFocus<O, M>> {
112 self.overlay.as_ref()
113 }
114
115 pub fn overlay_mut(&mut self) -> Option<&mut OverlayFocus<O, M>> {
116 self.overlay.as_mut()
117 }
118
119 pub fn register_page(&mut self, targets: Vec<FocusTarget<O>>) {
120 self.targets = targets;
121 self.index = 0;
122 self.entered_section = None;
123 self.section_items.clear();
124 }
125
126 pub fn set_section_items(&mut self, section_items: Vec<(usize, usize)>) {
130 self.section_items = section_items;
131 if let Some(section) = &mut self.entered_section {
132 match self
133 .section_items
134 .iter()
135 .find(|(id, _)| *id == section.section_id)
136 .map(|(_, count)| *count)
137 {
138 Some(0) | None => self.entered_section = None,
139 Some(count) => {
140 section.item_count = count;
141 section.item_index = section.item_index.min(count - 1);
142 }
143 }
144 }
145 }
146
147 fn section_item_count(&self, section_id: usize) -> Option<usize> {
149 self.section_items
150 .iter()
151 .find(|(id, _)| *id == section_id)
152 .map(|(_, count)| *count)
153 }
154
155 pub fn has_overlay(&self) -> bool {
156 self.overlay.is_some()
157 }
158
159 pub fn next(&mut self) {
160 let wrap = self.wrap;
161
162 if let Some(OverlayFocus::Modal { index, count, .. }) = &mut self.overlay {
163 if *count > 0 {
164 *index = wrap.step(*index, *count, true);
165 }
166 return;
167 }
168
169 if self.overlay.is_some() {
170 return;
171 }
172
173 if let Some(section) = &self.entered_section {
174 if section.item_index + 1 < section.item_count {
175 if let Some(section) = &mut self.entered_section {
176 section.item_index += 1;
177 }
178 return;
179 }
180 self.entered_section = None;
184 }
185
186 if self
187 .targets
188 .get(self.index)
189 .map(FocusTarget::is_canvas)
190 .unwrap_or(false)
191 {
192 return;
193 }
194
195 for index in (self.index + 1)..self.targets.len() {
196 if self.targets[index].is_top_level_navigable() {
197 self.index = index;
198 return;
199 }
200 }
201
202 if matches!(wrap, FocusWrap::Wrap) {
204 for index in 0..self.index {
205 if self.targets[index].is_top_level_navigable() {
206 self.index = index;
207 return;
208 }
209 }
210 }
211 }
212
213 pub fn prev(&mut self) {
214 let wrap = self.wrap;
215
216 if let Some(OverlayFocus::Modal { index, count, .. }) = &mut self.overlay {
217 if *count > 0 {
218 *index = wrap.step(*index, *count, false);
219 }
220 return;
221 }
222
223 if self.overlay.is_some() {
224 return;
225 }
226
227 if let Some(section) = &self.entered_section {
228 if section.item_index > 0 {
229 if let Some(section) = &mut self.entered_section {
230 section.item_index -= 1;
231 }
232 return;
233 }
234 self.entered_section = None;
237 }
238
239 if self
240 .targets
241 .get(self.index)
242 .map(FocusTarget::is_canvas)
243 .unwrap_or(false)
244 {
245 return;
246 }
247
248 for index in (0..self.index).rev() {
249 if self.targets[index].is_top_level_navigable() {
250 self.index = index;
251 return;
252 }
253 }
254
255 if matches!(wrap, FocusWrap::Wrap) {
257 for index in ((self.index + 1)..self.targets.len()).rev() {
258 if self.targets[index].is_top_level_navigable() {
259 self.index = index;
260 return;
261 }
262 }
263 }
264 }
265
266 pub fn show_modal(&mut self, data: M, count: usize) {
270 self.overlay = Some(OverlayFocus::Modal {
271 data,
272 index: 0,
273 count,
274 });
275 }
276
277 pub fn clear_overlay(&mut self) {
278 self.overlay = None;
279 }
280
281 pub fn exit_canvas_forward(&mut self) {
282 for index in (self.index + 1)..self.targets.len() {
283 if self.targets[index].is_top_level_navigable() && !self.targets[index].is_canvas() {
284 self.index = index;
285 return;
286 }
287 }
288 }
289
290 pub fn exit_canvas_backward(&mut self) {
291 for index in (0..self.index).rev() {
292 if self.targets[index].is_top_level_navigable() && !self.targets[index].is_canvas() {
293 self.index = index;
294 return;
295 }
296 }
297 }
298
299 pub fn enter_section(&mut self, item_count: usize) {
300 if item_count == 0 {
301 return;
302 }
303
304 if let Some(FocusTarget::Section(section_id)) = self.targets.get(self.index) {
305 self.entered_section = Some(EnteredSection {
306 section_id: *section_id,
307 item_index: 0,
308 item_count,
309 });
310 }
311 }
312
313 pub fn activate(&mut self) {
321 if self.overlay.is_some() || self.entered_section.is_some() {
322 return;
323 }
324 if let Some(FocusTarget::Section(section_id)) = self.targets.get(self.index) {
325 if let Some(item_count) = self.section_item_count(*section_id) {
326 self.enter_section(item_count);
327 }
328 }
329 }
330
331 pub fn enter_section_at(&mut self, section_id: usize, item_count: usize, item_index: usize) {
332 if item_count == 0 {
333 return;
334 }
335
336 if let Some(position) = self
337 .targets
338 .iter()
339 .position(|target| matches!(target, FocusTarget::Section(id) if *id == section_id))
340 {
341 self.index = position;
342 self.overlay = None;
343 self.entered_section = Some(EnteredSection {
344 section_id,
345 item_index: item_index.min(item_count.saturating_sub(1)),
346 item_count,
347 });
348 }
349 }
350
351 pub fn leave_section(&mut self) {
352 self.entered_section = None;
353 }
354}
355
356impl<O: Clone, M> FocusManager<O, M> {
358 pub fn current(&self) -> Option<FocusTarget<O>> {
359 if let Some(overlay) = &self.overlay {
360 return Some(match overlay {
361 OverlayFocus::Simple(kind) => FocusTarget::Overlay(kind.clone()),
362 OverlayFocus::Modal { index, .. } => FocusTarget::ModalItem(*index),
363 });
364 }
365
366 if let Some(section) = &self.entered_section {
367 return Some(FocusTarget::SectionItem {
368 section: section.section_id,
369 item: section.item_index,
370 });
371 }
372
373 self.targets.get(self.index).cloned()
374 }
375
376 pub fn query(&self) -> FocusQuery<O> {
377 FocusQuery {
378 current: self.current(),
379 }
380 }
381}
382
383impl<O: Clone + PartialEq, M> FocusManager<O, M> {
385 pub fn is_focused(&self, target: &FocusTarget<O>) -> bool {
386 self.current().as_ref() == Some(target)
387 }
388
389 pub fn add_target(&mut self, target: FocusTarget<O>) {
390 if !self.targets.contains(&target) {
391 self.targets.push(target);
392 }
393 }
394
395 pub fn remove_target(&mut self, target: &FocusTarget<O>) {
396 if let Some(position) = self
397 .targets
398 .iter()
399 .position(|candidate| candidate == target)
400 {
401 self.targets.remove(position);
402 if self.index >= self.targets.len() && !self.targets.is_empty() {
403 self.index = self.targets.len() - 1;
404 }
405 }
406 }
407
408 pub fn set_focus(&mut self, target: FocusTarget<O>) {
409 if let Some(kind) = target.to_overlay() {
410 self.overlay = Some(OverlayFocus::Simple(kind));
411 return;
412 }
413
414 if let FocusTarget::ModalItem(next_index) = target {
415 if let Some(OverlayFocus::Modal { index, count, .. }) = &mut self.overlay {
416 if next_index < *count {
417 *index = next_index;
418 }
419 }
420 return;
421 }
422
423 if let FocusTarget::Section(section_id) = target {
424 if let Some(position) = self.targets.iter().position(
425 |candidate| matches!(candidate, FocusTarget::Section(id) if *id == section_id),
426 ) {
427 self.index = position;
428 self.overlay = None;
429 self.entered_section = None;
430 }
431 return;
432 }
433
434 if let Some(position) = self
435 .targets
436 .iter()
437 .position(|candidate| candidate == &target)
438 {
439 self.index = position;
440 self.overlay = None;
441 self.entered_section = None;
442 }
443 }
444
445 pub fn open_overlay(&mut self, target: FocusTarget<O>) {
446 if let Some(kind) = target.to_overlay() {
447 self.overlay = Some(OverlayFocus::Simple(kind));
448 }
449 }
450
451 pub fn close_overlay(&mut self, target: FocusTarget<O>) {
452 let should_close = match (&self.overlay, target.to_overlay()) {
453 (Some(OverlayFocus::Simple(current)), Some(requested)) => current == &requested,
454 _ => false,
455 };
456
457 if should_close {
458 self.overlay = None;
459 }
460 }
461
462 pub fn toggle_overlay(&mut self, target: FocusTarget<O>) {
463 if self.is_overlay_open(&target) {
464 self.close_overlay(target);
465 } else {
466 self.open_overlay(target);
467 }
468 }
469
470 pub fn is_overlay_open(&self, target: &FocusTarget<O>) -> bool {
471 match (&self.overlay, target.to_overlay()) {
472 (Some(OverlayFocus::Simple(current)), Some(requested)) => current == &requested,
473 _ => false,
474 }
475 }
476}
477
478impl<O: Clone + PartialEq, M> FocusController<O, M> for FocusManager<O, M> {
479 fn apply_focus_intent(&mut self, intent: FocusIntent<O, M>) {
480 match intent {
481 FocusIntent::Next => self.next(),
482 FocusIntent::Prev => self.prev(),
483 FocusIntent::Set(target) => self.set_focus(target),
484 FocusIntent::Open(target) => self.open_overlay(target),
485 FocusIntent::Close(target) => self.close_overlay(target),
486 FocusIntent::Toggle(target) => self.toggle_overlay(target),
487 FocusIntent::RegisterPage(targets) => self.register_page(targets),
488 FocusIntent::RegisterPageAndEnterSection {
489 targets,
490 section,
491 item_count,
492 item,
493 } => {
494 self.register_page(targets);
495 self.enter_section_at(section, item_count, item);
496 }
497 FocusIntent::ShowModal { data, count } => self.show_modal(data, count),
498 FocusIntent::UpdateModal { data, count } => {
499 if let Some(OverlayFocus::Modal {
500 data: current_data,
501 count: current_count,
502 ..
503 }) = &mut self.overlay
504 {
505 *current_data = data;
506 *current_count = count;
507 }
508 }
509 FocusIntent::ClearOverlay => self.clear_overlay(),
510 FocusIntent::ExitCanvasForward => self.exit_canvas_forward(),
511 FocusIntent::ExitCanvasBackward => self.exit_canvas_backward(),
512 FocusIntent::EnterSection { item_count } => self.enter_section(item_count),
513 FocusIntent::LeaveSection => self.leave_section(),
514 FocusIntent::Activate => self.activate(),
515 }
516 }
517}