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