Skip to main content

pot_head/
config.rs

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        // Input range must be valid (min < max)
53        if self.input_min >= self.input_max {
54            return Err(ConfigError::InvalidInputRange);
55        }
56
57        // Output range must not be degenerate (min == max would cause division issues)
58        if self.output_min == self.output_max {
59            return Err(ConfigError::InvalidOutputRange);
60        }
61
62        // Validate hysteresis configuration
63        self.hysteresis
64            .validate()
65            .map_err(|_| ConfigError::InvalidHysteresis)?;
66
67        // Validate filter configuration
68        self.filter
69            .validate()
70            .map_err(|_| ConfigError::InvalidFilter)?;
71
72        Ok(())
73    }
74
75    /// Validate that no snap zones overlap.
76    /// This is an optional validation helper - overlaps are allowed by default.
77    /// Call this during development if you want to ensure clean, non-overlapping zones.
78    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}