1use glam::{Vec2, Vec4};
2use tessera_ui::{Color, DrawCommand, DrawRegion, PaddingRect, Px, PxPosition, PxSize};
3
4use super::pipeline::ShapeUniforms;
5
6const SHADOW_AA_MARGIN_PX: f32 = 1.0;
7
8pub(crate) fn shadow_padding_xy(shadow: &ShadowLayers) -> (Px, Px) {
9 let mut pad_x = 0.0f32;
10 let mut pad_y = 0.0f32;
11
12 let update = |pad_x: &mut f32, pad_y: &mut f32, layer: &ShadowLayer| {
13 if layer.color.a <= 0.0 {
14 return;
15 }
16 let layer_pad_x = (layer.smoothness + layer.offset[0].abs() + SHADOW_AA_MARGIN_PX).max(0.0);
17 let layer_pad_y = (layer.smoothness + layer.offset[1].abs() + SHADOW_AA_MARGIN_PX).max(0.0);
18 *pad_x = (*pad_x).max(layer_pad_x);
19 *pad_y = (*pad_y).max(layer_pad_y);
20 };
21
22 if let Some(layer) = shadow.ambient {
23 update(&mut pad_x, &mut pad_y, &layer);
24 }
25 if let Some(layer) = shadow.spot {
26 update(&mut pad_x, &mut pad_y, &layer);
27 }
28
29 (
30 Px::new(pad_x.ceil() as i32).max(Px::ZERO),
31 Px::new(pad_y.ceil() as i32).max(Px::ZERO),
32 )
33}
34
35#[derive(Debug, Clone, Copy, PartialEq)]
37pub struct ShadowLayer {
38 pub color: Color,
40 pub offset: [f32; 2],
42 pub smoothness: f32,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Default)]
48pub struct ShadowLayers {
49 pub ambient: Option<ShadowLayer>,
51 pub spot: Option<ShadowLayer>,
53}
54
55#[derive(Debug, Clone, PartialEq)]
57pub enum ShapeCommand {
58 Rect {
60 color: Color,
62 corner_radii: [f32; 4],
64 corner_g2: [f32; 4],
67 shadow: Option<ShadowLayers>,
69 },
70 OutlinedRect {
72 color: Color,
74 corner_radii: [f32; 4],
76 corner_g2: [f32; 4],
79 shadow: Option<ShadowLayers>,
81 border_width: f32,
83 },
84 RippleRect {
86 color: Color,
88 corner_radii: [f32; 4],
90 corner_g2: [f32; 4],
93 shadow: Option<ShadowLayers>,
95 ripple: RippleProps,
97 },
98 RippleOutlinedRect {
100 color: Color,
102 corner_radii: [f32; 4],
104 corner_g2: [f32; 4],
107 shadow: Option<ShadowLayers>,
109 border_width: f32,
111 ripple: RippleProps,
113 },
114 Ellipse {
116 color: Color,
118 shadow: Option<ShadowLayers>,
120 },
121 OutlinedEllipse {
123 color: Color,
125 shadow: Option<ShadowLayers>,
127 border_width: f32,
129 },
130 FilledOutlinedRect {
132 color: Color,
134 border_color: Color,
136 corner_radii: [f32; 4],
138 corner_g2: [f32; 4],
141 shadow: Option<ShadowLayers>,
143 border_width: f32,
145 },
146 RippleFilledOutlinedRect {
148 color: Color,
150 border_color: Color,
152 corner_radii: [f32; 4],
154 corner_g2: [f32; 4],
157 shadow: Option<ShadowLayers>,
159 border_width: f32,
161 ripple: RippleProps,
163 },
164 FilledOutlinedEllipse {
166 color: Color,
168 border_color: Color,
170 shadow: Option<ShadowLayers>,
172 border_width: f32,
174 },
175}
176
177impl ShapeCommand {
178 pub(crate) fn shadow(&self) -> Option<&ShadowLayers> {
179 match self {
180 ShapeCommand::Rect { shadow, .. }
181 | ShapeCommand::OutlinedRect { shadow, .. }
182 | ShapeCommand::RippleRect { shadow, .. }
183 | ShapeCommand::RippleOutlinedRect { shadow, .. }
184 | ShapeCommand::Ellipse { shadow, .. }
185 | ShapeCommand::OutlinedEllipse { shadow, .. }
186 | ShapeCommand::FilledOutlinedRect { shadow, .. }
187 | ShapeCommand::RippleFilledOutlinedRect { shadow, .. }
188 | ShapeCommand::FilledOutlinedEllipse { shadow, .. } => shadow.as_ref(),
189 }
190 }
191}
192
193impl DrawCommand for ShapeCommand {
194 fn sample_region(&self) -> Option<tessera_ui::SampleRegion> {
195 None
197 }
198
199 fn apply_opacity(&mut self, opacity: f32) {
200 fn scale_color(color: &mut Color, factor: f32) {
201 *color = color.with_alpha(color.a * factor);
202 }
203
204 fn scale_shadow(shadow: &mut Option<ShadowLayers>, factor: f32) {
205 if let Some(layers) = shadow {
206 if let Some(ref mut ambient) = layers.ambient {
207 scale_color(&mut ambient.color, factor);
208 }
209 if let Some(ref mut spot) = layers.spot {
210 scale_color(&mut spot.color, factor);
211 }
212 }
213 }
214
215 let factor = opacity.clamp(0.0, 1.0);
216 match self {
217 ShapeCommand::Rect { color, shadow, .. } => {
218 scale_color(color, factor);
219 scale_shadow(shadow, factor);
220 }
221 ShapeCommand::OutlinedRect { color, shadow, .. } => {
222 scale_color(color, factor);
223 scale_shadow(shadow, factor);
224 }
225 ShapeCommand::RippleRect {
226 color,
227 shadow,
228 ripple,
229 ..
230 } => {
231 scale_color(color, factor);
232 scale_shadow(shadow, factor);
233 ripple.alpha *= factor;
234 }
235 ShapeCommand::RippleOutlinedRect {
236 color,
237 shadow,
238 ripple,
239 ..
240 } => {
241 scale_color(color, factor);
242 scale_shadow(shadow, factor);
243 ripple.alpha *= factor;
244 }
245 ShapeCommand::Ellipse { color, shadow } => {
246 scale_color(color, factor);
247 scale_shadow(shadow, factor);
248 }
249 ShapeCommand::OutlinedEllipse { color, shadow, .. } => {
250 scale_color(color, factor);
251 scale_shadow(shadow, factor);
252 }
253 ShapeCommand::FilledOutlinedRect {
254 color,
255 border_color,
256 shadow,
257 ..
258 } => {
259 scale_color(color, factor);
260 scale_color(border_color, factor);
261 scale_shadow(shadow, factor);
262 }
263 ShapeCommand::RippleFilledOutlinedRect {
264 color,
265 border_color,
266 shadow,
267 ripple,
268 ..
269 } => {
270 scale_color(color, factor);
271 scale_color(border_color, factor);
272 scale_shadow(shadow, factor);
273 ripple.alpha *= factor;
274 }
275 ShapeCommand::FilledOutlinedEllipse {
276 color,
277 border_color,
278 shadow,
279 ..
280 } => {
281 scale_color(color, factor);
282 scale_color(border_color, factor);
283 scale_shadow(shadow, factor);
284 }
285 }
286 }
287
288 fn draw_region(&self) -> DrawRegion {
289 let Some(layers) = self.shadow() else {
290 return DrawRegion::PaddedLocal(PaddingRect::ZERO);
291 };
292
293 let (pad_x, pad_y) = shadow_padding_xy(layers);
294 DrawRegion::PaddedLocal(PaddingRect {
295 top: pad_y,
296 right: pad_x,
297 bottom: pad_y,
298 left: pad_x,
299 })
300 }
301}
302
303#[derive(Debug, Clone, Copy, PartialEq)]
305pub struct RippleProps {
306 pub center: [f32; 2],
308 pub bounded: bool,
313 pub radius: f32,
316 pub alpha: f32,
318 pub color: Color,
320}
321
322impl Default for RippleProps {
323 fn default() -> Self {
324 Self {
325 center: [0.0, 0.0],
326 bounded: true,
327 radius: 0.0,
328 alpha: 0.0,
329 color: Color::WHITE,
330 }
331 }
332}
333
334pub(crate) fn rect_to_uniforms(
335 command: &ShapeCommand,
336 size: PxSize,
337 position: PxPosition,
338) -> ShapeUniforms {
339 let (
340 primary_color_rgba,
341 border_color_rgba,
342 corner_radii,
343 corner_g2,
344 shadow,
345 border_width,
346 render_mode,
347 ripple,
348 ) = match command {
349 ShapeCommand::Rect {
350 color,
351 corner_radii,
352 corner_g2,
353 shadow,
354 } => (
355 *color,
356 Color::TRANSPARENT,
357 *corner_radii,
358 *corner_g2,
359 *shadow,
360 0.0,
361 0.0,
362 None,
363 ),
364 ShapeCommand::OutlinedRect {
365 color,
366 corner_radii,
367 corner_g2,
368 shadow,
369 border_width,
370 } => (
371 *color,
372 Color::TRANSPARENT,
373 *corner_radii,
374 *corner_g2,
375 *shadow,
376 *border_width,
377 1.0,
378 None,
379 ),
380 ShapeCommand::RippleRect {
381 color,
382 corner_radii,
383 corner_g2,
384 shadow,
385 ripple,
386 } => (
387 *color,
388 Color::TRANSPARENT,
389 *corner_radii,
390 *corner_g2,
391 *shadow,
392 0.0,
393 3.0,
394 Some(*ripple),
395 ),
396 ShapeCommand::RippleOutlinedRect {
397 color,
398 corner_radii,
399 corner_g2,
400 shadow,
401 border_width,
402 ripple,
403 } => (
404 *color,
405 Color::TRANSPARENT,
406 *corner_radii,
407 *corner_g2,
408 *shadow,
409 *border_width,
410 4.0,
411 Some(*ripple),
412 ),
413 ShapeCommand::Ellipse { color, shadow } => (
414 *color,
415 Color::TRANSPARENT,
416 [-1.0, -1.0, -1.0, -1.0],
417 [0.0; 4],
418 *shadow,
419 0.0,
420 0.0,
421 None,
422 ),
423 ShapeCommand::OutlinedEllipse {
424 color,
425 shadow,
426 border_width,
427 } => (
428 *color,
429 Color::TRANSPARENT,
430 [-1.0, -1.0, -1.0, -1.0],
431 [0.0; 4],
432 *shadow,
433 *border_width,
434 1.0,
435 None,
436 ),
437 ShapeCommand::FilledOutlinedRect {
438 color,
439 border_color,
440 corner_radii,
441 corner_g2,
442 shadow,
443 border_width,
444 } => (
445 *color,
446 *border_color,
447 *corner_radii,
448 *corner_g2,
449 *shadow,
450 *border_width,
451 5.0,
452 None,
453 ),
454 ShapeCommand::RippleFilledOutlinedRect {
455 color,
456 border_color,
457 corner_radii,
458 corner_g2,
459 shadow,
460 border_width,
461 ripple,
462 } => (
463 *color,
464 *border_color,
465 *corner_radii,
466 *corner_g2,
467 *shadow,
468 *border_width,
469 5.0,
470 Some(*ripple),
471 ),
472 ShapeCommand::FilledOutlinedEllipse {
473 color,
474 border_color,
475 shadow,
476 border_width,
477 } => (
478 *color,
479 *border_color,
480 [-1.0, -1.0, -1.0, -1.0],
481 [0.0; 4],
482 *shadow,
483 *border_width,
484 5.0,
485 None,
486 ),
487 };
488
489 let width = size.width;
490 let height = size.height;
491
492 let (ambient_color, ambient_offset, ambient_smooth) = if let Some(layers) = shadow {
493 if let Some(a) = layers.ambient {
494 (a.color, a.offset, a.smoothness)
495 } else {
496 (Color::TRANSPARENT, [0.0, 0.0], 0.0)
497 }
498 } else {
499 (Color::TRANSPARENT, [0.0, 0.0], 0.0)
500 };
501
502 let (spot_color, spot_offset, spot_smooth) = if let Some(layers) = shadow {
503 if let Some(s) = layers.spot {
504 (s.color, s.offset, s.smoothness)
505 } else {
506 (Color::TRANSPARENT, [0.0, 0.0], 0.0)
507 }
508 } else {
509 (Color::TRANSPARENT, [0.0, 0.0], 0.0)
510 };
511
512 let (ripple_params, ripple_color) = if let Some(r_props) = ripple {
513 let bounded_flag = if r_props.bounded { 1.0 } else { 0.0 };
514 (
515 Vec4::new(
516 r_props.center[0],
517 r_props.center[1],
518 r_props.radius,
519 r_props.alpha,
520 ),
521 Vec4::new(
522 r_props.color.r,
523 r_props.color.g,
524 r_props.color.b,
525 bounded_flag,
526 ),
527 )
528 } else {
529 (Vec4::ZERO, Vec4::ZERO)
530 };
531
532 ShapeUniforms {
533 corner_radii: corner_radii.into(),
534 corner_g2: corner_g2.into(),
535 primary_color: primary_color_rgba.to_array().into(),
536 border_color: border_color_rgba.to_array().into(),
537 shadow_ambient_color: ambient_color.to_array().into(),
538 shadow_ambient_params: [ambient_offset[0], ambient_offset[1], ambient_smooth].into(),
539 shadow_spot_color: spot_color.to_array().into(),
540 shadow_spot_params: [spot_offset[0], spot_offset[1], spot_smooth].into(),
541 render_mode,
542 ripple_params,
543 ripple_color,
544 border_width,
545 position: [
546 position.x.to_f32(),
547 position.y.to_f32(),
548 width.to_f32(),
549 height.to_f32(),
550 ]
551 .into(),
552 screen_size: Vec2::ZERO, }
554}