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 == "Fill" || name == "Stroke") && variant == "__layer" && values.len() == 1
135                {
136                    return write!(f, "{} {}", name, values[0]);
137                }
138                if name == "Color" {
139                    write!(f, "{}", variant)?;
140                } else {
141                    write!(f, "{}::{}", name, variant)?;
142                }
143                if !values.is_empty() {
144                    write!(f, "(")?;
145                    for (i, val) in values.iter().enumerate() {
146                        if i != 0 {
147                            write!(f, ", ")?;
148                        }
149                        write!(f, "{}", val)?;
150                    }
151                    write!(f, ")")?;
152                }
153                Ok(())
154            }
155        }
156    }
157}
158
159impl Default for PaxValue {
160    fn default() -> Self {
161        PaxValue::Numeric(Numeric::F64(0.0))
162    }
163}
164
165/// This type serves a similar purpose as `Box<dyn Any>`, but allows for special
166/// handling of some types, enabling things like coercion.
167pub enum PaxAny {
168    /// Built-in PAXEL/runtime value.
169    Builtin(PaxValue),
170    /// Arbitrary Rust value boxed for runtime storage.
171    Any(Box<dyn Any>),
172}
173
174impl std::fmt::Debug for PaxAny {
175    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176        write!(f, "PaxAny {{ .. }}")
177    }
178}
179
180impl Interpolatable for PaxValue {}
181
182/// Converts a Rust value into its built-in `PaxValue` representation.
183///
184/// This is exact conversion, not coercion; coercion is handled separately by
185/// `CoercionRules`.
186pub trait ToPaxValue {
187    /// Live properties read when snapshotting a compound value for PAXEL.
188    /// Adapters observe these as well as the containing property.
189    fn nested_properties(&self) -> Vec<crate::properties::UntypedProperty> {
190        Vec::new()
191    }
192
193    /// Converts this value into a `PaxValue`.
194    fn to_pax_value(self) -> PaxValue;
195    /// Converts against owner-thread graph views, including nested generated
196    /// property fields. Requires an entered property graph for shared fields.
197    /// Ordinary [`Self::to_pax_value`] reads published value snapshots.
198    fn to_pax_value_in_graph(self) -> PaxValue
199    where
200        Self: Sized,
201    {
202        crate::properties::with_graph_conversion(|| self.to_pax_value())
203    }
204}
205
206/// Trait that marks a type as being representable as a PaxAny, and provides
207/// the implementation for going to/from that type. For all builtins this
208/// means going to/from a pax value. For others to a `Box<dyn Any>`. This
209/// is automatically Implemented for PaxValue types through the macro
210/// impl_to_from_pax_value!, and for other types by implementing the marker
211/// trait ImplToFromPaxAny.
212pub trait ToFromPaxAny
213where
214    Self: Sized + 'static,
215{
216    /// Converts this value into a `PaxAny`.
217    fn to_pax_any(self) -> PaxAny;
218    /// Attempts to recover this concrete type from a `PaxAny`.
219    fn from_pax_any(pax_any: PaxAny) -> Result<Self, String>;
220    /// Attempts to borrow this concrete type from a `PaxAny`.
221    fn ref_from_pax_any(pax_any: &PaxAny) -> Result<&Self, String>;
222    /// Attempts to mutably borrow this concrete type from a `PaxAny`.
223    fn mut_from_pax_any(pax_any: &mut PaxAny) -> Result<&mut Self, String>;
224}
225
226impl ToFromPaxAny for PaxValue {
227    fn to_pax_any(self) -> PaxAny {
228        PaxAny::Builtin(self)
229    }
230
231    fn from_pax_any(pax_any: PaxAny) -> Result<Self, String> {
232        match pax_any {
233            PaxAny::Builtin(val) => Ok(val),
234            PaxAny::Any(_) => Err("tried to unwrap any as builtin".to_string()),
235        }
236    }
237
238    fn ref_from_pax_any(pax_any: &PaxAny) -> Result<&Self, String> {
239        match pax_any {
240            PaxAny::Builtin(val) => Ok(val),
241            PaxAny::Any(_) => Err("tried to unwrap any as builtin".to_string()),
242        }
243    }
244
245    fn mut_from_pax_any(pax_any: &mut PaxAny) -> Result<&mut Self, String> {
246        match pax_any {
247            PaxAny::Builtin(val) => Ok(val),
248            PaxAny::Any(_) => Err("tried to unwrap any as builtin".to_string()),
249        }
250    }
251}
252
253/// Marker trait for types that can be stored inside `PaxAny` without a built-in `PaxValue` representation.
254///
255/// If the type is part of `PaxValue`, implement `CoercionRules` instead.
256pub trait ImplToFromPaxAny: 'static {}
257
258// If a type has marker trait, implement to from
259// pax any automatically by wrapping in Box<dyn Any>
260impl<T: ImplToFromPaxAny> ToFromPaxAny for T {
261    fn to_pax_any(self) -> PaxAny {
262        PaxAny::Any(Box::new(self) as Box<dyn Any>)
263    }
264
265    fn from_pax_any(pax_any: PaxAny) -> Result<Self, String> {
266        match pax_any {
267            PaxAny::Any(v) => Ok(*v
268                .downcast::<Self>()
269                .map_err(|_e| "downcast failed".to_string())?),
270            _ => Err("wasn't any".to_string()),
271        }
272    }
273
274    fn ref_from_pax_any(pax_any: &PaxAny) -> Result<&Self, String> {
275        match pax_any {
276            PaxAny::Any(v) => v
277                .downcast_ref::<Self>()
278                .ok_or_else(|| "downcast failed".to_string()),
279            _ => Err("wasn't any".to_string()),
280        }
281    }
282
283    fn mut_from_pax_any(pax_any: &mut PaxAny) -> Result<&mut Self, String> {
284        match pax_any {
285            PaxAny::Any(v) => v
286                .downcast_mut::<Self>()
287                .ok_or_else(|| "downcast failed".to_string()),
288            _ => Err("wasn't any".to_string()),
289        }
290    }
291}
292
293// PaxAny can turn into PaxAny
294impl ToFromPaxAny for PaxAny {
295    fn to_pax_any(self) -> PaxAny {
296        self
297    }
298
299    fn from_pax_any(pax_any: PaxAny) -> Result<Self, String> {
300        Ok(pax_any)
301    }
302
303    fn ref_from_pax_any(pax_any: &PaxAny) -> Result<&Self, String> {
304        Ok(pax_any)
305    }
306
307    fn mut_from_pax_any(pax_any: &mut PaxAny) -> Result<&mut Self, String> {
308        Ok(pax_any)
309    }
310}