Skip to main content

anathema_widgets/
tabindex.rs

1use std::cmp::Ordering;
2use std::fmt::Debug;
3
4use anathema_state::StateId;
5
6use crate::{WidgetId, WidgetKind, WidgetTreeView};
7
8// TODO
9// Test this with
10// * One component
11// * Many components
12// * No components that accepts focus
13
14#[derive(Clone)]
15pub struct Index {
16    pub path: Box<[u16]>,
17    pub index: u16,
18    pub widget_id: WidgetId,
19    pub state_id: StateId,
20}
21
22impl Index {
23    fn to_ref(&self) -> IndexRef<'_> {
24        IndexRef {
25            path: &self.path,
26            index: self.index,
27        }
28    }
29}
30
31impl Debug for Index {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        write!(f, "{} | {:?}", self.index, self.path)
34    }
35}
36
37#[derive(Debug, Copy, Clone, PartialEq)]
38struct IndexRef<'a> {
39    path: &'a [u16],
40    index: u16,
41}
42
43impl IndexRef<'_> {
44    fn to_owned(self, widget_id: WidgetId, state_id: StateId) -> Index {
45        Index {
46            index: self.index,
47            path: self.path.into(),
48            widget_id,
49            state_id,
50        }
51    }
52}
53
54impl PartialOrd for IndexRef<'_> {
55    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
56        let ord = match self.index.cmp(&other.index) {
57            ord @ (Ordering::Less | Ordering::Greater) => ord,
58            Ordering::Equal => self.path.cmp(other.path),
59        };
60        Some(ord)
61    }
62}
63
64#[derive(Debug)]
65pub enum Direction {
66    Forward,
67    Backward,
68}
69
70pub struct TabIndex<'a, 'bp> {
71    pub previous: Option<Index>,
72    pub current: &'a mut Option<Index>,
73    tree: WidgetTreeView<'a, 'bp>,
74    pub changed: bool,
75}
76
77impl<'a, 'bp> TabIndex<'a, 'bp> {
78    pub fn new(current: &'a mut Option<Index>, tree: WidgetTreeView<'a, 'bp>) -> Self {
79        Self {
80            current,
81            previous: None,
82            tree,
83            changed: false,
84        }
85    }
86
87    pub fn consume(mut self) -> Option<Index> {
88        self.previous.take()
89    }
90
91    pub fn next(&mut self) {
92        self.find_component_accepting_focus(Direction::Forward);
93    }
94
95    pub fn prev(&mut self) {
96        self.find_component_accepting_focus(Direction::Backward);
97    }
98
99    fn find_component_accepting_focus(&mut self, dir: Direction) {
100        let values = self.tree.values.iter();
101
102        let mut next_index = NextIndex {
103            origin: self.current.as_ref().map(|i| i.to_ref()),
104            next: None,
105        };
106
107        let mut smallest_index = None;
108        let mut largest_index = None;
109
110        for (path, container) in values {
111            match &container.kind {
112                crate::WidgetKind::Component(component) if component.dyn_component.any_accept_focus() => {
113                    let index = IndexRef {
114                        path,
115                        index: component.tabindex,
116                    };
117
118                    // Keep track of the smallest index
119                    match &mut smallest_index {
120                        Some(smallest) if *smallest > index => *smallest = index,
121                        None => smallest_index = Some(index),
122                        Some(_) => {}
123                    }
124
125                    // Keep track of the largest index
126                    match &mut largest_index {
127                        Some(largest) if *largest < index => *largest = index,
128                        None => largest_index = Some(index),
129                        Some(_) => {}
130                    }
131
132                    // Skip the current index
133                    if let Some(origin) = &mut next_index.origin {
134                        match dir {
135                            Direction::Forward if *origin >= index => continue,
136                            Direction::Backward if *origin <= index => continue,
137                            _ => {}
138                        }
139                    }
140
141                    match &mut next_index.next {
142                        Some(next) => match dir {
143                            Direction::Forward if *next > index => *next = index,
144                            Direction::Backward if *next < index => *next = index,
145                            _ => {}
146                        },
147                        None => next_index.next = Some(index),
148                    }
149                }
150                _ => {}
151            }
152        }
153
154        // Handle wrapping around.
155        // I.e if the direction is forward and the current tab index is the same
156        // as the max index, then set the next index to be the smallest index
157        if let Some(origin) = next_index.origin.take() {
158            let largest_index = largest_index.expect("if there is a next, there is a largest");
159            let smallest_index = smallest_index.expect("if there is a largest, there is a smallest");
160            match dir {
161                Direction::Forward if origin == largest_index => {
162                    next_index.next.replace(smallest_index);
163                }
164                Direction::Backward if origin == smallest_index => {
165                    next_index.next.replace(largest_index);
166                }
167                _ => {}
168            }
169        }
170
171        let Some(next) = next_index.next.take() else { return };
172
173        let Some((widget_id, value)) = self.tree.get_node_and_value(next.path) else { return };
174        let WidgetKind::Component(comp) = &value.kind else { return };
175        let next = IndexRef::to_owned(next, widget_id, comp.state_id);
176
177        self.previous = self.current.replace(next);
178        self.changed = true;
179    }
180}
181
182#[derive(Debug)]
183struct NextIndex<'a> {
184    // The current index, before trying to find the next one
185    origin: Option<IndexRef<'a>>,
186    next: Option<IndexRef<'a>>,
187}
188
189#[cfg(test)]
190mod test {
191    use super::*;
192
193    #[test]
194    fn comparison() {
195        let a = IndexRef { path: &[0], index: 1 };
196
197        let b = IndexRef {
198            path: &[0, 0],
199            index: 1,
200        };
201
202        match a.partial_cmp(&b) {
203            Some(Ordering::Less) => (),
204            Some(Ordering::Greater) => unreachable!(),
205            Some(Ordering::Equal) => unreachable!(),
206            None => panic!(),
207        }
208    }
209}