pax-runtime-api 0.39.1

Userland constructs used at the runtime API boundary of Pax Engine
Documentation
use crate::{Color, Duration, Interpolatable, PathElement, Percent, Rotation, Size};
use std::{any::Any, fmt::Display, rc::Rc};

use self::numeric::Numeric;
pub use coercion_impls::{is_typed_binding_safe, CoercionRules};
use serde::{Deserialize, Serialize};

mod arithmetic;
mod coercion_impls;
pub mod functions;
mod macros;
pub mod numeric;
mod to_from_impls;

/// Shared runtime PaxValue handle.
pub type RcPaxValue = Rc<PaxValue>;
/// Runtime container for polymorphic values, for evaluating PAXEL.
///
/// Two important traits are related to this type:
/// ToFromPaxValue - responsible for converting to and from specific types (u8,
/// String, Color, etc)
/// CoercionRules - responsible for coercing a PaxValue to a specific type
/// (possibly from multiple different variants)
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(crate = "crate::serde")]
pub enum PaxValue {
    /// Boolean value.
    Bool(bool),
    /// Polymorphic numeric value.
    Numeric(Numeric),
    /// UTF-8 string value.
    String(String),
    /// Pax size value, such as `25px` or `50%`.
    Size(Size),
    /// Raw percent value.
    Percent(Percent),
    /// Pax color value.
    Color(Box<Color>),
    /// Pax rotation value.
    Rotation(Rotation),
    /// Pax animation duration value, such as `250ms`, `1s`, or `10f`.
    Duration(Duration),
    /// Vector path element value.
    PathElement(Box<PathElement>),
    /// Optional value.
    Option(Box<Option<PaxValue>>),
    /// Homogeneous or heterogeneous vector value.
    Vec(Vec<PaxValue>),
    /// Range value.
    Range(Box<PaxValue>, Box<PaxValue>),
    /// Object value represented by named fields.
    Object(Vec<(String, PaxValue)>),
    /// Enum value represented by type name, variant name, and payload values.
    Enum(Box<(String, String, Vec<PaxValue>)>),
}

impl Clone for PaxValue {
    fn clone(&self) -> Self {
        match self {
            PaxValue::Bool(b) => PaxValue::Bool(*b),
            PaxValue::Numeric(n) => PaxValue::Numeric(n.clone()),
            PaxValue::String(s) => PaxValue::String(s.clone()),
            PaxValue::Size(s) => PaxValue::Size(s.clone()),
            PaxValue::Percent(p) => PaxValue::Percent(p.clone()),
            PaxValue::Color(c) => PaxValue::Color(c.clone()),
            PaxValue::Rotation(r) => PaxValue::Rotation(r.clone()),
            PaxValue::Duration(d) => PaxValue::Duration(d.clone()),
            PaxValue::PathElement(pe) => PaxValue::PathElement(pe.clone()),
            PaxValue::Option(opt) => PaxValue::Option(opt.clone()),
            PaxValue::Vec(v) => PaxValue::Vec(v.clone()),
            PaxValue::Range(start, end) => PaxValue::Range(start.clone(), end.clone()),
            PaxValue::Object(pairs) => PaxValue::Object(pairs.clone()),
            PaxValue::Enum(contents) => {
                let (name, variant, values) = contents.as_ref();
                PaxValue::Enum(Box::new((name.clone(), variant.clone(), values.clone())))
            }
        }
    }
}

