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