1#![forbid(unsafe_code)]
2#[derive(Debug, Clone, Copy, PartialEq)]
25pub struct TouchTarget {
26 width_px: f64,
27 height_px: f64,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum TouchTargetError {
32 InvalidWidth,
33 InvalidHeight,
34}
35
36fn validate_positive(value: f64, error: TouchTargetError) -> Result<f64, TouchTargetError> {
37 if !value.is_finite() || value <= 0.0 {
38 Err(error)
39 } else {
40 Ok(value)
41 }
42}
43
44#[must_use]
45pub fn minimum_touch_target_px() -> f64 {
46 44.0
47}
48
49impl TouchTarget {
50 pub fn new(width_px: f64, height_px: f64) -> Result<Self, TouchTargetError> {
51 Ok(Self {
52 width_px: validate_positive(width_px, TouchTargetError::InvalidWidth)?,
53 height_px: validate_positive(height_px, TouchTargetError::InvalidHeight)?,
54 })
55 }
56
57 #[must_use]
58 pub fn area_px(&self) -> f64 {
59 self.width_px * self.height_px
60 }
61
62 #[must_use]
63 pub fn min_dimension_px(&self) -> f64 {
64 self.width_px.min(self.height_px)
65 }
66
67 #[must_use]
68 pub fn is_recommended_size(&self) -> bool {
69 self.min_dimension_px() >= minimum_touch_target_px()
70 }
71}
72
73pub fn is_touch_target_recommended(
74 width_px: f64,
75 height_px: f64,
76) -> Result<bool, TouchTargetError> {
77 Ok(TouchTarget::new(width_px, height_px)?.is_recommended_size())
78}
79
80pub fn touch_target_area(width_px: f64, height_px: f64) -> Result<f64, TouchTargetError> {
81 Ok(TouchTarget::new(width_px, height_px)?.area_px())
82}
83
84#[cfg(test)]
85mod tests {
86 use super::{
87 TouchTarget, TouchTargetError, is_touch_target_recommended, minimum_touch_target_px,
88 touch_target_area,
89 };
90
91 #[test]
92 fn validates_touch_target_sizing() {
93 let target = TouchTarget::new(48.0, 44.0).unwrap();
94
95 assert_eq!(minimum_touch_target_px(), 44.0);
96 assert_eq!(target.area_px(), 2_112.0);
97 assert_eq!(target.min_dimension_px(), 44.0);
98 assert!(target.is_recommended_size());
99 assert!(is_touch_target_recommended(48.0, 44.0).unwrap());
100 assert_eq!(touch_target_area(48.0, 44.0).unwrap(), 2_112.0);
101 assert!(!is_touch_target_recommended(40.0, 44.0).unwrap());
102 }
103
104 #[test]
105 fn rejects_invalid_target_dimensions() {
106 assert_eq!(
107 TouchTarget::new(0.0, 44.0),
108 Err(TouchTargetError::InvalidWidth)
109 );
110 assert_eq!(
111 TouchTarget::new(44.0, f64::NAN),
112 Err(TouchTargetError::InvalidHeight)
113 );
114 }
115}