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
100/// Properties for ripple effect animation
101#[derive(Debug, Clone, Copy, PartialEq)]
102pub struct RippleProps {
103    /// Center position of the ripple in normalized coordinates [-0.5, 0.5]
104    pub center: [f32; 2],
105    /// Current radius of the ripple (0.0 to 1.0, where 1.0 covers the entire shape)
106    pub radius: f32,
107    /// Alpha value for the ripple effect (0.0 to 1.0)
108    pub alpha: f32,
109    /// Color of the ripple effect (RGB)
110    pub color: Color,
111}
112
113impl Default for RippleProps {
114    fn default() -> Self {
115        Self {
116            center: [0.0, 0.0],
117            radius: 0.0,
118            alpha: 0.0,
119            color: Color::WHITE,
120        }
121    }
122}
123
124pub struct ShapeCommandComputed {
125    pub(crate) vertices: Vec<ShapeVertex>,
126    pub(crate) uniforms: ShapeUniforms,
127}
128
129impl ShapeCommandComputed {
130    pub fn from_command(command: ShapeCommand, size: PxSize, position: PxPosition) -> Self {
131        match command {
132            ShapeCommand::Rect {
133                color,
134                corner_radius,
135                g2_k_value,
136                shadow,
137            } => rect_to_computed_draw_command(
138                size,
139                position,
140                color, // RGBA
141                corner_radius,
142                g2_k_value,
143                shadow,
144                0.0, // border_width for fill is 0
145                0.0, // render_mode for fill is 0.0
146            ),
147            ShapeCommand::OutlinedRect {
148                color,
149                corner_radius,
150                g2_k_value,
151                shadow,
152                border_width,
153            } => rect_to_computed_draw_command(
154                size,
155                position,
156                color, // RGBA, This color is for the border
157                corner_radius,
158                g2_k_value,
159                shadow,
160                border_width,
161                1.0, // render_mode for outline is 1.0
162            ),
163            ShapeCommand::RippleRect {
164                color,
165                corner_radius,
166                g2_k_value,
167                shadow,
168                ripple,
169            } => ripple_rect_to_computed_draw_command(
170                size,
171                position,
172                color,
173                corner_radius,
174                g2_k_value,
175                shadow,
176                0.0, // border_width for fill is 0
177                0.0, // render_mode for fill is 0.0
178                ripple,
179            ),
180            ShapeCommand::RippleOutlinedRect {
181                color,
182                corner_radius,
183                g2_k_value,
184                shadow,
185                border_width,
186                ripple,
187            } => ripple_rect_to_computed_draw_command(
188                size,
189                position,
190                color,
191                corner_radius,
192                g2_k_value,
193                shadow,
194                border_width,
195                1.0, // render_mode for outline is 1.0
196                ripple,
197            ),
198            ShapeCommand::Ellipse { color, shadow } => rect_to_computed_draw_command(
199                size, position, color,
200                -1.0, // Use negative corner_radius to signify an ellipse to the shader
201                0.0, shadow, 0.0, // border_width for fill is 0
202                0.0, // render_mode for fill
203            ),
204            ShapeCommand::OutlinedEllipse {
205                color,
206                shadow,
207                border_width,
208            } => rect_to_computed_draw_command(
209                size,
210                position,
211                color,
212                -1.0, // Use negative corner_radius to signify an ellipse to the shader
213                0.0,
214                shadow,
215                border_width,
216                1.0, // render_mode for outline
217            ),
218        }
219    }
220}
221
222/// Helper function to create Shape DrawCommand for both Rect and OutlinedRect
223fn rect_to_computed_draw_command(
224    size: PxSize,
225    position: PxPosition,
226    primary_color_rgba: Color,
227    corner_radius: f32,
228    g2_k_value: f32,
229    shadow: Option<ShadowProps>,
230    border_width: f32,
231    render_mode: f32,
232) -> ShapeCommandComputed {
233    let width = size.width;
234    let height = size.height;
235
236    let rect_local_pos = [
237        [-0.5, -0.5], // Top-Left
238        [0.5, -0.5],  // Top-Right
239        [0.5, 0.5],   // Bottom-Right
240        [-0.5, 0.5],  // Bottom-Left
241    ];
242
243    let vertex_color_placeholder_rgb = [0.0, 0.0, 0.0];
244    let top_left = position.to_f32_arr3();
245    let top_right = [top_left[0] + width.to_f32(), top_left[1], top_left[2]];
246    let bottom_right = [
247        top_left[0] + width.to_f32(),
248        top_left[1] + height.to_f32(),
249        top_left[2],
250    ];
251    let bottom_left = [top_left[0], top_left[1] + height.to_f32(), top_left[2]];
252
253    let vertices = vec![
254        ShapeVertex {
255            position: top_left,
256            color: vertex_color_placeholder_rgb,
257            local_pos: rect_local_pos[0],
258        },
259        ShapeVertex {
260            position: top_right,
261            color: vertex_color_placeholder_rgb,
262            local_pos: rect_local_pos[1],
263        },
264        ShapeVertex {
265            position: bottom_right,
266            color: vertex_color_placeholder_rgb,
267            local_pos: rect_local_pos[2],
268        },
269        ShapeVertex {
270            position: bottom_left,
271            color: vertex_color_placeholder_rgb,
272            local_pos: rect_local_pos[3],
273        },
274    ];
275
276    let (shadow_rgba_color, shadow_offset_vec, shadow_smooth_val) = if let Some(s_props) = shadow {
277        (s_props.color, s_props.offset, s_props.smoothness)
278    } else {
279        (Color::TRANSPARENT, [0.0, 0.0], 0.0)
280    };
281
282    let uniforms = ShapeUniforms {
283        size_cr_border_width: [width.to_f32(), height.to_f32(), corner_radius, border_width].into(),
284        primary_color: primary_color_rgba.to_array().into(),
285        shadow_color: shadow_rgba_color.to_array().into(),
286        render_params: [
287            shadow_offset_vec[0],
288            shadow_offset_vec[1],
289            shadow_smooth_val,
290            render_mode,
291        ]
292        .into(),
293        ripple_params: [0.0, 0.0, 0.0, 0.0].into(),
294        ripple_color: [0.0, 0.0, 0.0, 0.0].into(),
295        g2_k_value,
296    };
297
298    ShapeCommandComputed { vertices, uniforms }
299}
300
301/// Helper function to create Shape DrawCommand for ripple effects
302fn ripple_rect_to_computed_draw_command(
303    size: PxSize,
304    position: PxPosition,
305    primary_color_rgba: Color,
306    corner_radius: f32,
307    g2_k_value: f32,
308    shadow: Option<ShadowProps>,
309    border_width: f32,
310    render_mode: f32,
311    ripple: RippleProps,
312) -> ShapeCommandComputed {
313    let width = size.width;
314    let height = size.height;
315
316    let rect_local_pos = [
317        [-0.5, -0.5], // Top-Left
318        [0.5, -0.5],  // Top-Right
319        [0.5, 0.5],   // Bottom-Right
320        [-0.5, 0.5],  // Bottom-Left
321    ];
322
323    let vertex_color_placeholder_rgb = [0.0, 0.0, 0.0];
324    let top_left = position.to_f32_arr3();
325    let top_right = [top_left[0] + width.to_f32(), top_left[1], top_left[2]];
326    let bottom_right = [
327        top_left[0] + width.to_f32(),
328        top_left[1] + height.to_f32(),
329        top_left[2],
330    ];
331    let bottom_left = [top_left[0], top_left[1] + height.to_f32(), top_left[2]];
332
333    let vertices = vec![
334        ShapeVertex {
335            position: top_left,
336            color: vertex_color_placeholder_rgb,
337            local_pos: rect_local_pos[0],
338        },
339        ShapeVertex {
340            position: top_right,
341            color: vertex_color_placeholder_rgb,
342            local_pos: rect_local_pos[1],
343        },
344        ShapeVertex {
345            position: bottom_right,
346            color: vertex_color_placeholder_rgb,
347            local_pos: rect_local_pos[2],
348        },
349        ShapeVertex {
350            position: bottom_left,
351            color: vertex_color_placeholder_rgb,
352            local_pos: rect_local_pos[3],
353        },
354    ];
355
356    let (shadow_rgba_color, shadow_offset_vec, shadow_smooth_val) = if let Some(s_props) = shadow {
357        (s_props.color.into(), s_props.offset, s_props.smoothness)
358    } else {
359        ([0.0, 0.0, 0.0, 0.0], [0.0, 0.0], 0.0)
360    };
361
362    let ripple_render_mode = if render_mode == 0.0 { 3.0 } else { 4.0 };
363
364    let uniforms = ShapeUniforms {
365        size_cr_border_width: [width.to_f32(), height.to_f32(), corner_radius, border_width].into(),
366        primary_color: primary_color_rgba.to_array().into(),
367        shadow_color: shadow_rgba_color.into(),
368        render_params: [
369            shadow_offset_vec[0],
370            shadow_offset_vec[1],
371            shadow_smooth_val,
372            ripple_render_mode,
373        ]
374        .into(),
375        ripple_params: [
376            ripple.center[0],
377            ripple.center[1],
378            ripple.radius,
379            ripple.alpha,
380        ]
381        .into(),
382        ripple_color: [ripple.color.r, ripple.color.g, ripple.color.b, 0.0].into(),
383        g2_k_value,
384    };
385
386    ShapeCommandComputed { vertices, uniforms }
387}