anathema_widgets/
tabindex.rs1use std::cmp::Ordering;
2use std::fmt::Debug;
3
4use anathema_state::StateId;
5
6use crate::{WidgetId, WidgetKind, WidgetTreeView};
7
8#[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 match &mut smallest_index {
120 Some(smallest) if *smallest > index => *smallest = index,
121 None => smallest_index = Some(index),
122 Some(_) => {}
123 }
124
125 match &mut largest_index {
127 Some(largest) if *largest < index => *largest = index,
128 None => largest_index = Some(index),
129 Some(_) => {}
130 }
131
132 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 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 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}