// Make sure Enum looks like an enum and vec looks like a vec
impl Display for PaxValue {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            PaxValue::Bool(b) => write!(f, "{}", b),
            PaxValue::Numeric(n) => write!(f, "{}", n),
            PaxValue::String(s) => write!(f, "\"{}\"", s),
            PaxValue::Size(s) => write!(f, "{}", s),
            PaxValue::Percent(p) => write!(f, "{}", p),
            PaxValue::Color(c) => write!(f, "{}", c),
            PaxValue::Rotation(r) => write!(f, "{}", r),
            PaxValue::Duration(d) => write!(f, "{}", d),
            PaxValue::PathElement(path_elem) => {
                write!(f, "PathElement::")?;
                match path_elem.as_ref() {
                    PathElement::Empty => write!(f, "Empty"),
                    PathElement::Point(s1, s2) => write!(f, "Point({}, {})", s1, s2),
                    PathElement::Line => write!(f, "Line"),
                    PathElement::Quadratic(s1, s2) => write!(f, "Quadratic({}, {})", s1, s2),
                    PathElement::Cubic(v1, v2, v3, v4) => {
                        write!(f, "Cubic({}, {}, {}, {})", v1, v2, v3, v4)
                    }
                    PathElement::Close => write!(f, "Close"),
                }?;
                Ok(())
            }
            PaxValue::Option(o) => match o.as_ref() {
                Some(v) => write!(f, "Some({})", v),
                None => write!(f, "None"),
            },
            PaxValue::Vec(v) => {
                write!(f, "[")?;
                for (i, val) in v.iter().enumerate() {
                    if i != 0 {
                        write!(f, ", ")?;
                    }
                    write!(f, "{}", val)?;
                }
                write!(f, "]")
            }
            PaxValue::Range(start, end) => write!(f, "{}..{}", start, end),
            PaxValue::Object(o) => {
                write!(f, "{{")?;
                for (i, (key, val)) in o.iter().enumerate() {
                    if i != 0 {
                        write!(f, ", ")?;
                    }
                    write!(f, "{}: {}", key, val)?;
                }
                write!(f, "}}")
            }
            PaxValue::Enum(contents) => {
                let (name, variant, values) = contents.as_ref();
                if (name == "Fill" || name == "Stroke") && variant == "__layer" && values.len() == 1
                {
                    return write!(f, "{} {}", name, values[0]);
                }
                if name == "Color" {
                    write!(f, "{}", variant)?;
                } else {
                    write!(f, "{}::{}", name, variant)?;
                }
                if !values.is_empty() {
                    write!(f, "(")?;
                    for (i, val) in values.iter().enumerate() {
                        if i != 0 {
                            write!(f, ", ")?;
                        }
                        write!(f, "{}", val)?;
                    }
                    write!(f, ")")?;
                }
                Ok(())
            }
        }
    }
}

impl Default for PaxValue {
    fn default() -> Self {
        PaxValue::Numeric(Numeric::F64(0.0))
    }
}

/// This type serves a similar purpose as `Box<dyn Any>`, but allows for special
/// handling of some types, enabling things like coercion.
pub enum PaxAny {
    /// Built-in PAXEL/runtime value.
    Builtin(PaxValue),
    /// Arbitrary Rust value boxed for runtime storage.
    Any(Box<dyn Any>),
}

impl std::fmt::Debug for PaxAny {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "PaxAny {{ .. }}")
    }
}

impl Interpolatable for PaxValue {}

/// Converts a Rust value into its built-in `PaxValue` representation.
///
/// This is exact conversion, not coercion; coercion is handled separately by
/// `CoercionRules`.
pub trait ToPaxValue {
    /// Live properties read when snapshotting a compound value for PAXEL.
    /// Adapters observe these as well as the containing property.
    fn nested_properties(&self) -> Vec<crate::properties::UntypedProperty> {
        Vec::new()
    }

    /// Converts this value into a `PaxValue`.
    fn to_pax_value(self) -> PaxValue;
    /// Converts against owner-thread graph views, including nested generated
    /// property fields. Requires an entered property graph for shared fields.
    /// Ordinary [`Self::to_pax_value`] reads published value snapshots.
    fn to_pax_value_in_graph(self) -> PaxValue
    where
        Self: Sized,
    {
        crate::properties::with_graph_conversion(|| self.to_pax_value())
    }
}

