Skip to main content

pax_runtime_api/pax_value/
mod.rs

1use crate::{Color, Duration, Interpolatable, PathElement, Percent, Rotation, Size};
2use std::{any::Any, fmt::Display, rc::Rc};
3
4use self::numeric::Numeric;
5pub use coercion_impls::{is_typed_binding_safe, CoercionRules};
6use serde::{Deserialize, Serialize};
7
8mod arithmetic;
9mod coercion_impls;
10pub mod functions;
11mod macros;
12pub mod numeric;
13mod to_from_impls;
14
15/// Shared runtime PaxValue handle.
16pub type RcPaxValue = Rc<PaxValue>;
17/// Runtime container for polymorphic values, for evaluating PAXEL.
18///
19/// Two important traits are related to this type:
20/// ToFromPaxValue - responsible for converting to and from specific types (u8,
21/// String, Color, etc)
22/// CoercionRules - responsible for coercing a PaxValue to a specific type
23/// (possibly from multiple different variants)
24#[derive(Debug, Serialize, Deserialize, PartialEq)]
25#[serde(crate = "crate::serde")]
26pub enum PaxValue {
27    /// Boolean value.
28    Bool(bool),
29    /// Polymorphic numeric value.
30    Numeric(Numeric),
31    /// UTF-8 string value.
32    String(String),
33    /// Pax size value, such as `25px` or `50%`.
34    Size(Size),
35    /// Raw percent value.
36    Percent(Percent),
37    /// Pax color value.
38    Color(Box<Color>),
39    /// Pax rotation value.
40    Rotation(Rotation),
41    /// Pax animation duration value, such as `250ms`, `1s`, or `10f`.
42    Duration(Duration),
43    /// Vector path element value.
44    PathElement(Box<PathElement>),
45    /// Optional value.
46    Option(Box<Option<PaxValue>>),
47    /// Homogeneous or heterogeneous vector value.
48    Vec(Vec<PaxValue>),
49    /// Range value.
50    Range(Box<PaxValue>, Box<PaxValue>),
51    /// Object value represented by named fields.
52    Object(Vec<(String, PaxValue)>),
53    /// Enum value represented by type name, variant name, and payload values.
54    Enum(Box<(String, String, Vec<PaxValue>)>),
55}
56
57impl Clone for PaxValue {
58    fn clone(&self) -> Self {
59        match self {
60            PaxValue::Bool(b) => PaxValue::Bool(*b),
61            PaxValue::Numeric(n) => PaxValue::Numeric(n.clone()),
62            PaxValue::String(s) => PaxValue::String(s.clone()),
63            PaxValue::Size(s) => PaxValue::Size(s.clone()),
64            PaxValue::Percent(p) => PaxValue::Percent(p.clone()),
65            PaxValue::Color(c) => PaxValue::Color(c.clone()),
66            PaxValue::Rotation(r) => PaxValue::Rotation(r.clone()),
67            PaxValue::Duration(d) => PaxValue::Duration(d.clone()),
68            PaxValue::PathElement(pe) => PaxValue::PathElement(pe.clone()),
69            PaxValue::Option(opt) => PaxValue::Option(opt.clone()),
70            PaxValue::Vec(v) => PaxValue::Vec(v.clone()),
71            PaxValue::Range(start, end) => PaxValue::Range(start.clone(), end.clone()),
72            PaxValue::Object(pairs) => PaxValue::Object(pairs.clone()),
73            PaxValue::Enum(contents) => {
74                let (name, variant, values) = contents.as_ref();
75                PaxValue::Enum(Box::new((name.clone(), variant.clone(), values.clone())))
76            }
77        }
78    }
79}
80
81// Make sure Enum looks like an enum and vec looks like a vec
82impl Display for PaxValue {
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        match self {
85            PaxValue::Bool(b) => write!(f, "{}", b),
86            PaxValue::Numeric(n) => write!(f, "{}", n),
87            PaxValue::String(s) => write!(f, "\"{}\"", s),
88            PaxValue::Size(s) => write!(f, "{}", s),
89            PaxValue::Percent(p) => write!(f, "{}", p),
90            PaxValue::Color(c) => write!(f, "{}", c),
91            PaxValue::Rotation(r) => write!(f, "{}", r),
92            PaxValue::Duration(d) => write!(f, "{}", d),
93            PaxValue::PathElement(path_elem) => {
94                write!(f, "PathElement::")?;
95                match path_elem.as_ref() {
96                    PathElement::Empty => write!(f, "Empty"),
97                    PathElement::Point(s1, s2) => write!(f, "Point({}, {})", s1, s2),
98                    PathElement::Line => write!(f, "Line"),
99                    PathElement::Quadratic(s1, s2) => write!(f, "Quadratic({}, {})", s1, s2),
100                    PathElement::Cubic(v1, v2, v3, v4) => {
101                        write!(f, "Cubic({}, {}, {}, {})", v1, v2, v3, v4)
102                    }
103                    PathElement::Close => write!(f, "Close"),
104                }?;
105                Ok(())
106            }
107            PaxValue::Option(o) => match o.as_ref() {
108                Some(v) => write!(f, "Some({})", v),
109                None => write!(f, "None"),
110            },
111            PaxValue::Vec(v) => {
112                write!(f, "[")?;
113                for (i, val) in v.iter().enumerate() {
114                    if i != 0 {
115                        write!(f, ", ")?;
116                    }
117                    write!(f, "{}", val)?;
118                }
119                write!(f, "]")
120            }
121            PaxValue::Range(start, end) => write!(f, "{}..{}", start, end),
122            PaxValue::Object(o) => {
123                write!(f, "{{")?;
124                for (i, (key, val)) in o.iter().enumerate() {
125                    if i != 0 {
126                        write!(f, ", ")?;
127                    }
128                    write!(f, "{}: {}", key, val)?;
129                }
130                write!(f, "}}")
131            }
132            PaxValue::Enum(contents) => {
133                let (name, variant, values) = contents.as_ref();
134                if name == "Color" {
135                    write!(f, "{}", variant)?;
136                } else {
137                    write!(f, "{}::{}", name, variant)?;
138                }
139                if !values.is_empty() {
140                    write!(f, "(")?;
141                    for (i, val) in values.iter().enumerate() {
142                        if i != 0 {
143                            write!(f, ", ")?;
144                        }
145                        write!(f, "{}", val)?;
146                    }
147                    write!(f, ")")?;
148                }
149                Ok(())
150            }
151        }
152    }
153}
154
155impl Default for PaxValue {
156    fn default() -> Self {
157        PaxValue::Numeric(Numeric::F64(0.0))
158    }
159}
160
161/// This type serves a similar purpose as `Box<dyn Any>`, but allows for special
162/// handling of some types, enabling things like coercion.
163pub enum PaxAny {
164    /// Built-in PAXEL/runtime value.
165    Builtin(PaxValue),
166    /// Arbitrary Rust value boxed for runtime storage.
167    Any(Box<dyn Any>),
168}
169
170impl std::fmt::Debug for PaxAny {
171    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172        write!(f, "PaxAny {{ .. }}")
173    }
174}
175
176impl Interpolatable for PaxValue {}
177
178/// Converts a Rust value into its built-in `PaxValue` representation.
179///
180/// This is exact conversion, not coercion; coercion is handled separately by
181/// `CoercionRules`.
182pub trait ToPaxValue {
183    /// Converts this value into a `PaxValue`.
184    fn to_pax_value(self) -> PaxValue;
185}
186
187/// Trait that marks a type as being representable as a PaxAny, and provides
188/// the implementation for going to/from that type. For all builtins this
189/// means going to/from a pax value. For others to a `Box<dyn Any>`. This
190/// is automatically Implemented for PaxValue types through the macro
191/// impl_to_from_pax_value!, and for other types by implementing the marker
192/// trait ImplToFromPaxAny.
193pub trait ToFromPaxAny
194where
195    Self: Sized + 'static,
196{
197    /// Converts this value into a `PaxAny`.
198    fn to_pax_any(self) -> PaxAny;
199    /// Attempts to recover this concrete type from a `PaxAny`.
200    fn from_pax_any(pax_any: PaxAny) -> Result<Self, String>;
201    /// Attempts to borrow this concrete type from a `PaxAny`.
202    fn ref_from_pax_any(pax_any: &PaxAny) -> Result<&Self, String>;
203    /// Attempts to mutably borrow this concrete type from a `PaxAny`.
204    fn mut_from_pax_any(pax_any: &mut PaxAny) -> Result<&mut Self, String>;
205}
206
207impl ToFromPaxAny for PaxValue {
208    fn to_pax_any(self) -> PaxAny {
209        PaxAny::Builtin(self)
210    }
211
212    fn from_pax_any(pax_any: PaxAny) -> Result<Self, String> {
213        match pax_any {
214            PaxAny::Builtin(val) => Ok(val),
215            PaxAny::Any(_) => Err("tried to unwrap any as builtin".to_string()),
216        }
217    }
218
219    fn ref_from_pax_any(pax_any: &PaxAny) -> Result<&Self, String> {
220        match pax_any {
221            PaxAny::Builtin(val) => Ok(val),
222            PaxAny::Any(_) => Err("tried to unwrap any as builtin".to_string()),
223        }
224    }
225
226    fn mut_from_pax_any(pax_any: &mut PaxAny) -> Result<&mut Self, String> {
227        match pax_any {
228            PaxAny::Builtin(val) => Ok(val),
229            PaxAny::Any(_) => Err("tried to unwrap any as builtin".to_string()),
230        }
231    }
232}
233
234/// Marker trait for types that can be stored inside `PaxAny` without a built-in `PaxValue` representation.
235///
236/// If the type is part of `PaxValue`, implement `CoercionRules` instead.
237pub trait ImplToFromPaxAny: 'static {}
238
239// If a type has marker trait, implement to from
240// pax any automatically by wrapping in Box<dyn Any>
241impl<T: ImplToFromPaxAny> ToFromPaxAny for T {
242    fn to_pax_any(self) -> PaxAny {
243        PaxAny::Any(Box::new(self) as Box<dyn Any>)
244    }
245
246    fn from_pax_any(pax_any: PaxAny) -> Result<Self, String> {
247        match pax_any {
248            PaxAny::Any(v) => Ok(*v
249                .downcast::<Self>()
250                .map_err(|_e| "downcast failed".to_string())?),
251            _ => Err("wasn't any".to_string()),
252        }
253    }
254
255    fn ref_from_pax_any(pax_any: &PaxAny) -> Result<&Self, String> {
256        match pax_any {
257            PaxAny::Any(v) => v
258                .downcast_ref::<Self>()
259                .ok_or_else(|| "downcast failed".to_string()),
260            _ => Err("wasn't any".to_string()),
261        }
262    }
263
264    fn mut_from_pax_any(pax_any: &mut PaxAny) -> Result<&mut Self, String> {
265        match pax_any {
266            PaxAny::Any(v) => v
267                .downcast_mut::<Self>()
268                .ok_or_else(|| "downcast failed".to_string()),
269            _ => Err("wasn't any".to_string()),
270        }
271    }
272}
273
274// PaxAny can turn into PaxAny
275impl ToFromPaxAny for PaxAny {
276    fn to_pax_any(self) -> PaxAny {
277        self
278    }
279
280    fn from_pax_any(pax_any: PaxAny) -> Result<Self, String> {
281        Ok(pax_any)
282    }
283
284    fn ref_from_pax_any(pax_any: &PaxAny) -> Result<&Self, String> {
285        Ok(pax_any)
286    }
287
288    fn mut_from_pax_any(pax_any: &mut PaxAny) -> Result<&mut Self, String> {
289        Ok(pax_any)
290    }
291}