1// todo("windows"): remove
2#![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.clone());
92 }
93 Primitive::Quad(quad) => {
94 quad.order = order;
95 self.quads.push(quad.clone());
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.clone());
105 }
106 Primitive::MonochromeSprite(sprite) => {
107 sprite.order = order;
108 self.monochrome_sprites.push(sprite.clone());
109 }
110 Primitive::SubpixelSprite(sprite) => {
111 sprite.order = order;
112 self.subpixel_sprites.push(sprite.clone());
113 }
114 Primitive::PolychromeSprite(sprite) => {
115 sprite.order = order;
116 self.polychrome_sprites.push(sprite.clone());
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, 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, Clone)]
505#[repr(C)]
506#[expect(missing_docs)]
507pub struct Underline {
508 pub order: DrawOrder,
509 pub pad: u32, // align to 8 bytes
510 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, 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}
534
535impl From<Shadow> for Primitive {
536 fn from(shadow: Shadow) -> Self {
537 Primitive::Shadow(shadow)
538 }
539}
540
541/// The style of a border.
542#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
543#[repr(C)]
544pub enum BorderStyle {
545 /// A solid border.
546 #[default]
547 Solid = 0,
548 /// A dashed border.
549 Dashed = 1,
550}
551
552/// A data type representing a 2 dimensional transformation that can be applied to an element.
553#[derive(Debug, Clone, Copy, PartialEq)]
554#[repr(C)]
555pub struct TransformationMatrix {
556 /// 2x2 matrix containing rotation and scale,
557 /// stored row-major
558 pub rotation_scale: [[f32; 2]; 2],
559 /// translation vector
560 pub translation: [f32; 2],
561}
562
563impl Eq for TransformationMatrix {}
564
565impl TransformationMatrix {
566 /// The unit matrix, has no effect.
567 pub fn unit() -> Self {
568 Self {
569 rotation_scale: [[1.0, 0.0], [0.0, 1.0]],
570 translation: [0.0, 0.0],
571 }
572 }
573
574 /// Move the origin by a given point
575 pub fn translate(mut self, point: Point<ScaledPixels>) -> Self {
576 self.compose(Self {
577 rotation_scale: [[1.0, 0.0], [0.0, 1.0]],
578 translation: [point.x.0, point.y.0],
579 })
580 }
581
582 /// Clockwise rotation in radians around the origin
583 pub fn rotate(self, angle: Radians) -> Self {
584 self.compose(Self {
585 rotation_scale: [
586 [angle.0.cos(), -angle.0.sin()],
587 [angle.0.sin(), angle.0.cos()],
588 ],
589 translation: [0.0, 0.0],
590 })
591 }
592
593 /// Scale around the origin
594 pub fn scale(self, size: Size<f32>) -> Self {
595 self.compose(Self {
596 rotation_scale: [[size.width, 0.0], [0.0, size.height]],
597 translation: [0.0, 0.0],
598 })
599 }
600
601 /// Perform matrix multiplication with another transformation
602 /// to produce a new transformation that is the result of
603 /// applying both transformations: first, `other`, then `self`.
604 #[inline]
605 pub fn compose(self, other: TransformationMatrix) -> TransformationMatrix {
606 if other == Self::unit() {
607 return self;
608 }
609 // Perform matrix multiplication
610 TransformationMatrix {
611 rotation_scale: [
612 [
613 self.rotation_scale[0][0] * other.rotation_scale[0][0]
614 + self.rotation_scale[0][1] * other.rotation_scale[1][0],
615 self.rotation_scale[0][0] * other.rotation_scale[0][1]
616 + self.rotation_scale[0][1] * other.rotation_scale[1][1],
617 ],
618 [
619 self.rotation_scale[1][0] * other.rotation_scale[0][0]
620 + self.rotation_scale[1][1] * other.rotation_scale[1][0],
621 self.rotation_scale[1][0] * other.rotation_scale[0][1]
622 + self.rotation_scale[1][1] * other.rotation_scale[1][1],
623 ],
624 ],
625 translation: [
626 self.translation[0]
627 + self.rotation_scale[0][0] * other.translation[0]
628 + self.rotation_scale[0][1] * other.translation[1],
629 self.translation[1]
630 + self.rotation_scale[1][0] * other.translation[0]
631 + self.rotation_scale[1][1] * other.translation[1],
632 ],
633 }
634 }
635
636 /// Apply transformation to a point, mainly useful for debugging
637 pub fn apply(&self, point: Point<Pixels>) -> Point<Pixels> {
638 let input = [point.x.0, point.y.0];
639 let mut output = self.translation;
640 for (i, output_cell) in output.iter_mut().enumerate() {
641 for (k, input_cell) in input.iter().enumerate() {
642 *output_cell += self.rotation_scale[i][k] * *input_cell;
643 }
644 }
645 Point::new(output[0].into(), output[1].into())
646 }
647}
648
649impl Default for TransformationMatrix {
650 fn default() -> Self {
651 Self::unit()
652 }
653}
654
655#[derive(Clone, Debug)]
656#[repr(C)]
657#[expect(missing_docs)]
658pub struct MonochromeSprite {
659 pub order: DrawOrder,
660 pub pad: u32, // align to 8 bytes
661 pub bounds: Bounds<ScaledPixels>,
662 pub content_mask: ContentMask<ScaledPixels>,
663 pub color: Hsla,
664 pub tile: AtlasTile,
665 pub transformation: TransformationMatrix,
666}
667
668impl From<MonochromeSprite> for Primitive {
669 fn from(sprite: MonochromeSprite) -> Self {
670 Primitive::MonochromeSprite(sprite)
671 }
672}
673
674#[derive(Clone, Debug)]
675#[repr(C)]
676#[expect(missing_docs)]
677pub struct SubpixelSprite {
678 pub order: DrawOrder,
679 pub pad: u32, // align to 8 bytes
680 pub bounds: Bounds<ScaledPixels>,
681 pub content_mask: ContentMask<ScaledPixels>,
682 pub color: Hsla,
683 pub tile: AtlasTile,
684 pub transformation: TransformationMatrix,
685}
686
687impl From<SubpixelSprite> for Primitive {
688 fn from(sprite: SubpixelSprite) -> Self {
689 Primitive::SubpixelSprite(sprite)
690 }
691}
692
693#[derive(Clone, Debug)]
694#[repr(C)]
695#[expect(missing_docs)]
696pub struct PolychromeSprite {
697 pub order: DrawOrder,
698 pub pad: u32, // align to 8 bytes
699 pub grayscale: bool,
700 pub opacity: f32,
701 pub bounds: Bounds<ScaledPixels>,
702 pub content_mask: ContentMask<ScaledPixels>,
703 pub corner_radii: Corners<ScaledPixels>,
704 pub tile: AtlasTile,
705}
706
707impl From<PolychromeSprite> for Primitive {
708 fn from(sprite: PolychromeSprite) -> Self {
709 Primitive::PolychromeSprite(sprite)
710 }
711}
712
713#[derive(Clone, Debug)]
714#[allow(missing_docs)]
715pub struct PaintSurface {
716 pub order: DrawOrder,
717 pub bounds: Bounds<ScaledPixels>,
718 pub content_mask: ContentMask<ScaledPixels>,
719 #[cfg(target_os = "macos")]
720 pub image_buffer: core_video::pixel_buffer::CVPixelBuffer,
721}
722
723impl From<PaintSurface> for Primitive {
724 fn from(surface: PaintSurface) -> Self {
725 Primitive::Surface(surface)
726 }
727}
728
729#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
730#[expect(missing_docs)]
731pub struct PathId(pub usize);
732
733/// A line made up of a series of vertices and control points.
734#[derive(Clone, Debug)]
735#[expect(missing_docs)]
736pub struct Path<P: Clone + Debug + Default + PartialEq> {
737 pub id: PathId,
738 pub order: DrawOrder,
739 pub bounds: Bounds<P>,
740 pub content_mask: ContentMask<P>,
741 pub vertices: Vec<PathVertex<P>>,
742 pub color: Background,
743 start: Point<P>,
744 current: Point<P>,
745 contour_count: usize,
746}
747
748impl Path<Pixels> {
749 /// Create a new path with the given starting point.
750 pub fn new(start: Point<Pixels>) -> Self {
751 Self {
752 id: PathId(0),
753 order: DrawOrder::default(),
754 vertices: Vec::new(),
755 start,
756 current: start,
757 bounds: Bounds {
758 origin: start,
759 size: Default::default(),
760 },
761 content_mask: Default::default(),
762 color: Default::default(),
763 contour_count: 0,
764 }
765 }
766
767 /// Scale this path by the given factor.
768 pub fn scale(&self, factor: f32) -> Path<ScaledPixels> {
769 Path {
770 id: self.id,
771 order: self.order,
772 bounds: self.bounds.scale(factor),
773 content_mask: self.content_mask.scale(factor),
774 vertices: self
775 .vertices
776 .iter()
777 .map(|vertex| vertex.scale(factor))
778 .collect(),
779 start: self.start.map(|start| start.scale(factor)),
780 current: self.current.scale(factor),
781 contour_count: self.contour_count,
782 color: self.color,
783 }
784 }
785
786 /// Move the start, current point to the given point.
787 pub fn move_to(&mut self, to: Point<Pixels>) {
788 self.contour_count += 1;
789 self.start = to;
790 self.current = to;
791 }
792
793 /// Draw a straight line from the current point to the given point.
794 pub fn line_to(&mut self, to: Point<Pixels>) {
795 self.contour_count += 1;
796 if self.contour_count > 1 {
797 self.push_triangle(
798 (self.start, self.current, to),
799 (point(0., 1.), point(0., 1.), point(0., 1.)),
800 );
801 }
802 self.current = to;
803 }
804
805 /// Draw a curve from the current point to the given point, using the given control point.
806 pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
807 self.contour_count += 1;
808 if self.contour_count > 1 {
809 self.push_triangle(
810 (self.start, self.current, to),
811 (point(0., 1.), point(0., 1.), point(0., 1.)),
812 );
813 }
814
815 self.push_triangle(
816 (self.current, ctrl, to),
817 (point(0., 0.), point(0.5, 0.), point(1., 1.)),
818 );
819 self.current = to;
820 }
821
822 /// Push a triangle to the Path.
823 pub fn push_triangle(
824 &mut self,
825 xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>),
826 st: (Point<f32>, Point<f32>, Point<f32>),
827 ) {
828 self.bounds = self
829 .bounds
830 .union(&Bounds {
831 origin: xy.0,
832 size: Default::default(),
833 })
834 .union(&Bounds {
835 origin: xy.1,
836 size: Default::default(),
837 })
838 .union(&Bounds {
839 origin: xy.2,
840 size: Default::default(),
841 });
842
843 self.vertices.push(PathVertex {
844 xy_position: xy.0,
845 st_position: st.0,
846 content_mask: Default::default(),
847 });
848 self.vertices.push(PathVertex {
849 xy_position: xy.1,
850 st_position: st.1,
851 content_mask: Default::default(),
852 });
853 self.vertices.push(PathVertex {
854 xy_position: xy.2,
855 st_position: st.2,
856 content_mask: Default::default(),
857 });
858 }
859}
860
861impl<T> Path<T>
862where
863 T: Clone + Debug + Default + PartialEq + PartialOrd + Add<T, Output = T> + Sub<Output = T>,
864{
865 #[allow(unused)]
866 #[expect(missing_docs)]
867 pub fn clipped_bounds(&self) -> Bounds<T> {
868 self.bounds.intersect(&self.content_mask.bounds)
869 }
870}
871
872impl From<Path<ScaledPixels>> for Primitive {
873 fn from(path: Path<ScaledPixels>) -> Self {
874 Primitive::Path(path)
875 }
876}
877
878#[derive(Clone, Debug)]
879#[repr(C)]
880#[expect(missing_docs)]
881pub struct PathVertex<P: Clone + Debug + Default + PartialEq> {
882 pub xy_position: Point<P>,
883 pub st_position: Point<f32>,
884 pub content_mask: ContentMask<P>,
885}
886
887#[expect(missing_docs)]
888impl PathVertex<Pixels> {
889 pub fn scale(&self, factor: f32) -> PathVertex<ScaledPixels> {
890 PathVertex {
891 xy_position: self.xy_position.scale(factor),
892 st_position: self.st_position,
893 content_mask: self.content_mask.scale(factor),
894 }
895 }
896}