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