Skip to repository content950 lines · 31.1 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T02:36:10.518Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
scene.rs
1// todo("windows"): remove
2#![cfg_attr(windows, allow(dead_code))]
3
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7use crate::{
8 AtlasTextureId, AtlasTile, Background, Bounds, ContentMask, Corners, Edges, Hsla, Pixels,
9 Point, Radians, ScaledPixels, Size, bounds_tree::BoundsTree, point,
10};
11use std::{
12 fmt::Debug,
13 iter::Peekable,
14 ops::{Add, Range, Sub},
15 slice,
16};
17
18#[allow(non_camel_case_types, unused)]
19#[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