1#![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#[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 pub backdrop_blurs: Vec<BackdropBlur>,
57}
58
59#[expect(missing_docs)]
60impl Scene {
61 pub fn clear(&mut self) {
62 self.paint_operations.clear();
63 self.primitive_bounds.clear();
64 self.layer_stack.clear();
65 self.paths.clear();
66 self.shadows.clear();
67 self.quads.clear();
68 self.underlines.clear();
69 self.monochrome_sprites.clear();
70 self.subpixel_sprites.clear();
71 self.polychrome_sprites.clear();
72 self.surfaces.clear();
73 self.backdrop_blurs.clear();
74 }
75
76 pub fn len(&self) -> usize {
77 self.paint_operations.len()
78 }
79
80 pub fn push_layer(&mut self, bounds: Bounds<ScaledPixels>) {
81 let order = self.primitive_bounds.insert(bounds);
82 self.layer_stack.push(order);
83 self.paint_operations
84 .push(PaintOperation::StartLayer(bounds));
85 }
86
87 pub fn pop_layer(&mut self) {
88 self.layer_stack.pop();
89 self.paint_operations.push(PaintOperation::EndLayer);
90 }
91
92 pub fn batch_first_order(&self, batch: &PrimitiveBatch) -> DrawOrder {
95 match batch {
96 PrimitiveBatch::Shadows(range) => self.shadows[range.start].order,
97 PrimitiveBatch::Quads(range) => self.quads[range.start].order,
98 PrimitiveBatch::Paths(range) => self.paths[range.start].order,
99 PrimitiveBatch::Underlines(range) => self.underlines[range.start].order,
100 PrimitiveBatch::MonochromeSprites { range, .. } => {
101 self.monochrome_sprites[range.start].order
102 }
103 PrimitiveBatch::SubpixelSprites { range, .. } => {
104 self.subpixel_sprites[range.start].order
105 }
106 PrimitiveBatch::PolychromeSprites { range, .. } => {
107 self.polychrome_sprites[range.start].order
108 }
109 PrimitiveBatch::Surfaces(range) => self.surfaces[range.start].order,
110 }
111 }
112
113 pub fn insert_backdrop_blur(&mut self, mut blur: BackdropBlur) {
114 let clipped_bounds = blur.bounds.intersect(&blur.content_mask.bounds);
115 if clipped_bounds.is_empty() {
116 return;
117 }
118 blur.order = self
119 .layer_stack
120 .last()
121 .copied()
122 .unwrap_or_else(|| self.primitive_bounds.insert(clipped_bounds));
123 self.backdrop_blurs.push(blur);
124 self.paint_operations
125 .push(PaintOperation::BackdropBlur(blur));
126 }
127
128 pub fn insert_primitive(&mut self, primitive: impl Into<Primitive>) {
129 let mut primitive = primitive.into();
130 let clipped_bounds = primitive
131 .bounds()
132 .intersect(&primitive.content_mask().bounds);
133
134 if clipped_bounds.is_empty() {
135 return;
136 }
137
138 let order = self
139 .layer_stack
140 .last()
141 .copied()
142 .unwrap_or_else(|| self.primitive_bounds.insert(clipped_bounds));
143 match &mut primitive {
144 Primitive::Shadow(shadow) => {
145 shadow.order = order;
146 self.shadows.push(*shadow);
147 }
148 Primitive::Quad(quad) => {
149 quad.order = order;
150 self.quads.push(*quad);
151 }
152 Primitive::Path(path) => {
153 path.order = order;
154 path.id = PathId(self.paths.len());
155 self.paths.push(path.clone());
156 }
157 Primitive::Underline(underline) => {
158 underline.order = order;
159 self.underlines.push(*underline);
160 }
161 Primitive::MonochromeSprite(sprite) => {
162 sprite.order = order;
163 self.monochrome_sprites.push(*sprite);
164 }
165 Primitive::SubpixelSprite(sprite) => {
166 sprite.order = order;
167 self.subpixel_sprites.push(*sprite);
168 }
169 Primitive::PolychromeSprite(sprite) => {
170 sprite.order = order;
171 self.polychrome_sprites.push(*sprite);
172 }
173 Primitive::Surface(surface) => {
174 surface.order = order;
175 self.surfaces.push(surface.clone());
176 }
177 }
178 self.paint_operations
179 .push(PaintOperation::Primitive(primitive));
180 }
181
182 pub fn replay(&mut self, range: Range<usize>, prev_scene: &Scene) {
183 for operation in &prev_scene.paint_operations[range] {
184 match operation {
185 PaintOperation::Primitive(primitive) => self.insert_primitive(primitive.clone()),
186 PaintOperation::BackdropBlur(blur) => self.insert_backdrop_blur(*blur),
187 PaintOperation::StartLayer(bounds) => self.push_layer(*bounds),
188 PaintOperation::EndLayer => self.pop_layer(),
189 }
190 }
191 }
192
193 pub fn finish(&mut self) {
194 self.shadows.sort_by_key(|shadow| shadow.order);
195 self.quads.sort_by_key(|quad| quad.order);
196 self.paths.sort_by_key(|path| path.order);
197 self.underlines.sort_by_key(|underline| underline.order);
198 self.monochrome_sprites
199 .sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
200 self.subpixel_sprites
201 .sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
202 self.polychrome_sprites
203 .sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
204 self.surfaces.sort_by_key(|surface| surface.order);
205 self.backdrop_blurs.sort_by_key(|blur| blur.order);
206 }
207
208 #[cfg_attr(
209 all(
210 any(target_os = "linux", target_os = "freebsd"),
211 not(any(feature = "x11", feature = "wayland"))
212 ),
213 allow(dead_code)
214 )]
215 pub fn batches(&self) -> impl Iterator<Item = PrimitiveBatch> + '_ {
216 BatchIterator {
217 shadows_start: 0,
218 shadows_iter: self.shadows.iter().peekable(),
219 quads_start: 0,
220 quads_iter: self.quads.iter().peekable(),
221 paths_start: 0,
222 paths_iter: self.paths.iter().peekable(),
223 underlines_start: 0,
224 underlines_iter: self.underlines.iter().peekable(),
225 monochrome_sprites_start: 0,
226 monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(),
227 subpixel_sprites_start: 0,
228 subpixel_sprites_iter: self.subpixel_sprites.iter().peekable(),
229 polychrome_sprites_start: 0,
230 polychrome_sprites_iter: self.polychrome_sprites.iter().peekable(),
231 surfaces_start: 0,
232 surfaces_iter: self.surfaces.iter().peekable(),
233 }
234 }
235}
236
237#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)]
238#[cfg_attr(
239 all(
240 any(target_os = "linux", target_os = "freebsd"),
241 not(any(feature = "x11", feature = "wayland"))
242 ),
243 allow(dead_code)
244)]
245pub(crate) enum PrimitiveKind {
246 Shadow,
247 #[default]
248 Quad,
249 Path,
250 Underline,
251 MonochromeSprite,
252 SubpixelSprite,
253 PolychromeSprite,
254 Surface,
255}
256
257pub(crate) enum PaintOperation {
258 Primitive(Primitive),
259 BackdropBlur(BackdropBlur),
260 StartLayer(Bounds<ScaledPixels>),
261 EndLayer,
262}
263
264#[derive(Clone)]
265#[expect(missing_docs)]
266pub enum Primitive {
267 Shadow(Shadow),
268 Quad(Quad),
269 Path(Path<ScaledPixels>),
270 Underline(Underline),
271 MonochromeSprite(MonochromeSprite),
272 SubpixelSprite(SubpixelSprite),
273 PolychromeSprite(PolychromeSprite),
274 Surface(PaintSurface),
275}
276
277#[expect(missing_docs)]
278impl Primitive {
279 pub fn bounds(&self) -> &Bounds<ScaledPixels> {
280 match self {
281 Primitive::Shadow(shadow) => &shadow.bounds,
282 Primitive::Quad(quad) => &quad.bounds,
283 Primitive::Path(path) => &path.bounds,
284 Primitive::Underline(underline) => &underline.bounds,
285 Primitive::MonochromeSprite(sprite) => &sprite.bounds,
286 Primitive::SubpixelSprite(sprite) => &sprite.bounds,
287 Primitive::PolychromeSprite(sprite) => &sprite.bounds,
288 Primitive::Surface(surface) => &surface.bounds,
289 }
290 }
291
292 pub fn content_mask(&self) -> &ContentMask<ScaledPixels> {
293 match self {
294 Primitive::Shadow(shadow) => &shadow.content_mask,
295 Primitive::Quad(quad) => &quad.content_mask,
296 Primitive::Path(path) => &path.content_mask,
297 Primitive::Underline(underline) => &underline.content_mask,
298 Primitive::MonochromeSprite(sprite) => &sprite.content_mask,
299 Primitive::SubpixelSprite(sprite) => &sprite.content_mask,
300 Primitive::PolychromeSprite(sprite) => &sprite.content_mask,
301 Primitive::Surface(surface) => &surface.content_mask,
302 }
303 }
304}
305
306#[cfg_attr(
307 all(
308 any(target_os = "linux", target_os = "freebsd"),
309 not(any(feature = "x11", feature = "wayland"))
310 ),
311 allow(dead_code)
312)]
313struct BatchIterator<'a> {
314 shadows_start: usize,
315 shadows_iter: Peekable<slice::Iter<'a, Shadow>>,
316 quads_start: usize,
317 quads_iter: Peekable<slice::Iter<'a, Quad>>,
318 paths_start: usize,
319 paths_iter: Peekable<slice::Iter<'a, Path<ScaledPixels>>>,
320 underlines_start: usize,
321 underlines_iter: Peekable<slice::Iter<'a, Underline>>,
322 monochrome_sprites_start: usize,
323 monochrome_sprites_iter: Peekable<slice::Iter<'a, MonochromeSprite>>,
324 subpixel_sprites_start: usize,
325 subpixel_sprites_iter: Peekable<slice::Iter<'a, SubpixelSprite>>,
326 polychrome_sprites_start: usize,
327 polychrome_sprites_iter: Peekable<slice::Iter<'a, PolychromeSprite>>,
328 surfaces_start: usize,
329 surfaces_iter: Peekable<slice::Iter<'a, PaintSurface>>,
330}
331
332impl<'a> Iterator for BatchIterator<'a> {
333 type Item = PrimitiveBatch;
334
335 fn next(&mut self) -> Option<Self::Item> {
336 let mut orders_and_kinds = [
337 (
338 self.shadows_iter.peek().map(|s| s.order),
339 PrimitiveKind::Shadow,
340 ),
341 (self.quads_iter.peek().map(|q| q.order), PrimitiveKind::Quad),
342 (self.paths_iter.peek().map(|q| q.order), PrimitiveKind::Path),
343 (
344 self.underlines_iter.peek().map(|u| u.order),
345 PrimitiveKind::Underline,
346 ),
347 (
348 self.monochrome_sprites_iter.peek().map(|s| s.order),
349 PrimitiveKind::MonochromeSprite,
350 ),
351 (
352 self.subpixel_sprites_iter.peek().map(|s| s.order),
353 PrimitiveKind::SubpixelSprite,
354 ),
355 (
356 self.polychrome_sprites_iter.peek().map(|s| s.order),
357 PrimitiveKind::PolychromeSprite,
358 ),
359 (
360 self.surfaces_iter.peek().map(|s| s.order),
361 PrimitiveKind::Surface,
362 ),
363 ];
364 orders_and_kinds.sort_by_key(|(order, kind)| (order.unwrap_or(u32::MAX), *kind));
365
366 let first = orders_and_kinds[0];
367 let second = orders_and_kinds[1];
368 let (batch_kind, max_order_and_kind) = if first.0.is_some() {
369 (first.1, (second.0.unwrap_or(u32::MAX), second.1))
370 } else {
371 return None;
372 };
373
374 match batch_kind {
375 PrimitiveKind::Shadow => {
376 let shadows_start = self.shadows_start;
377 let mut shadows_end = shadows_start + 1;
378 self.shadows_iter.next();
379 while self
380 .shadows_iter
381 .next_if(|shadow| (shadow.order, batch_kind) < max_order_and_kind)
382 .is_some()
383 {
384 shadows_end += 1;
385 }
386 self.shadows_start = shadows_end;
387 Some(PrimitiveBatch::Shadows(shadows_start..shadows_end))
388 }
389 PrimitiveKind::Quad => {
390 let quads_start = self.quads_start;
391 let mut quads_end = quads_start + 1;
392 self.quads_iter.next();
393 while self
394 .quads_iter
395 .next_if(|quad| (quad.order, batch_kind) < max_order_and_kind)
396 .is_some()
397 {
398 quads_end += 1;
399 }
400 self.quads_start = quads_end;
401 Some(PrimitiveBatch::Quads(quads_start..quads_end))
402 }
403 PrimitiveKind::Path => {
404 let paths_start = self.paths_start;
405 let mut paths_end = paths_start + 1;
406 self.paths_iter.next();
407 while self
408 .paths_iter
409 .next_if(|path| (path.order, batch_kind) < max_order_and_kind)
410 .is_some()
411 {
412 paths_end += 1;
413 }
414 self.paths_start = paths_end;
415 Some(PrimitiveBatch::Paths(paths_start..paths_end))
416 }
417 PrimitiveKind::Underline => {
418 let underlines_start = self.underlines_start;
419 let mut underlines_end = underlines_start + 1;
420 self.underlines_iter.next();
421 while self
422 .underlines_iter
423 .next_if(|underline| (underline.order, batch_kind) < max_order_and_kind)
424 .is_some()
425 {
426 underlines_end += 1;
427 }
428 self.underlines_start = underlines_end;
429 Some(PrimitiveBatch::Underlines(underlines_start..underlines_end))
430 }
431 PrimitiveKind::MonochromeSprite => {
432 let texture_id = self.monochrome_sprites_iter.peek().unwrap().tile.texture_id;
433 let sprites_start = self.monochrome_sprites_start;
434 let mut sprites_end = sprites_start + 1;
435 self.monochrome_sprites_iter.next();
436 while self
437 .monochrome_sprites_iter
438 .next_if(|sprite| {
439 (sprite.order, batch_kind) < max_order_and_kind
440 && sprite.tile.texture_id == texture_id
441 })
442 .is_some()
443 {
444 sprites_end += 1;
445 }
446 self.monochrome_sprites_start = sprites_end;
447 Some(PrimitiveBatch::MonochromeSprites {
448 texture_id,
449 range: sprites_start..sprites_end,
450 })
451 }
452 PrimitiveKind::SubpixelSprite => {
453 let texture_id = self.subpixel_sprites_iter.peek().unwrap().tile.texture_id;
454 let sprites_start = self.subpixel_sprites_start;
455 let mut sprites_end = sprites_start + 1;
456 self.subpixel_sprites_iter.next();
457 while self
458 .subpixel_sprites_iter
459 .next_if(|sprite| {
460 (sprite.order, batch_kind) < max_order_and_kind
461 && sprite.tile.texture_id == texture_id
462 })
463 .is_some()
464 {
465 sprites_end += 1;
466 }
467 self.subpixel_sprites_start = sprites_end;
468 Some(PrimitiveBatch::SubpixelSprites {
469 texture_id,
470 range: sprites_start..sprites_end,
471 })
472 }
473 PrimitiveKind::PolychromeSprite => {
474 let texture_id = self.polychrome_sprites_iter.peek().unwrap().tile.texture_id;
475 let sprites_start = self.polychrome_sprites_start;
476 let mut sprites_end = sprites_start + 1;
477 self.polychrome_sprites_iter.next();
478 while self
479 .polychrome_sprites_iter
480 .next_if(|sprite| {
481 (sprite.order, batch_kind) < max_order_and_kind
482 && sprite.tile.texture_id == texture_id
483 })
484 .is_some()
485 {
486 sprites_end += 1;
487 }
488 self.polychrome_sprites_start = sprites_end;
489 Some(PrimitiveBatch::PolychromeSprites {
490 texture_id,
491 range: sprites_start..sprites_end,
492 })
493 }
494 PrimitiveKind::Surface => {
495 let surfaces_start = self.surfaces_start;
496 let mut surfaces_end = surfaces_start + 1;
497 self.surfaces_iter.next();
498 while self
499 .surfaces_iter
500 .next_if(|surface| (surface.order, batch_kind) < max_order_and_kind)
501 .is_some()
502 {
503 surfaces_end += 1;
504 }
505 self.surfaces_start = surfaces_end;
506 Some(PrimitiveBatch::Surfaces(surfaces_start..surfaces_end))
507 }
508 }
509 }
510}
511
512#[derive(Debug)]
513#[cfg_attr(
514 all(
515 any(target_os = "linux", target_os = "freebsd"),
516 not(any(feature = "x11", feature = "wayland"))
517 ),
518 allow(dead_code)
519)]
520#[allow(missing_docs)]
521pub enum PrimitiveBatch {
522 Shadows(Range<usize>),
523 Quads(Range<usize>),
524 Paths(Range<usize>),
525 Underlines(Range<usize>),
526 MonochromeSprites {
527 texture_id: AtlasTextureId,
528 range: Range<usize>,
529 },
530 #[cfg_attr(target_os = "macos", allow(dead_code))]
531 SubpixelSprites {
532 texture_id: AtlasTextureId,
533 range: Range<usize>,
534 },
535 PolychromeSprites {
536 texture_id: AtlasTextureId,
537 range: Range<usize>,
538 },
539 Surfaces(Range<usize>),
540}
541
542impl PrimitiveBatch {
543 #[expect(missing_docs)]
544 pub fn label(&self) -> String {
545 match self {
546 Self::Shadows(range) => format!("shadows ({})", range.len()),
547 Self::Quads(range) => format!("quads ({})", range.len()),
548 Self::Paths(range) => format!("paths ({})", range.len()),
549 Self::Underlines(range) => format!("underlines ({})", range.len()),
550 Self::MonochromeSprites { texture_id, range } => {
551 format!(
552 "monochrome sprites ({}) on atlas {}",
553 range.len(),
554 texture_id.index
555 )
556 }
557 Self::SubpixelSprites { texture_id, range } => {
558 format!(
559 "subpixel sprites ({}) on atlas {}",
560 range.len(),
561 texture_id.index
562 )
563 }
564 Self::PolychromeSprites { texture_id, range } => {
565 format!(
566 "polychrome sprites ({}) on atlas {}",
567 range.len(),
568 texture_id.index
569 )
570 }
571 Self::Surfaces(range) => format!("surfaces ({})", range.len()),
572 }
573 }
574}
575
576#[derive(Default, Debug, Copy, Clone)]
577#[repr(C)]
578#[expect(missing_docs)]
579pub struct Quad {
580 pub order: DrawOrder,
581 pub border_style: BorderStyle,
582 pub bounds: Bounds<ScaledPixels>,
583 pub content_mask: ContentMask<ScaledPixels>,
584 pub background: Background,
585 pub border_color: Hsla,
586 pub corner_radii: Corners<ScaledPixels>,
587 pub border_widths: Edges<ScaledPixels>,
588}
589
590impl From<Quad> for Primitive {
591 fn from(quad: Quad) -> Self {
592 Primitive::Quad(quad)
593 }
594}
595
596#[derive(Debug, Copy, Clone)]
597#[repr(C)]
598#[expect(missing_docs)]
599pub struct Underline {
600 pub order: DrawOrder,
601 pub pad: u32, pub bounds: Bounds<ScaledPixels>,
603 pub content_mask: ContentMask<ScaledPixels>,
604 pub color: Hsla,
605 pub thickness: ScaledPixels,
606 pub wavy: PaddedBool32,
607}
608
609impl From<Underline> for Primitive {
610 fn from(underline: Underline) -> Self {
611 Primitive::Underline(underline)
612 }
613}
614
615#[derive(Debug, Copy, Clone)]
620#[repr(C)]
621#[expect(missing_docs)]
622pub struct BackdropBlur {
623 pub order: DrawOrder,
624 pub blur_radius: ScaledPixels,
625 pub bounds: Bounds<ScaledPixels>,
626 pub content_mask: ContentMask<ScaledPixels>,
627 pub corner_radii: Corners<ScaledPixels>,
628 pub lens: ScaledPixels,
630 pub reach: ScaledPixels,
632 pub magnify: f32,
636 pub dispersion: f32,
638 pub gain: f32,
640 pub saturation: f32,
644 pub tint: Hsla,
646 pub edge: f32,
648 pub edge_width: ScaledPixels,
650 pub edge_aa: ScaledPixels,
652 pub opacity: f32,
656}
657
658#[derive(Debug, Copy, Clone)]
659#[repr(C)]
660#[expect(missing_docs)]
661pub struct Shadow {
662 pub order: DrawOrder,
663 pub blur_radius: ScaledPixels,
664 pub bounds: Bounds<ScaledPixels>,
665 pub corner_radii: Corners<ScaledPixels>,
666 pub content_mask: ContentMask<ScaledPixels>,
667 pub color: Hsla,
668 pub element_bounds: Bounds<ScaledPixels>,
669 pub element_corner_radii: Corners<ScaledPixels>,
670 pub inset: u32,
676 pub pad: u32, }
678
679impl From<Shadow> for Primitive {
680 fn from(shadow: Shadow) -> Self {
681 Primitive::Shadow(shadow)
682 }
683}
684
685#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
687#[repr(C)]
688pub enum BorderStyle {
689 #[default]
691 Solid = 0,
692 Dashed = 1,
694}
695
696#[derive(Debug, Clone, Copy, PartialEq)]
698#[repr(C)]
699pub struct TransformationMatrix {
700 pub rotation_scale: [[f32; 2]; 2],
703 pub translation: [f32; 2],
705}
706
707impl Eq for TransformationMatrix {}
708
709impl TransformationMatrix {
710 pub fn unit() -> Self {
712 Self {
713 rotation_scale: [[1.0, 0.0], [0.0, 1.0]],
714 translation: [0.0, 0.0],
715 }
716 }
717
718 pub fn translate(mut self, point: Point<ScaledPixels>) -> Self {
720 self.compose(Self {
721 rotation_scale: [[1.0, 0.0], [0.0, 1.0]],
722 translation: [point.x.0, point.y.0],
723 })
724 }
725
726 pub fn rotate(self, angle: Radians) -> Self {
728 self.compose(Self {
729 rotation_scale: [
730 [angle.0.cos(), -angle.0.sin()],
731 [angle.0.sin(), angle.0.cos()],
732 ],
733 translation: [0.0, 0.0],
734 })
735 }
736
737 pub fn scale(self, size: Size<f32>) -> Self {
739 self.compose(Self {
740 rotation_scale: [[size.width, 0.0], [0.0, size.height]],
741 translation: [0.0, 0.0],
742 })
743 }
744
745 #[inline]
749 pub fn compose(self, other: TransformationMatrix) -> TransformationMatrix {
750 if other == Self::unit() {
751 return self;
752 }
753 TransformationMatrix {
755 rotation_scale: [
756 [
757 self.rotation_scale[0][0] * other.rotation_scale[0][0]
758 + self.rotation_scale[0][1] * other.rotation_scale[1][0],
759 self.rotation_scale[0][0] * other.rotation_scale[0][1]
760 + self.rotation_scale[0][1] * other.rotation_scale[1][1],
761 ],
762 [
763 self.rotation_scale[1][0] * other.rotation_scale[0][0]
764 + self.rotation_scale[1][1] * other.rotation_scale[1][0],
765 self.rotation_scale[1][0] * other.rotation_scale[0][1]
766 + self.rotation_scale[1][1] * other.rotation_scale[1][1],
767 ],
768 ],
769 translation: [
770 self.translation[0]
771 + self.rotation_scale[0][0] * other.translation[0]
772 + self.rotation_scale[0][1] * other.translation[1],
773 self.translation[1]
774 + self.rotation_scale[1][0] * other.translation[0]
775 + self.rotation_scale[1][1] * other.translation[1],
776 ],
777 }
778 }
779
780 pub fn apply(&self, point: Point<Pixels>) -> Point<Pixels> {
782 let input = [point.x.0, point.y.0];
783 let mut output = self.translation;
784 for (i, output_cell) in output.iter_mut().enumerate() {
785 for (k, input_cell) in input.iter().enumerate() {
786 *output_cell += self.rotation_scale[i][k] * *input_cell;
787 }
788 }
789 Point::new(output[0].into(), output[1].into())
790 }
791}
792
793impl Default for TransformationMatrix {
794 fn default() -> Self {
795 Self::unit()
796 }
797}
798
799#[derive(Copy, Clone, Debug)]
800#[repr(C)]
801#[expect(missing_docs)]
802pub struct MonochromeSprite {
803 pub order: DrawOrder,
804 pub pad: u32,
805 pub bounds: Bounds<ScaledPixels>,
806 pub content_mask: ContentMask<ScaledPixels>,
807 pub color: Hsla,
808 pub tile: AtlasTile,
809 pub transformation: TransformationMatrix,
810}
811
812impl From<MonochromeSprite> for Primitive {
813 fn from(sprite: MonochromeSprite) -> Self {
814 Primitive::MonochromeSprite(sprite)
815 }
816}
817
818#[derive(Copy, Clone, Debug)]
819#[repr(C)]
820#[expect(missing_docs)]
821pub struct SubpixelSprite {
822 pub order: DrawOrder,
823 pub pad: u32, pub bounds: Bounds<ScaledPixels>,
825 pub content_mask: ContentMask<ScaledPixels>,
826 pub color: Hsla,
827 pub tile: AtlasTile,
828 pub transformation: TransformationMatrix,
829}
830
831impl From<SubpixelSprite> for Primitive {
832 fn from(sprite: SubpixelSprite) -> Self {
833 Primitive::SubpixelSprite(sprite)
834 }
835}
836
837#[derive(Copy, Clone, Debug)]
838#[repr(C)]
839#[expect(missing_docs)]
840pub struct PolychromeSprite {
841 pub order: DrawOrder,
842 pub pad: u32,
843 pub grayscale: PaddedBool32,
844 pub opacity: f32,
845 pub bounds: Bounds<ScaledPixels>,
846 pub content_mask: ContentMask<ScaledPixels>,
847 pub corner_radii: Corners<ScaledPixels>,
848 pub tile: AtlasTile,
849}
850
851impl From<PolychromeSprite> for Primitive {
852 fn from(sprite: PolychromeSprite) -> Self {
853 Primitive::PolychromeSprite(sprite)
854 }
855}
856
857#[derive(Clone, Debug)]
858#[allow(missing_docs)]
859pub struct PaintSurface {
860 pub order: DrawOrder,
861 pub bounds: Bounds<ScaledPixels>,
862 pub content_mask: ContentMask<ScaledPixels>,
863 #[cfg(target_os = "macos")]
864 pub image_buffer: core_video::pixel_buffer::CVPixelBuffer,
865}
866
867impl From<PaintSurface> for Primitive {
868 fn from(surface: PaintSurface) -> Self {
869 Primitive::Surface(surface)
870 }
871}
872
873#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
874#[expect(missing_docs)]
875pub struct PathId(pub usize);
876
877#[derive(Clone, Debug)]
879#[expect(missing_docs)]
880pub struct Path<P: Clone + Debug + Default + PartialEq> {
881 pub id: PathId,
882 pub order: DrawOrder,
883 pub bounds: Bounds<P>,
884 pub content_mask: ContentMask<P>,
885 pub vertices: Vec<PathVertex<P>>,
886 pub color: Background,
887 start: Point<P>,
888 current: Point<P>,
889 contour_count: usize,
890}
891
892impl Path<Pixels> {
893 pub fn new(start: Point<Pixels>) -> Self {
895 Self {
896 id: PathId(0),
897 order: DrawOrder::default(),
898 vertices: Vec::new(),
899 start,
900 current: start,
901 bounds: Bounds {
902 origin: start,
903 size: Default::default(),
904 },
905 content_mask: Default::default(),
906 color: Default::default(),
907 contour_count: 0,
908 }
909 }
910
911 pub fn scale(&self, factor: f32) -> Path<ScaledPixels> {
913 Path {
914 id: self.id,
915 order: self.order,
916 bounds: self.bounds.scale(factor),
917 content_mask: self.content_mask.scale(factor),
918 vertices: self
919 .vertices
920 .iter()
921 .map(|vertex| vertex.scale(factor))
922 .collect(),
923 start: self.start.map(|start| start.scale(factor)),
924 current: self.current.scale(factor),
925 contour_count: self.contour_count,
926 color: self.color,
927 }
928 }
929
930 pub fn move_to(&mut self, to: Point<Pixels>) {
932 self.contour_count += 1;
933 self.start = to;
934 self.current = to;
935 }
936
937 pub fn line_to(&mut self, to: Point<Pixels>) {
939 self.contour_count += 1;
940 if self.contour_count > 1 {
941 self.push_triangle(
942 (self.start, self.current, to),
943 (point(0., 1.), point(0., 1.), point(0., 1.)),
944 );
945 }
946 self.current = to;
947 }
948
949 pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
951 self.contour_count += 1;
952 if self.contour_count > 1 {
953 self.push_triangle(
954 (self.start, self.current, to),
955 (point(0., 1.), point(0., 1.), point(0., 1.)),
956 );
957 }
958
959 self.push_triangle(
960 (self.current, ctrl, to),
961 (point(0., 0.), point(0.5, 0.), point(1., 1.)),
962 );
963 self.current = to;
964 }
965
966 pub fn push_triangle(
968 &mut self,
969 xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>),
970 st: (Point<f32>, Point<f32>, Point<f32>),
971 ) {
972 self.bounds = self
973 .bounds
974 .union(&Bounds {
975 origin: xy.0,
976 size: Default::default(),
977 })
978 .union(&Bounds {
979 origin: xy.1,
980 size: Default::default(),
981 })
982 .union(&Bounds {
983 origin: xy.2,
984 size: Default::default(),
985 });
986
987 self.vertices.push(PathVertex {
988 xy_position: xy.0,
989 st_position: st.0,
990 content_mask: Default::default(),
991 });
992 self.vertices.push(PathVertex {
993 xy_position: xy.1,
994 st_position: st.1,
995 content_mask: Default::default(),
996 });
997 self.vertices.push(PathVertex {
998 xy_position: xy.2,
999 st_position: st.2,
1000 content_mask: Default::default(),
1001 });
1002 }
1003}
1004
1005impl<T> Path<T>
1006where
1007 T: Clone + Debug + Default + PartialEq + PartialOrd + Add<T, Output = T> + Sub<Output = T>,
1008{
1009 #[allow(unused)]
1010 #[expect(missing_docs)]
1011 pub fn clipped_bounds(&self) -> Bounds<T> {
1012 self.bounds.intersect(&self.content_mask.bounds)
1013 }
1014}
1015
1016impl From<Path<ScaledPixels>> for Primitive {
1017 fn from(path: Path<ScaledPixels>) -> Self {
1018 Primitive::Path(path)
1019 }
1020}
1021
1022#[derive(Clone, Debug)]
1023#[repr(C)]
1024#[expect(missing_docs)]
1025pub struct PathVertex<P: Clone + Debug + Default + PartialEq> {
1026 pub xy_position: Point<P>,
1027 pub st_position: Point<f32>,
1028 pub content_mask: ContentMask<P>,
1029}
1030
1031#[expect(missing_docs)]
1032impl PathVertex<Pixels> {
1033 pub fn scale(&self, factor: f32) -> PathVertex<ScaledPixels> {
1034 PathVertex {
1035 xy_position: self.xy_position.scale(factor),
1036 st_position: self.st_position,
1037 content_mask: self.content_mask.scale(factor),
1038 }
1039 }
1040}