/// Trait that marks a type as being representable as a PaxAny, and provides
/// the implementation for going to/from that type. For all builtins this
/// means going to/from a pax value. For others to a `Box<dyn Any>`. This
/// is automatically Implemented for PaxValue types through the macro
/// impl_to_from_pax_value!, and for other types by implementing the marker
/// trait ImplToFromPaxAny.
pub trait ToFromPaxAny
where
    Self: Sized + 'static,
{
    /// Converts this value into a `PaxAny`.
    fn to_pax_any(self) -> PaxAny;
    /// Attempts to recover this concrete type from a `PaxAny`.
    fn from_pax_any(pax_any: PaxAny) -> Result<Self, String>;
    /// Attempts to borrow this concrete type from a `PaxAny`.
    fn ref_from_pax_any(pax_any: &PaxAny) -> Result<&Self, String>;
    /// Attempts to mutably borrow this concrete type from a `PaxAny`.
    fn mut_from_pax_any(pax_any: &mut PaxAny) -> Result<&mut Self, String>;
}

impl ToFromPaxAny for PaxValue {
    fn to_pax_any(self) -> PaxAny {
        PaxAny::Builtin(self)
    }

    fn from_pax_any(pax_any: PaxAny) -> Result<Self, String> {
        match pax_any {
            PaxAny::Builtin(val) => Ok(val),
            PaxAny::Any(_) => Err("tried to unwrap any as builtin".to_string()),
        }
    }

    fn ref_from_pax_any(pax_any: &PaxAny) -> Result<&Self, String> {
        match pax_any {
            PaxAny::Builtin(val) => Ok(val),
            PaxAny::Any(_) => Err("tried to unwrap any as builtin".to_string()),
        }
    }

    fn mut_from_pax_any(pax_any: &mut PaxAny) -> Result<&mut Self, String> {
        match pax_any {
            PaxAny::Builtin(val) => Ok(val),
            PaxAny::Any(_) => Err("tried to unwrap any as builtin".to_string()),
        }
    }
}

/// Marker trait for types that can be stored inside `PaxAny` without a built-in `PaxValue` representation.
///
/// If the type is part of `PaxValue`, implement `CoercionRules` instead.
pub trait ImplToFromPaxAny: 'static {}

// If a type has marker trait, implement to from
// pax any automatically by wrapping in Box<dyn Any>
impl<T: ImplToFromPaxAny> ToFromPaxAny for T {
    fn to_pax_any(self) -> PaxAny {
        PaxAny::Any(Box::new(self) as Box<dyn Any>)
    }

    fn from_pax_any(pax_any: PaxAny) -> Result<Self, String> {
        match pax_any {
            PaxAny::Any(v) => Ok(*v
                .downcast::<Self>()
                .map_err(|_e| "downcast failed".to_string())?),
            _ => Err("wasn't any".to_string()),
        }
    }

    fn ref_from_pax_any(pax_any: &PaxAny) -> Result<&Self, String> {
        match pax_any {
            PaxAny::Any(v) => v
                .downcast_ref::<Self>()
                .ok_or_else(|| "downcast failed".to_string()),
            _ => Err("wasn't any".to_string()),
        }
    }

    fn mut_from_pax_any(pax_any: &mut PaxAny) -> Result<&mut Self, String> {
        match pax_any {
            PaxAny::Any(v) => v
                .downcast_mut::<Self>()
                .ok_or_else(|| "downcast failed".to_string()),
            _ => Err("wasn't any".to_string()),
        }
    }
}

// PaxAny can turn into PaxAny
impl ToFromPaxAny for PaxAny {
    fn to_pax_any(self) -> PaxAny {
        self
    }

    fn from_pax_any(pax_any: PaxAny) -> Result<Self, String> {
        Ok(pax_any)
    }

    fn ref_from_pax_any(pax_any: &PaxAny) -> Result<&Self, String> {
        Ok(pax_any)
    }

    fn mut_from_pax_any(pax_any: &mut PaxAny) -> Result<&mut Self, String> {
        Ok(pax_any)
    }
}