Skip to main content

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