scene.rs

  1// todo("windows"): remove
  2#![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(Default)]
 26#[expect(missing_docs)]
 27pub struct Scene {
 28    pub(crate) paint_operations: Vec<PaintOperation>,
 29    primitive_bounds: BoundsTree<ScaledPixels>,
 30    layer_stack: Vec<DrawOrder>,
 31    pub shadows: Vec<Shadow>,
 32    pub quads: Vec<Quad>,
 33    pub paths: Vec<Path<ScaledPixels>>,
 34    pub underlines: Vec<Underline>,
 35    pub monochrome_sprites: Vec<MonochromeSprite>,
 36    pub subpixel_sprites: Vec<SubpixelSprite>,
 37    pub polychrome_sprites: Vec<PolychromeSprite>,
 38    pub surfaces: Vec<PaintSurface>,
 39}
 40
 41#[expect(missing_docs)]
 42impl Scene {
 43    pub fn clear(&mut self) {
 44        self.paint_operations.clear();
 45        self.primitive_bounds.clear();
 46        self.layer_stack.clear();
 47        self.paths.clear();
 48        self.shadows.clear();
 49        self.quads.clear();
 50        self.underlines.clear();
 51        self.monochrome_sprites.clear();
 52        self.subpixel_sprites.clear();
 53        self.polychrome_sprites.clear();
 54        self.surfaces.clear();
 55    }
 56
 57    pub fn len(&self) -> usize {
 58        self.paint_operations.len()
 59    }
 60
 61    pub fn push_layer(&mut self, bounds: Bounds<ScaledPixels>) {
 62        let order = self.primitive_bounds.insert(bounds);
 63        self.layer_stack.push(order);
 64        self.paint_operations
 65            .push(PaintOperation::StartLayer(bounds));
 66    }
 67
 68    pub fn pop_layer(&mut self) {
 69        self.layer_stack.pop();
 70        self.paint_operations.push(PaintOperation::EndLayer);
 71    }
 72
 73    pub fn insert_primitive(&mut self, primitive: impl Into<Primitive>) {
 74        let mut primitive = primitive.into();
 75        let clipped_bounds = primitive
 76            .bounds()
 77            .intersect(&primitive.content_mask().bounds);
 78
 79        if clipped_bounds.is_empty() {
 80            return;
 81        }
 82
 83        let order = self
 84            .layer_stack
 85            .last()
 86            .copied()
 87            .unwrap_or_else(|| self.primitive_bounds.insert(clipped_bounds));
 88        match &mut primitive {
 89            Primitive::Shadow(shadow) => {
 90                shadow.order = order;
 91                self.shadows.push(shadow.clone());
 92            }
 93            Primitive::Quad(quad) => {
 94                quad.order = order;
 95                self.quads.push(quad.clone());
 96            }
 97            Primitive::Path(path) => {
 98                path.order = order;
 99                path.id = PathId(self.paths.len());
100                self.paths.push(path.clone());
101            }
102            Primitive::Underline(underline) => {
103                underline.order = order;
104                self.underlines.push(underline.clone());
105            }
106            Primitive::MonochromeSprite(sprite) => {
107                sprite.order = order;
108                self.monochrome_sprites.push(sprite.clone());
109            }
110            Primitive::SubpixelSprite(sprite) => {
111                sprite.order = order;
112                self.subpixel_sprites.push(sprite.clone());
113            }
114            Primitive::PolychromeSprite(sprite) => {
115                sprite.order = order;
116                self.polychrome_sprites.push(sprite.clone());
117            }
118            Primitive::Surface(surface) => {
119                surface.order = order;
120                self.surfaces.push(surface.clone());
121            }
122        }
123        self.paint_operations
124            .push(PaintOperation::Primitive(primitive));
125    }
126
127    pub fn replay(&mut self, range: Range<usize>, prev_scene: &Scene) {
128        for operation in &prev_scene.paint_operations[range] {
129            match operation {
130                PaintOperation::Primitive(primitive) => self.insert_primitive(primitive.clone()),
131                PaintOperation::StartLayer(bounds) => self.push_layer(*bounds),
132                PaintOperation::EndLayer => self.pop_layer(),
133            }
134        }
135    }
136
137    pub fn finish(&mut self) {
138        self.shadows.sort_by_key(|shadow| shadow.order);
139        self.quads.sort_by_key(|quad| quad.order);
140        self.paths.sort_by_key(|path| path.order);
141        self.underlines.sort_by_key(|underline| underline.order);
142        self.monochrome_sprites
143            .sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
144        self.subpixel_sprites
145            .sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
146        self.polychrome_sprites
147            .sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
148        self.surfaces.sort_by_key(|surface| surface.order);
149    }
150
151    #[cfg_attr(
152        all(
153            any(target_os = "linux", target_os = "freebsd"),
154            not(any(feature = "x11", feature = "wayland"))
155        ),
156        allow(dead_code)
157    )]
158    pub fn batches(&self) -> impl Iterator<Item = PrimitiveBatch> + '_ {
159        BatchIterator {
160            shadows_start: 0,
161            shadows_iter: self.shadows.iter().peekable(),
162            quads_start: 0,
163            quads_iter: self.quads.iter().peekable(),
164            paths_start: 0,
165            paths_iter: self.paths.iter().peekable(),
166            underlines_start: 0,
167            underlines_iter: self.underlines.iter().peekable(),
168            monochrome_sprites_start: 0,
169            monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(),
170            subpixel_sprites_start: 0,
171            subpixel_sprites_iter: self.subpixel_sprites.iter().peekable(),
172            polychrome_sprites_start: 0,
173            polychrome_sprites_iter: self.polychrome_sprites.iter().peekable(),
174            surfaces_start: 0,
175            surfaces_iter: self.surfaces.iter().peekable(),
176        }
177    }
178}
179
180#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)]
181#[cfg_attr(
182    all(
183        any(target_os = "linux", target_os = "freebsd"),
184        not(any(feature = "x11", feature = "wayland"))
185    ),
186    allow(dead_code)
187)]
188pub(crate) enum PrimitiveKind {
189    Shadow,
190    #[default]
191    Quad,
192    Path,
193    Underline,
194    MonochromeSprite,
195    SubpixelSprite,
196    PolychromeSprite,
197    Surface,
198}
199
200pub(crate) enum PaintOperation {
201    Primitive(Primitive),
202    StartLayer(Bounds<ScaledPixels>),
203    EndLayer,
204}
205
206#[derive(Clone)]
207#[expect(missing_docs)]
208pub enum Primitive {
209    Shadow(Shadow),
210    Quad(Quad),
211    Path(Path<ScaledPixels>),
212    Underline(Underline),
213    MonochromeSprite(MonochromeSprite),
214    SubpixelSprite(SubpixelSprite),
215    PolychromeSprite(PolychromeSprite),
216    Surface(PaintSurface),
217}
218
219#[expect(missing_docs)]
220impl Primitive {
221    pub fn bounds(&self) -> &Bounds<ScaledPixels> {
222        match self {
223            Primitive::Shadow(shadow) => &shadow.bounds,
224            Primitive::Quad(quad) => &quad.bounds,
225            Primitive::Path(path) => &path.bounds,
226            Primitive::Underline(underline) => &underline.bounds,
227            Primitive::MonochromeSprite(sprite) => &sprite.bounds,
228            Primitive::SubpixelSprite(sprite) => &sprite.bounds,
229            Primitive::PolychromeSprite(sprite) => &sprite.bounds,
230            Primitive::Surface(surface) => &surface.bounds,
231        }
232    }
233
234    pub fn content_mask(&self) -> &ContentMask<ScaledPixels> {
235        match self {
236            Primitive::Shadow(shadow) => &shadow.content_mask,
237            Primitive::Quad(quad) => &quad.content_mask,
238            Primitive::Path(path) => &path.content_mask,
239            Primitive::Underline(underline) => &underline.content_mask,
240            Primitive::MonochromeSprite(sprite) => &sprite.content_mask,
241            Primitive::SubpixelSprite(sprite) => &sprite.content_mask,
242            Primitive::PolychromeSprite(sprite) => &sprite.content_mask,
243            Primitive::Surface(surface) => &surface.content_mask,
244        }
245    }
246}
247
248#[cfg_attr(
249    all(
250        any(target_os = "linux", target_os = "freebsd"),
251        not(any(feature = "x11", feature = "wayland"))
252    ),
253    allow(dead_code)
254)]
255struct BatchIterator<'a> {
256    shadows_start: usize,
257    shadows_iter: Peekable<slice::Iter<'a, Shadow>>,
258    quads_start: usize,
259    quads_iter: Peekable<slice::Iter<'a, Quad>>,
260    paths_start: usize,
261    paths_iter: Peekable<slice::Iter<'a, Path<ScaledPixels>>>,
262    underlines_start: usize,
263    underlines_iter: Peekable<slice::Iter<'a, Underline>>,
264    monochrome_sprites_start: usize,
265    monochrome_sprites_iter: Peekable<slice::Iter<'a, MonochromeSprite>>,
266    subpixel_sprites_start: usize,
267    subpixel_sprites_iter: Peekable<slice::Iter<'a, SubpixelSprite>>,
268    polychrome_sprites_start: usize,
269    polychrome_sprites_iter: Peekable<slice::Iter<'a, PolychromeSprite>>,
270    surfaces_start: usize,
271    surfaces_iter: Peekable<slice::Iter<'a, PaintSurface>>,
272}
273
274impl<'a> Iterator for BatchIterator<'a> {
275    type Item = PrimitiveBatch;
276
277    fn next(&mut self) -> Option<Self::Item> {
278        let mut orders_and_kinds = [
279            (
280                self.shadows_iter.peek().map(|s| s.order),
281                PrimitiveKind::Shadow,
282            ),
283            (self.quads_iter.peek().map(|q| q.order), PrimitiveKind::Quad),
284            (self.paths_iter.peek().map(|q| q.order), PrimitiveKind::Path),
285            (
286                self.underlines_iter.peek().map(|u| u.order),
287                PrimitiveKind::Underline,
288            ),
289            (
290                self.monochrome_sprites_iter.peek().map(|s| s.order),
291                PrimitiveKind::MonochromeSprite,
292            ),
293            (
294                self.subpixel_sprites_iter.peek().map(|s| s.order),
295                PrimitiveKind::SubpixelSprite,
296            ),
297            (
298                self.polychrome_sprites_iter.peek().map(|s| s.order),
299                PrimitiveKind::PolychromeSprite,
300            ),
301            (
302                self.surfaces_iter.peek().map(|s| s.order),
303                PrimitiveKind::Surface,
304            ),
305        ];
306        orders_and_kinds.sort_by_key(|(order, kind)| (order.unwrap_or(u32::MAX), *kind));
307
308        let first = orders_and_kinds[0];
309        let second = orders_and_kinds[1];
310        let (batch_kind, max_order_and_kind) = if first.0.is_some() {
311            (first.1, (second.0.unwrap_or(u32::MAX), second.1))
312        } else {
313            return None;
314        };
315
316        match batch_kind {
317            PrimitiveKind::Shadow => {
318                let shadows_start = self.shadows_start;
319                let mut shadows_end = shadows_start + 1;
320                self.shadows_iter.next();
321                while self
322                    .shadows_iter
323                    .next_if(|shadow| (shadow.order, batch_kind) < max_order_and_kind)
324                    .is_some()
325                {
326                    shadows_end += 1;
327                }
328                self.shadows_start = shadows_end;
329                Some(PrimitiveBatch::Shadows(shadows_start..shadows_end))
330            }
331            PrimitiveKind::Quad => {
332                let quads_start = self.quads_start;
333                let mut quads_end = quads_start + 1;
334                self.quads_iter.next();
335                while self
336                    .quads_iter
337                    .next_if(|quad| (quad.order, batch_kind) < max_order_and_kind)
338                    .is_some()
339                {
340                    quads_end += 1;
341                }
342                self.quads_start = quads_end;
343                Some(PrimitiveBatch::Quads(quads_start..quads_end))
344            }
345            PrimitiveKind::Path => {
346                let paths_start = self.paths_start;
347                let mut paths_end = paths_start + 1;
348                self.paths_iter.next();
349                while self
350                    .paths_iter
351                    .next_if(|path| (path.order, batch_kind) < max_order_and_kind)
352                    .is_some()
353                {
354                    paths_end += 1;
355                }
356                self.paths_start = paths_end;
357                Some(PrimitiveBatch::Paths(paths_start..paths_end))
358            }
359            PrimitiveKind::Underline => {
360                let underlines_start = self.underlines_start;
361                let mut underlines_end = underlines_start + 1;
362                self.underlines_iter.next();
363                while self
364                    .underlines_iter
365                    .next_if(|underline| (underline.order, batch_kind) < max_order_and_kind)
366                    .is_some()
367                {
368                    underlines_end += 1;
369                }
370                self.underlines_start = underlines_end;
371                Some(PrimitiveBatch::Underlines(underlines_start..underlines_end))
372            }
373            PrimitiveKind::MonochromeSprite => {
374                let texture_id = self.monochrome_sprites_iter.peek().unwrap().tile.texture_id;
375                let sprites_start = self.monochrome_sprites_start;
376                let mut sprites_end = sprites_start + 1;
377                self.monochrome_sprites_iter.next();
378                while self
379                    .monochrome_sprites_iter
380                    .next_if(|sprite| {
381                        (sprite.order, batch_kind) < max_order_and_kind
382                            && sprite.tile.texture_id == texture_id
383                    })
384                    .is_some()
385                {
386                    sprites_end += 1;
387                }
388                self.monochrome_sprites_start = sprites_end;
389                Some(PrimitiveBatch::MonochromeSprites {
390                    texture_id,
391                    range: sprites_start..sprites_end,
392                })
393            }
394            PrimitiveKind::SubpixelSprite => {
395                let texture_id = self.subpixel_sprites_iter.peek().unwrap().tile.texture_id;
396                let sprites_start = self.subpixel_sprites_start;
397                let mut sprites_end = sprites_start + 1;
398                self.subpixel_sprites_iter.next();
399                while self
400                    .subpixel_sprites_iter
401                    .next_if(|sprite| {
402                        (sprite.order, batch_kind) < max_order_and_kind
403                            && sprite.tile.texture_id == texture_id
404                    })
405                    .is_some()
406                {
407                    sprites_end += 1;
408                }
409                self.subpixel_sprites_start = sprites_end;
410                Some(PrimitiveBatch::SubpixelSprites {
411                    texture_id,
412                    range: sprites_start..sprites_end,
413                })
414            }
415            PrimitiveKind::PolychromeSprite => {
416                let texture_id = self.polychrome_sprites_iter.peek().unwrap().tile.texture_id;
417                let sprites_start = self.polychrome_sprites_start;
418                let mut sprites_end = sprites_start + 1;
419                self.polychrome_sprites_iter.next();
420                while self
421                    .polychrome_sprites_iter
422                    .next_if(|sprite| {
423                        (sprite.order, batch_kind) < max_order_and_kind
424                            && sprite.tile.texture_id == texture_id
425                    })
426                    .is_some()
427                {
428                    sprites_end += 1;
429                }
430                self.polychrome_sprites_start = sprites_end;
431                Some(PrimitiveBatch::PolychromeSprites {
432                    texture_id,
433                    range: sprites_start..sprites_end,
434                })
435            }
436            PrimitiveKind::Surface => {
437                let surfaces_start = self.surfaces_start;
438                let mut surfaces_end = surfaces_start + 1;
439                self.surfaces_iter.next();
440                while self
441                    .surfaces_iter
442                    .next_if(|surface| (surface.order, batch_kind) < max_order_and_kind)
443                    .is_some()
444                {
445                    surfaces_end += 1;
446                }
447                self.surfaces_start = surfaces_end;
448                Some(PrimitiveBatch::Surfaces(surfaces_start..surfaces_end))
449            }
450        }
451    }
452}
453
454#[derive(Debug)]
455#[cfg_attr(
456    all(
457        any(target_os = "linux", target_os = "freebsd"),
458        not(any(feature = "x11", feature = "wayland"))
459    ),
460    allow(dead_code)
461)]
462#[allow(missing_docs)]
463pub enum PrimitiveBatch {
464    Shadows(Range<usize>),
465    Quads(Range<usize>),
466    Paths(Range<usize>),
467    Underlines(Range<usize>),
468    MonochromeSprites {
469        texture_id: AtlasTextureId,
470        range: Range<usize>,
471    },
472    #[cfg_attr(target_os = "macos", allow(dead_code))]
473    SubpixelSprites {
474        texture_id: AtlasTextureId,
475        range: Range<usize>,
476    },
477    PolychromeSprites {
478        texture_id: AtlasTextureId,
479        range: Range<usize>,
480    },
481    Surfaces(Range<usize>),
482}
483
484#[derive(Default, Debug, Clone)]
485#[repr(C)]
486#[expect(missing_docs)]
487pub struct Quad {
488    pub order: DrawOrder,
489    pub border_style: BorderStyle,
490    pub bounds: Bounds<ScaledPixels>,
491    pub content_mask: ContentMask<ScaledPixels>,
492    pub background: Background,
493    pub border_color: Hsla,
494    pub corner_radii: Corners<ScaledPixels>,
495    pub border_widths: Edges<ScaledPixels>,
496}
497
498impl From<Quad> for Primitive {
499    fn from(quad: Quad) -> Self {
500        Primitive::Quad(quad)
501    }
502}
503
504#[derive(Debug, Clone)]
505#[repr(C)]
506#[expect(missing_docs)]
507pub struct Underline {
508    pub order: DrawOrder,
509    pub pad: u32, // align to 8 bytes
510    pub bounds: Bounds<ScaledPixels>,
511    pub content_mask: ContentMask<ScaledPixels>,
512    pub color: Hsla,
513    pub thickness: ScaledPixels,
514    pub wavy: u32,
515}
516
517impl From<Underline> for Primitive {
518    fn from(underline: Underline) -> Self {
519        Primitive::Underline(underline)
520    }
521}
522
523#[derive(Debug, Clone)]
524#[repr(C)]
525#[expect(missing_docs)]
526pub struct Shadow {
527    pub order: DrawOrder,
528    pub blur_radius: ScaledPixels,
529    pub bounds: Bounds<ScaledPixels>,
530    pub corner_radii: Corners<ScaledPixels>,
531    pub content_mask: ContentMask<ScaledPixels>,
532    pub color: Hsla,
533}
534
535impl From<Shadow> for Primitive {
536    fn from(shadow: Shadow) -> Self {
537        Primitive::Shadow(shadow)
538    }
539}
540
541/// The style of a border.
542#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
543#[repr(C)]
544pub enum BorderStyle {
545    /// A solid border.
546    #[default]
547    Solid = 0,
548    /// A dashed border.
549    Dashed = 1,
550}
551
552/// A data type representing a 2 dimensional transformation that can be applied to an element.
553#[derive(Debug, Clone, Copy, PartialEq)]
554#[repr(C)]
555pub struct TransformationMatrix {
556    /// 2x2 matrix containing rotation and scale,
557    /// stored row-major
558    pub rotation_scale: [[f32; 2]; 2],
559    /// translation vector
560    pub translation: [f32; 2],
561}
562
563impl Eq for TransformationMatrix {}
564
565impl TransformationMatrix {
566    /// The unit matrix, has no effect.
567    pub fn unit() -> Self {
568        Self {
569            rotation_scale: [[1.0, 0.0], [0.0, 1.0]],
570            translation: [0.0, 0.0],
571        }
572    }
573
574    /// Move the origin by a given point
575    pub fn translate(mut self, point: Point<ScaledPixels>) -> Self {
576        self.compose(Self {
577            rotation_scale: [[1.0, 0.0], [0.0, 1.0]],
578            translation: [point.x.0, point.y.0],
579        })
580    }
581
582    /// Clockwise rotation in radians around the origin
583    pub fn rotate(self, angle: Radians) -> Self {
584        self.compose(Self {
585            rotation_scale: [
586                [angle.0.cos(), -angle.0.sin()],
587                [angle.0.sin(), angle.0.cos()],
588            ],
589            translation: [0.0, 0.0],
590        })
591    }
592
593    /// Scale around the origin
594    pub fn scale(self, size: Size<f32>) -> Self {
595        self.compose(Self {
596            rotation_scale: [[size.width, 0.0], [0.0, size.height]],
597            translation: [0.0, 0.0],
598        })
599    }
600
601    /// Perform matrix multiplication with another transformation
602    /// to produce a new transformation that is the result of
603    /// applying both transformations: first, `other`, then `self`.
604    #[inline]
605    pub fn compose(self, other: TransformationMatrix) -> TransformationMatrix {
606        if other == Self::unit() {
607            return self;
608        }
609        // Perform matrix multiplication
610        TransformationMatrix {
611            rotation_scale: [
612                [
613                    self.rotation_scale[0][0] * other.rotation_scale[0][0]
614                        + self.rotation_scale[0][1] * other.rotation_scale[1][0],
615                    self.rotation_scale[0][0] * other.rotation_scale[0][1]
616                        + self.rotation_scale[0][1] * other.rotation_scale[1][1],
617                ],
618                [
619                    self.rotation_scale[1][0] * other.rotation_scale[0][0]
620                        + self.rotation_scale[1][1] * other.rotation_scale[1][0],
621                    self.rotation_scale[1][0] * other.rotation_scale[0][1]
622                        + self.rotation_scale[1][1] * other.rotation_scale[1][1],
623                ],
624            ],
625            translation: [
626                self.translation[0]
627                    + self.rotation_scale[0][0] * other.translation[0]
628                    + self.rotation_scale[0][1] * other.translation[1],
629                self.translation[1]
630                    + self.rotation_scale[1][0] * other.translation[0]
631                    + self.rotation_scale[1][1] * other.translation[1],
632            ],
633        }
634    }
635
636    /// Apply transformation to a point, mainly useful for debugging
637    pub fn apply(&self, point: Point<Pixels>) -> Point<Pixels> {
638        let input = [point.x.0, point.y.0];
639        let mut output = self.translation;
640        for (i, output_cell) in output.iter_mut().enumerate() {
641            for (k, input_cell) in input.iter().enumerate() {
642                *output_cell += self.rotation_scale[i][k] * *input_cell;
643            }
644        }
645        Point::new(output[0].into(), output[1].into())
646    }
647}
648
649impl Default for TransformationMatrix {
650    fn default() -> Self {
651        Self::unit()
652    }
653}
654
655#[derive(Clone, Debug)]
656#[repr(C)]
657#[expect(missing_docs)]
658pub struct MonochromeSprite {
659    pub order: DrawOrder,
660    pub pad: u32, // align to 8 bytes
661    pub bounds: Bounds<ScaledPixels>,
662    pub content_mask: ContentMask<ScaledPixels>,
663    pub color: Hsla,
664    pub tile: AtlasTile,
665    pub transformation: TransformationMatrix,
666}
667
668impl From<MonochromeSprite> for Primitive {
669    fn from(sprite: MonochromeSprite) -> Self {
670        Primitive::MonochromeSprite(sprite)
671    }
672}
673
674#[derive(Clone, Debug)]
675#[repr(C)]
676#[expect(missing_docs)]
677pub struct SubpixelSprite {
678    pub order: DrawOrder,
679    pub pad: u32, // align to 8 bytes
680    pub bounds: Bounds<ScaledPixels>,
681    pub content_mask: ContentMask<ScaledPixels>,
682    pub color: Hsla,
683    pub tile: AtlasTile,
684    pub transformation: TransformationMatrix,
685}
686
687impl From<SubpixelSprite> for Primitive {
688    fn from(sprite: SubpixelSprite) -> Self {
689        Primitive::SubpixelSprite(sprite)
690    }
691}
692
693#[derive(Clone, Debug)]
694#[repr(C)]
695#[expect(missing_docs)]
696pub struct PolychromeSprite {
697    pub order: DrawOrder,
698    pub pad: u32, // align to 8 bytes
699    pub grayscale: bool,
700    pub opacity: f32,
701    pub bounds: Bounds<ScaledPixels>,
702    pub content_mask: ContentMask<ScaledPixels>,
703    pub corner_radii: Corners<ScaledPixels>,
704    pub tile: AtlasTile,
705}
706
707impl From<PolychromeSprite> for Primitive {
708    fn from(sprite: PolychromeSprite) -> Self {
709        Primitive::PolychromeSprite(sprite)
710    }
711}
712
713#[derive(Clone, Debug)]
714#[allow(missing_docs)]
715pub struct PaintSurface {
716    pub order: DrawOrder,
717    pub bounds: Bounds<ScaledPixels>,
718    pub content_mask: ContentMask<ScaledPixels>,
719    #[cfg(target_os = "macos")]
720    pub image_buffer: core_video::pixel_buffer::CVPixelBuffer,
721}
722
723impl From<PaintSurface> for Primitive {
724    fn from(surface: PaintSurface) -> Self {
725        Primitive::Surface(surface)
726    }
727}
728
729#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
730#[expect(missing_docs)]
731pub struct PathId(pub usize);
732
733/// A line made up of a series of vertices and control points.
734#[derive(Clone, Debug)]
735#[expect(missing_docs)]
736pub struct Path<P: Clone + Debug + Default + PartialEq> {
737    pub id: PathId,
738    pub order: DrawOrder,
739    pub bounds: Bounds<P>,
740    pub content_mask: ContentMask<P>,
741    pub vertices: Vec<PathVertex<P>>,
742    pub color: Background,
743    start: Point<P>,
744    current: Point<P>,
745    contour_count: usize,
746}
747
748impl Path<Pixels> {
749    /// Create a new path with the given starting point.
750    pub fn new(start: Point<Pixels>) -> Self {
751        Self {
752            id: PathId(0),
753            order: DrawOrder::default(),
754            vertices: Vec::new(),
755            start,
756            current: start,
757            bounds: Bounds {
758                origin: start,
759                size: Default::default(),
760            },
761            content_mask: Default::default(),
762            color: Default::default(),
763            contour_count: 0,
764        }
765    }
766
767    /// Scale this path by the given factor.
768    pub fn scale(&self, factor: f32) -> Path<ScaledPixels> {
769        Path {
770            id: self.id,
771            order: self.order,
772            bounds: self.bounds.scale(factor),
773            content_mask: self.content_mask.scale(factor),
774            vertices: self
775                .vertices
776                .iter()
777                .map(|vertex| vertex.scale(factor))
778                .collect(),
779            start: self.start.map(|start| start.scale(factor)),
780            current: self.current.scale(factor),
781            contour_count: self.contour_count,
782            color: self.color,
783        }
784    }
785
786    /// Move the start, current point to the given point.
787    pub fn move_to(&mut self, to: Point<Pixels>) {
788        self.contour_count += 1;
789        self.start = to;
790        self.current = to;
791    }
792
793    /// Draw a straight line from the current point to the given point.
794    pub fn line_to(&mut self, to: Point<Pixels>) {
795        self.contour_count += 1;
796        if self.contour_count > 1 {
797            self.push_triangle(
798                (self.start, self.current, to),
799                (point(0., 1.), point(0., 1.), point(0., 1.)),
800            );
801        }
802        self.current = to;
803    }
804
805    /// Draw a curve from the current point to the given point, using the given control point.
806    pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
807        self.contour_count += 1;
808        if self.contour_count > 1 {
809            self.push_triangle(
810                (self.start, self.current, to),
811                (point(0., 1.), point(0., 1.), point(0., 1.)),
812            );
813        }
814
815        self.push_triangle(
816            (self.current, ctrl, to),
817            (point(0., 0.), point(0.5, 0.), point(1., 1.)),
818        );
819        self.current = to;
820    }
821
822    /// Push a triangle to the Path.
823    pub fn push_triangle(
824        &mut self,
825        xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>),
826        st: (Point<f32>, Point<f32>, Point<f32>),
827    ) {
828        self.bounds = self
829            .bounds
830            .union(&Bounds {
831                origin: xy.0,
832                size: Default::default(),
833            })
834            .union(&Bounds {
835                origin: xy.1,
836                size: Default::default(),
837            })
838            .union(&Bounds {
839                origin: xy.2,
840                size: Default::default(),
841            });
842
843        self.vertices.push(PathVertex {
844            xy_position: xy.0,
845            st_position: st.0,
846            content_mask: Default::default(),
847        });
848        self.vertices.push(PathVertex {
849            xy_position: xy.1,
850            st_position: st.1,
851            content_mask: Default::default(),
852        });
853        self.vertices.push(PathVertex {
854            xy_position: xy.2,
855            st_position: st.2,
856            content_mask: Default::default(),
857        });
858    }
859}
860
861impl<T> Path<T>
862where
863    T: Clone + Debug + Default + PartialEq + PartialOrd + Add<T, Output = T> + Sub<Output = T>,
864{
865    #[allow(unused)]
866    #[expect(missing_docs)]
867    pub fn clipped_bounds(&self) -> Bounds<T> {
868        self.bounds.intersect(&self.content_mask.bounds)
869    }
870}
871
872impl From<Path<ScaledPixels>> for Primitive {
873    fn from(path: Path<ScaledPixels>) -> Self {
874        Primitive::Path(path)
875    }
876}
877
878#[derive(Clone, Debug)]
879#[repr(C)]
880#[expect(missing_docs)]
881pub struct PathVertex<P: Clone + Debug + Default + PartialEq> {
882    pub xy_position: Point<P>,
883    pub st_position: Point<f32>,
884    pub content_mask: ContentMask<P>,
885}
886
887#[expect(missing_docs)]
888impl PathVertex<Pixels> {
889    pub fn scale(&self, factor: f32) -> PathVertex<ScaledPixels> {
890        PathVertex {
891            xy_position: self.xy_position.scale(factor),
892            st_position: self.st_position,
893            content_mask: self.content_mask.scale(factor),
894        }
895    }
896}