embedded_menu/interaction/
mod.rs1pub mod programmed;
2pub mod single_touch;
3
4#[cfg(feature = "simulator")]
5pub mod simulator;
6
7#[derive(Copy, Clone, Debug, PartialEq, Eq)]
8pub enum Interaction<R> {
9 Navigation(Navigation),
11 Action(Action<R>),
13}
14
15#[derive(Copy, Clone, Debug, PartialEq, Eq)]
16pub enum Action<R> {
17 Select,
19 Return(R),
21}
22
23#[derive(Copy, Clone, Debug, PartialEq, Eq)]
24#[must_use]
25pub enum Navigation {
26 Previous,
28 Next,
30 ForwardWrapping(usize),
32 Forward(usize),
34 BackwardWrapping(usize),
36 Backward(usize),
38 Beginning,
40 End,
42 JumpTo(usize),
44}
45
46impl Navigation {
47 pub(crate) fn calculate_selection(
50 self,
51 mut selected: usize,
52 count: usize,
53 selectable: impl Fn(usize) -> bool,
54 ) -> usize {
55 selected = selected.clamp(0, count - 1);
57 let original = selected;
58
59 #[allow(clippy::unnecessary_lazy_evaluations)]
61 match self {
62 Self::Next => loop {
63 selected = (selected + 1) % count;
64 if selectable(selected) {
65 break selected;
66 }
67 else if selected == original {
69 return 0;
70 }
71 },
72 Self::Previous => loop {
73 selected = selected.checked_sub(1).unwrap_or(count - 1);
74 if selectable(selected) {
75 break selected;
76 }
77 else if selected == original {
79 return 0;
80 }
81 },
82 Self::ForwardWrapping(n) => {
83 selected = (selected + n) % count;
84 if !selectable(selected) {
85 Self::Next.calculate_selection(selected, count, selectable)
86 } else {
87 selected
88 }
89 }
90 Self::Forward(n) => {
91 selected = selected.saturating_add(n).min(count - 1);
92 if !selectable(selected) {
93 Self::Next.calculate_selection(selected, count, selectable)
94 } else {
95 selected
96 }
97 }
98 Self::BackwardWrapping(n) => {
99 selected = selected
100 .checked_sub(n)
101 .unwrap_or_else(|| count - (n - selected) % count);
102 if !selectable(selected) {
103 Self::Previous.calculate_selection(selected, count, selectable)
104 } else {
105 selected
106 }
107 }
108 Self::Backward(n) => {
109 selected = selected.saturating_sub(n);
110 if !selectable(selected) {
111 Self::Previous.calculate_selection(selected, count, selectable)
112 } else {
113 selected
114 }
115 }
116 Self::Beginning => {
117 if !selectable(0) {
118 Self::Next.calculate_selection(0, count, selectable)
119 } else {
120 0
121 }
122 }
123 Self::End => {
124 if !selectable(count - 1) {
125 Self::Previous.calculate_selection(count - 1, count, selectable)
126 } else {
127 count - 1
128 }
129 }
130 Self::JumpTo(n) => {
131 selected = n.min(count - 1);
132 if !selectable(selected) {
133 Self::Next.calculate_selection(selected, count, selectable)
134 } else {
135 selected
136 }
137 }
138 }
139 }
140}
141
142#[derive(Copy, Clone, Debug, PartialEq, Eq)]
143pub enum InputResult<R> {
144 StateUpdate(InputState),
145 Interaction(Interaction<R>),
146}
147
148impl<R> From<Interaction<R>> for InputResult<R> {
149 fn from(interaction: Interaction<R>) -> Self {
150 Self::Interaction(interaction)
151 }
152}
153
154impl<R> From<InputState> for InputResult<R> {
155 fn from(state: InputState) -> Self {
156 Self::StateUpdate(state)
157 }
158}
159
160#[derive(Copy, Clone, Debug, PartialEq, Eq)]
161pub enum InputState {
162 Idle,
163 InProgress(u8),
164}
165
166pub trait InputAdapterSource<R>: Copy {
167 type InputAdapter: InputAdapter<Value = R>;
168
169 fn adapter(&self) -> Self::InputAdapter;
170}
171
172pub trait InputAdapter: Copy {
173 type Input;
174 type Value;
175 type State: Default + Copy;
176
177 fn handle_input(
178 &self,
179 state: &mut Self::State,
180 action: Self::Input,
181 ) -> InputResult<Self::Value>;
182}
183
184#[cfg(test)]
185mod test {
186 use super::*;
187
188 #[test]
189 fn selection() {
190 let count = 30;
191 let mut selected = 3;
192 for _ in 0..5 {
193 selected = Navigation::Previous.calculate_selection(selected, count, |_| true);
194 }
195 assert_eq!(selected, 28);
196
197 for _ in 0..5 {
198 selected = Navigation::Next.calculate_selection(selected, count, |_| true);
199 }
200 assert_eq!(selected, 3);
201
202 for _ in 0..5 {
203 selected =
204 Navigation::BackwardWrapping(5).calculate_selection(selected, count, |_| true);
205 }
206 assert_eq!(selected, 8);
207
208 for _ in 0..5 {
209 selected =
210 Navigation::ForwardWrapping(5).calculate_selection(selected, count, |_| true);
211 }
212 assert_eq!(selected, 3);
213
214 selected = Navigation::JumpTo(20).calculate_selection(selected, count, |_| true);
215 assert_eq!(selected, 20);
216
217 selected = Navigation::Beginning.calculate_selection(selected, count, |_| true);
218 assert_eq!(selected, 0);
219
220 selected = Navigation::End.calculate_selection(selected, count, |_| true);
221 assert_eq!(selected, 29);
222
223 for _ in 0..5 {
224 selected = Navigation::Backward(5).calculate_selection(selected, count, |_| true);
225 }
226 assert_eq!(selected, 4);
227
228 for _ in 0..5 {
229 selected = Navigation::Forward(5).calculate_selection(selected, count, |_| true);
230 }
231 assert_eq!(selected, 29);
232 }
233
234 #[test]
235 fn selection_large_stupid_numbers() {
236 let count = 30;
237 let mut selected = 3;
238
239 selected = Navigation::BackwardWrapping(75).calculate_selection(selected, count, |_| true);
240 assert_eq!(selected, 18);
241
242 selected = Navigation::ForwardWrapping(75).calculate_selection(selected, count, |_| true);
243 assert_eq!(selected, 3);
244
245 selected =
246 Navigation::BackwardWrapping(100000).calculate_selection(selected, count, |_| true);
247 assert_eq!(selected, 23);
248
249 selected =
250 Navigation::ForwardWrapping(100000).calculate_selection(selected, count, |_| true);
251 assert_eq!(selected, 3);
252
253 selected = Navigation::JumpTo(100).calculate_selection(selected, count, |_| true);
254 assert_eq!(selected, 29);
255
256 selected = Navigation::JumpTo(0).calculate_selection(selected, count, |_| true);
257 assert_eq!(selected, 0);
258
259 selected = Navigation::Forward(100000).calculate_selection(selected, count, |_| true);
260 assert_eq!(selected, 29);
261
262 selected = Navigation::Backward(100000).calculate_selection(selected, count, |_| true);
263 assert_eq!(selected, 0);
264 }
265
266 #[test]
267 fn unselectable_selection_infinite_loop() {
268 let selected = Navigation::BackwardWrapping(75).calculate_selection(5, 10, |_| false);
269 assert_eq!(selected, 0);
270 let selected = Navigation::ForwardWrapping(75).calculate_selection(5, 10, |_| false);
271 assert_eq!(selected, 0);
272 let selected = Navigation::BackwardWrapping(75).calculate_selection(5, 10, |_| false);
273 assert_eq!(selected, 0);
274 let selected = Navigation::ForwardWrapping(75).calculate_selection(5, 10, |_| false);
275 assert_eq!(selected, 0);
276 let selected = Navigation::JumpTo(75).calculate_selection(5, 10, |_| false);
277 assert_eq!(selected, 0);
278 let selected = Navigation::JumpTo(75).calculate_selection(5, 10, |_| false);
279 assert_eq!(selected, 0);
280 let selected = Navigation::Forward(75).calculate_selection(5, 10, |_| false);
281 assert_eq!(selected, 0);
282 let selected = Navigation::Backward(75).calculate_selection(5, 10, |_| false);
283 assert_eq!(selected, 0);
284 let selected = Navigation::Next.calculate_selection(5, 10, |_| false);
285 assert_eq!(selected, 0);
286 let selected = Navigation::Previous.calculate_selection(5, 10, |_| false);
287 assert_eq!(selected, 0);
288 let selected = Navigation::Beginning.calculate_selection(5, 10, |_| false);
289 assert_eq!(selected, 0);
290 let selected = Navigation::End.calculate_selection(5, 10, |_| false);
291 assert_eq!(selected, 0);
292 }
293}