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