Skip to main content

open_gpui/
scene.rs

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