embedded_menu/interaction/
mod.rspub mod programmed;
pub mod single_touch;
#[cfg(feature = "simulator")]
pub mod simulator;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Interaction<R> {
Navigation(Navigation),
Action(Action<R>),
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Action<R> {
Select,
Return(R),
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[must_use]
pub enum Navigation {
Previous,
Next,
ForwardWrapping(usize),
Forward(usize),
BackwardWrapping(usize),
Backward(usize),
Beginning,
End,
JumpTo(usize),
}
impl Navigation {
pub(crate) fn calculate_selection(
self,
mut selected: usize,
count: usize,
selectable: impl Fn(usize) -> bool,
) -> usize {
selected = selected.clamp(0, count - 1);
let original = selected;
#[allow(clippy::unnecessary_lazy_evaluations)]
match self {
Self::Next => loop {
selected = (selected + 1) % count;
if selectable(selected) {
break selected;
}
else if selected == original {
return 0;
}
},
Self::Previous => loop {
selected = selected.checked_sub(1).unwrap_or(count - 1);
if selectable(selected) {
break selected;
}
else if selected == original {
return 0;
}
},
Self::ForwardWrapping(n) => {
selected = (selected + n) % count;
if !selectable(selected) {
Self::Next.calculate_selection(selected, count, selectable)
} else {
selected
}
}
Self::Forward(n) => {
selected = selected.saturating_add(n).min(count - 1);
if !selectable(selected) {
Self::Next.calculate_selection(selected, count, selectable)
} else {
selected
}
}
Self::BackwardWrapping(n) => {
selected = selected
.checked_sub(n)
.unwrap_or_else(|| count - (n - selected) % count);
if !selectable(selected) {
Self::Previous.calculate_selection(selected, count, selectable)
} else {
selected
}
}
Self::Backward(n) => {
selected = selected.saturating_sub(n);
if !selectable(selected) {
Self::Previous.calculate_selection(selected, count, selectable)
} else {
selected
}
}
Self::Beginning => {
if !selectable(0) {
Self::Next.calculate_selection(0, count, selectable)
} else {
0
}
}
Self::End => {
if !selectable(count - 1) {
Self::Previous.calculate_selection(count - 1, count, selectable)
} else {
count - 1
}
}
Self::JumpTo(n) => {
selected = n.min(count - 1);
if !selectable(selected) {
Self::Next.calculate_selection(selected, count, selectable)
} else {
selected
}
}
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum InputResult<R> {
StateUpdate(InputState),
Interaction(Interaction<R>),
}
impl<R> From<Interaction<R>> for InputResult<R> {
fn from(interaction: Interaction<R>) -> Self {
Self::Interaction(interaction)
}
}
impl<R> From<InputState> for InputResult<R> {
fn from(state: InputState) -> Self {
Self::StateUpdate(state)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum InputState {
Idle,
InProgress(u8),
}
pub trait InputAdapterSource<R>: Copy {
type InputAdapter: InputAdapter<Value = R>;
fn adapter(&self) -> Self::InputAdapter;
}
pub trait InputAdapter: Copy {
type Input;
type Value;
type State: Default + Copy;
fn handle_input(
&self,
state: &mut Self::State,
action: Self::Input,
) -> InputResult<Self::Value>;
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn selection() {
let count = 30;
let mut selected = 3;
for _ in 0..5 {
selected = Navigation::Previous.calculate_selection(selected, count, |_| true);
}
assert_eq!(selected, 28);
for _ in 0..5 {
selected = Navigation::Next.calculate_selection(selected, count, |_| true);
}
assert_eq!(selected, 3);
for _ in 0..5 {
selected =
Navigation::BackwardWrapping(5).calculate_selection(selected, count, |_| true);
}
assert_eq!(selected, 8);
for _ in 0..5 {
selected =
Navigation::ForwardWrapping(5).calculate_selection(selected, count, |_| true);
}
assert_eq!(selected, 3);
selected = Navigation::JumpTo(20).calculate_selection(selected, count, |_| true);
assert_eq!(selected, 20);
selected = Navigation::Beginning.calculate_selection(selected, count, |_| true);
assert_eq!(selected, 0);
selected = Navigation::End.calculate_selection(selected, count, |_| true);
assert_eq!(selected, 29);
for _ in 0..5 {
selected = Navigation::Backward(5).calculate_selection(selected, count, |_| true);
}
assert_eq!(selected, 4);
for _ in 0..5 {
selected = Navigation::Forward(5).calculate_selection(selected, count, |_| true);
}
assert_eq!(selected, 29);
}
#[test]
fn selection_large_stupid_numbers() {
let count = 30;
let mut selected = 3;
selected = Navigation::BackwardWrapping(75).calculate_selection(selected, count, |_| true);
assert_eq!(selected, 18);
selected = Navigation::ForwardWrapping(75).calculate_selection(selected, count, |_| true);
assert_eq!(selected, 3);
selected =
Navigation::BackwardWrapping(100000).calculate_selection(selected, count, |_| true);
assert_eq!(selected, 23);
selected =
Navigation::ForwardWrapping(100000).calculate_selection(selected, count, |_| true);
assert_eq!(selected, 3);
selected = Navigation::JumpTo(100).calculate_selection(selected, count, |_| true);
assert_eq!(selected, 29);
selected = Navigation::JumpTo(0).calculate_selection(selected, count, |_| true);
assert_eq!(selected, 0);
selected = Navigation::Forward(100000).calculate_selection(selected, count, |_| true);
assert_eq!(selected, 29);
selected = Navigation::Backward(100000).calculate_selection(selected, count, |_| true);
assert_eq!(selected, 0);
}
#[test]
fn unselectable_selection_infinite_loop() {
let selected = Navigation::BackwardWrapping(75).calculate_selection(5, 10, |_| false);
assert_eq!(selected, 0);
let selected = Navigation::ForwardWrapping(75).calculate_selection(5, 10, |_| false);
assert_eq!(selected, 0);
let selected = Navigation::BackwardWrapping(75).calculate_selection(5, 10, |_| false);
assert_eq!(selected, 0);
let selected = Navigation::ForwardWrapping(75).calculate_selection(5, 10, |_| false);
assert_eq!(selected, 0);
let selected = Navigation::JumpTo(75).calculate_selection(5, 10, |_| false);
assert_eq!(selected, 0);
let selected = Navigation::JumpTo(75).calculate_selection(5, 10, |_| false);
assert_eq!(selected, 0);
let selected = Navigation::Forward(75).calculate_selection(5, 10, |_| false);
assert_eq!(selected, 0);
let selected = Navigation::Backward(75).calculate_selection(5, 10, |_| false);
assert_eq!(selected, 0);
let selected = Navigation::Next.calculate_selection(5, 10, |_| false);
assert_eq!(selected, 0);
let selected = Navigation::Previous.calculate_selection(5, 10, |_| false);
assert_eq!(selected, 0);
let selected = Navigation::Beginning.calculate_selection(5, 10, |_| false);
assert_eq!(selected, 0);
let selected = Navigation::End.calculate_selection(5, 10, |_| false);
assert_eq!(selected, 0);
}
}