plushie-core 0.7.0

Core types and protocol for Plushie (no iced dependency)
Documentation
//! Builder for constructing widget placeholder nodes with typed properties.
//!
//! Widget authors use typed setters (generated by `WidgetProps` derive)
//! to set properties with compile-time type checking. The builder
//! collects props into a [`PropMap`] that becomes the placeholder node's
//! properties.
//!
//! # Usage
//!
//! The `WidgetProps` derive generates a `{Name}Builder` wrapper with
//! typed setters for each field. The inner `WidgetBuilder` is accessible
//! via `.0` for passing to `WidgetView::from_builder`.
//!
//! ```ignore
//! // Generated by #[derive(WidgetProps)]:
//! // StarRatingBuilder wraps WidgetBuilder with typed setters.
//!
//! let builder = StarRating::builder("stars")
//!     .rating(5u64)
//!     .max(10u64);
//!
//! // Pass to WidgetView for registration:
//! WidgetView::<StarRating>::from_builder(builder.0).register(widgets)
//! ```

use crate::protocol::{PropMap, PropValue, TreeNode};

/// Builder for constructing widget placeholder nodes with typed properties.
///
/// Holds the widget instance ID, type name, a prop map, and any
/// children declared by the builder. The `WidgetProps` derive
/// generates a typed wrapper around this with setters that enforce
/// correct prop types at compile time. Container widgets (those
/// marked `#[widget_props(container)]`) get additional `children` /
/// `child` methods on the generated wrapper.
pub struct WidgetBuilder {
    /// Widget instance ID (unique within the view tree).
    pub id: String,
    /// Widget type name for the wire protocol.
    pub type_name: &'static str,
    /// Collected properties.
    pub props: PropMap,
    /// Child nodes, for container widgets.
    pub children: Vec<TreeNode>,
}

impl WidgetBuilder {
    /// Create a new widget builder with the given type name and ID.
    pub fn new(type_name: &'static str, id: &str) -> Self {
        Self {
            id: id.to_string(),
            type_name,
            props: PropMap::new(),
            children: Vec::new(),
        }
    }

    /// Set a property by key (untyped). Prefer generated typed setters.
    pub fn prop(mut self, key: &str, value: impl Into<PropValue>) -> Self {
        self.props.insert(key, value.into());
        self
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn new_builder() {
        let b = WidgetBuilder::new("star_rating", "stars");
        assert_eq!(b.id, "stars");
        assert_eq!(b.type_name, "star_rating");
        assert!(b.props.is_empty());
    }

    #[test]
    fn prop_chaining() {
        let b = WidgetBuilder::new("gauge", "g1")
            .prop("value", 0.5f64)
            .prop("label", "Speed");
        assert_eq!(b.props.len(), 2);
        assert_eq!(b.props.get("value").unwrap().as_f64(), Some(0.5));
        assert_eq!(b.props.get("label").unwrap().as_str(), Some("Speed"));
    }

    #[test]
    fn prop_overwrites() {
        let b = WidgetBuilder::new("test", "t1")
            .prop("x", 1.0f64)
            .prop("x", 2.0f64);
        assert_eq!(b.props.len(), 1);
        assert_eq!(b.props.get("x").unwrap().as_f64(), Some(2.0));
    }
}