Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T03:34:22.260Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

scene.rs

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