tessera_ui_basic_components/pipelines/shape/
command.rs

1use tessera_ui::{Color, DrawCommand, PxPosition, PxSize};
2
3use super::{ShapeUniforms, ShapeVertex};
4
5/// Represents a shape drawable
6#[derive(Debug, Clone)]
7pub enum ShapeCommand {
8    /// A filled rectangle
9    Rect {
10        /// Color of the rectangle (RGBA)
11        color: Color,
12        /// Corner radius of the rectangle
13        corner_radius: f32,
14        /// G2 exponent for rounded corners.
15        /// k=2.0 results in standard G1 circular corners.
16        g2_k_value: f32,
17        /// Shadow properties of the rectangle
18        shadow: Option<ShadowProps>,
19    },
20    /// An outlined rectangle
21    OutlinedRect {
22        /// Color of the border (RGBA)
23        color: Color,
24        /// Corner radius of the rectangle
25        corner_radius: f32,
26        /// G2 exponent for rounded corners.
27        /// k=2.0 results in standard G1 circular corners.
28        g2_k_value: f32,
29        /// Shadow properties of the rectangle (applied to the outline shape)
30        shadow: Option<ShadowProps>,
31        /// Width of the border
32        border_width: f32,
33    },
34    /// A filled rectangle with ripple effect animation
35    RippleRect {
36        /// Color of the rectangle (RGBA)
37        color: Color,
38        /// Corner radius of the rectangle
39        corner_radius: f32,
40        /// G2 exponent for rounded corners.
41        /// k=2.0 results in standard G1 circular corners.
42        g2_k_value: f32,
43        /// Shadow properties of the rectangle
44        shadow: Option<ShadowProps>,
45        /// Ripple effect properties
46        ripple: RippleProps,
47    },
48    /// An outlined rectangle with ripple effect animation
49    RippleOutlinedRect {
50        /// Color of the border (RGBA)
51        color: Color,
52        /// Corner radius of the rectangle
53        corner_radius: f32,
54        /// G2 exponent for rounded corners.
55        /// k=2.0 results in standard G1 circular corners.
56        g2_k_value: f32,
57        /// Shadow properties of the rectangle (applied to the outline shape)
58        shadow: Option<ShadowProps>,
59        /// Width of the border
60        border_width: f32,
61        /// Ripple effect properties
62        ripple: RippleProps,
63    },
64    /// A filled ellipse
65    Ellipse {
66        /// Color of the ellipse (RGBA)
67        color: Color,
68        /// Shadow properties of the ellipse
69        shadow: Option<ShadowProps>,
70    },
71    /// An outlined ellipse
72    OutlinedEllipse {
73        /// Color of the border (RGBA)
74        color: Color,
75        /// Shadow properties of the ellipse (applied to the outline shape)
76        shadow: Option<ShadowProps>,
77        /// Width of the border
78        border_width: f32,
79    },
80}
81
82impl DrawCommand for ShapeCommand {
83    fn barrier(&self) -> Option<tessera_ui::BarrierRequirement> {
84        // No specific barrier requirements for shape commands
85        None
86    }
87}
88
89/// Properties for shadow, used in BasicDrawable variants
90#[derive(Debug, Clone, Copy, PartialEq)]
91pub struct ShadowProps {
92    /// Color of the shadow (RGBA)
93    pub color: Color,
94    /// Offset of the shadow in the format [x, y]
95    pub offset: [f32; 2],
96    /// Smoothness of the shadow, typically a value between 0.0 and 1.0
97    pub smoothness: f32,
98}
99
100impl Default for ShadowProps {
101    fn default() -> Self {
102        Self {
103            color: Color::BLACK.with_alpha(0.25),
104            offset: [0.0, 2.0],
105            smoothness: 4.0,
106        }
107    }
108}
109
110/// Properties for ripple effect animation
111#[derive(Debug, Clone, Copy, PartialEq)]
112pub struct RippleProps {
113    /// Center position of the ripple in normalized coordinates [-0.5, 0.5]
114    pub center: [f32; 2],
115    /// Current radius of the ripple (0.0 to 1.0, where 1.0 covers the entire shape)
116    pub radius: f32,
117    /// Alpha value for the ripple effect (0.0 to 1.0)
118    pub alpha: f32,
119    /// Color of the ripple effect (RGB)
120    pub color: Color,
121}
122
123impl Default for RippleProps {
124    fn default() -> Self {
125        Self {
126            center: [0.0, 0.0],
127            radius: 0.0,
128            alpha: 0.0,
129            color: Color::WHITE,
130        }
131    }
132}
133
134pub struct ShapeCommandComputed {
135    pub(crate) vertices: Vec<ShapeVertex>,
136    pub(crate) uniforms: ShapeUniforms,
137}
138
139impl ShapeCommandComputed {
140    pub fn from_command(command: ShapeCommand, size: PxSize, position: PxPosition) -> Self {
141        match command {
142            ShapeCommand::Rect {
143                color,
144                corner_radius,
145                g2_k_value,
146                shadow,
147            } => rect_to_computed_draw_command(
148                size,
149                position,
150                color, // RGBA
151                corner_radius,
152                g2_k_value,
153                shadow,
154                0.0, // border_width for fill is 0
155                0.0, // render_mode for fill is 0.0
156            ),
157            ShapeCommand::OutlinedRect {
158                color,
159                corner_radius,
160                g2_k_value,
161                shadow,
162                border_width,
163            } => rect_to_computed_draw_command(
164                size,
165                position,
166                color, // RGBA, This color is for the border
167                corner_radius,
168                g2_k_value,
169                shadow,
170                border_width,
171                1.0, // render_mode for outline is 1.0
172            ),
173            ShapeCommand::RippleRect {
174                color,
175                corner_radius,
176                g2_k_value,
177                shadow,
178                ripple,
179            } => ripple_rect_to_computed_draw_command(
180                size,
181                position,
182                color,
183                corner_radius,
184                g2_k_value,
185                shadow,
186                0.0, // border_width for fill is 0
187                0.0, // render_mode for fill is 0.0
188                ripple,
189            ),
190            ShapeCommand::RippleOutlinedRect {
191                color,
192                corner_radius,
193                g2_k_value,
194                shadow,
195                border_width,
196                ripple,
197            } => ripple_rect_to_computed_draw_command(
198                size,
199                position,
200                color,
201                corner_radius,
202                g2_k_value,
203                shadow,
204                border_width,
205                1.0, // render_mode for outline is 1.0
206                ripple,
207            ),
208            ShapeCommand::Ellipse { color, shadow } => rect_to_computed_draw_command(
209                size, position, color,
210                -1.0, // Use negative corner_radius to signify an ellipse to the shader
211                0.0, shadow, 0.0, // border_width for fill is 0
212                0.0, // render_mode for fill
213            ),
214            ShapeCommand::OutlinedEllipse {
215                color,
216                shadow,
217                border_width,
218            } => rect_to_computed_draw_command(
219                size,
220                position,
221                color,
222                -1.0, // Use negative corner_radius to signify an ellipse to the shader
223                0.0,
224                shadow,
225                border_width,
226                1.0, // render_mode for outline
227            ),
228        }
229    }
230}
231
232/// Helper function to create Shape DrawCommand for both Rect and OutlinedRect
233fn rect_to_computed_draw_command(
234    size: PxSize,
235    position: PxPosition,
236    primary_color_rgba: Color,
237    corner_radius: f32,
238    g2_k_value: f32,
239    shadow: Option<ShadowProps>,
240    border_width: f32,
241    render_mode: f32,
242) -> ShapeCommandComputed {
243    let width = size.width;
244    let height = size.height;
245
246    let rect_local_pos = [
247        [-0.5, -0.5], // Top-Left
248        [0.5, -0.5],  // Top-Right
249        [0.5, 0.5],   // Bottom-Right
250        [-0.5, 0.5],  // Bottom-Left
251    ];
252
253    let vertex_color_placeholder_rgb = [0.0, 0.0, 0.0];
254    let top_left = position.to_f32_arr3();
255    let top_right = [top_left[0] + width.to_f32(), top_left[1], top_left[2]];
256    let bottom_right = [
257        top_left[0] + width.to_f32(),
258        top_left[1] + height.to_f32(),
259        top_left[2],
260    ];
261    let bottom_left = [top_left[0], top_left[1] + height.to_f32(), top_left[2]];
262
263    let vertices = vec![
264        ShapeVertex {
265            position: top_left,
266            color: vertex_color_placeholder_rgb,
267            local_pos: rect_local_pos[0],
268        },
269        ShapeVertex {
270            position: top_right,
271            color: vertex_color_placeholder_rgb,
272            local_pos: rect_local_pos[1],
273        },
274        ShapeVertex {
275            position: bottom_right,
276            color: vertex_color_placeholder_rgb,
277            local_pos: rect_local_pos[2],
278        },
279        ShapeVertex {
280            position: bottom_left,
281            color: vertex_color_placeholder_rgb,
282            local_pos: rect_local_pos[3],
283        },
284    ];
285
286    let (shadow_rgba_color, shadow_offset_vec, shadow_smooth_val) = if let Some(s_props) = shadow {
287        (s_props.color, s_props.offset, s_props.smoothness)
288    } else {
289        (Color::TRANSPARENT, [0.0, 0.0], 0.0)
290    };
291
292    let uniforms = ShapeUniforms {
293        size_cr_border_width: [width.to_f32(), height.to_f32(), corner_radius, border_width].into(),
294        primary_color: primary_color_rgba.to_array().into(),
295        shadow_color: shadow_rgba_color.to_array().into(),
296        render_params: [
297            shadow_offset_vec[0],
298            shadow_offset_vec[1],
299            shadow_smooth_val,
300            render_mode,
301        ]
302        .into(),
303        ripple_params: [0.0, 0.0, 0.0, 0.0].into(),
304        ripple_color: [0.0, 0.0, 0.0, 0.0].into(),
305        g2_k_value,
306    };
307
308    ShapeCommandComputed { vertices, uniforms }
309}
310
311/// Helper function to create Shape DrawCommand for ripple effects
312fn ripple_rect_to_computed_draw_command(
313    size: PxSize,
314    position: PxPosition,
315    primary_color_rgba: Color,
316    corner_radius: f32,
317    g2_k_value: f32,
318    shadow: Option<ShadowProps>,
319    border_width: f32,
320    render_mode: f32,
321    ripple: RippleProps,
322) -> ShapeCommandComputed {
323    let width = size.width;
324    let height = size.height;
325
326    let rect_local_pos = [
327        [-0.5, -0.5], // Top-Left
328        [0.5, -0.5],  // Top-Right
329        [0.5, 0.5],   // Bottom-Right
330        [-0.5, 0.5],  // Bottom-Left
331    ];
332
333    let vertex_color_placeholder_rgb = [0.0, 0.0, 0.0];
334    let top_left = position.to_f32_arr3();
335    let top_right = [top_left[0] + width.to_f32(), top_left[1], top_left[2]];
336    let bottom_right = [
337        top_left[0] + width.to_f32(),
338        top_left[1] + height.to_f32(),
339        top_left[2],
340    ];
341    let bottom_left = [top_left[0], top_left[1] + height.to_f32(), top_left[2]];
342
343    let vertices = vec![
344        ShapeVertex {
345            position: top_left,
346            color: vertex_color_placeholder_rgb,
347            local_pos: rect_local_pos[0],
348        },
349        ShapeVertex {
350            position: top_right,
351            color: vertex_color_placeholder_rgb,
352            local_pos: rect_local_pos[1],
353        },
354        ShapeVertex {
355            position: bottom_right,
356            color: vertex_color_placeholder_rgb,
357            local_pos: rect_local_pos[2],
358        },
359        ShapeVertex {
360            position: bottom_left,
361            color: vertex_color_placeholder_rgb,
362            local_pos: rect_local_pos[3],
363        },
364    ];
365
366    let (shadow_rgba_color, shadow_offset_vec, shadow_smooth_val) = if let Some(s_props) = shadow {
367        (s_props.color.into(), s_props.offset, s_props.smoothness)
368    } else {
369        ([0.0, 0.0, 0.0, 0.0], [0.0, 0.0], 0.0)
370    };
371
372    let ripple_render_mode = if render_mode == 0.0 { 3.0 } else { 4.0 };
373
374    let uniforms = ShapeUniforms {
375        size_cr_border_width: [width.to_f32(), height.to_f32(), corner_radius, border_width].into(),
376        primary_color: primary_color_rgba.to_array().into(),
377        shadow_color: shadow_rgba_color.into(),
378        render_params: [
379            shadow_offset_vec[0],
380            shadow_offset_vec[1],
381            shadow_smooth_val,
382            ripple_render_mode,
383        ]
384        .into(),
385        ripple_params: [
386            ripple.center[0],
387            ripple.center[1],
388            ripple.radius,
389            ripple.alpha,
390        ]
391        .into(),
392        ripple_color: [ripple.color.r, ripple.color.g, ripple.color.b, 0.0].into(),
393        g2_k_value,
394    };
395
396    ShapeCommandComputed { vertices, uniforms }
397}