raui_core/
props.rs

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