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