embedded_menu/interaction/
mod.rs

1pub 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    /// Change the selection
10    Navigation(Navigation),
11    /// Return a value
12    Action(Action<R>),
13}
14
15#[derive(Copy, Clone, Debug, PartialEq, Eq)]
16pub enum Action<R> {
17    /// Select the currently selected item, executing any relevant action.
18    Select,
19    /// Return a value
20    Return(R),
21}
22
23#[derive(Copy, Clone, Debug, PartialEq, Eq)]
24#[must_use]
25pub enum Navigation {
26    /// Equivalent to `BackwardWrapping(1)`, kept for backward compatibility.
27    Previous,
28    /// Equivalent to `ForwardWrapping(1)`, kept for backward compatibility.
29    Next,
30    /// Move the selection forward by `usize` items, wrapping around to the beginning if necessary.
31    ForwardWrapping(usize),
32    /// Move the selection forward by `usize` items, clamping at the end if necessary.
33    Forward(usize),
34    /// Move the selection backward by `usize` items, wrapping around to the end if necessary.
35    BackwardWrapping(usize),
36    /// Move the selection backward by `usize` items, clamping at the beginning if necessary.
37    Backward(usize),
38    /// Equivalent to `JumpTo(0)`, but simpler in semantics.
39    Beginning,
40    /// Equivalent to `JumpTo(usize::MAX)`, but simpler in semantics.
41    End,
42    /// Jump to the `usize`th item in the list, clamping at the beginning and end if necessary.
43    JumpTo(usize),
44}
45
46impl Navigation {
47    /// Internal function to change the selection based on interaction.
48    /// Separated to allow for easier testing.
49    pub(crate) fn calculate_selection(
50        self,
51        mut selected: usize,
52        count: usize,
53        selectable: impl Fn(usize) -> bool,
54    ) -> usize {
55        // Clamp the selection to the range of selectable items.
56        selected = selected.clamp(0, count - 1);
57        let original = selected;
58
59        // The lazy evaluation is necessary to prevent overflows.
60        #[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                // Prevent infinite loop if nothing is selectable.
68                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                // Prevent infinite loop if nothing is selectable.
78                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}