Skip to main content

lumen_engine/node/vector/
shape.rs

1use super::paint::Paint;
2use crate::gpu::{BoundFrame, CompiledOutput, FrameBindContext, GpuCompileNode, GpuCompiledNode};
3use crate::node::{NodeId, NodeParamEvalContext, NodeParams, PortRef};
4
5#[derive(
6    Debug, Clone, Copy, PartialEq, Eq, Default, lumen_macros::NodeEnum, lumen_macros::Delegate,
7)]
8#[repr(i64)]
9#[delegate(kind = "enum")]
10pub enum ShapeGeometryKind {
11    #[default]
12    Rectangle = 0,
13    Ellipse = 1,
14    Polygon = 2,
15}
16
17/// Produces a vector shape layer for GPU rasterization.
18#[derive(Debug, Clone, lumen_macros::Delegate)]
19pub struct ShapeParams {
20    /// Geometry primitive to rasterize.
21    #[meta(kind = "enum", enum_type = ShapeGeometryKind)]
22    pub geometry_kind: ShapeGeometryKind,
23    /// Shape width in pixels.
24    #[meta(min = 1, step = 1)]
25    pub width: i64,
26    /// Shape height in pixels.
27    #[meta(min = 1, step = 1)]
28    pub height: i64,
29    /// Corner radius for rectangle geometry.
30    #[meta(min = 0, step = 1)]
31    pub border_radius: f64,
32    /// Polygon point list formatted as `x,y; x,y`.
33    #[meta(
34        name = "Polygon points",
35        format = "point_list",
36        multiline,
37        recommended_rows = 3
38    )]
39    pub polygon_points: String,
40    /// Shape origin in pixels.
41    #[meta()]
42    pub position: (f64, f64),
43    /// Enables fill rendering.
44    #[meta()]
45    pub fill_enabled: bool,
46    /// Fill paint. Accepts a solid color or gradient.
47    #[meta()]
48    pub fill_paint: Paint,
49    /// Enables stroke rendering.
50    #[meta()]
51    pub stroke_enabled: bool,
52    /// Stroke paint. Accepts a solid color or gradient.
53    #[meta()]
54    pub stroke_paint: Paint,
55    /// Stroke width in pixels.
56    #[meta(min = 0, step = 0.5)]
57    pub stroke_width: f64,
58}
59
60impl Default for ShapeParams {
61    fn default() -> Self {
62        Self {
63            geometry_kind: ShapeGeometryKind::Rectangle,
64            width: 1,
65            height: 1,
66            border_radius: 0.0,
67            polygon_points: String::new(),
68            position: (0.0, 0.0),
69            fill_enabled: true,
70            fill_paint: Paint::solid([255, 255, 255, 255]),
71            stroke_enabled: false,
72            stroke_paint: Paint::solid([0, 0, 0, 255]),
73            stroke_width: 1.0,
74        }
75    }
76}
77
78/// Produces a vector shape layer for GPU rasterization.
79#[derive(Debug, Clone, lumen_macros::Node)]
80#[node(kind = "shape", name = "Shape", category = "vector")]
81pub struct Shape {
82    pub id: NodeId,
83    #[params]
84    pub params: ShapeParamsDelegate,
85}
86
87impl Default for Shape {
88    fn default() -> Self {
89        Self {
90            id: NodeId::new(0),
91            params: ShapeParamsDelegate::default(),
92        }
93    }
94}
95
96impl GpuCompileNode for Shape {
97    fn compile_gpu(
98        &self,
99        ctx: &mut crate::gpu::CompileContext<'_>,
100        port: &PortRef,
101    ) -> crate::Result<CompiledOutput> {
102        crate::node::vector::renderer::VectorRenderer::new(ctx).compile_shape(self, port)
103    }
104}
105
106#[derive(Debug, Clone)]
107pub(crate) struct CompiledShape {
108    pub(crate) node_id: NodeId,
109    pub(crate) params: ShapeParamsDelegate,
110    pub(crate) buffer: lumen_gpu::BufferId,
111}
112
113impl GpuCompiledNode for CompiledShape {
114    fn node_id(&self) -> NodeId {
115        self.node_id
116    }
117
118    fn bind(&self, ctx: &FrameBindContext<'_>, bound: &mut BoundFrame) -> crate::Result<()> {
119        let evaluated = self.params.eval(&NodeParamEvalContext {
120            node_id: self.node_id,
121            expr: &ctx.expr_context(self.node_id, "params"),
122        })?;
123        let (x, y) = evaluated.position;
124        let mut flags = 0;
125        if evaluated.fill_enabled {
126            flags |= 1;
127        }
128        if evaluated.stroke_enabled {
129            flags |= 2;
130        }
131        let params = super::renderer::ShapeParams {
132            fill_paint: evaluated.fill_paint.to_gpu([255, 255, 255, 255]),
133            stroke_paint: evaluated.stroke_paint.to_gpu([0, 0, 0, 255]),
134            position: [x as f32, y as f32],
135            size: [
136                evaluated.width.max(1) as f32,
137                evaluated.height.max(1) as f32,
138            ],
139            border_radius: evaluated.border_radius as f32,
140            stroke_width: evaluated.stroke_width as f32,
141            geometry_kind: evaluated.geometry_kind as u32,
142            flags,
143        };
144        bound.write_buffer(self.buffer, 0, bytemuck::bytes_of(&params));
145        Ok(())
146    }
147}