Skip to main content

lumen_engine/node/vector/
shape.rs

1use crate::gpu::{
2    BoundFrame, CompiledOutput, FrameBindContext, FrameBinding, GpuCompileNode, GpuFrameBindNode,
3};
4use crate::node::{NodeId, NodeProperty, PortRef};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, lumen_macros::NodeEnum)]
7#[repr(i64)]
8pub enum ShapeGeometryKind {
9    Rectangle = 0,
10    Ellipse = 1,
11    Polygon = 2,
12}
13
14impl ShapeGeometryKind {
15    pub fn from_int(value: i64) -> Self {
16        match value {
17            1 => Self::Ellipse,
18            2 => Self::Polygon,
19            _ => Self::Rectangle,
20        }
21    }
22}
23
24/// Produces a vector shape layer for GPU rasterization.
25#[derive(Debug, Clone, lumen_macros::Node)]
26#[node(kind = "shape", name = "Shape", category = "vector")]
27pub struct Shape {
28    pub id: NodeId,
29    /// Geometry primitive to rasterize.
30    #[property(kind = "enum", enum_type = ShapeGeometryKind)]
31    pub geometry_kind: NodeProperty,
32    /// Shape width in pixels.
33    #[property(kind = "int", min = 1, step = 1)]
34    pub width: NodeProperty,
35    /// Shape height in pixels.
36    #[property(kind = "int", min = 1, step = 1)]
37    pub height: NodeProperty,
38    /// Corner radius for rectangle geometry.
39    #[property(kind = "float", min = 0, step = 1)]
40    pub border_radius: NodeProperty,
41    /// Polygon point list formatted as `x,y; x,y`.
42    #[property(
43        kind = "string",
44        name = "Polygon points",
45        format = "point_list",
46        multiline,
47        recommended_rows = 3
48    )]
49    pub polygon_points: NodeProperty,
50    /// Shape origin in pixels.
51    #[property(kind = "vec2")]
52    pub position: NodeProperty,
53    /// Enables fill rendering.
54    #[property(kind = "bool")]
55    pub fill_enabled: NodeProperty,
56    /// Fill color.
57    #[property(kind = "color")]
58    pub fill_color: NodeProperty,
59    /// Enables stroke rendering.
60    #[property(kind = "bool")]
61    pub stroke_enabled: NodeProperty,
62    /// Stroke color.
63    #[property(kind = "color")]
64    pub stroke_color: NodeProperty,
65    /// Stroke width in pixels.
66    #[property(kind = "float", min = 0, step = 0.5)]
67    pub stroke_width: NodeProperty,
68}
69
70impl Default for Shape {
71    fn default() -> Self {
72        Self {
73            id: NodeId::new(0),
74            geometry_kind: NodeProperty::Int(ShapeGeometryKind::Rectangle as i64),
75            width: NodeProperty::Int(1),
76            height: NodeProperty::Int(1),
77            border_radius: NodeProperty::Float(0.0),
78            polygon_points: NodeProperty::String(String::new()),
79            position: NodeProperty::Vec2((0.0, 0.0)),
80            fill_enabled: NodeProperty::Bool(true),
81            fill_color: NodeProperty::Color([255, 255, 255, 255]),
82            stroke_enabled: NodeProperty::Bool(false),
83            stroke_color: NodeProperty::Color([0, 0, 0, 255]),
84            stroke_width: NodeProperty::Float(1.0),
85        }
86    }
87}
88
89impl GpuCompileNode for Shape {
90    fn compile_gpu(
91        &self,
92        ctx: &mut crate::gpu::CompileContext<'_>,
93        port: &PortRef,
94    ) -> crate::Result<CompiledOutput> {
95        crate::node::vector::renderer::VectorRenderer::new(ctx).compile_shape(self, port)
96    }
97}
98
99impl GpuFrameBindNode for Shape {
100    fn bind_gpu_frame(
101        &self,
102        ctx: &FrameBindContext<'_>,
103        binding: &FrameBinding,
104        bound: &mut BoundFrame,
105    ) -> crate::Result<()> {
106        let FrameBinding::Shape {
107            node_id,
108            geometry_kind,
109            width,
110            height,
111            border_radius,
112            position,
113            fill_enabled,
114            fill_color,
115            stroke_enabled,
116            stroke_color,
117            stroke_width,
118            buffer,
119        } = binding
120        else {
121            return Ok(());
122        };
123        let (x, y) = position.resolve_vec2(
124            *node_id,
125            "position",
126            &ctx.expr_context(*node_id, "position"),
127        )?;
128        let fill = fill_color.resolve_color(
129            *node_id,
130            "fill_color",
131            &ctx.expr_context(*node_id, "fill_color"),
132        )?;
133        let stroke = stroke_color.resolve_color(
134            *node_id,
135            "stroke_color",
136            &ctx.expr_context(*node_id, "stroke_color"),
137        )?;
138        let mut flags = 0;
139        if fill_enabled.resolve_bool(
140            *node_id,
141            "fill_enabled",
142            &ctx.expr_context(*node_id, "fill_enabled"),
143        )? {
144            flags |= 1;
145        }
146        if stroke_enabled.resolve_bool(
147            *node_id,
148            "stroke_enabled",
149            &ctx.expr_context(*node_id, "stroke_enabled"),
150        )? {
151            flags |= 2;
152        }
153        let params = super::renderer::ShapeParams {
154            fill_color: rgba8_to_f32(fill),
155            stroke_color: rgba8_to_f32(stroke),
156            position: [x as f32, y as f32],
157            size: [
158                width
159                    .resolve_int(*node_id, "width", &ctx.expr_context(*node_id, "width"))?
160                    .max(1) as f32,
161                height
162                    .resolve_int(*node_id, "height", &ctx.expr_context(*node_id, "height"))?
163                    .max(1) as f32,
164            ],
165            border_radius: border_radius.resolve_float(
166                *node_id,
167                "border_radius",
168                &ctx.expr_context(*node_id, "border_radius"),
169            )? as f32,
170            stroke_width: stroke_width.resolve_float(
171                *node_id,
172                "stroke_width",
173                &ctx.expr_context(*node_id, "stroke_width"),
174            )? as f32,
175            geometry_kind: ShapeGeometryKind::from_int(geometry_kind.resolve_int(
176                *node_id,
177                "geometry_kind",
178                &ctx.expr_context(*node_id, "geometry_kind"),
179            )?) as u32,
180            flags,
181        };
182        bound.write_buffer(*buffer, 0, bytemuck::bytes_of(&params));
183        Ok(())
184    }
185}
186
187fn rgba8_to_f32(color: [u8; 4]) -> [f32; 4] {
188    [
189        f32::from(color[0]) / 255.0,
190        f32::from(color[1]) / 255.0,
191        f32::from(color[2]) / 255.0,
192        f32::from(color[3]) / 255.0,
193    ]
194}