adui_dioxus/components/
number_utils.rs1#[derive(Debug, Clone, Copy, PartialEq, Default)]
7pub struct NumberRules {
8 pub min: Option<f64>,
10 pub max: Option<f64>,
12 pub step: Option<f64>,
14 pub precision: Option<u32>,
16}
17
18impl NumberRules {
19 pub fn effective_step(&self) -> f64 {
21 let step = self.step.unwrap_or(1.0);
22 if step.is_sign_negative() { -step } else { step }
23 }
24}
25
26pub fn clamp(value: f64, rules: &NumberRules) -> f64 {
28 let after_min = if let Some(min) = rules.min {
29 value.max(min)
30 } else {
31 value
32 };
33 if let Some(max) = rules.max {
34 after_min.min(max)
35 } else {
36 after_min
37 }
38}
39
40pub fn round_with_precision(value: f64, precision: Option<u32>) -> f64 {
42 if let Some(p) = precision {
43 if p == 0 {
44 value.round()
45 } else {
46 let factor = 10_f64.powi(p as i32);
47 (value * factor).round() / factor
48 }
49 } else {
50 value
51 }
52}
53
54pub fn apply_step(value: f64, delta_steps: i32, rules: &NumberRules) -> f64 {
56 let step = rules.effective_step();
57 let next = value + step * (delta_steps as f64);
58 let clamped = clamp(next, rules);
59 round_with_precision(clamped, rules.precision)
60}
61
62pub fn parse_and_normalize(input: &str, rules: &NumberRules) -> Option<f64> {
64 let raw = input.trim();
65 if raw.is_empty() {
66 return None;
67 }
68 let parsed: f64 = raw.parse().ok()?;
69 let clamped = clamp(parsed, rules);
70 Some(round_with_precision(clamped, rules.precision))
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 #[test]
78 fn clamp_respects_bounds() {
79 let rules = NumberRules {
80 min: Some(0.0),
81 max: Some(10.0),
82 step: None,
83 precision: None,
84 };
85 assert_eq!(clamp(-2.0, &rules), 0.0);
86 assert_eq!(clamp(5.5, &rules), 5.5);
87 assert_eq!(clamp(20.0, &rules), 10.0);
88 }
89
90 #[test]
91 fn apply_step_clamps_and_rounds() {
92 let rules = NumberRules {
93 min: Some(0.0),
94 max: Some(2.0),
95 step: Some(0.3),
96 precision: Some(2),
97 };
98 assert_eq!(apply_step(1.0, 2, &rules), 1.6);
100 assert_eq!(apply_step(0.2, -2, &rules), 0.0);
102 assert_eq!(apply_step(1.9, 2, &rules), 2.0);
104 }
105
106 #[test]
107 fn parse_and_normalize_handles_precision() {
108 let rules = NumberRules {
109 min: None,
110 max: None,
111 step: None,
112 precision: Some(1),
113 };
114 assert_eq!(parse_and_normalize("3.1415", &rules), Some(3.1));
115 assert_eq!(parse_and_normalize(" 2.05 ", &rules), Some(2.1));
116 assert_eq!(parse_and_normalize("", &rules), None);
117 assert_eq!(parse_and_normalize("abc", &rules), None);
118 }
119}