1#[derive(Debug, Clone)]
6pub struct ParamDescriptor {
7 pub id: &'static str,
9 pub label: &'static str,
11 pub kind: ParamKind,
13 pub apply: ParamApply,
15 pub format: ParamFormat,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
21pub enum ParamFormat {
22 #[default]
24 Plain,
25 Percent,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
34pub enum ParamApply {
35 #[default]
37 Live,
38 OnReload,
40}
41
42impl ParamDescriptor {
43 pub fn on_reload(mut self) -> Self {
45 self.apply = ParamApply::OnReload;
46 self
47 }
48
49 pub fn percent(mut self) -> Self {
51 self.format = ParamFormat::Percent;
52 self
53 }
54
55 pub fn is_live(&self) -> bool {
57 self.apply == ParamApply::Live
58 }
59}
60
61#[derive(Debug, Clone)]
63pub enum ParamKind {
64 F32 {
66 min: f32,
68 max: f32,
70 default: f32,
72 step: Option<f32>,
74 },
75 U32 {
77 min: u32,
79 max: u32,
81 default: u32,
83 },
84 Bool {
86 default: bool,
88 },
89 Choice {
91 options: &'static [&'static str],
93 default: usize,
95 },
96}
97
98#[derive(Debug, Clone, PartialEq)]
100pub enum ParamValue {
101 F32(f32),
103 U32(u32),
105 Bool(bool),
107 Choice(usize),
109}
110
111impl From<f32> for ParamValue {
114 fn from(value: f32) -> Self {
115 Self::F32(value)
116 }
117}
118
119impl From<u32> for ParamValue {
120 fn from(value: u32) -> Self {
121 Self::U32(value)
122 }
123}
124
125impl From<bool> for ParamValue {
126 fn from(value: bool) -> Self {
127 Self::Bool(value)
128 }
129}
130
131impl ParamKind {
132 pub fn default_value(&self) -> ParamValue {
134 match *self {
135 Self::F32 { default, .. } => ParamValue::F32(default),
136 Self::U32 { default, .. } => ParamValue::U32(default),
137 Self::Bool { default } => ParamValue::Bool(default),
138 Self::Choice { default, .. } => ParamValue::Choice(default),
139 }
140 }
141}
142
143#[derive(Debug)]
148pub struct ParamStore {
149 values: Vec<ParamValue>,
150 live: Vec<bool>,
151}
152
153impl ParamStore {
154 pub fn new(descriptors: &[ParamDescriptor], values: &[ParamValue]) -> Self {
156 Self {
157 values: values.to_vec(),
158 live: descriptors.iter().map(ParamDescriptor::is_live).collect(),
159 }
160 }
161
162 pub fn values(&self) -> &[ParamValue] {
164 &self.values
165 }
166
167 pub fn set(&mut self, index: usize, value: &ParamValue) -> bool {
169 if self.live.get(index) == Some(&true) && index < self.values.len() {
170 self.values[index] = value.clone();
171 true
172 } else {
173 false
174 }
175 }
176}
177
178#[macro_export]
190macro_rules! params {
191 ($($(#[$meta:meta])* $vis:vis const $name:ident = $descriptor:expr;)+) => {
192 $crate::__indices!(0usize, $([$(#[$meta])* $vis $name],)+);
193
194 fn descriptors() -> ::std::vec::Vec<$crate::__macro_support::ParamDescriptor> {
196 ::std::vec![$($descriptor),+]
197 }
198 };
199}
200
201#[doc(hidden)]
203#[macro_export]
204macro_rules! __indices {
205 ($i:expr,) => {};
206 ($i:expr, [$(#[$meta:meta])* $vis:vis $first:ident], $($rest:tt,)*) => {
207 $(#[$meta])*
208 $vis const $first: usize = $i;
209 $crate::__indices!($i + 1usize, $($rest,)*);
210 };
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216
217 fn descriptors() -> Vec<ParamDescriptor> {
218 vec![
219 ParamDescriptor {
220 id: "live",
221 label: "Live",
222 kind: ParamKind::F32 {
223 min: 0.0,
224 max: 1.0,
225 default: 0.5,
226 step: None,
227 },
228 apply: ParamApply::Live,
229 format: ParamFormat::Plain,
230 },
231 ParamDescriptor {
232 id: "reload",
233 label: "Reload",
234 kind: ParamKind::U32 {
235 min: 0,
236 max: 10,
237 default: 1,
238 },
239 apply: ParamApply::OnReload,
240 format: ParamFormat::Plain,
241 },
242 ]
243 }
244
245 #[test]
246 fn store_accepts_live_edits_and_rejects_reload_ones() {
247 let descs = descriptors();
248 let mut store = ParamStore::new(&descs, &[ParamValue::F32(0.5), ParamValue::U32(1)]);
249
250 assert!(store.set(0, &ParamValue::F32(0.9)));
251 assert_eq!(store.values()[0], ParamValue::F32(0.9));
252
253 assert!(!store.set(1, &ParamValue::U32(7)));
254 assert_eq!(store.values()[1], ParamValue::U32(1), "a rejected edit must not land");
255
256 assert!(!store.set(9, &ParamValue::F32(0.0)), "out of range index");
257 }
258
259 #[test]
262 fn params_macro_expands_in_function_scope() {
263 crate::params! {
264 const FIRST = crate::helpers::f32_param("first", "First", 0.0, 0.0, 1.0, None);
265 const SECOND = crate::helpers::f32_param("second", "Second", 1.0, 0.0, 1.0, None);
267 }
268 assert_eq!((FIRST, SECOND), (0, 1), "indices follow declaration order");
269 assert_eq!(descriptors().len(), 2);
270 }
271
272 #[test]
273 fn param_kind_defaults() {
274 let f = ParamKind::F32 {
275 min: 0.0,
276 max: 1.0,
277 default: 0.5,
278 step: None,
279 };
280 assert_eq!(f.default_value(), ParamValue::F32(0.5));
281
282 let u = ParamKind::U32 {
283 min: 0,
284 max: 100,
285 default: 42,
286 };
287 assert_eq!(u.default_value(), ParamValue::U32(42));
288
289 let b = ParamKind::Bool { default: true };
290 assert_eq!(b.default_value(), ParamValue::Bool(true));
291
292 let c = ParamKind::Choice {
293 options: &["a", "b"],
294 default: 1,
295 };
296 assert_eq!(c.default_value(), ParamValue::Choice(1));
297 }
298}