use std::time::{Duration, Instant};
const DEFAULT_DOUBLE_CLICK_THRESHOLD: Duration = Duration::from_millis(500);
const DEFAULT_MAX_DISTANCE: u16 = 5;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ClickType {
Single,
Double,
Triple,
}
impl ClickType {
pub fn count(&self) -> u8 {
match self {
Self::Single => 1,
Self::Double => 2,
Self::Triple => 3,
}
}
}
#[derive(Debug, Clone)]
pub struct ClickDetector {
last_click: Option<(Instant, u16, u16)>,
click_count: u8,
double_click_threshold: Duration,
max_distance: u16,
}
impl Default for ClickDetector {
fn default() -> Self {
Self::new()
}
}
impl ClickDetector {
pub fn new() -> Self {
Self {
last_click: None,
click_count: 0,
double_click_threshold: DEFAULT_DOUBLE_CLICK_THRESHOLD,
max_distance: DEFAULT_MAX_DISTANCE,
}
}
pub fn with_threshold(threshold: Duration) -> Self {
Self {
double_click_threshold: threshold,
..Self::new()
}
}
pub fn with_max_distance(distance: u16) -> Self {
Self {
max_distance: distance,
..Self::new()
}
}
pub fn set_threshold(&mut self, threshold: Duration) {
self.double_click_threshold = threshold;
}
pub fn set_max_distance(&mut self, distance: u16) {
self.max_distance = distance;
}
pub fn click_count(&self) -> u8 {
self.click_count
}
pub fn reset(&mut self) {
self.last_click = None;
self.click_count = 0;
}
pub fn handle_click(&mut self, x: u16, y: u16) -> Option<ClickType> {
let now = Instant::now();
if let Some((last_time, last_x, last_y)) = self.last_click {
if now.duration_since(last_time) > self.double_click_threshold {
self.click_count = 1;
self.last_click = Some((now, x, y));
return None;
}
let dx = x.abs_diff(last_x);
let dy = y.abs_diff(last_y);
if dx > self.max_distance || dy > self.max_distance {
self.click_count = 1;
self.last_click = Some((now, x, y));
return None;
}
self.click_count += 1;
self.last_click = Some((now, x, y));
match self.click_count {
1 => None,
2 => Some(ClickType::Double),
3 => {
self.click_count = 0;
Some(ClickType::Triple)
}
_ => {
self.click_count = 1;
None
}
}
} else {
self.click_count = 1;
self.last_click = Some((now, x, y));
None
}
}
pub fn check_timeout(&mut self) -> Option<ClickType> {
if self.click_count == 1 {
if let Some((last_time, _, _)) = self.last_click {
if last_time.elapsed() > self.double_click_threshold {
let result = Some(ClickType::Single);
self.reset();
return result;
}
}
}
None
}
pub fn time_until_timeout(&self) -> Option<Duration> {
if self.click_count == 1 {
if let Some((last_time, _, _)) = self.last_click {
let elapsed = last_time.elapsed();
if elapsed < self.double_click_threshold {
return Some(self.double_click_threshold - elapsed);
}
}
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_single_click() {
let mut detector = ClickDetector::new();
assert_eq!(detector.handle_click(10, 10), None);
assert_eq!(detector.click_count(), 1);
std::thread::sleep(detector.double_click_threshold + Duration::from_millis(50));
assert_eq!(detector.check_timeout(), Some(ClickType::Single));
}
#[test]
fn test_double_click() {
let mut detector = ClickDetector::new();
assert_eq!(detector.handle_click(10, 10), None);
std::thread::sleep(Duration::from_millis(100));
assert_eq!(detector.handle_click(10, 10), Some(ClickType::Double));
}
#[test]
fn test_triple_click() {
let mut detector = ClickDetector::new();
assert_eq!(detector.handle_click(10, 10), None);
std::thread::sleep(Duration::from_millis(100));
assert_eq!(detector.handle_click(10, 10), Some(ClickType::Double));
std::thread::sleep(Duration::from_millis(100));
assert_eq!(detector.handle_click(10, 10), Some(ClickType::Triple));
}
#[test]
fn test_click_timeout_between_clicks() {
let mut detector = ClickDetector::with_threshold(Duration::from_millis(200));
assert_eq!(detector.handle_click(10, 10), None);
std::thread::sleep(Duration::from_millis(250));
assert_eq!(detector.handle_click(10, 10), None);
assert_eq!(detector.click_count(), 1);
}
#[test]
fn test_click_distance_limit() {
let mut detector = ClickDetector::with_max_distance(3);
assert_eq!(detector.handle_click(10, 10), None);
assert_eq!(detector.handle_click(20, 20), None);
assert_eq!(detector.click_count(), 1);
}
#[test]
fn test_reset() {
let mut detector = ClickDetector::new();
detector.handle_click(10, 10);
assert_eq!(detector.click_count(), 1);
detector.reset();
assert_eq!(detector.click_count(), 0);
assert!(detector.last_click.is_none());
}
#[test]
fn test_click_type_count() {
assert_eq!(ClickType::Single.count(), 1);
assert_eq!(ClickType::Double.count(), 2);
assert_eq!(ClickType::Triple.count(), 3);
}
#[test]
fn test_four_clicks_resets() {
let mut detector = ClickDetector::new();
detector.handle_click(10, 10);
std::thread::sleep(Duration::from_millis(50));
detector.handle_click(10, 10);
std::thread::sleep(Duration::from_millis(50));
assert_eq!(detector.handle_click(10, 10), Some(ClickType::Triple));
std::thread::sleep(Duration::from_millis(50));
assert_eq!(detector.handle_click(10, 10), None);
assert_eq!(detector.click_count(), 1);
}
}