Skip to main content

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