Skip to main content

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}