1#![cfg_attr(windows, allow(dead_code))]
3
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7use crate::{
8 AtlasTextureId, AtlasTile, Background, Bounds, ContentMask, Corners, Edges, Hsla, Pixels,
9 Point, Radians, ScaledPixels, Size, bounds_tree::BoundsTree, point,
10};
11use std::{
12 fmt::Debug,
13 iter::Peekable,
14 ops::{Add, Range, Sub},
15 slice,
16};
17
18#[allow(non_camel_case_types, unused)]
19#[expect(missing_docs)]
20pub type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
21
22#[expect(missing_docs)]
23pub type DrawOrder = u32;
24
25#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
30#[repr(transparent)]
31pub struct PaddedBool32(u32);
32
33impl From<bool> for PaddedBool32 {
34 fn from(value: bool) -> Self {
35 PaddedBool32(value as u32)
36 }
37}
38
39#[derive(Default)]
40#[expect(missing_docs)]
41pub struct Scene {
42 pub(crate) paint_operations: Vec<PaintOperation>,
43 primitive_bounds: BoundsTree<ScaledPixels>,
44 layer_stack: Vec<DrawOrder>,
45 pub shadows: Vec<Shadow>,
46 pub quads: Vec<Quad>,
47 pub paths: Vec<Path<ScaledPixels>>,
48 pub underlines: Vec<Underline>,
49 pub monochrome_sprites: Vec<MonochromeSprite>,
50 pub subpixel_sprites: Vec<SubpixelSprite>,
51 pub polychrome_sprites: Vec<PolychromeSprite>,
52 pub surfaces: Vec<PaintSurface>,
53 pub backdrop_blurs: Vec<BackdropBlur>,
56}
57
58#[expect(missing_docs)]
59impl Scene {
60 pub fn clear(&mut self) {
61 self.paint_operations.clear();
62 self.primitive_bounds.clear();
63 self.layer_stack.clear();
64 self.paths.clear();
65 self.shadows.clear();
66 self.quads.clear();
67 self.underlines.clear();
68 self.monochrome_sprites.clear();
69 self.subpixel_sprites.clear();
70 self.polychrome_sprites.clear();
71 self.surfaces.clear();
72 self.backdrop_blurs.clear();
73 }
74
75 pub fn len(&self) -> usize {
76 self.paint_operations.len()
77 }
78
79 pub fn is_empty(&self) -> bool {
85 self.shadows.is_empty()
86 && self.quads.is_empty()
87 && self.paths.is_empty()
88 && self.underlines.is_empty()
89 && self.monochrome_sprites.is_empty()
90 && self.subpixel_sprites.is_empty()
91 && self.polychrome_sprites.is_empty()
92 && self.surfaces.is_empty()
93 }
94
95 pub fn push_layer(&mut self, bounds: Bounds<ScaledPixels>) {
96 let order = self.primitive_bounds.insert(bounds);
97 self.layer_stack.push(order);
98 self.paint_operations
99 .push(PaintOperation::StartLayer(bounds));
100 }
101
102 pub fn pop_layer(&mut self) {
103 self.layer_stack.pop();
104 self.paint_operations.push(PaintOperation::EndLayer);
105 }
106
107 pub fn insert_backdrop_blur(&mut self, mut blur: BackdropBlur) {
108 if !blur.blur_radius.0.is_finite() || blur.blur_radius.0 < 0.0 {
109 return;
110 }
111 let clipped_bounds = blur.bounds.intersect(&blur.content_mask.bounds);
112 if clipped_bounds.is_empty() {
113 return;
114 }
115 blur.order = self
116 .layer_stack
117 .last()
118 .copied()
119 .unwrap_or_else(|| self.primitive_bounds.insert(clipped_bounds));
120 self.backdrop_blurs.push(blur);
121 self.paint_operations
122 .push(PaintOperation::BackdropBlur(blur));
123 }
124
125 pub fn insert_primitive(&mut self, primitive: impl Into<Primitive>) {
126 let mut primitive = primitive.into();
127 let clipped_bounds = primitive
128 .bounds()
129 .intersect(&primitive.content_mask().bounds);
130
131 if clipped_bounds.is_empty() {
132 return;
133 }
134
135 let order = self
136 .layer_stack
137 .last()
138 .copied()
139 .unwrap_or_else(|| self.primitive_bounds.insert(clipped_bounds));
140 match &mut primitive {
141 Primitive::Shadow(shadow) => {
142 shadow.order = order;
143 self.shadows.push(*shadow);
144 }
145 Primitive::Quad(quad) => {
146 quad.order = order;
147 self.quads.push(*quad);
148 }
149 Primitive::Path(path) => {
150 path.order = order;
151 path.id = PathId(self.paths.len());
152 self.paths.push(path.clone());
153 }
154 Primitive::Underline(underline) => {
155 underline.order = order;
156 self.underlines.push(*underline);
157 }
158 Primitive::MonochromeSprite(sprite) => {
159 sprite.order = order;
160 self.monochrome_sprites.push(*sprite);
161 }
162 Primitive::SubpixelSprite(sprite) => {
163 sprite.order = order;
164 self.subpixel_sprites.push(*sprite);
165 }
166 Primitive::PolychromeSprite(sprite) => {
167 sprite.order = order;
168 self.polychrome_sprites.push(*sprite);
169 }
170 Primitive::Surface(surface) => {
171 surface.order = order;
172 self.surfaces.push(surface.clone());
173 }
174 }
175 self.paint_operations
176 .push(PaintOperation::Primitive(primitive));
177 }
178
179 pub fn replay(&mut self, range: Range<usize>, prev_scene: &Scene) {
180 for operation in &prev_scene.paint_operations[range] {
181 match operation {
182 PaintOperation::Primitive(primitive) => self.insert_primitive(primitive.clone()),
183 PaintOperation::BackdropBlur(blur) => self.insert_backdrop_blur(*blur),
184 PaintOperation::StartLayer(bounds) => self.push_layer(*bounds),
185 PaintOperation::EndLayer => self.pop_layer(),
186 }
187 }
188 }
189
190 pub fn finish(&mut self) {
191 self.shadows.sort_by_key(|shadow| shadow.order);
192 self.quads.sort_by_key(|quad| quad.order);
193 self.paths.sort_by_key(|path| path.order);
194 self.underlines.sort_by_key(|underline| underline.order);
195 self.monochrome_sprites
196 .sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
197 self.subpixel_sprites
198 .sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
199 self.polychrome_sprites
200 .sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
201 self.surfaces.sort_by_key(|surface| surface.order);
202 self.backdrop_blurs.sort_by_key(|blur| blur.order);
203 }
204
205 #[cfg_attr(
206 all(
207 any(target_os = "linux", target_os = "freebsd"),
208 not(any(feature = "x11", feature = "wayland"))
209 ),
210 allow(dead_code)
211 )]
212 pub fn batches(&self) -> impl Iterator<Item = PrimitiveBatch> + '_ {
213 BatchIterator {
214 shadows_start: 0,
215 shadows_iter: self.shadows.iter().peekable(),
216 quads_start: 0,
217 quads_iter: self.quads.iter().peekable(),
218 paths_start: 0,
219 paths_iter: self.paths.iter().peekable(),
220 underlines_start: 0,
221 underlines_iter: self.underlines.iter().peekable(),
222 monochrome_sprites_start: 0,
223 monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(),
224 subpixel_sprites_start: 0,
225 subpixel_sprites_iter: self.subpixel_sprites.iter().peekable(),
226 polychrome_sprites_start: 0,
227 polychrome_sprites_iter: self.polychrome_sprites.iter().peekable(),
228 surfaces_start: 0,
229 surfaces_iter: self.surfaces.iter().peekable(),
230 backdrop_blurs_iter: self.backdrop_blurs.iter().peekable(),
231 }
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238
239 #[test]
240 fn empty_layers_do_not_make_a_scene_drawable() {
241 let mut scene = Scene::default();
242 let bounds = Bounds {
243 origin: Point::default(),
244 size: Size {
245 width: ScaledPixels::from(100.),
246 height: ScaledPixels::from(100.),
247 },
248 };
249
250 scene.push_layer(bounds);
251 scene.pop_layer();
252
253 assert_ne!(scene.len(), 0);
254 assert!(scene.is_empty());
255 }
256
257 #[test]
258 fn drawable_primitives_make_a_scene_non_empty() {
259 let mut scene = Scene::default();
260 let bounds = Bounds {
261 origin: Point::default(),
262 size: Size {
263 width: ScaledPixels::from(100.),
264 height: ScaledPixels::from(100.),
265 },
266 };
267
268 scene.insert_primitive(Quad {
269 bounds,
270 content_mask: ContentMask { bounds },
271 ..Default::default()
272 });
273
274 assert!(!scene.is_empty());
275 }
276
277 #[test]
278 fn replay_preserves_scene_emptiness() {
279 let mut source = Scene::default();
280 let bounds = Bounds {
281 origin: Point::default(),
282 size: Size {
283 width: ScaledPixels::from(100.),
284 height: ScaledPixels::from(100.),
285 },
286 };
287 source.push_layer(bounds);
288 source.pop_layer();
289
290 let mut replayed = Scene::default();
291 replayed.replay(0..source.len(), &source);
292
293 assert!(replayed.is_empty());
294 }
295
296 #[test]
297 fn invalid_backdrop_blur_radii_are_ignored() {
298 let bounds = Bounds {
299 origin: Point::default(),
300 size: Size {
301 width: ScaledPixels::from(100.),
302 height: ScaledPixels::from(100.),
303 },
304 };
305 let mut scene = Scene::default();
306
307 for radius in [-1.0, f32::NAN, f32::INFINITY, f32::NEG_INFINITY] {
308 scene.insert_backdrop_blur(BackdropBlur {
309 order: 0,
310 blur_radius: ScaledPixels(radius),
311 bounds,
312 content_mask: ContentMask { bounds },
313 corner_radii: Corners::default(),
314 });
315 }
316
317 assert!(scene.backdrop_blurs.is_empty());
318 assert_eq!(scene.len(), 0);
319 }
320
321 #[test]
322 fn backdrop_blurs_split_same_kind_primitive_batches() {
323 let shadow = |order| Shadow {
324 order,
325 blur_radius: ScaledPixels::default(),
326 bounds: Bounds::default(),
327 corner_radii: Corners::default(),
328 content_mask: ContentMask::default(),
329 color: Hsla::default(),
330 element_bounds: Bounds::default(),
331 element_corner_radii: Corners::default(),
332 inset: 0,
333 pad: 0,
334 };
335 let mut scene = Scene {
336 shadows: vec![shadow(1), shadow(2), shadow(4)],
337 backdrop_blurs: vec![BackdropBlur {
338 order: 3,
339 blur_radius: ScaledPixels::default(),
340 bounds: Bounds::default(),
341 content_mask: ContentMask::default(),
342 corner_radii: Corners::default(),
343 }],
344 ..Scene::default()
345 };
346 scene.finish();
347
348 let batches = scene.batches().collect::<Vec<_>>();
349 assert!(matches!(
350 batches.as_slice(),
351 [PrimitiveBatch::Shadows(first), PrimitiveBatch::Shadows(second)]
352 if first == &(0..2) && second == &(2..3)
353 ));
354 }
355}
356
357#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)]
358#[cfg_attr(
359 all(
360 any(target_os = "linux", target_os = "freebsd"),
361 not(any(feature = "x11", feature = "wayland"))
362 ),
363 allow(dead_code)
364)]
365pub(crate) enum PrimitiveKind {
366 Shadow,
367 #[default]
368 Quad,
369 Path,
370 Underline,
371 MonochromeSprite,
372 SubpixelSprite,
373 PolychromeSprite,
374 Surface,
375}
376
377pub(crate) enum PaintOperation {
378 Primitive(Primitive),
379 BackdropBlur(BackdropBlur),
380 StartLayer(Bounds<ScaledPixels>),
381 EndLayer,
382}
383
384#[derive(Clone)]
385#[expect(missing_docs)]
386pub enum Primitive {
387 Shadow(Shadow),
388 Quad(Quad),
389 Path(Path<ScaledPixels>),
390 Underline(Underline),
391 MonochromeSprite(MonochromeSprite),
392 SubpixelSprite(SubpixelSprite),
393 PolychromeSprite(PolychromeSprite),
394 Surface(PaintSurface),
395}
396
397#[expect(missing_docs)]
398impl Primitive {
399 pub fn bounds(&self) -> &Bounds<ScaledPixels> {
400 match self {
401 Primitive::Shadow(shadow) => &shadow.bounds,
402 Primitive::Quad(quad) => &quad.bounds,
403 Primitive::Path(path) => &path.bounds,
404 Primitive::Underline(underline) => &underline.bounds,
405 Primitive::MonochromeSprite(sprite) => &sprite.bounds,
406 Primitive::SubpixelSprite(sprite) => &sprite.bounds,
407 Primitive::PolychromeSprite(sprite) => &sprite.bounds,
408 Primitive::Surface(surface) => &surface.bounds,
409 }
410 }
411
412 pub fn content_mask(&self) -> &ContentMask<ScaledPixels> {
413 match self {
414 Primitive::Shadow(shadow) => &shadow.content_mask,
415 Primitive::Quad(quad) => &quad.content_mask,
416 Primitive::Path(path) => &path.content_mask,
417 Primitive::Underline(underline) => &underline.content_mask,
418 Primitive::MonochromeSprite(sprite) => &sprite.content_mask,
419 Primitive::SubpixelSprite(sprite) => &sprite.content_mask,
420 Primitive::PolychromeSprite(sprite) => &sprite.content_mask,
421 Primitive::Surface(surface) => &surface.content_mask,
422 }
423 }
424}
425
426#[cfg_attr(
427 all(
428 any(target_os = "linux", target_os = "freebsd"),
429 not(any(feature = "x11", feature = "wayland"))
430 ),
431 allow(dead_code)
432)]
433struct BatchIterator<'a> {
434 shadows_start: usize,
435 shadows_iter: Peekable<slice::Iter<'a, Shadow>>,
436 quads_start: usize,
437 quads_iter: Peekable<slice::Iter<'a, Quad>>,
438 paths_start: usize,
439 paths_iter: Peekable<slice::Iter<'a, Path<ScaledPixels>>>,
440 underlines_start: usize,
441 underlines_iter: Peekable<slice::Iter<'a, Underline>>,
442 monochrome_sprites_start: usize,
443 monochrome_sprites_iter: Peekable<slice::Iter<'a, MonochromeSprite>>,
444 subpixel_sprites_start: usize,
445 subpixel_sprites_iter: Peekable<slice::Iter<'a, SubpixelSprite>>,
446 polychrome_sprites_start: usize,
447 polychrome_sprites_iter: Peekable<slice::Iter<'a, PolychromeSprite>>,
448 surfaces_start: usize,
449 surfaces_iter: Peekable<slice::Iter<'a, PaintSurface>>,
450 backdrop_blurs_iter: Peekable<slice::Iter<'a, BackdropBlur>>,
451}
452
453impl<'a> Iterator for BatchIterator<'a> {
454 type Item = PrimitiveBatch;
455
456 fn next(&mut self) -> Option<Self::Item> {
457 let mut orders_and_kinds = [
458 (
459 self.shadows_iter.peek().map(|s| s.order),
460 PrimitiveKind::Shadow,
461 ),
462 (self.quads_iter.peek().map(|q| q.order), PrimitiveKind::Quad),
463 (self.paths_iter.peek().map(|q| q.order), PrimitiveKind::Path),
464 (
465 self.underlines_iter.peek().map(|u| u.order),
466 PrimitiveKind::Underline,
467 ),
468 (
469 self.monochrome_sprites_iter.peek().map(|s| s.order),
470 PrimitiveKind::MonochromeSprite,
471 ),
472 (
473 self.subpixel_sprites_iter.peek().map(|s| s.order),
474 PrimitiveKind::SubpixelSprite,
475 ),
476 (
477 self.polychrome_sprites_iter.peek().map(|s| s.order),
478 PrimitiveKind::PolychromeSprite,
479 ),
480 (
481 self.surfaces_iter.peek().map(|s| s.order),
482 PrimitiveKind::Surface,
483 ),
484 ];
485 orders_and_kinds.sort_by_key(|(order, kind)| (order.unwrap_or(u32::MAX), *kind));
486
487 let first = orders_and_kinds[0];
488 let second = orders_and_kinds[1];
489 while self
490 .backdrop_blurs_iter
491 .next_if(|blur| first.0.is_some_and(|order| blur.order <= order))
492 .is_some()
493 {}
494 let next_blur_order = self
495 .backdrop_blurs_iter
496 .peek()
497 .map_or(u32::MAX, |blur| blur.order);
498 let (batch_kind, max_order_and_kind) = if first.0.is_some() {
499 (first.1, (second.0.unwrap_or(u32::MAX), second.1))
500 } else {
501 return None;
502 };
503
504 match batch_kind {
505 PrimitiveKind::Shadow => {
506 let shadows_start = self.shadows_start;
507 let mut shadows_end = shadows_start + 1;
508 self.shadows_iter.next();
509 while self
510 .shadows_iter
511 .next_if(|shadow| {
512 shadow.order < next_blur_order
513 && (shadow.order, batch_kind) < max_order_and_kind
514 })
515 .is_some()
516 {
517 shadows_end += 1;
518 }
519 self.shadows_start = shadows_end;
520 Some(PrimitiveBatch::Shadows(shadows_start..shadows_end))
521 }
522 PrimitiveKind::Quad => {
523 let quads_start = self.quads_start;
524 let mut quads_end = quads_start + 1;
525 self.quads_iter.next();
526 while self
527 .quads_iter
528 .next_if(|quad| {
529 quad.order < next_blur_order
530 && (quad.order, batch_kind) < max_order_and_kind
531 })
532 .is_some()
533 {
534 quads_end += 1;
535 }
536 self.quads_start = quads_end;
537 Some(PrimitiveBatch::Quads(quads_start..quads_end))
538 }
539 PrimitiveKind::Path => {
540 let paths_start = self.paths_start;
541 let mut paths_end = paths_start + 1;
542 self.paths_iter.next();
543 while self
544 .paths_iter
545 .next_if(|path| {
546 path.order < next_blur_order
547 && (path.order, batch_kind) < max_order_and_kind
548 })
549 .is_some()
550 {
551 paths_end += 1;
552 }
553 self.paths_start = paths_end;
554 Some(PrimitiveBatch::Paths(paths_start..paths_end))
555 }
556 PrimitiveKind::Underline => {
557 let underlines_start = self.underlines_start;
558 let mut underlines_end = underlines_start + 1;
559 self.underlines_iter.next();
560 while self
561 .underlines_iter
562 .next_if(|underline| {
563 underline.order < next_blur_order
564 && (underline.order, batch_kind) < max_order_and_kind
565 })
566 .is_some()
567 {
568 underlines_end += 1;
569 }
570 self.underlines_start = underlines_end;
571 Some(PrimitiveBatch::Underlines(underlines_start..underlines_end))
572 }
573 PrimitiveKind::MonochromeSprite => {
574 let texture_id = self.monochrome_sprites_iter.peek().unwrap().tile.texture_id;
575 let sprites_start = self.monochrome_sprites_start;
576 let mut sprites_end = sprites_start + 1;
577 self.monochrome_sprites_iter.next();
578 while self
579 .monochrome_sprites_iter
580 .next_if(|sprite| {
581 sprite.order < next_blur_order
582 && (sprite.order, batch_kind) < max_order_and_kind
583 && sprite.tile.texture_id == texture_id
584 })
585 .is_some()
586 {
587 sprites_end += 1;
588 }
589 self.monochrome_sprites_start = sprites_end;
590 Some(PrimitiveBatch::MonochromeSprites {
591 texture_id,
592 range: sprites_start..sprites_end,
593 })
594 }
595 PrimitiveKind::SubpixelSprite => {
596 let texture_id = self.subpixel_sprites_iter.peek().unwrap().tile.texture_id;
597 let sprites_start = self.subpixel_sprites_start;
598 let mut sprites_end = sprites_start + 1;
599 self.subpixel_sprites_iter.next();
600 while self
601 .subpixel_sprites_iter
602 .next_if(|sprite| {
603 sprite.order < next_blur_order
604 && (sprite.order, batch_kind) < max_order_and_kind
605 && sprite.tile.texture_id == texture_id
606 })
607 .is_some()
608 {
609 sprites_end += 1;
610 }
611 self.subpixel_sprites_start = sprites_end;
612 Some(PrimitiveBatch::SubpixelSprites {
613 texture_id,
614 range: sprites_start..sprites_end,
615 })
616 }
617 PrimitiveKind::PolychromeSprite => {
618 let texture_id = self.polychrome_sprites_iter.peek().unwrap().tile.texture_id;
619 let sprites_start = self.polychrome_sprites_start;
620 let mut sprites_end = sprites_start + 1;
621 self.polychrome_sprites_iter.next();
622 while self
623 .polychrome_sprites_iter
624 .next_if(|sprite| {
625 sprite.order < next_blur_order
626 && (sprite.order, batch_kind) < max_order_and_kind
627 && sprite.tile.texture_id == texture_id
628 })
629 .is_some()
630 {
631 sprites_end += 1;
632 }
633 self.polychrome_sprites_start = sprites_end;
634 Some(PrimitiveBatch::PolychromeSprites {
635 texture_id,
636 range: sprites_start..sprites_end,
637 })
638 }
639 PrimitiveKind::Surface => {
640 let surfaces_start = self.surfaces_start;
641 let mut surfaces_end = surfaces_start + 1;
642 self.surfaces_iter.next();
643 while self
644 .surfaces_iter
645 .next_if(|surface| {
646 surface.order < next_blur_order
647 && (surface.order, batch_kind) < max_order_and_kind
648 })
649 .is_some()
650 {
651 surfaces_end += 1;
652 }
653 self.surfaces_start = surfaces_end;
654 Some(PrimitiveBatch::Surfaces(surfaces_start..surfaces_end))
655 }
656 }
657 }
658}
659
660#[derive(Debug)]
661#[cfg_attr(
662 all(
663 any(target_os = "linux", target_os = "freebsd"),
664 not(any(feature = "x11", feature = "wayland"))
665 ),
666 allow(dead_code)
667)]
668#[allow(missing_docs)]
669pub enum PrimitiveBatch {
670 Shadows(Range<usize>),
671 Quads(Range<usize>),
672 Paths(Range<usize>),
673 Underlines(Range<usize>),
674 MonochromeSprites {
675 texture_id: AtlasTextureId,
676 range: Range<usize>,
677 },
678 #[cfg_attr(target_os = "macos", allow(dead_code))]
679 SubpixelSprites {
680 texture_id: AtlasTextureId,
681 range: Range<usize>,
682 },
683 PolychromeSprites {
684 texture_id: AtlasTextureId,
685 range: Range<usize>,
686 },
687 Surfaces(Range<usize>),
688}
689
690impl PrimitiveBatch {
691 #[expect(missing_docs)]
692 pub fn label(&self) -> String {
693 match self {
694 Self::Shadows(range) => format!("shadows ({})", range.len()),
695 Self::Quads(range) => format!("quads ({})", range.len()),
696 Self::Paths(range) => format!("paths ({})", range.len()),
697 Self::Underlines(range) => format!("underlines ({})", range.len()),
698 Self::MonochromeSprites { texture_id, range } => {
699 format!(
700 "monochrome sprites ({}) on atlas {}",
701 range.len(),
702 texture_id.index
703 )
704 }
705 Self::SubpixelSprites { texture_id, range } => {
706 format!(
707 "subpixel sprites ({}) on atlas {}",
708 range.len(),
709 texture_id.index
710 )
711 }
712 Self::PolychromeSprites { texture_id, range } => {
713 format!(
714 "polychrome sprites ({}) on atlas {}",
715 range.len(),
716 texture_id.index
717 )
718 }
719 Self::Surfaces(range) => format!("surfaces ({})", range.len()),
720 }
721 }
722}
723
724#[derive(Default, Debug, Copy, Clone)]
725#[repr(C)]
726#[expect(missing_docs)]
727pub struct Quad {
728 pub order: DrawOrder,
729 pub border_style: BorderStyle,
730 pub bounds: Bounds<ScaledPixels>,
731 pub content_mask: ContentMask<ScaledPixels>,
732 pub background: Background,
733 pub border_color: Hsla,
734 pub corner_radii: Corners<ScaledPixels>,
735 pub border_widths: Edges<ScaledPixels>,
736}
737
738impl From<Quad> for Primitive {
739 fn from(quad: Quad) -> Self {
740 Primitive::Quad(quad)
741 }
742}
743
744#[derive(Debug, Copy, Clone)]
745#[repr(C)]
746#[expect(missing_docs)]
747pub struct Underline {
748 pub order: DrawOrder,
749 pub pad: u32, pub bounds: Bounds<ScaledPixels>,
751 pub content_mask: ContentMask<ScaledPixels>,
752 pub color: Hsla,
753 pub thickness: ScaledPixels,
754 pub wavy: PaddedBool32,
755}
756
757impl From<Underline> for Primitive {
758 fn from(underline: Underline) -> Self {
759 Primitive::Underline(underline)
760 }
761}
762
763#[derive(Debug, Copy, Clone)]
767#[repr(C)]
768#[expect(missing_docs)]
769pub struct BackdropBlur {
770 pub order: DrawOrder,
771 pub blur_radius: ScaledPixels,
772 pub bounds: Bounds<ScaledPixels>,
773 pub content_mask: ContentMask<ScaledPixels>,
774 pub corner_radii: Corners<ScaledPixels>,
775}
776
777#[derive(Debug, Copy, Clone)]
778#[repr(C)]
779#[expect(missing_docs)]
780pub struct Shadow {
781 pub order: DrawOrder,
782 pub blur_radius: ScaledPixels,
783 pub bounds: Bounds<ScaledPixels>,
784 pub corner_radii: Corners<ScaledPixels>,
785 pub content_mask: ContentMask<ScaledPixels>,
786 pub color: Hsla,
787 pub element_bounds: Bounds<ScaledPixels>,
788 pub element_corner_radii: Corners<ScaledPixels>,
789 pub inset: u32,
791 pub pad: u32, }
793
794impl From<Shadow> for Primitive {
795 fn from(shadow: Shadow) -> Self {
796 Primitive::Shadow(shadow)
797 }
798}
799
800#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
802#[repr(C)]
803pub enum BorderStyle {
804 #[default]
806 Solid = 0,
807 Dashed = 1,
809}
810
811#[derive(Debug, Clone, Copy, PartialEq)]
813#[repr(C)]
814pub struct TransformationMatrix {
815 pub rotation_scale: [[f32; 2]; 2],
818 pub translation: [f32; 2],
820}
821
822impl Eq for TransformationMatrix {}
823
824impl TransformationMatrix {
825 pub fn unit() -> Self {
827 Self {
828 rotation_scale: [[1.0, 0.0], [0.0, 1.0]],
829 translation: [0.0, 0.0],
830 }
831 }
832
833 pub fn translate(mut self, point: Point<ScaledPixels>) -> Self {
835 self.compose(Self {
836 rotation_scale: [[1.0, 0.0], [0.0, 1.0]],
837 translation: [point.x.0, point.y.0],
838 })
839 }
840
841 pub fn rotate(self, angle: Radians) -> Self {
843 self.compose(Self {
844 rotation_scale: [
845 [angle.0.cos(), -angle.0.sin()],
846 [angle.0.sin(), angle.0.cos()],
847 ],
848 translation: [0.0, 0.0],
849 })
850 }
851
852 pub fn scale(self, size: Size<f32>) -> Self {
854 self.compose(Self {
855 rotation_scale: [[size.width, 0.0], [0.0, size.height]],
856 translation: [0.0, 0.0],
857 })
858 }
859
860 #[inline]
864 pub fn compose(self, other: TransformationMatrix) -> TransformationMatrix {
865 if other == Self::unit() {
866 return self;
867 }
868 TransformationMatrix {
870 rotation_scale: [
871 [
872 self.rotation_scale[0][0] * other.rotation_scale[0][0]
873 + self.rotation_scale[0][1] * other.rotation_scale[1][0],
874 self.rotation_scale[0][0] * other.rotation_scale[0][1]
875 + self.rotation_scale[0][1] * other.rotation_scale[1][1],
876 ],
877 [
878 self.rotation_scale[1][0] * other.rotation_scale[0][0]
879 + self.rotation_scale[1][1] * other.rotation_scale[1][0],
880 self.rotation_scale[1][0] * other.rotation_scale[0][1]
881 + self.rotation_scale[1][1] * other.rotation_scale[1][1],
882 ],
883 ],
884 translation: [
885 self.translation[0]
886 + self.rotation_scale[0][0] * other.translation[0]
887 + self.rotation_scale[0][1] * other.translation[1],
888 self.translation[1]
889 + self.rotation_scale[1][0] * other.translation[0]
890 + self.rotation_scale[1][1] * other.translation[1],
891 ],
892 }
893 }
894
895 pub fn apply(&self, point: Point<Pixels>) -> Point<Pixels> {
897 let input = [point.x.0, point.y.0];
898 let mut output = self.translation;
899 for (i, output_cell) in output.iter_mut().enumerate() {
900 for (k, input_cell) in input.iter().enumerate() {
901 *output_cell += self.rotation_scale[i][k] * *input_cell;
902 }
903 }
904 Point::new(output[0].into(), output[1].into())
905 }
906}
907
908impl Default for TransformationMatrix {
909 fn default() -> Self {
910 Self::unit()
911 }
912}
913
914#[derive(Copy, Clone, Debug)]
915#[repr(C)]
916#[expect(missing_docs)]
917pub struct MonochromeSprite {
918 pub order: DrawOrder,
919 pub pad: u32,
920 pub bounds: Bounds<ScaledPixels>,
921 pub content_mask: ContentMask<ScaledPixels>,
922 pub color: Hsla,
923 pub tile: AtlasTile,
924 pub transformation: TransformationMatrix,
925}
926
927impl From<MonochromeSprite> for Primitive {
928 fn from(sprite: MonochromeSprite) -> Self {
929 Primitive::MonochromeSprite(sprite)
930 }
931}
932
933#[derive(Copy, Clone, Debug)]
934#[repr(C)]
935#[expect(missing_docs)]
936pub struct SubpixelSprite {
937 pub order: DrawOrder,
938 pub pad: u32, pub bounds: Bounds<ScaledPixels>,
940 pub content_mask: ContentMask<ScaledPixels>,
941 pub color: Hsla,
942 pub tile: AtlasTile,
943 pub transformation: TransformationMatrix,
944}
945
946impl From<SubpixelSprite> for Primitive {
947 fn from(sprite: SubpixelSprite) -> Self {
948 Primitive::SubpixelSprite(sprite)
949 }
950}
951
952#[derive(Copy, Clone, Debug)]
953#[repr(C)]
954#[expect(missing_docs)]
955pub struct PolychromeSprite {
956 pub order: DrawOrder,
957 pub pad: u32,
958 pub grayscale: PaddedBool32,
959 pub opacity: f32,
960 pub bounds: Bounds<ScaledPixels>,
961 pub content_mask: ContentMask<ScaledPixels>,
962 pub corner_radii: Corners<ScaledPixels>,
963 pub tile: AtlasTile,
964}
965
966impl From<PolychromeSprite> for Primitive {
967 fn from(sprite: PolychromeSprite) -> Self {
968 Primitive::PolychromeSprite(sprite)
969 }
970}
971
972#[derive(Clone, Debug)]
973#[allow(missing_docs)]
974pub struct PaintSurface {
975 pub order: DrawOrder,
976 pub bounds: Bounds<ScaledPixels>,
977 pub content_mask: ContentMask<ScaledPixels>,
978 #[cfg(target_os = "macos")]
979 pub image_buffer: core_video::pixel_buffer::CVPixelBuffer,
980}
981
982impl From<PaintSurface> for Primitive {
983 fn from(surface: PaintSurface) -> Self {
984 Primitive::Surface(surface)
985 }
986}
987
988#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
989#[expect(missing_docs)]
990pub struct PathId(pub usize);
991
992#[derive(Clone, Debug)]
994#[expect(missing_docs)]
995pub struct Path<P: Clone + Debug + Default + PartialEq> {
996 pub id: PathId,
997 pub order: DrawOrder,
998 pub bounds: Bounds<P>,
999 pub content_mask: ContentMask<P>,
1000 pub vertices: Vec<PathVertex<P>>,
1001 pub color: Background,
1002 start: Point<P>,
1003 current: Point<P>,
1004 contour_count: usize,
1005}
1006
1007impl Path<Pixels> {
1008 pub fn new(start: Point<Pixels>) -> Self {
1010 Self {
1011 id: PathId(0),
1012 order: DrawOrder::default(),
1013 vertices: Vec::new(),
1014 start,
1015 current: start,
1016 bounds: Bounds {
1017 origin: start,
1018 size: Default::default(),
1019 },
1020 content_mask: Default::default(),
1021 color: Default::default(),
1022 contour_count: 0,
1023 }
1024 }
1025
1026 pub fn scale(&self, factor: f32) -> Path<ScaledPixels> {
1028 Path {
1029 id: self.id,
1030 order: self.order,
1031 bounds: self.bounds.scale(factor),
1032 content_mask: self.content_mask.scale(factor),
1033 vertices: self
1034 .vertices
1035 .iter()
1036 .map(|vertex| vertex.scale(factor))
1037 .collect(),
1038 start: self.start.map(|start| start.scale(factor)),
1039 current: self.current.scale(factor),
1040 contour_count: self.contour_count,
1041 color: self.color,
1042 }
1043 }
1044
1045 pub fn move_to(&mut self, to: Point<Pixels>) {
1047 self.contour_count += 1;
1048 self.start = to;
1049 self.current = to;
1050 }
1051
1052 pub fn line_to(&mut self, to: Point<Pixels>) {
1054 self.contour_count += 1;
1055 if self.contour_count > 1 {
1056 self.push_triangle(
1057 (self.start, self.current, to),
1058 (point(0., 1.), point(0., 1.), point(0., 1.)),
1059 );
1060 }
1061 self.current = to;
1062 }
1063
1064 pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
1066 self.contour_count += 1;
1067 if self.contour_count > 1 {
1068 self.push_triangle(
1069 (self.start, self.current, to),
1070 (point(0., 1.), point(0., 1.), point(0., 1.)),
1071 );
1072 }
1073
1074 self.push_triangle(
1075 (self.current, ctrl, to),
1076 (point(0., 0.), point(0.5, 0.), point(1., 1.)),
1077 );
1078 self.current = to;
1079 }
1080
1081 pub fn push_triangle(
1083 &mut self,
1084 xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>),
1085 st: (Point<f32>, Point<f32>, Point<f32>),
1086 ) {
1087 self.bounds = self
1088 .bounds
1089 .union(&Bounds {
1090 origin: xy.0,
1091 size: Default::default(),
1092 })
1093 .union(&Bounds {
1094 origin: xy.1,
1095 size: Default::default(),
1096 })
1097 .union(&Bounds {
1098 origin: xy.2,
1099 size: Default::default(),
1100 });
1101
1102 self.vertices.push(PathVertex {
1103 xy_position: xy.0,
1104 st_position: st.0,
1105 content_mask: Default::default(),
1106 });
1107 self.vertices.push(PathVertex {
1108 xy_position: xy.1,
1109 st_position: st.1,
1110 content_mask: Default::default(),
1111 });
1112 self.vertices.push(PathVertex {
1113 xy_position: xy.2,
1114 st_position: st.2,
1115 content_mask: Default::default(),
1116 });
1117 }
1118}
1119
1120impl<T> Path<T>
1121where
1122 T: Clone + Debug + Default + PartialEq + PartialOrd + Add<T, Output = T> + Sub<Output = T>,
1123{
1124 #[allow(unused)]
1125 #[expect(missing_docs)]
1126 pub fn clipped_bounds(&self) -> Bounds<T> {
1127 self.bounds.intersect(&self.content_mask.bounds)
1128 }
1129}
1130
1131impl From<Path<ScaledPixels>> for Primitive {
1132 fn from(path: Path<ScaledPixels>) -> Self {
1133 Primitive::Path(path)
1134 }
1135}
1136
1137#[derive(Clone, Debug)]
1138#[repr(C)]
1139#[expect(missing_docs)]
1140pub struct PathVertex<P: Clone + Debug + Default + PartialEq> {
1141 pub xy_position: Point<P>,
1142 pub st_position: Point<f32>,
1143 pub content_mask: ContentMask<P>,
1144}
1145
1146#[expect(missing_docs)]
1147impl PathVertex<Pixels> {
1148 pub fn scale(&self, factor: f32) -> PathVertex<ScaledPixels> {
1149 PathVertex {
1150 xy_position: self.xy_position.scale(factor),
1151 st_position: self.st_position,
1152 content_mask: self.content_mask.scale(factor),
1153 }
1154 }
1155}