1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
use crate::interaction::{InteractionController, InteractionType};

#[derive(Default)]
pub struct State {
    interaction_time: u32,
    was_released: bool,
}

#[derive(Clone, Copy)]
pub struct SingleTouch {
    ignore_time: u32,
    max_time: u32,
    interacted_before_release: bool,
}

/// Single touch navigation in hierarchical lists
///
/// Short press: select next item
/// Long press: activate current item
///             Holding the input does not cause the current item to fire repeatedly
///
/// Requires a `back` element.
impl SingleTouch {
    /// `ignore`: Ignore pulses with at most this many active samples
    /// `max`: Detect pulses with this many active samples as `Select`
    pub const fn new(ignore: u32, max: u32) -> Self {
        Self {
            ignore_time: ignore,
            max_time: max,
            interacted_before_release: false,
        }
    }
}

impl InteractionController for SingleTouch {
    type Input = bool;
    type State = State;

    fn reset(&self, state: &mut Self::State) {
        state.interaction_time = 0;
    }

    fn fill_area_width(&self, state: &Self::State, max: u32) -> u32 {
        if self.ignore_time <= state.interaction_time && state.interaction_time < self.max_time {
            // Draw indicator bar
            let time = (state.interaction_time - self.ignore_time) as f32
                / ((self.max_time - self.ignore_time) as f32 * 0.9);

            ((time * (max - 1) as f32) as u32).max(0)
        } else {
            // Don't draw anything
            0
        }
    }

    fn update(&mut self, state: &mut Self::State, action: Self::Input) -> Option<InteractionType> {
        if !state.was_released {
            if action {
                return None;
            }
            state.was_released = true;
        }

        if action {
            if state.interaction_time < self.max_time {
                state.interaction_time = state.interaction_time.saturating_add(1);
                None
            } else {
                state.interaction_time = 0;
                self.interacted_before_release = true;
                Some(InteractionType::Select)
            }
        } else {
            let time = core::mem::replace(&mut state.interaction_time, 0);

            if 0 < time && time < self.max_time && !self.interacted_before_release {
                Some(InteractionType::Next)
            } else {
                // Already interacted
                self.interacted_before_release = false;
                None
            }
        }
    }
}

#[cfg(test)]
mod test {
    use crate::interaction::{single_touch::SingleTouch, InteractionController, InteractionType};

    #[test]
    fn test_interaction() {
        // ignore 1 long pulses, accept 2-4 as short press, 5- as long press
        let controller = SingleTouch::new(1, 5);

        let mut controller_state = <SingleTouch as InteractionController>::State::default();

        let expectations: [&[_]; 6] = [
            &[(5, false, None)],
            // repeated short pulse ignored
            &[
                (1, true, None),
                (1, false, None),
                (1, true, None),
                (1, false, None),
                (1, true, None),
                (1, false, None),
            ],
            // longer pulse recongised as Next event on falling edge
            &[(2, true, None), (1, false, Some(InteractionType::Next))],
            &[(3, true, None), (1, false, Some(InteractionType::Next))],
            // long pulse NOT recognised as Select event on falling edge
            &[(4, true, None), (1, false, Some(InteractionType::Next))],
            // long pulse recognised as Select event immediately
            &[
                (4, true, None),
                (1, true, Some(InteractionType::Select)),
                (10, true, None),
                (1, false, None),
            ],
        ];

        for (row, &inputs) in expectations.iter().enumerate() {
            controller.reset(&mut controller_state);

            let mut sample = 0;
            for (repeat, input, expectation) in inputs.iter() {
                for _ in 0..*repeat {
                    let ret = controller.update(&mut controller_state, *input);

                    assert_eq!(
                        ret, *expectation,
                        "Mismatch at row {}, sample {}",
                        row, sample
                    );
                    sample += 1;
                }
            }
        }
    }
}