1use serde::{Deserialize, Serialize};
2use std::cmp::Ordering;
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub enum Bound {
6 Auto,
7 Accurate { value: f64, strict: bool },
8}
9
10impl Default for Bound {
11 fn default() -> Self {
12 Self::Auto
13 }
14}
15
16impl Bound {
17 pub fn from_options(value: Option<f64>, loose: Option<bool>) -> Self {
18 match value {
19 Some(value) => {
20 let strict = !loose.unwrap_or_default();
21 Self::Accurate { value, strict }
22 }
23 None => Self::Auto,
24 }
25 }
26
27 pub fn auto() -> Self {
30 Self::Auto
31 }
32
33 pub fn strict(value: impl Into<f64>) -> Self {
34 Self::Accurate {
35 value: value.into(),
36 strict: true,
37 }
38 }
39
40 pub fn loose(value: impl Into<f64>) -> Self {
41 Self::Accurate {
42 value: value.into(),
43 strict: false,
44 }
45 }
46
47 pub fn min(&self, active_min: f64) -> f64 {
48 self.clamp(active_min, Ordering::Less)
49 }
50
51 pub fn max(&self, active_max: f64) -> f64 {
52 self.clamp(active_max, Ordering::Greater)
53 }
54
55 fn clamp(&self, active: f64, ordering: Ordering) -> f64 {
56 match *self {
57 Self::Auto => active,
58 Self::Accurate { value, strict } => {
59 if active.partial_cmp(&value) == Some(ordering) {
60 if strict {
61 value
62 } else {
63 active
64 }
65 } else {
66 value
67 }
68 }
69 }
70 }
71}
72
73#[cfg(test)]
74#[allow(clippy::float_cmp)] mod tests {
76 use super::*;
77
78 #[test]
79 fn test_bound_min() {
80 let auto = Bound::Auto;
81 assert_eq!(auto.min(10.0), 10.0);
82 assert_eq!(auto.min(-5.0), -5.0);
83 let strict = Bound::Accurate {
84 value: 0.0,
85 strict: true,
86 };
87 assert_eq!(strict.min(10.0), 0.0);
88 assert_eq!(strict.min(0.0), 0.0);
89 assert_eq!(strict.min(-5.0), 0.0);
90 let loose = Bound::Accurate {
91 value: 0.0,
92 strict: false,
93 };
94 assert_eq!(loose.min(10.0), 0.0);
95 assert_eq!(loose.min(0.0), 0.0);
96 assert_eq!(loose.min(-5.0), -5.0);
97 }
98
99 #[test]
100 fn test_bound_max() {
101 let auto = Bound::Auto;
102 assert_eq!(auto.max(120.0), 120.0);
103 assert_eq!(auto.max(90.0), 90.0);
104 let strict = Bound::Accurate {
105 value: 100.0,
106 strict: true,
107 };
108 assert_eq!(strict.max(120.0), 100.0);
109 assert_eq!(strict.max(100.0), 100.0);
110 assert_eq!(strict.max(90.0), 100.0);
111 let loose = Bound::Accurate {
112 value: 100.0,
113 strict: false,
114 };
115 assert_eq!(loose.max(120.0), 120.0);
116 assert_eq!(loose.max(100.0), 100.0);
117 assert_eq!(loose.max(90.0), 100.0);
118 }
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize, Default)]
123pub struct Range {
124 pub min: Bound,
125 pub max: Bound,
126}
127
128impl Range {
129 pub fn new(mut min: f64, mut max: f64) -> Self {
131 if min > max {
132 std::mem::swap(&mut min, &mut max);
133 }
134 Self {
135 min: Bound::Accurate {
136 value: min,
137 strict: true,
138 },
139 max: Bound::Accurate {
140 value: max,
141 strict: true,
142 },
143 }
144 }
145
146 pub fn min(min: f64) -> Self {
147 Self {
148 min: Bound::Accurate {
149 value: min,
150 strict: true,
151 },
152 max: Bound::Auto,
153 }
154 }
155
156 pub fn max(max: f64) -> Self {
157 Self {
158 min: Bound::Auto,
159 max: Bound::Accurate {
160 value: max,
161 strict: true,
162 },
163 }
164 }
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct Label {
169 pub caption: String,
170 pub divisor: f64,
171}
172
173impl Default for Label {
174 fn default() -> Self {
175 Self {
176 caption: String::new(),
177 divisor: 1.0,
178 }
179 }
180}
181
182impl Label {
183 pub fn from_options(caption: Option<String>, divisor: Option<f64>) -> Self {
184 Self {
185 caption: caption.unwrap_or_else(String::new),
186 divisor: divisor.unwrap_or(1.0),
187 }
188 }
189
190 pub fn new(caption: impl Into<String>, divisor: f64) -> Self {
193 Self {
194 caption: caption.into(),
195 divisor,
196 }
197 }
198
199 pub fn pct_100() -> Self {
200 Self::new("%", 1.0)
201 }
202
203 pub fn pct_1() -> Self {
204 Self::new("%", 1.0 / 100.0)
205 }
206}