1use num_traits::AsPrimitive;
2
3use crate::curves::ResponseCurve;
4use crate::filters::NoiseFilter;
5use crate::hysteresis::HysteresisMode;
6use crate::snap_zones::SnapZone;
7
8#[cfg(feature = "grab-mode")]
9use crate::grab_mode::GrabMode;
10
11#[derive(Debug, PartialEq)]
12pub enum ConfigError {
13 InvalidInputRange,
14 InvalidOutputRange,
15 InvalidHysteresis,
16 InvalidFilter,
17 OverlappingSnapZones,
18}
19
20impl core::fmt::Display for ConfigError {
21 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
22 match self {
23 ConfigError::InvalidInputRange => write!(f, "input_min must be less than input_max"),
24 ConfigError::InvalidOutputRange => write!(f, "output_min must not equal output_max"),
25 ConfigError::InvalidHysteresis => write!(f, "invalid hysteresis configuration"),
26 ConfigError::InvalidFilter => write!(f, "invalid filter configuration"),
27 ConfigError::OverlappingSnapZones => write!(f, "snap zones must not overlap"),
28 }
29 }
30}
31
32pub struct Config<TIn, TOut = TIn> {
33 pub input_min: TIn,
34 pub input_max: TIn,
35 pub output_min: TOut,
36 pub output_max: TOut,
37 pub hysteresis: HysteresisMode<f32>,
38 pub curve: ResponseCurve,
39 pub filter: NoiseFilter,
40 pub snap_zones: &'static [SnapZone<f32>],
41
42 #[cfg(feature = "grab-mode")]
43 pub grab_mode: GrabMode,
44}
45
46impl<TIn, TOut> Config<TIn, TOut>
47where
48 TIn: Copy + PartialOrd + AsPrimitive<f32>,
49 TOut: Copy + PartialOrd + AsPrimitive<f32>,
50{
51 pub fn validate(&self) -> Result<(), ConfigError> {
52 if self.input_min >= self.input_max {
54 return Err(ConfigError::InvalidInputRange);
55 }
56
57 if self.output_min == self.output_max {
59 return Err(ConfigError::InvalidOutputRange);
60 }
61
62 self.hysteresis
64 .validate()
65 .map_err(|_| ConfigError::InvalidHysteresis)?;
66
67 self.filter
69 .validate()
70 .map_err(|_| ConfigError::InvalidFilter)?;
71
72 Ok(())
73 }
74
75 pub fn validate_snap_zones(&self) -> Result<(), ConfigError> {
79 for (i, zone1) in self.snap_zones.iter().enumerate() {
80 for zone2 in self.snap_zones.iter().skip(i + 1) {
81 if zone1.overlaps(zone2) {
82 return Err(ConfigError::OverlappingSnapZones);
83 }
84 }
85 }
86 Ok(())
87 }
88}