1#![allow(
2 clippy::cast_lossless,
3 clippy::cast_possible_truncation,
4 clippy::cast_precision_loss,
5 clippy::cast_sign_loss,
6 clippy::float_cmp
7)]
8
9use std::any::Any;
10
11use serde::{Deserialize, Serialize};
12use thiserror::Error;
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub enum TextMode {
16 SingleLine,
17 Multiline,
18}
19
20#[derive(Clone, Copy, Debug, PartialEq)]
21pub struct NumberConstraints {
22 pub min: Option<f64>,
23 pub max: Option<f64>,
24 pub step: f64,
25}
26
27impl Default for NumberConstraints {
28 fn default() -> Self {
29 Self {
30 min: None,
31 max: None,
32 step: 1.0,
33 }
34 }
35}
36
37#[derive(Clone, Copy, Debug, PartialEq)]
38pub enum ControlKind {
39 Boolean,
40 Text { mode: TextMode },
41 Number { constraints: NumberConstraints },
42 Enum { options: &'static [&'static str] },
43}
44
45impl ControlKind {
46 #[must_use]
47 pub const fn name(self) -> &'static str {
48 match self {
49 Self::Boolean => "boolean",
50 Self::Text { .. } => "text",
51 Self::Number { .. } => "number",
52 Self::Enum { .. } => "enum",
53 }
54 }
55
56 #[doc(hidden)]
57 #[must_use]
58 pub const fn multiline(self) -> Self {
59 match self {
60 Self::Text { .. } => Self::Text {
61 mode: TextMode::Multiline,
62 },
63 _ => panic!("multiline is only valid for text controls"),
64 }
65 }
66
67 #[doc(hidden)]
68 #[must_use]
69 pub const fn constrained(self, constraints: NumberConstraints) -> Self {
70 match self {
71 Self::Number { .. } => Self::Number { constraints },
72 _ => panic!("min, max, and step are only valid for numeric controls"),
73 }
74 }
75}
76
77#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
78pub enum ControlValue {
79 Boolean(bool),
80 Text(String),
81 Number(f64),
82 Enum(String),
83}
84
85impl ControlValue {
86 #[must_use]
87 pub const fn kind_name(&self) -> &'static str {
88 match self {
89 Self::Boolean(_) => "boolean",
90 Self::Text(_) => "text",
91 Self::Number(_) => "number",
92 Self::Enum(_) => "enum",
93 }
94 }
95}
96
97#[derive(Clone, Copy, Debug, PartialEq)]
98pub struct ControlDefinition {
99 pub id: &'static str,
100 pub label: &'static str,
101 pub docs: &'static str,
102 pub kind: ControlKind,
103}
104
105impl ControlDefinition {
106 pub fn validate(&self, value: &ControlValue) -> Result<(), ControlError> {
111 let (ControlKind::Number { constraints }, ControlValue::Number(value)) = (self.kind, value)
112 else {
113 return Ok(());
114 };
115 if let Some(min) = constraints.min
116 && *value < min
117 {
118 return Err(ControlError::BelowMinimum {
119 control: self.id,
120 min,
121 value: *value,
122 });
123 }
124 if let Some(max) = constraints.max
125 && *value > max
126 {
127 return Err(ControlError::AboveMaximum {
128 control: self.id,
129 max,
130 value: *value,
131 });
132 }
133 let origin = constraints.min.unwrap_or(0.0);
134 let quotient = (*value - origin) / constraints.step;
135 let tolerance = f64::EPSILON * 16.0 * quotient.abs().max(1.0);
136 if (quotient - quotient.round()).abs() > tolerance {
137 return Err(ControlError::StepMismatch {
138 control: self.id,
139 step: constraints.step,
140 value: *value,
141 });
142 }
143 Ok(())
144 }
145}
146
147#[derive(Debug, Error, PartialEq)]
148pub enum ControlError {
149 #[error("unknown control '{0}'")]
150 UnknownControl(String),
151 #[error("control '{control}' expects {expected}, received {actual}")]
152 TypeMismatch {
153 control: &'static str,
154 expected: &'static str,
155 actual: &'static str,
156 },
157 #[error("{value} is not a valid value for numeric control '{control}'")]
158 InvalidNumber { control: &'static str, value: f64 },
159 #[error("{value} is below the minimum {min} for numeric control '{control}'")]
160 BelowMinimum {
161 control: &'static str,
162 min: f64,
163 value: f64,
164 },
165 #[error("{value} is above the maximum {max} for numeric control '{control}'")]
166 AboveMaximum {
167 control: &'static str,
168 max: f64,
169 value: f64,
170 },
171 #[error("{value} does not align to step {step} for numeric control '{control}'")]
172 StepMismatch {
173 control: &'static str,
174 step: f64,
175 value: f64,
176 },
177 #[error("'{value}' is not a valid option for control '{control}'")]
178 InvalidOption {
179 control: &'static str,
180 value: String,
181 },
182}
183
184pub trait HblankProps: Any + Send {
185 fn definitions(&self) -> &'static [ControlDefinition];
186 fn control_value(&self, id: &str) -> Option<ControlValue>;
187 fn set_control(&mut self, id: &str, value: ControlValue) -> Result<(), ControlError>;
192 fn clone_box(&self) -> Box<dyn HblankProps>;
193 fn as_any(&self) -> &dyn Any;
194}
195
196impl Clone for Box<dyn HblankProps> {
197 fn clone(&self) -> Self {
198 self.clone_box()
199 }
200}
201
202pub trait HblankControlAdapter<T> {
207 type Value: ControlField;
208
209 fn to_control(value: &T) -> Self::Value;
210 fn apply_control(value: &mut T, control: Self::Value);
211}
212
213#[doc(hidden)]
214pub trait ControlField: Sized {
215 const KIND: ControlKind;
216
217 fn to_control_value(&self) -> ControlValue;
218 fn set_control_value(
219 &mut self,
220 control: &'static str,
221 value: ControlValue,
222 ) -> Result<(), ControlError>;
223}
224
225impl ControlField for bool {
226 const KIND: ControlKind = ControlKind::Boolean;
227
228 fn to_control_value(&self) -> ControlValue {
229 ControlValue::Boolean(*self)
230 }
231
232 fn set_control_value(
233 &mut self,
234 control: &'static str,
235 value: ControlValue,
236 ) -> Result<(), ControlError> {
237 let ControlValue::Boolean(value) = value else {
238 return Err(ControlError::TypeMismatch {
239 control,
240 expected: Self::KIND.name(),
241 actual: value.kind_name(),
242 });
243 };
244 *self = value;
245 Ok(())
246 }
247}
248
249impl ControlField for String {
250 const KIND: ControlKind = ControlKind::Text {
251 mode: TextMode::SingleLine,
252 };
253
254 fn to_control_value(&self) -> ControlValue {
255 ControlValue::Text(self.clone())
256 }
257
258 fn set_control_value(
259 &mut self,
260 control: &'static str,
261 value: ControlValue,
262 ) -> Result<(), ControlError> {
263 let ControlValue::Text(value) = value else {
264 return Err(ControlError::TypeMismatch {
265 control,
266 expected: Self::KIND.name(),
267 actual: value.kind_name(),
268 });
269 };
270 *self = value;
271 Ok(())
272 }
273}
274
275macro_rules! numeric_control {
276 ($($type:ty),+ $(,)?) => {
277 $(
278 impl ControlField for $type {
279 const KIND: ControlKind = ControlKind::Number {
280 constraints: NumberConstraints {
281 min: None,
282 max: None,
283 step: 1.0,
284 },
285 };
286
287 fn to_control_value(&self) -> ControlValue {
288 ControlValue::Number(*self as f64)
289 }
290
291 fn set_control_value(
292 &mut self,
293 control: &'static str,
294 value: ControlValue,
295 ) -> Result<(), ControlError> {
296 let ControlValue::Number(value) = value else {
297 return Err(ControlError::TypeMismatch {
298 control,
299 expected: Self::KIND.name(),
300 actual: value.kind_name(),
301 });
302 };
303 if !value.is_finite()
304 || value < <$type>::MIN as f64
305 || value > <$type>::MAX as f64
306 || (value as $type) as f64 != value
307 {
308 return Err(ControlError::InvalidNumber { control, value });
309 }
310 *self = value as $type;
311 Ok(())
312 }
313 }
314 )+
315 };
316}
317
318numeric_control!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize);
319
320macro_rules! floating_control {
321 ($($type:ty),+ $(,)?) => {
322 $(
323 impl ControlField for $type {
324 const KIND: ControlKind = ControlKind::Number {
325 constraints: NumberConstraints {
326 min: None,
327 max: None,
328 step: 1.0,
329 },
330 };
331
332 fn to_control_value(&self) -> ControlValue {
333 ControlValue::Number(f64::from(*self))
334 }
335
336 fn set_control_value(
337 &mut self,
338 control: &'static str,
339 value: ControlValue,
340 ) -> Result<(), ControlError> {
341 let ControlValue::Number(value) = value else {
342 return Err(ControlError::TypeMismatch {
343 control,
344 expected: Self::KIND.name(),
345 actual: value.kind_name(),
346 });
347 };
348 if !value.is_finite() || value < <$type>::MIN as f64 || value > <$type>::MAX as f64 {
349 return Err(ControlError::InvalidNumber { control, value });
350 }
351 *self = value as $type;
352 Ok(())
353 }
354 }
355 )+
356 };
357}
358
359floating_control!(f32, f64);
360
361pub trait HblankEnum: Clone + Send + 'static {
362 const VARIANTS: &'static [&'static str];
363
364 fn variant_name(&self) -> &'static str;
365 fn from_variant_name(value: &str) -> Option<Self>;
366}
367
368impl<T: HblankEnum> ControlField for T {
369 const KIND: ControlKind = ControlKind::Enum {
370 options: T::VARIANTS,
371 };
372
373 fn to_control_value(&self) -> ControlValue {
374 ControlValue::Enum(self.variant_name().to_owned())
375 }
376
377 fn set_control_value(
378 &mut self,
379 control: &'static str,
380 value: ControlValue,
381 ) -> Result<(), ControlError> {
382 let ControlValue::Enum(value) = value else {
383 return Err(ControlError::TypeMismatch {
384 control,
385 expected: Self::KIND.name(),
386 actual: value.kind_name(),
387 });
388 };
389 let Some(next) = T::from_variant_name(&value) else {
390 return Err(ControlError::InvalidOption { control, value });
391 };
392 *self = next;
393 Ok(())
394 }
395}