Skip to main content

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