raui_core/
props.rs

1//! Widget property types
2
3use crate::{Prefab, PrefabError, PrefabValue};
4use serde::{Deserialize, Serialize};
5use std::{
6    any::{type_name, Any, TypeId},
7    collections::HashMap,
8};
9
10type PropsSerializeFactory =
11    Box<dyn Fn(&dyn PropsData) -> Result<PrefabValue, PrefabError> + Send + Sync>;
12type PropsDeserializeFactory =
13    Box<dyn Fn(PrefabValue, &mut Props) -> Result<(), PrefabError> + Send + Sync>;
14
15#[derive(Default)]
16pub struct PropsRegistry {
17    type_mapping: HashMap<TypeId, String>,
18    factories: HashMap<String, (PropsSerializeFactory, PropsDeserializeFactory)>,
19}
20
21impl PropsRegistry {
22    pub fn register_factory<T>(&mut self, name: &str)
23    where
24        T: 'static + Prefab + PropsData,
25    {
26        let s: PropsSerializeFactory = Box::new(move |data| {
27            if let Some(data) = data.as_any().downcast_ref::<T>() {
28                data.to_prefab()
29            } else {
30                Err(PrefabError::CouldNotSerialize(
31                    "Could not downcast to concrete type!".to_owned(),
32                ))
33            }
34        });
35        let d: PropsDeserializeFactory = Box::new(move |data, props| {
36            props.write(T::from_prefab(data)?);
37            Ok(())
38        });
39        self.factories.insert(name.to_owned(), (s, d));
40        self.type_mapping.insert(TypeId::of::<T>(), name.to_owned());
41    }
42
43    pub fn unregister_factory(&mut self, name: &str) {
44        self.factories.remove(name);
45    }
46
47    pub fn serialize(&self, props: &Props) -> Result<PrefabValue, PrefabError> {
48        let mut group = PropsGroupPrefab::default();
49        for (t, p) in &props.0 {
50            if let Some(name) = self.type_mapping.get(t) {
51                if let Some(factory) = self.factories.get(name) {
52                    group.data.insert(name.to_owned(), (factory.0)(p.as_ref())?);
53                }
54            } else {
55                return Err(PrefabError::CouldNotSerialize(
56                    "No type mapping found!".to_owned(),
57                ));
58            }
59        }
60        group.to_prefab()
61    }
62
63    pub fn deserialize(&self, data: PrefabValue) -> Result<Props, PrefabError> {
64        let data = if data.is_null() {
65            PropsGroupPrefab::default()
66        } else {
67            PropsGroupPrefab::from_prefab(data)?
68        };
69        let mut props = Props::default();
70        for (key, value) in data.data {
71            if let Some(factory) = self.factories.get(&key) {
72                (factory.1)(value, &mut props)?;
73            } else {
74                return Err(PrefabError::CouldNotDeserialize(format!(
75                    "Could not find properties factory: {:?}",
76                    key
77                )));
78            }
79        }
80        Ok(props)
81    }
82}
83
84#[derive(Debug, Clone)]
85pub enum PropsError {
86    CouldNotReadData,
87    HasNoDataOfType(String),
88}
89
90impl Prefab for PrefabValue {}
91
92impl PropsData for PrefabValue
93where
94    Self: Clone,
95{
96    fn clone_props(&self) -> Box<dyn PropsData> {
97        Box::new(self.clone())
98    }
99
100    fn as_any(&self) -> &dyn Any {
101        self
102    }
103}
104
105#[derive(Debug, Default, Clone, Serialize, Deserialize)]
106pub struct PropsGroupPrefab {
107    #[serde(default)]
108    #[serde(skip_serializing_if = "HashMap::is_empty")]
109    pub data: HashMap<String, PrefabValue>,
110}
111
112impl Prefab for PropsGroupPrefab {}
113
114impl PropsData for PropsGroupPrefab
115where
116    Self: Clone,
117{
118    fn clone_props(&self) -> Box<dyn PropsData> {
119        Box::new(self.clone())
120    }
121
122    fn as_any(&self) -> &dyn Any {
123        self
124    }
125}
126
127pub trait PropsData: Any + std::fmt::Debug + Send + Sync {
128    fn clone_props(&self) -> Box<dyn PropsData>;
129    fn as_any(&self) -> &dyn Any;
130}
131
132impl Clone for Box<dyn PropsData> {
133    fn clone(&self) -> Self {
134        self.clone_props()
135    }
136}
137
138#[derive(Default, Clone)]
139pub struct Props(HashMap<TypeId, Box<dyn PropsData>>);
140
141impl Props {
142    pub fn new<T>(data: T) -> Self
143    where
144        T: 'static + PropsData,
145    {
146        let mut result = HashMap::with_capacity(1);
147        result.insert(TypeId::of::<T>(), Box::new(data) as Box<dyn PropsData>);
148        Self(result)
149    }
150
151    pub fn is_empty(&self) -> bool {
152        self.0.is_empty()
153    }
154
155    pub fn has<T>(&self) -> bool
156    where
157        T: 'static + PropsData,
158    {
159        let e = TypeId::of::<T>();
160        self.0.iter().any(|(t, _)| *t == e)
161    }
162
163    pub fn remove<T>(&mut self)
164    where
165        T: 'static + PropsData,
166    {
167        self.0.remove(&TypeId::of::<T>());
168    }
169
170    pub(crate) unsafe fn remove_by_type(&mut self, id: TypeId) {
171        self.0.remove(&id);
172    }
173
174    pub fn consume<T>(&mut self) -> Result<Box<dyn PropsData>, PropsError>
175    where
176        T: 'static + PropsData,
177    {
178        if let Some(v) = self.0.remove(&TypeId::of::<T>()) {
179            Ok(v)
180        } else {
181            Err(PropsError::HasNoDataOfType(type_name::<T>().to_owned()))
182        }
183    }
184
185    pub fn consume_unwrap_cloned<T>(&mut self) -> Result<T, PropsError>
186    where
187        T: 'static + PropsData + Clone,
188    {
189        if let Some(data) = self.consume::<T>()?.as_any().downcast_ref::<T>() {
190            Ok(data.clone())
191        } else {
192            Err(PropsError::CouldNotReadData)
193        }
194    }
195
196    pub fn read<T>(&self) -> Result<&T, PropsError>
197    where
198        T: 'static + PropsData,
199    {
200        let e = TypeId::of::<T>();
201        if let Some((_, v)) = self.0.iter().find(|(t, _)| **t == e) {
202            if let Some(data) = v.as_any().downcast_ref::<T>() {
203                Ok(data)
204            } else {
205                Err(PropsError::CouldNotReadData)
206            }
207        } else {
208            Err(PropsError::HasNoDataOfType(type_name::<T>().to_owned()))
209        }
210    }
211
212    pub fn map_or_default<T, R, F>(&self, mut f: F) -> R
213    where
214        T: 'static + PropsData,
215        R: Default,
216        F: FnMut(&T) -> R,
217    {
218        match self.read() {
219            Ok(data) => f(data),
220            Err(_) => R::default(),
221        }
222    }
223
224    pub fn map_or_else<T, R, F, E>(&self, mut f: F, mut e: E) -> R
225    where
226        T: 'static + PropsData,
227        F: FnMut(&T) -> R,
228        E: FnMut() -> R,
229    {
230        match self.read() {
231            Ok(data) => f(data),
232            Err(_) => e(),
233        }
234    }
235
236    pub fn read_cloned<T>(&self) -> Result<T, PropsError>
237    where
238        T: 'static + PropsData + Clone,
239    {
240        self.read::<T>().cloned()
241    }
242
243    pub fn read_cloned_or_default<T>(&self) -> T
244    where
245        T: 'static + PropsData + Clone + Default,
246    {
247        self.read_cloned().unwrap_or_default()
248    }
249
250    pub fn read_cloned_or_else<T, F>(&self, mut f: F) -> T
251    where
252        T: 'static + PropsData + Clone + Default,
253        F: FnMut() -> T,
254    {
255        self.read_cloned().unwrap_or_else(|_| f())
256    }
257
258    pub fn write<T>(&mut self, data: T)
259    where
260        T: 'static + PropsData,
261    {
262        self.0
263            .insert(TypeId::of::<T>(), Box::new(data) as Box<dyn PropsData>);
264    }
265
266    pub fn mutate<T, F>(&mut self, mut f: F)
267    where
268        T: 'static + PropsData,
269        F: FnMut(&T) -> T,
270    {
271        if let Ok(data) = self.read() {
272            let data = f(data);
273            self.write(data);
274        }
275    }
276
277    pub fn mutate_cloned<T, F>(&mut self, mut f: F)
278    where
279        T: 'static + PropsData + Clone,
280        F: FnMut(&mut T),
281    {
282        if let Ok(data) = self.read::<T>() {
283            let mut data = data.clone();
284            f(&mut data);
285            self.write(data);
286        }
287    }
288
289    pub fn mutate_or_write<T, F, W>(&mut self, mut f: F, mut w: W)
290    where
291        T: 'static + PropsData,
292        F: FnMut(&T) -> T,
293        W: FnMut() -> T,
294    {
295        if let Ok(data) = self.read() {
296            let data = f(data);
297            self.write(data);
298        } else {
299            let data = w();
300            self.write(data);
301        }
302    }
303
304    pub fn with<T>(mut self, data: T) -> Self
305    where
306        T: 'static + PropsData,
307    {
308        self.write(data);
309        self
310    }
311
312    pub fn without<T>(mut self) -> Self
313    where
314        T: 'static + PropsData,
315    {
316        self.0.remove(&TypeId::of::<T>());
317        self
318    }
319
320    pub fn merge(self, other: Self) -> Self {
321        let mut result = self.into_inner();
322        result.extend(other.into_inner());
323        Self(result)
324    }
325
326    pub fn merge_from(&mut self, other: Self) {
327        self.0.extend(other.into_inner());
328    }
329
330    pub(crate) fn into_inner(self) -> HashMap<TypeId, Box<dyn PropsData>> {
331        self.0
332    }
333}
334
335impl std::fmt::Debug for Props {
336    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
337        f.write_str("Props ")?;
338        f.debug_set().entries(self.0.values()).finish()
339    }
340}
341
342impl<T> From<T> for Props
343where
344    T: 'static + PropsData,
345{
346    fn from(data: T) -> Self {
347        Self::new(data)
348    }
349}
350
351impl From<&Self> for Props {
352    fn from(data: &Self) -> Self {
353        data.clone()
354    }
355}
356
357/// Macro for implementing [`PropsData`].
358///
359/// You may prefer to use the [derive macro][`macro@crate::PropsData`] instead, but in case of
360/// auto-implementing PropsData and Prefab traits for remote or std types, you might find this macro
361/// useful.
362#[macro_export]
363macro_rules! implement_props_data {
364    ($type_name:ty) => {
365        impl $crate::props::PropsData for $type_name
366        where
367            Self: Clone,
368        {
369            fn clone_props(&self) -> Box<dyn $crate::props::PropsData> {
370                Box::new(self.clone())
371            }
372
373            fn as_any(&self) -> &dyn std::any::Any {
374                self
375            }
376        }
377
378        impl $crate::Prefab for $type_name {}
379    };
380}
381
382implement_props_data!(());
383implement_props_data!(i8);
384implement_props_data!(i16);
385implement_props_data!(i32);
386implement_props_data!(i64);
387implement_props_data!(i128);
388implement_props_data!(u8);
389implement_props_data!(u16);
390implement_props_data!(u32);
391implement_props_data!(u64);
392implement_props_data!(u128);
393implement_props_data!(f32);
394implement_props_data!(f64);
395implement_props_data!(isize);
396implement_props_data!(usize);
397implement_props_data!(bool);
398implement_props_data!(String);
399
400macro_rules! impl_tuple_props_conversion {
401    ($($id:ident),+) => {
402        #[allow(non_snake_case)]
403        impl<$($id: $crate::props::PropsData),+> From<($($id,)+)> for $crate::props::Props {
404            fn from(($($id,)+): ($($id,)+)) -> $crate::props::Props {
405                let mut result = std::collections::HashMap::default();
406                $(
407                    result.insert(
408                        std::any::TypeId::of::<$id>(),
409                        Box::new($id) as Box<dyn $crate::props::PropsData>,
410                    );
411                )+
412                Self(result)
413            }
414        }
415
416        #[allow(non_snake_case)]
417        impl<$($id: $crate::props::PropsData + Clone + Default),+> From<&$crate::props::Props> for ($($id,)+) {
418            fn from(props: &$crate::props::Props) -> ($($id,)+) {
419                ( $( props.read_cloned_or_default::<$id>(), )+ )
420            }
421        }
422    };
423}
424
425impl_tuple_props_conversion!(A);
426impl_tuple_props_conversion!(A, B);
427impl_tuple_props_conversion!(A, B, C);
428impl_tuple_props_conversion!(A, B, C, D);
429impl_tuple_props_conversion!(A, B, C, D, E);
430impl_tuple_props_conversion!(A, B, C, D, E, F);
431impl_tuple_props_conversion!(A, B, C, D, E, F, G);
432impl_tuple_props_conversion!(A, B, C, D, E, F, G, H);
433impl_tuple_props_conversion!(A, B, C, D, E, F, G, H, I);
434impl_tuple_props_conversion!(A, B, C, D, E, F, G, H, I, J);
435impl_tuple_props_conversion!(A, B, C, D, E, F, G, H, I, J, K);
436impl_tuple_props_conversion!(A, B, C, D, E, F, G, H, I, J, K, L);
437impl_tuple_props_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M);
438impl_tuple_props_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
439impl_tuple_props_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
440impl_tuple_props_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
441impl_tuple_props_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q);
442impl_tuple_props_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R);
443impl_tuple_props_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S);
444impl_tuple_props_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T);
445impl_tuple_props_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U);
446impl_tuple_props_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V);
447impl_tuple_props_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, X);
448impl_tuple_props_conversion!(
449    A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, X, Y
450);
451impl_tuple_props_conversion!(
452    A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, X, Y, Z
453);