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