1use std::ops::Deref;
8use std::rc::Rc;
9use web_sys::{HtmlInputElement, HtmlSelectElement, HtmlSpanElement};
10
11pub trait InputElement<E>: Deref<Target = E> + From<Rc<E>> + Clone {
21 type T;
23
24 fn get(&self) -> Option<Self::T>;
32
33 fn set(&self, value: &Self::T);
41}
42
43pub trait NumberPresentation: Clone {
61 const SCALE: f64;
65 const RESOLUTION: Option<f64>;
70}
71
72macro_rules! presentation {
73 ($t:ident, $scale:expr, $resolution:expr, $doc:expr) => {
74 #[doc = $doc]
75 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
76 pub struct $t {}
77
78 impl NumberPresentation for $t {
79 const SCALE: f64 = $scale;
80 const RESOLUTION: Option<f64> = $resolution;
81 }
82 };
83}
84
85presentation!(
86 DefaultPresentation,
87 1.0,
88 None,
89 "Default number presentation.\n\n\
90 The Rust value and the text field use the same units, \
91 and there is no resolution rounding."
92);
93presentation!(
94 IntegerPresentation,
95 1.0,
96 Some(1.0),
97 "Integer number presentation.\n\n\
98 The Rust value and the text field use the same units, \
99 but the text field is rounded to always display an integer."
100);
101presentation!(
102 MHzPresentation,
103 1e6,
104 Some(1e3),
105 "MHz presentation.\n\n\
106 The Rust value has units of Hz, and the text field has units of MHz. \
107 The text field is rounded to the nearest integer kHz (three decimal digits).\n\n\
108 This is often used to display frequencies such as LO frequency in the UI."
109);
110presentation!(
111 KHzPresentation,
112 1e3,
113 Some(1.0),
114 "kHz presentation.\n\n\
115 The Rust value has units of Hz, and the text field has units of kHz. \
116 The text field is rounded to the nearest integer Hz (three decimal digits).\n\n\
117 This is often used to display small frequency up to a few MHz in the UI."
118);
119
120#[derive(Clone)]
127pub struct NumberInput<T, P = DefaultPresentation> {
128 element: Rc<HtmlInputElement>,
129 _phantom: std::marker::PhantomData<(T, P)>,
130}
131
132impl<T, P> From<Rc<HtmlInputElement>> for NumberInput<T, P> {
133 fn from(element: Rc<HtmlInputElement>) -> NumberInput<T, P> {
134 NumberInput {
135 element,
136 _phantom: std::marker::PhantomData,
137 }
138 }
139}
140
141impl<T, P> Deref for NumberInput<T, P> {
142 type Target = HtmlInputElement;
143
144 fn deref(&self) -> &HtmlInputElement {
145 &self.element
146 }
147}
148
149macro_rules! number_input_set {
150 ($t:ty) => {
151 fn set(&self, value: &$t) {
152 let value = value.clone() as f64;
153 let value = if let Some(resolution) = P::RESOLUTION {
154 (value / resolution).round() * resolution
155 } else {
156 value
157 };
158 self.element.set_value_as_number(value / P::SCALE);
159 }
160 };
161}
162
163macro_rules! number_input_int {
164 ($($t:ty),*) => {
165 $(
166 impl<P: NumberPresentation> InputElement<HtmlInputElement> for NumberInput<$t, P> {
167 type T = $t;
168
169 fn get(&self) -> Option<$t> {
170 let x = (self.element.value_as_number() * P::SCALE).round() as i64;
171 <$t>::try_from(x).ok()
172 }
173
174 number_input_set!($t);
175 }
176 )*
177 }
178}
179
180macro_rules! number_input_float {
181 ($($t:ty),*) => {
182 $(
183 impl<P: NumberPresentation> InputElement<HtmlInputElement> for NumberInput<$t, P> {
184 type T = $t;
185
186 fn get(&self) -> Option<$t> {
187 Some((self.element.value_as_number() * P::SCALE) as $t)
188 }
189
190 number_input_set!($t);
191 }
192 )*
193 }
194}
195
196number_input_int!(u64, u32);
197number_input_float!(f64, f32);
198
199#[derive(Clone)]
206pub struct NumberSpan<T, P = DefaultPresentation> {
207 element: Rc<HtmlSpanElement>,
208 _phantom: std::marker::PhantomData<(T, P)>,
209}
210
211impl<T, P> From<Rc<HtmlSpanElement>> for NumberSpan<T, P> {
212 fn from(element: Rc<HtmlSpanElement>) -> NumberSpan<T, P> {
213 NumberSpan {
214 element,
215 _phantom: std::marker::PhantomData,
216 }
217 }
218}
219
220impl<T, P> Deref for NumberSpan<T, P> {
221 type Target = HtmlSpanElement;
222
223 fn deref(&self) -> &HtmlSpanElement {
224 &self.element
225 }
226}
227
228macro_rules! number_span_set {
229 ($t:ty) => {
230 fn set(&self, value: &$t) {
231 let value = value.clone() as f64;
232 let value = if let Some(resolution) = P::RESOLUTION {
233 (value / resolution).round() * resolution
234 } else {
235 value
236 };
237 let value = value / P::SCALE;
238 self.element.set_text_content(Some(&value.to_string()));
239 }
240 };
241}
242
243macro_rules! number_span_int {
244 ($($t:ty),*) => {
245 $(
246 impl<P: NumberPresentation> InputElement<HtmlSpanElement> for NumberSpan<$t, P> {
247 type T = $t;
248
249 fn get(&self) -> Option<$t> {
250 let value: f64 = self.element.text_content()?.parse().ok()?;
251 let value = (value * P::SCALE).round() as i64;
252 <$t>::try_from(value).ok()
253 }
254
255 number_span_set!($t);
256 }
257 )*
258 }
259}
260
261macro_rules! number_span_float {
262 ($($t:ty),*) => {
263 $(
264 impl<P: NumberPresentation> InputElement<HtmlSpanElement> for NumberSpan<$t, P> {
265 type T = $t;
266
267 fn get(&self) -> Option<$t> {
268 let value: f64 = self.element.text_content()?.parse().ok()?;
269 Some((value * P::SCALE) as $t)
270 }
271
272 number_span_set!($t);
273 }
274 )*
275 }
276}
277
278number_span_int!(u64, u32);
279number_span_float!(f64, f32);
280
281#[derive(Clone)]
287pub struct TextInput {
288 element: Rc<HtmlInputElement>,
289}
290
291impl From<Rc<HtmlInputElement>> for TextInput {
292 fn from(element: Rc<HtmlInputElement>) -> TextInput {
293 TextInput { element }
294 }
295}
296
297impl Deref for TextInput {
298 type Target = HtmlInputElement;
299
300 fn deref(&self) -> &HtmlInputElement {
301 &self.element
302 }
303}
304
305impl InputElement<HtmlInputElement> for TextInput {
306 type T = String;
307
308 fn get(&self) -> Option<String> {
309 Some(self.element.value())
310 }
311
312 fn set(&self, value: &String) {
313 self.element.set_value(value)
314 }
315}
316
317pub struct EnumInput<E> {
325 element: Rc<HtmlSelectElement>,
326 _phantom: std::marker::PhantomData<E>,
327}
328
329impl<E> Clone for EnumInput<E> {
330 fn clone(&self) -> Self {
331 EnumInput {
332 element: Rc::clone(&self.element),
333 _phantom: std::marker::PhantomData,
334 }
335 }
336}
337
338impl<E> From<Rc<HtmlSelectElement>> for EnumInput<E> {
339 fn from(element: Rc<HtmlSelectElement>) -> EnumInput<E> {
340 EnumInput {
341 element,
342 _phantom: std::marker::PhantomData,
343 }
344 }
345}
346
347impl<E> Deref for EnumInput<E> {
348 type Target = HtmlSelectElement;
349
350 fn deref(&self) -> &HtmlSelectElement {
351 &self.element
352 }
353}
354
355impl<E: std::str::FromStr + ToString> InputElement<HtmlSelectElement> for EnumInput<E> {
356 type T = E;
357
358 fn get(&self) -> Option<E> {
359 Some(match self.element.value().parse() {
360 Ok(x) => x,
361 Err(_) => panic!("could not parse HtmlSelectElement value"),
362 })
363 }
364
365 fn set(&self, value: &E) {
366 self.element.set_value(&value.to_string());
367 }
368}
369
370#[derive(Clone)]
376pub struct CheckboxInput {
377 element: Rc<HtmlInputElement>,
378}
379
380impl From<Rc<HtmlInputElement>> for CheckboxInput {
381 fn from(element: Rc<HtmlInputElement>) -> CheckboxInput {
382 CheckboxInput { element }
383 }
384}
385
386impl Deref for CheckboxInput {
387 type Target = HtmlInputElement;
388
389 fn deref(&self) -> &HtmlInputElement {
390 &self.element
391 }
392}
393
394impl InputElement<HtmlInputElement> for CheckboxInput {
395 type T = bool;
396
397 fn get(&self) -> Option<bool> {
398 Some(self.element.checked())
399 }
400
401 fn set(&self, &value: &bool) {
402 self.element.set_checked(value)
403 }
404}