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