Skip to main content

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