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