Skip to main content

ply_engine/
render_commands.rs

1use crate::{color::Color, engine::{self, ShapeRotationConfig, VisualRotationConfig}, math::BoundingBox, renderer::ImageSource, shaders::ShaderConfig};
2
3/// Represents a rectangle with a specified color and corner radii.
4#[derive(Debug, Clone)]
5pub struct Rectangle {
6    /// The fill color of the rectangle.
7    pub color: Color,
8    /// The corner radii for rounded edges.
9    pub corner_radii: CornerRadii,
10}
11
12/// Represents a text element with styling attributes.
13#[derive(Debug, Clone)]
14pub struct Text {
15    /// The text content.
16    pub text: String,
17    /// The color of the text.
18    pub color: Color,
19    /// The font size.
20    pub font_size: u16,
21    /// The spacing between letters.
22    pub letter_spacing: u16,
23    /// The line height.
24    pub line_height: u16,
25    /// The font asset, if specified via `.font()`.
26    pub font_asset: Option<&'static crate::renderer::FontAsset>,
27}
28
29/// Defines individual corner radii for an element.
30#[derive(Debug, Clone)]
31pub struct CornerRadii {
32    /// The radius for the top-left corner.
33    pub top_left: f32,
34    /// The radius for the top-right corner.
35    pub top_right: f32,
36    /// The radius for the bottom-left corner.
37    pub bottom_left: f32,
38    /// The radius for the bottom-right corner.
39    pub bottom_right: f32,
40}
41
42/// Defines the border width for each side of an element.
43#[derive(Debug, Clone)]
44pub struct BorderWidth {
45    /// Border width on the left side.
46    pub left: u16,
47    /// Border width on the right side.
48    pub right: u16,
49    /// Border width on the top side.
50    pub top: u16,
51    /// Border width on the bottom side.
52    pub bottom: u16,
53    /// Border width between child elements.
54    pub between_children: u16,
55}
56
57/// Represents a border with a specified color, width, and corner radii.
58#[derive(Debug, Clone)]
59pub struct Border {
60    /// The border color.
61    pub color: Color,
62    /// The corner radii for rounded border edges.
63    pub corner_radii: CornerRadii,
64    /// The width of the border on each side.
65    pub width: BorderWidth,
66}
67
68/// Represents an image with defined dimensions and data.
69#[derive(Debug, Clone)]
70pub struct Image {
71    /// Background color
72    pub background_color: Color,
73    /// The corner radii for rounded border edges.
74    pub corner_radii: CornerRadii,
75    /// A reference to the image source data.
76    pub data: ImageSource,
77}
78
79/// Represents a custom element with a background color, corner radii, and associated data.
80#[derive(Debug, Clone)]
81pub struct Custom<CustomElementData> {
82    /// The background color of the custom element.
83    pub background_color: Color,
84    /// The corner radii for rounded edges.
85    pub corner_radii: CornerRadii,
86    /// The custom element data.
87    pub data: CustomElementData,
88}
89
90impl CornerRadii {
91    pub fn clamp_to_size(&mut self, width: f32, height: f32) {
92        let max_r = width.min(height) / 2.0;
93        self.top_left = self.top_left.clamp(0.0, max_r);
94        self.top_right = self.top_right.clamp(0.0, max_r);
95        self.bottom_left = self.bottom_left.clamp(0.0, max_r);
96        self.bottom_right = self.bottom_right.clamp(0.0, max_r);
97    }
98}
99
100impl From<crate::layout::CornerRadius> for CornerRadii {
101    fn from(value: crate::layout::CornerRadius) -> Self {
102        Self {
103            top_left: value.top_left,
104            top_right: value.top_right,
105            bottom_left: value.bottom_left,
106            bottom_right: value.bottom_right,
107        }
108    }
109}
110
111#[derive(Debug, Clone)]
112pub enum RenderCommandConfig<CustomElementData> {
113    None(),
114    Rectangle(Rectangle),
115    Border(Border),
116    Text(Text),
117    Image(Image),
118    ScissorStart(),
119    ScissorEnd(),
120    Custom(Custom<CustomElementData>),
121    /// Begin a group: Renders children to an offscreen buffer.
122    /// Optionally applies a fragment shader and/or visual rotation.
123    GroupBegin {
124        /// Fragment shader to apply as post-process.
125        shader: Option<ShaderConfig>,
126        /// Visual rotation applied when compositing the render target.
127        visual_rotation: Option<VisualRotationConfig>,
128    },
129    GroupEnd,
130}
131
132impl<CustomElementData: Clone + Default + std::fmt::Debug>
133    RenderCommandConfig<CustomElementData>
134{
135    pub(crate) fn from_engine_render_command(value: &engine::InternalRenderCommand<CustomElementData>) -> Self {
136        match value.command_type {
137            engine::RenderCommandType::None => Self::None(),
138            engine::RenderCommandType::Rectangle => {
139                if let engine::InternalRenderData::Rectangle { background_color, corner_radius } = &value.render_data {
140                    Self::Rectangle(Rectangle {
141                        color: *background_color,
142                        corner_radii: (*corner_radius).into(),
143                    })
144                } else {
145                    Self::None()
146                }
147            }
148            engine::RenderCommandType::Text => {
149                if let engine::InternalRenderData::Text { text, text_color, font_size, letter_spacing, line_height, font_asset } = &value.render_data {
150                    Self::Text(Text {
151                        text: text.clone(),
152                        color: *text_color,
153                        font_size: *font_size,
154                        letter_spacing: *letter_spacing,
155                        line_height: *line_height,
156                        font_asset: *font_asset,
157                    })
158                } else {
159                    Self::None()
160                }
161            }
162            engine::RenderCommandType::Border => {
163                if let engine::InternalRenderData::Border { color, corner_radius, width } = &value.render_data {
164                    Self::Border(Border {
165                        color: *color,
166                        corner_radii: (*corner_radius).into(),
167                        width: BorderWidth {
168                            left: width.left,
169                            right: width.right,
170                            top: width.top,
171                            bottom: width.bottom,
172                            between_children: width.between_children,
173                        },
174                    })
175                } else {
176                    Self::None()
177                }
178            }
179            engine::RenderCommandType::Image => {
180                if let engine::InternalRenderData::Image { background_color, corner_radius, image_data } = &value.render_data {
181                    Self::Image(Image {
182                        data: image_data.clone(),
183                        corner_radii: (*corner_radius).into(),
184                        background_color: *background_color,
185                    })
186                } else {
187                    Self::None()
188                }
189            }
190            engine::RenderCommandType::ScissorStart => Self::ScissorStart(),
191            engine::RenderCommandType::ScissorEnd => Self::ScissorEnd(),
192            engine::RenderCommandType::GroupBegin => {
193                // GroupBegin uses the first effect from the render command as its shader config,
194                // and carries the visual_rotation from the render command.
195                let shader = value.effects.first().cloned();
196                let visual_rotation = value.visual_rotation;
197                Self::GroupBegin { shader, visual_rotation }
198            }
199            engine::RenderCommandType::GroupEnd => Self::GroupEnd,
200            engine::RenderCommandType::Custom => {
201                if let engine::InternalRenderData::Custom { background_color, corner_radius, custom_data } = &value.render_data {
202                    Self::Custom(Custom {
203                        background_color: *background_color,
204                        corner_radii: (*corner_radius).into(),
205                        data: custom_data.clone(),
206                    })
207                } else {
208                    Self::None()
209                }
210            }
211        }
212    }
213}
214
215/// Represents a render command for drawing an element on the screen.
216#[derive(Debug, Clone)]
217pub struct RenderCommand<CustomElementData> {
218    /// The bounding box defining the area occupied by the element.
219    pub bounding_box: BoundingBox,
220    /// The specific configuration for rendering this command.
221    pub config: RenderCommandConfig<CustomElementData>,
222    /// A unique identifier for the render command.
223    pub id: u32,
224    /// The z-index determines the stacking order of elements.
225    /// Higher values are drawn above lower values.
226    pub z_index: i16,
227    /// Per-element shader effects (chained in order).
228    pub effects: Vec<ShaderConfig>,
229    /// Shape rotation applied at the vertex level (only for Rectangle / Image / Custom / Border).
230    pub shape_rotation: Option<ShapeRotationConfig>,
231}
232
233impl<CustomElementData: Clone + Default + std::fmt::Debug> RenderCommand<CustomElementData> {
234    pub(crate) fn from_engine_render_command(value: &engine::InternalRenderCommand<CustomElementData>) -> Self {
235        let mut config = RenderCommandConfig::from_engine_render_command(value);
236        let bb = value.bounding_box;
237        match &mut config {
238            RenderCommandConfig::Rectangle(r)  => r.corner_radii.clamp_to_size(bb.width, bb.height),
239            RenderCommandConfig::Border(b)     => b.corner_radii.clamp_to_size(bb.width, bb.height),
240            RenderCommandConfig::Image(i)      => i.corner_radii.clamp_to_size(bb.width, bb.height),
241            RenderCommandConfig::Custom(c)     => c.corner_radii.clamp_to_size(bb.width, bb.height),
242            _ => {}
243        }
244        Self {
245            id: value.id,
246            z_index: value.z_index,
247            bounding_box: bb,
248            config,
249            effects: value.effects.clone(),
250            shape_rotation: value.shape_rotation,
251        }
252    }
253}