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