Skip to main content

gpui_engine/
scene.rs

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