lumen-engine 0.2.1

Core composition model and rendering orchestration for Lumen.
Documentation
use crate::{
    error::{LumenError, PropertyError},
    expr::Expression,
};

use super::{ids::NodeId, vector};

#[derive(Debug, Clone)]
pub enum PropertyValue {
    Float(f64),
    Int(i64),
    Bool(bool),
    String(String),
    Color([u8; 4]),
    Paint(vector::paint::Paint),
    Vec2((f64, f64)),
    FloatVec(Vec<f64>),
    IntVec(Vec<i64>),
    StringVec(Vec<String>),
}

#[derive(Debug, Clone)]
pub enum PropertyExpression {
    Value(PropertyValue),
    Expr(Expression),
}

impl PropertyValue {
    pub(crate) fn invalid_type(
        node_id: NodeId,
        property_path: &str,
        expected: &'static str,
        actual: &'static str,
    ) -> LumenError {
        LumenError::Property(PropertyError::InvalidType {
            node_id,
            property_path: property_path.to_string(),
            expected,
            actual,
        })
    }

    pub(crate) fn coerce_float(&self) -> Option<f64> {
        match self {
            Self::Float(value) => Some(*value),
            Self::Int(value) => Some(*value as f64),
            Self::String(value) => value.parse().ok(),
            _ => None,
        }
    }

    pub(crate) fn coerce_int(&self) -> Option<i64> {
        match self {
            Self::Int(value) => Some(*value),
            Self::Float(value) => Some(*value as i64),
            Self::Bool(value) => Some(i64::from(*value)),
            Self::String(value) => value.parse().ok(),
            _ => None,
        }
    }

    pub(crate) fn coerce_bool(&self) -> Option<bool> {
        match self {
            Self::Bool(value) => Some(*value),
            Self::Int(value) => Some(*value != 0),
            Self::Float(value) => Some(*value != 0.0),
            Self::String(value) => match value.to_ascii_lowercase().as_str() {
                "true" | "1" => Some(true),
                "false" | "0" => Some(false),
                _ => None,
            },
            _ => None,
        }
    }

    pub(crate) fn coerce_string(&self) -> Option<String> {
        match self {
            Self::String(value) => Some(value.clone()),
            Self::Int(value) => Some(value.to_string()),
            Self::Float(value) => Some(value.to_string()),
            Self::Bool(value) => Some(value.to_string()),
            _ => None,
        }
    }

    pub fn resolve_float(
        &self,
        node_id: NodeId,
        property_path: &str,
        _ctx: &crate::expr::ExpressionContext<'_>,
    ) -> crate::Result<f64> {
        self.coerce_float().ok_or_else(|| {
            Self::invalid_type(
                node_id,
                property_path,
                "Float",
                if matches!(self, Self::String(_)) {
                    "String"
                } else {
                    "unsupported"
                },
            )
        })
    }

    pub fn resolve_int(
        &self,
        node_id: NodeId,
        property_path: &str,
        _ctx: &crate::expr::ExpressionContext<'_>,
    ) -> crate::Result<i64> {
        self.coerce_int().ok_or_else(|| {
            Self::invalid_type(
                node_id,
                property_path,
                "Int",
                if matches!(self, Self::String(_)) {
                    "String"
                } else {
                    "unsupported"
                },
            )
        })
    }

    pub fn resolve_bool(
        &self,
        node_id: NodeId,
        property_path: &str,
        _ctx: &crate::expr::ExpressionContext<'_>,
    ) -> crate::Result<bool> {
        self.coerce_bool()
            .ok_or_else(|| Self::invalid_type(node_id, property_path, "Bool", "unsupported"))
    }

    pub fn resolve_string(
        &self,
        node_id: NodeId,
        property_path: &str,
        _ctx: &crate::expr::ExpressionContext<'_>,
    ) -> crate::Result<String> {
        self.coerce_string()
            .ok_or_else(|| Self::invalid_type(node_id, property_path, "String", "unsupported"))
    }

    pub fn resolve_color(
        &self,
        node_id: NodeId,
        property_path: &str,
        _ctx: &crate::expr::ExpressionContext<'_>,
    ) -> crate::Result<[u8; 4]> {
        match self {
            Self::Color(value) => Ok(*value),
            _ => Err(Self::invalid_type(
                node_id,
                property_path,
                "Color",
                "unsupported",
            )),
        }
    }

    pub fn resolve_paint(
        &self,
        node_id: NodeId,
        property_path: &str,
        _ctx: &crate::expr::ExpressionContext<'_>,
    ) -> crate::Result<vector::paint::Paint> {
        match self {
            Self::Paint(value) => Ok(value.clone()),
            Self::Color(value) => Ok(vector::paint::Paint::solid(*value)),
            _ => Err(Self::invalid_type(
                node_id,
                property_path,
                "Paint",
                "unsupported",
            )),
        }
    }

    pub fn resolve_vec2(
        &self,
        node_id: NodeId,
        property_path: &str,
        _ctx: &crate::expr::ExpressionContext<'_>,
    ) -> crate::Result<(f64, f64)> {
        match self {
            Self::Vec2(value) => Ok(*value),
            _ => Err(Self::invalid_type(
                node_id,
                property_path,
                "Vec2",
                "unsupported",
            )),
        }
    }
}