butt_head/butt_head.rs
1use crate::TimeDuration;
2use crate::config::Config;
3use crate::event::Event;
4use crate::service_timing::ServiceTiming;
5use crate::state_machine::{Edge, StateMachine};
6use crate::time::TimeInstant;
7
8/// The result of a single `update()` call.
9#[derive(Debug, Clone, Copy)]
10#[cfg_attr(feature = "defmt", derive(defmt::Format))]
11pub struct UpdateResult<D: TimeDuration, I: TimeInstant<Duration = D>> {
12 /// The event produced by this update, if any.
13 pub event: Option<Event<D, I>>,
14 /// When to call `update()` again. See [`ServiceTiming`].
15 pub next_service: ServiceTiming<D>,
16}
17
18/// Button input processor.
19///
20/// Expects clean, debounced input. If your button is subject to mechanical
21/// bounce, debounce the signal before passing it to `update()`.
22pub struct ButtHead<I: TimeInstant> {
23 prev_input: bool,
24 state_machine: StateMachine<I>,
25 config: &'static Config<I::Duration>,
26}
27
28impl<I: TimeInstant> ButtHead<I> {
29 /// Creates a new `ButtHead` instance with the given configuration.
30 pub fn new(config: &'static Config<I::Duration>) -> Self {
31 Self {
32 prev_input: false,
33 state_machine: StateMachine::new(config),
34 config,
35 }
36 }
37
38 /// Returns `true` if the button is currently physically pressed.
39 pub fn is_pressed(&self) -> bool {
40 self.prev_input
41 }
42
43 /// Returns the instant at which the button was last pressed, or `None` if
44 /// the button is not currently pressed.
45 pub fn press_instant(&self) -> Option<I> {
46 self.state_machine.pressed_at()
47 }
48
49 /// Returns how long the button has been continuously held, or `None` if it
50 /// is not currently pressed.
51 pub fn pressed_duration(&self, now: I) -> Option<I::Duration> {
52 self.state_machine
53 .pressed_at()
54 .map(|at| now.duration_since(at))
55 }
56
57 /// Cancels the pending `Click` event when the state machine is in
58 /// `WaitForMultiClick`. Returns `true` if cancelled, `false` if the state
59 /// machine was not waiting for a click (nothing to cancel).
60 ///
61 /// Call this from an `Event::Release { click_follows: true, .. }` handler
62 /// to suppress the upcoming `Click` (e.g. when the release was part of a
63 /// multi-button combo gesture). The state machine returns to `Idle` and no
64 /// `Click` event will fire.
65 pub fn cancel_pending_click(&mut self) -> bool {
66 self.state_machine.cancel_pending_click()
67 }
68
69 /// Advances the state machine.
70 ///
71 /// `is_pressed` is the raw pin state (before active-low inversion).
72 /// `now` is the current timestamp. Returns the resulting event and the
73 /// recommended time for the next call.
74 pub fn update(&mut self, is_pressed: bool, now: I) -> UpdateResult<I::Duration, I> {
75 let input = if self.config.active_low {
76 !is_pressed
77 } else {
78 is_pressed
79 };
80
81 let edge = if input != self.prev_input {
82 self.prev_input = input;
83 Some(if input { Edge::Press } else { Edge::Release })
84 } else {
85 None
86 };
87
88 let (event, next_service) = self.state_machine.update(edge, now);
89
90 UpdateResult {
91 event,
92 next_service,
93 }
94 }
95}