tessera_ui_basic_components/pipelines/shape/
command.rs

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