Skip to main content

ply_engine/
render_commands.rs

1use crate::{color::Color, engine::{self, ShapeRotationConfig, VisualRotationConfig}, math::BoundingBox, elements::BorderPosition, 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    /// The position of the border relative to the bounding box.
67    pub position: BorderPosition,
68}
69
70/// Represents an image with defined dimensions and data.
71#[derive(Debug, Clone)]
72pub struct Image {
73    /// Background color
74    pub background_color: Color,
75    /// The corner radii for rounded border edges.
76    pub corner_radii: CornerRadii,
77    /// A reference to the image source data.
78    pub data: ImageSource,
79}
80
81/// Represents a custom element with a background color, corner radii, and associated data.
82#[derive(Debug, Clone)]
83pub struct Custom<CustomElementData> {
84    /// The background color of the custom element.
85    pub background_color: Color,
86    /// The corner radii for rounded edges.
87    pub corner_radii: CornerRadii,
88    /// The custom element data.
89    pub data: CustomElementData,
90}
91
92impl CornerRadii {
93    pub fn clamp_to_size(&mut self, width: f32, height: f32) {
94        let max_r = width.min(height) / 2.0;
95        self.top_left = self.top_left.clamp(0.0, max_r);
96        self.top_right = self.top_right.clamp(0.0, max_r);
97        self.bottom_left = self.bottom_left.clamp(0.0, max_r);
98        self.bottom_right = self.bottom_right.clamp(0.0, max_r);
99    }
100}
101
102impl From<crate::layout::CornerRadius> for CornerRadii {
103    fn from(value: crate::layout::CornerRadius) -> Self {
104        Self {
105            top_left: value.top_left,
106            top_right: value.top_right,
107            bottom_left: value.bottom_left,
108            bottom_right: value.bottom_right,
109        }
110    }
111}
112
113#[derive(Debug, Clone)]
114pub enum RenderCommandConfig<CustomElementData> {
115    None(),
116    Rectangle(Rectangle),
117    Border(Border),
118    Text(Text),
119    Image(Image),
120    ScissorStart(),
121    ScissorEnd(),
122    Custom(Custom<CustomElementData>),
123    /// Begin a group: Renders children to an offscreen buffer.
124    /// Optionally applies a fragment shader and/or visual rotation.
125    GroupBegin {
126        /// Fragment shader to apply as post-process.
127        shader: Option<ShaderConfig>,
128        /// Visual rotation applied when compositing the render target.
129        visual_rotation: Option<VisualRotationConfig>,
130    },
131    GroupEnd,
132}
133
134impl<CustomElementData: Clone + Default + std::fmt::Debug>
135    RenderCommandConfig<CustomElementData>
136{
137    pub(crate) fn from_engine_render_command(value: &engine::InternalRenderCommand<CustomElementData>) -> Self {
138        match value.command_type {
139            engine::RenderCommandType::None => Self::None(),
140            engine::RenderCommandType::Rectangle => {
141                if let engine::InternalRenderData::Rectangle { background_color, corner_radius } = &value.render_data {
142                    Self::Rectangle(Rectangle {
143                        color: *background_color,
144                        corner_radii: (*corner_radius).into(),
145                    })
146                } else {
147                    Self::None()
148                }
149            }
150            engine::RenderCommandType::Text => {
151                if let engine::InternalRenderData::Text { text, text_color, font_size, letter_spacing, line_height, font_asset } = &value.render_data {
152                    Self::Text(Text {
153                        text: text.clone(),
154                        color: *text_color,
155                        font_size: *font_size,
156                        letter_spacing: *letter_spacing,
157                        line_height: *line_height,
158                        font_asset: *font_asset,
159                    })
160                } else {
161                    Self::None()
162                }
163            }
164            engine::RenderCommandType::Border => {
165                if let engine::InternalRenderData::Border { color, corner_radius, width, position } = &value.render_data {
166                    Self::Border(Border {
167                        color: *color,
168                        corner_radii: (*corner_radius).into(),
169                        width: BorderWidth {
170                            left: width.left,
171                            right: width.right,
172                            top: width.top,
173                            bottom: width.bottom,
174                            between_children: width.between_children,
175                        },
176                        position: *position,
177                    })
178                } else {
179                    Self::None()
180                }
181            }
182            engine::RenderCommandType::Image => {
183                if let engine::InternalRenderData::Image { background_color, corner_radius, image_data } = &value.render_data {
184                    Self::Image(Image {
185                        data: image_data.clone(),
186                        corner_radii: (*corner_radius).into(),
187                        background_color: *background_color,
188                    })
189                } else {
190                    Self::None()
191                }
192            }
193            engine::RenderCommandType::ScissorStart => Self::ScissorStart(),
194            engine::RenderCommandType::ScissorEnd => Self::ScissorEnd(),
195            engine::RenderCommandType::GroupBegin => {
196                // GroupBegin uses the first effect from the render command as its shader config,
197                // and carries the visual_rotation from the render command.
198                let shader = value.effects.first().cloned();
199                let visual_rotation = value.visual_rotation;
200                Self::GroupBegin { shader, visual_rotation }
201            }
202            engine::RenderCommandType::GroupEnd => Self::GroupEnd,
203            engine::RenderCommandType::Custom => {
204                if let engine::InternalRenderData::Custom { background_color, corner_radius, custom_data } = &value.render_data {
205                    Self::Custom(Custom {
206                        background_color: *background_color,
207                        corner_radii: (*corner_radius).into(),
208                        data: custom_data.clone(),
209                    })
210                } else {
211                    Self::None()
212                }
213            }
214        }
215    }
216}
217
218/// Represents a render command for drawing an element on the screen.
219#[derive(Debug, Clone)]
220pub struct RenderCommand<CustomElementData> {
221    /// The bounding box defining the area occupied by the element.
222    pub bounding_box: BoundingBox,
223    /// The specific configuration for rendering this command.
224    pub config: RenderCommandConfig<CustomElementData>,
225    /// A unique identifier for the render command.
226    pub id: u32,
227    /// The z-index determines the stacking order of elements.
228    /// Higher values are drawn above lower values.
229    pub z_index: i16,
230    /// Per-element shader effects (chained in order).
231    pub effects: Vec<ShaderConfig>,
232    /// Shape rotation applied at the vertex level (only for Rectangle / Image / Custom / Border).
233    pub shape_rotation: Option<ShapeRotationConfig>,
234}
235
236impl<CustomElementData: Clone + Default + std::fmt::Debug> RenderCommand<CustomElementData> {
237    pub(crate) fn from_engine_render_command(value: &engine::InternalRenderCommand<CustomElementData>) -> Self {
238        let mut config = RenderCommandConfig::from_engine_render_command(value);
239        let bb = value.bounding_box;
240        match &mut config {
241            RenderCommandConfig::Rectangle(r)  => r.corner_radii.clamp_to_size(bb.width, bb.height),
242            RenderCommandConfig::Border(b)     => b.corner_radii.clamp_to_size(bb.width, bb.height),
243            RenderCommandConfig::Image(i)      => i.corner_radii.clamp_to_size(bb.width, bb.height),
244            RenderCommandConfig::Custom(c)     => c.corner_radii.clamp_to_size(bb.width, bb.height),
245            _ => {}
246        }
247        Self {
248            id: value.id,
249            z_index: value.z_index,
250            bounding_box: bb,
251            config,
252            effects: value.effects.clone(),
253            shape_rotation: value.shape_rotation,
254        }
255    }
256}