#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
use core::time::Duration;
pub use config::{ButtonConfig, Mode};
pub use instant::InstantProvider;
pub use pin_wrapper::PinWrapper;
pub mod config;
pub mod instant;
mod pin_wrapper;
#[cfg(all(test, feature = "std"))]
mod tests;
#[derive(Clone, Debug)]
pub struct Button<P, I, D = Duration> {
pub pin: P,
state: State<I>,
clicks: usize,
held: Option<D>,
holds: usize,
config: ButtonConfig<D>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum State<I> {
Down(I),
Pressed(I),
Up(I),
Held(I),
Released,
Unknown,
}
impl<I: PartialEq> State<I> {
pub fn is_down(&self) -> bool {
matches!(self, Self::Down(_))
}
pub fn is_pressed(&self) -> bool {
matches!(self, Self::Pressed(_))
}
pub fn is_up(&self) -> bool {
matches!(self, Self::Up(_))
}
pub fn is_held(&self) -> bool {
matches!(self, Self::Held(_))
}
pub fn is_released(&self) -> bool {
*self == Self::Released
}
pub fn is_unknown(&self) -> bool {
*self == Self::Unknown
}
}
impl<P, I, D> Button<P, I, D>
where
P: PinWrapper,
I: InstantProvider<D> + PartialEq,
D: Clone + Ord,
{
pub const fn new(pin: P, config: ButtonConfig<D>) -> Self {
Self {
pin,
config,
state: State::Unknown,
clicks: 0,
holds: 0,
held: None,
}
}
pub fn clicks(&self) -> usize {
if self.state == State::Released {
self.clicks
} else {
0
}
}
pub fn holds(&self) -> usize {
if self.state == State::Released {
self.holds
} else {
0
}
}
pub fn reset(&mut self) {
if self.state == State::Released {
self.clicks = 0;
self.holds = 0;
self.held = None;
}
}
pub fn is_clicked(&self) -> bool {
self.clicks() == 1
}
pub fn is_double_clicked(&self) -> bool {
self.clicks() == 2
}
pub fn is_triple_clicked(&self) -> bool {
self.clicks() == 3
}
pub fn held_time(&self) -> Option<D> {
if self.state == State::Released {
self.held.clone()
} else {
None
}
}
pub fn current_holding_time(&self) -> Option<D> {
if let State::Held(dur) = &self.state {
Some(dur.elapsed())
} else {
None
}
}
pub const fn raw_state(&self) -> &State<I> {
&self.state
}
pub const fn raw_clicks(&self) -> usize {
self.clicks
}
pub const fn raw_holds(&self) -> usize {
self.holds
}
pub fn tick(&mut self) {
match self.state.clone() {
State::Unknown if self.is_pin_pressed() => {
self.clicks += 1;
self.state = State::Down(I::now());
}
State::Unknown if self.is_pin_released() => self.state = State::Released,
State::Down(elapsed) => {
if self.is_pin_pressed() {
if elapsed.elapsed() >= self.config.debounce {
self.state = State::Pressed(elapsed.clone());
} else {
}
} else {
self.state = State::Released;
}
}
State::Pressed(elapsed) => {
if self.is_pin_pressed() {
if elapsed.elapsed() >= self.config.hold {
self.clicks -= 1;
self.holds += 1;
self.state = State::Held(elapsed.clone());
} else {
}
} else {
self.state = State::Up(I::now())
}
}
State::Up(elapsed) => {
if elapsed.elapsed() < self.config.release {
if self.is_pin_pressed() {
self.clicks += 1;
self.state = State::Down(I::now());
} else {
}
} else {
self.state = State::Released;
}
}
State::Released if self.is_pin_pressed() => {
self.clicks += 1;
self.held = None;
self.state = State::Down(I::now());
}
State::Held(elapsed) => {
if self.is_pin_released() {
self.held = Some(elapsed.elapsed());
self.state = State::Up(I::now());
} else {
}
}
_ => {}
}
}
fn is_pin_released(&mut self) -> bool {
self.pin.is_high() == self.config.mode.is_pullup()
}
fn is_pin_pressed(&mut self) -> bool {
!self.is_pin_released()
}
}