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}