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, Pixels, Point,
9 Radians, ScaledFilter, ScaledPixels, Size, bounds_tree::BoundsTree, point,
10};
11use smallvec::SmallVec;
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#[expect(missing_docs)]
24pub type DrawOrder = u32;
25
26#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
31#[repr(transparent)]
32pub struct PaddedBool32(u32);
33
34impl From<bool> for PaddedBool32 {
35 fn from(value: bool) -> Self {
36 PaddedBool32(value as u32)
37 }
38}
39
40#[derive(Default)]
41#[expect(missing_docs)]
42pub struct Scene {
43 pub(crate) paint_operations: Vec<PaintOperation>,
44 primitive_bounds: BoundsTree<ScaledPixels>,
45 layer_stack: Vec<DrawOrder>,
46 pub shadows: Vec<Shadow>,
47 pub quads: Vec<Quad>,
48 pub paths: Vec<Path<ScaledPixels>>,
49 pub underlines: Vec<Underline>,
50 pub monochrome_sprites: Vec<MonochromeSprite>,
51 pub subpixel_sprites: Vec<SubpixelSprite>,
52 pub polychrome_sprites: Vec<PolychromeSprite>,
53 pub surfaces: Vec<PaintSurface>,
54 pub backdrop_filters: Vec<BackdropFilter>,
55 pub filter_boundaries: Vec<FilterBoundary>,
56}
57
58#[expect(missing_docs)]
59impl Scene {
60 pub fn clear(&mut self) {
61 self.paint_operations.clear();
62 self.primitive_bounds.clear();
63 self.layer_stack.clear();
64 self.paths.clear();
65 self.shadows.clear();
66 self.quads.clear();
67 self.underlines.clear();
68 self.monochrome_sprites.clear();
69 self.subpixel_sprites.clear();
70 self.polychrome_sprites.clear();
71 self.surfaces.clear();
72 self.backdrop_filters.clear();
73 self.filter_boundaries.clear();
74 }
75
76 pub fn len(&self) -> usize {
77 self.paint_operations.len()
78 }
79
80 pub fn push_layer(&mut self, bounds: Bounds<ScaledPixels>) {
81 let order = self.primitive_bounds.insert(bounds);
82 self.layer_stack.push(order);
83 self.paint_operations
84 .push(PaintOperation::StartLayer(bounds));
85 }
86
87 pub fn pop_layer(&mut self) {
88 self.layer_stack.pop();
89 self.paint_operations.push(PaintOperation::EndLayer);
90 }
91
92 pub fn raise_order_floor(&mut self) {
97 let floor = self.primitive_bounds.max_order() + 1;
98 self.primitive_bounds.set_order_floor(floor);
99 }
100
101 pub fn insert_primitive(&mut self, primitive: impl Into<Primitive>) {
102 let mut primitive = primitive.into();
103 let clipped_bounds = primitive
104 .bounds()
105 .intersect(&primitive.content_mask().bounds);
106
107 let is_filter_boundary = matches!(primitive, Primitive::FilterBoundary(_));
118
119 if clipped_bounds.is_empty() && !is_filter_boundary {
120 return;
121 }
122
123 let order = if is_filter_boundary {
124 let order_bounds = if clipped_bounds.is_empty() {
125 *primitive.bounds()
126 } else {
127 clipped_bounds
128 };
129 self.primitive_bounds.insert_above_all(order_bounds)
130 } else {
131 self.layer_stack
132 .last()
133 .copied()
134 .unwrap_or_else(|| self.primitive_bounds.insert(clipped_bounds))
135 };
136 match &mut primitive {
137 Primitive::Shadow(shadow) => {
138 shadow.order = order;
139 self.shadows.push(*shadow);
140 }
141 Primitive::Quad(quad) => {
142 quad.order = order;
143 self.quads.push(*quad);
144 }
145 Primitive::Path(path) => {
146 path.order = order;
147 path.id = PathId(self.paths.len());
148 self.paths.push(path.clone());
149 }
150 Primitive::Underline(underline) => {
151 underline.order = order;
152 self.underlines.push(*underline);
153 }
154 Primitive::MonochromeSprite(sprite) => {
155 sprite.order = order;
156 self.monochrome_sprites.push(*sprite);
157 }
158 Primitive::SubpixelSprite(sprite) => {
159 sprite.order = order;
160 self.subpixel_sprites.push(*sprite);
161 }
162 Primitive::PolychromeSprite(sprite) => {
163 sprite.order = order;
164 self.polychrome_sprites.push(*sprite);
165 }
166 Primitive::Surface(surface) => {
167 surface.order = order;
168 self.surfaces.push(surface.clone());
169 }
170 Primitive::BackdropFilter(filter) => {
171 filter.order = order;
172 self.backdrop_filters.push(filter.clone());
173 }
174 Primitive::FilterBoundary(boundary) => {
175 boundary.order = order;
176 if !boundary.is_start {
177 self.primitive_bounds.set_order_floor(order + 1);
183 }
184 self.filter_boundaries.push(boundary.clone());
185 }
186 }
187 self.paint_operations
188 .push(PaintOperation::Primitive(primitive));
189 }
190
191 pub fn replay(&mut self, range: Range<usize>, prev_scene: &Scene) {
192 for operation in &prev_scene.paint_operations[range] {
193 match operation {
194 PaintOperation::Primitive(primitive) => self.insert_primitive(primitive.clone()),
195 PaintOperation::StartLayer(bounds) => self.push_layer(*bounds),
196 PaintOperation::EndLayer => self.pop_layer(),
197 }
198 }
199 }
200
201 pub fn finish(&mut self) {
202 self.shadows.sort_by_key(|shadow| shadow.order);
203 self.quads.sort_by_key(|quad| quad.order);
204 self.paths.sort_by_key(|path| path.order);
205 self.underlines.sort_by_key(|underline| underline.order);
206 self.monochrome_sprites
207 .sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
208 self.subpixel_sprites
209 .sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
210 self.polychrome_sprites
211 .sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
212 self.surfaces.sort_by_key(|surface| surface.order);
213 self.backdrop_filters.sort_by_key(|filter| filter.order);
214 self.filter_boundaries
219 .sort_by_key(|boundary| (boundary.order, !boundary.is_start));
220 }
221
222 #[cfg_attr(
223 all(
224 any(target_os = "linux", target_os = "freebsd"),
225 not(any(feature = "x11", feature = "wayland"))
226 ),
227 allow(dead_code)
228 )]
229 pub fn batches(&self) -> impl Iterator<Item = PrimitiveBatch> + '_ {
230 BatchIterator {
231 shadows_start: 0,
232 shadows_iter: self.shadows.iter().peekable(),
233 quads_start: 0,
234 quads_iter: self.quads.iter().peekable(),
235 paths_start: 0,
236 paths_iter: self.paths.iter().peekable(),
237 underlines_start: 0,
238 underlines_iter: self.underlines.iter().peekable(),
239 monochrome_sprites_start: 0,
240 monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(),
241 subpixel_sprites_start: 0,
242 subpixel_sprites_iter: self.subpixel_sprites.iter().peekable(),
243 polychrome_sprites_start: 0,
244 polychrome_sprites_iter: self.polychrome_sprites.iter().peekable(),
245 surfaces_start: 0,
246 surfaces_iter: self.surfaces.iter().peekable(),
247 backdrop_filters_start: 0,
248 backdrop_filters_iter: self.backdrop_filters.iter().peekable(),
249 filter_boundaries_start: 0,
250 filter_boundaries_iter: self.filter_boundaries.iter().peekable(),
251 }
252 }
253}
254
255#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
257#[repr(C)]
258pub struct SceneHsla {
259 pub(crate) h: f32,
261 pub(crate) s: f32,
263 pub(crate) l: f32,
265 pub(crate) a: f32,
267}
268impl Into<palette::Hsla> for SceneHsla {
269 fn into(self) -> palette::Hsla {
270 palette::Hsla::new(self.h * 360.0, self.s, self.l, self.a)
271 }
272}
273impl From<palette::Hsla> for SceneHsla {
274 fn from(hsla: palette::Hsla) -> Self {
275 Self {
276 h: hsla.hue.into_positive_degrees() / 360.0,
277 s: hsla.saturation,
278 l: hsla.lightness,
279 a: hsla.alpha,
280 }
281 }
282}
283
284#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)]
285#[cfg_attr(
286 all(
287 any(target_os = "linux", target_os = "freebsd"),
288 not(any(feature = "x11", feature = "wayland"))
289 ),
290 allow(dead_code)
291)]
292pub(crate) enum PrimitiveKind {
293 FilterBoundaryStart,
296 Shadow,
297 #[default]
298 Quad,
299 Path,
300 Underline,
301 MonochromeSprite,
302 SubpixelSprite,
303 PolychromeSprite,
304 Surface,
305 BackdropFilter,
306 FilterBoundaryEnd,
309}
310
311pub(crate) enum PaintOperation {
312 Primitive(Primitive),
313 StartLayer(Bounds<ScaledPixels>),
314 EndLayer,
315}
316
317#[derive(Clone)]
318#[expect(missing_docs)]
319pub enum Primitive {
320 Shadow(Shadow),
321 Quad(Quad),
322 Path(Path<ScaledPixels>),
323 Underline(Underline),
324 MonochromeSprite(MonochromeSprite),
325 SubpixelSprite(SubpixelSprite),
326 PolychromeSprite(PolychromeSprite),
327 Surface(PaintSurface),
328 BackdropFilter(BackdropFilter),
329 FilterBoundary(FilterBoundary),
330}
331
332#[expect(missing_docs)]
333impl Primitive {
334 pub fn bounds(&self) -> &Bounds<ScaledPixels> {
335 match self {
336 Primitive::Shadow(shadow) => &shadow.bounds,
337 Primitive::Quad(quad) => &quad.bounds,
338 Primitive::Path(path) => &path.bounds,
339 Primitive::Underline(underline) => &underline.bounds,
340 Primitive::MonochromeSprite(sprite) => &sprite.bounds,
341 Primitive::SubpixelSprite(sprite) => &sprite.bounds,
342 Primitive::PolychromeSprite(sprite) => &sprite.bounds,
343 Primitive::Surface(surface) => &surface.bounds,
344 Primitive::BackdropFilter(filter) => &filter.bounds,
345 Primitive::FilterBoundary(boundary) => &boundary.bounds,
346 }
347 }
348
349 pub fn content_mask(&self) -> &ContentMask<ScaledPixels> {
350 match self {
351 Primitive::Shadow(shadow) => &shadow.content_mask,
352 Primitive::Quad(quad) => &quad.content_mask,
353 Primitive::Path(path) => &path.content_mask,
354 Primitive::Underline(underline) => &underline.content_mask,
355 Primitive::MonochromeSprite(sprite) => &sprite.content_mask,
356 Primitive::SubpixelSprite(sprite) => &sprite.content_mask,
357 Primitive::PolychromeSprite(sprite) => &sprite.content_mask,
358 Primitive::Surface(surface) => &surface.content_mask,
359 Primitive::BackdropFilter(filter) => &filter.content_mask,
360 Primitive::FilterBoundary(boundary) => &boundary.content_mask,
361 }
362 }
363}
364
365#[cfg_attr(
366 all(
367 any(target_os = "linux", target_os = "freebsd"),
368 not(any(feature = "x11", feature = "wayland"))
369 ),
370 allow(dead_code)
371)]
372struct BatchIterator<'a> {
373 shadows_start: usize,
374 shadows_iter: Peekable<slice::Iter<'a, Shadow>>,
375 quads_start: usize,
376 quads_iter: Peekable<slice::Iter<'a, Quad>>,
377 paths_start: usize,
378 paths_iter: Peekable<slice::Iter<'a, Path<ScaledPixels>>>,
379 underlines_start: usize,
380 underlines_iter: Peekable<slice::Iter<'a, Underline>>,
381 monochrome_sprites_start: usize,
382 monochrome_sprites_iter: Peekable<slice::Iter<'a, MonochromeSprite>>,
383 subpixel_sprites_start: usize,
384 subpixel_sprites_iter: Peekable<slice::Iter<'a, SubpixelSprite>>,
385 polychrome_sprites_start: usize,
386 polychrome_sprites_iter: Peekable<slice::Iter<'a, PolychromeSprite>>,
387 surfaces_start: usize,
388 surfaces_iter: Peekable<slice::Iter<'a, PaintSurface>>,
389 backdrop_filters_start: usize,
390 backdrop_filters_iter: Peekable<slice::Iter<'a, BackdropFilter>>,
391 filter_boundaries_start: usize,
392 filter_boundaries_iter: Peekable<slice::Iter<'a, FilterBoundary>>,
393}
394
395impl<'a> Iterator for BatchIterator<'a> {
396 type Item = PrimitiveBatch;
397
398 fn next(&mut self) -> Option<Self::Item> {
399 let mut orders_and_kinds = [
400 (
401 self.shadows_iter.peek().map(|s| s.order),
402 PrimitiveKind::Shadow,
403 ),
404 (self.quads_iter.peek().map(|q| q.order), PrimitiveKind::Quad),
405 (self.paths_iter.peek().map(|q| q.order), PrimitiveKind::Path),
406 (
407 self.underlines_iter.peek().map(|u| u.order),
408 PrimitiveKind::Underline,
409 ),
410 (
411 self.monochrome_sprites_iter.peek().map(|s| s.order),
412 PrimitiveKind::MonochromeSprite,
413 ),
414 (
415 self.subpixel_sprites_iter.peek().map(|s| s.order),
416 PrimitiveKind::SubpixelSprite,
417 ),
418 (
419 self.polychrome_sprites_iter.peek().map(|s| s.order),
420 PrimitiveKind::PolychromeSprite,
421 ),
422 (
423 self.surfaces_iter.peek().map(|s| s.order),
424 PrimitiveKind::Surface,
425 ),
426 (
427 self.backdrop_filters_iter.peek().map(|f| f.order),
428 PrimitiveKind::BackdropFilter,
429 ),
430 (
431 self.filter_boundaries_iter.peek().map(|b| b.order),
432 match self.filter_boundaries_iter.peek() {
436 Some(boundary) if boundary.is_start => PrimitiveKind::FilterBoundaryStart,
437 _ => PrimitiveKind::FilterBoundaryEnd,
438 },
439 ),
440 ];
441 orders_and_kinds.sort_by_key(|(order, kind)| (order.unwrap_or(u32::MAX), *kind));
442
443 let first = orders_and_kinds[0];
444 let second = orders_and_kinds[1];
445 let (batch_kind, max_order_and_kind) = if first.0.is_some() {
446 (first.1, (second.0.unwrap_or(u32::MAX), second.1))
447 } else {
448 return None;
449 };
450
451 match batch_kind {
452 PrimitiveKind::Shadow => {
453 let shadows_start = self.shadows_start;
454 let mut shadows_end = shadows_start + 1;
455 self.shadows_iter.next();
456 while self
457 .shadows_iter
458 .next_if(|shadow| (shadow.order, batch_kind) < max_order_and_kind)
459 .is_some()
460 {
461 shadows_end += 1;
462 }
463 self.shadows_start = shadows_end;
464 Some(PrimitiveBatch::Shadows(shadows_start..shadows_end))
465 }
466 PrimitiveKind::Quad => {
467 let quads_start = self.quads_start;
468 let mut quads_end = quads_start + 1;
469 self.quads_iter.next();
470 while self
471 .quads_iter
472 .next_if(|quad| (quad.order, batch_kind) < max_order_and_kind)
473 .is_some()
474 {
475 quads_end += 1;
476 }
477 self.quads_start = quads_end;
478 Some(PrimitiveBatch::Quads(quads_start..quads_end))
479 }
480 PrimitiveKind::Path => {
481 let paths_start = self.paths_start;
482 let mut paths_end = paths_start + 1;
483 self.paths_iter.next();
484 while self
485 .paths_iter
486 .next_if(|path| (path.order, batch_kind) < max_order_and_kind)
487 .is_some()
488 {
489 paths_end += 1;
490 }
491 self.paths_start = paths_end;
492 Some(PrimitiveBatch::Paths(paths_start..paths_end))
493 }
494 PrimitiveKind::Underline => {
495 let underlines_start = self.underlines_start;
496 let mut underlines_end = underlines_start + 1;
497 self.underlines_iter.next();
498 while self
499 .underlines_iter
500 .next_if(|underline| (underline.order, batch_kind) < max_order_and_kind)
501 .is_some()
502 {
503 underlines_end += 1;
504 }
505 self.underlines_start = underlines_end;
506 Some(PrimitiveBatch::Underlines(underlines_start..underlines_end))
507 }
508 PrimitiveKind::MonochromeSprite => {
509 let texture_id = self.monochrome_sprites_iter.peek().unwrap().tile.texture_id;
510 let sprites_start = self.monochrome_sprites_start;
511 let mut sprites_end = sprites_start + 1;
512 self.monochrome_sprites_iter.next();
513 while self
514 .monochrome_sprites_iter
515 .next_if(|sprite| {
516 (sprite.order, batch_kind) < max_order_and_kind
517 && sprite.tile.texture_id == texture_id
518 })
519 .is_some()
520 {
521 sprites_end += 1;
522 }
523 self.monochrome_sprites_start = sprites_end;
524 Some(PrimitiveBatch::MonochromeSprites {
525 texture_id,
526 range: sprites_start..sprites_end,
527 })
528 }
529 PrimitiveKind::SubpixelSprite => {
530 let texture_id = self.subpixel_sprites_iter.peek().unwrap().tile.texture_id;
531 let sprites_start = self.subpixel_sprites_start;
532 let mut sprites_end = sprites_start + 1;
533 self.subpixel_sprites_iter.next();
534 while self
535 .subpixel_sprites_iter
536 .next_if(|sprite| {
537 (sprite.order, batch_kind) < max_order_and_kind
538 && sprite.tile.texture_id == texture_id
539 })
540 .is_some()
541 {
542 sprites_end += 1;
543 }
544 self.subpixel_sprites_start = sprites_end;
545 Some(PrimitiveBatch::SubpixelSprites {
546 texture_id,
547 range: sprites_start..sprites_end,
548 })
549 }
550 PrimitiveKind::PolychromeSprite => {
551 let texture_id = self.polychrome_sprites_iter.peek().unwrap().tile.texture_id;
552 let sprites_start = self.polychrome_sprites_start;
553 let mut sprites_end = sprites_start + 1;
554 self.polychrome_sprites_iter.next();
555 while self
556 .polychrome_sprites_iter
557 .next_if(|sprite| {
558 (sprite.order, batch_kind) < max_order_and_kind
559 && sprite.tile.texture_id == texture_id
560 })
561 .is_some()
562 {
563 sprites_end += 1;
564 }
565 self.polychrome_sprites_start = sprites_end;
566 Some(PrimitiveBatch::PolychromeSprites {
567 texture_id,
568 range: sprites_start..sprites_end,
569 })
570 }
571 PrimitiveKind::Surface => {
572 let surfaces_start = self.surfaces_start;
573 let mut surfaces_end = surfaces_start + 1;
574 self.surfaces_iter.next();
575 while self
576 .surfaces_iter
577 .next_if(|surface| (surface.order, batch_kind) < max_order_and_kind)
578 .is_some()
579 {
580 surfaces_end += 1;
581 }
582 self.surfaces_start = surfaces_end;
583 Some(PrimitiveBatch::Surfaces(surfaces_start..surfaces_end))
584 }
585 PrimitiveKind::BackdropFilter => {
586 let backdrop_filters_start = self.backdrop_filters_start;
587 let mut backdrop_filters_end = backdrop_filters_start + 1;
588 self.backdrop_filters_iter.next();
589 while self
590 .backdrop_filters_iter
591 .next_if(|filter| (filter.order, batch_kind) < max_order_and_kind)
592 .is_some()
593 {
594 backdrop_filters_end += 1;
595 }
596 self.backdrop_filters_start = backdrop_filters_end;
597 Some(PrimitiveBatch::BackdropFilters(
598 backdrop_filters_start..backdrop_filters_end,
599 ))
600 }
601 PrimitiveKind::FilterBoundaryStart | PrimitiveKind::FilterBoundaryEnd => {
604 let index = self.filter_boundaries_start;
605 self.filter_boundaries_iter.next();
606 self.filter_boundaries_start = index + 1;
607 Some(PrimitiveBatch::FilterBoundary(index))
608 }
609 }
610 }
611}
612
613#[derive(Debug)]
614#[cfg_attr(
615 all(
616 any(target_os = "linux", target_os = "freebsd"),
617 not(any(feature = "x11", feature = "wayland"))
618 ),
619 allow(dead_code)
620)]
621#[allow(missing_docs)]
622pub enum PrimitiveBatch {
623 Shadows(Range<usize>),
624 Quads(Range<usize>),
625 Paths(Range<usize>),
626 Underlines(Range<usize>),
627 MonochromeSprites {
628 texture_id: AtlasTextureId,
629 range: Range<usize>,
630 },
631 #[cfg_attr(target_os = "macos", allow(dead_code))]
632 SubpixelSprites {
633 texture_id: AtlasTextureId,
634 range: Range<usize>,
635 },
636 PolychromeSprites {
637 texture_id: AtlasTextureId,
638 range: Range<usize>,
639 },
640 Surfaces(Range<usize>),
641 BackdropFilters(Range<usize>),
642 FilterBoundary(usize),
646}
647
648impl PrimitiveBatch {
649 #[expect(missing_docs)]
650 pub fn label(&self) -> String {
651 match self {
652 Self::Shadows(range) => format!("shadows ({})", range.len()),
653 Self::Quads(range) => format!("quads ({})", range.len()),
654 Self::Paths(range) => format!("paths ({})", range.len()),
655 Self::Underlines(range) => format!("underlines ({})", range.len()),
656 Self::MonochromeSprites { texture_id, range } => {
657 format!(
658 "monochrome sprites ({}) on atlas {}",
659 range.len(),
660 texture_id.index
661 )
662 }
663 Self::SubpixelSprites { texture_id, range } => {
664 format!(
665 "subpixel sprites ({}) on atlas {}",
666 range.len(),
667 texture_id.index
668 )
669 }
670 Self::PolychromeSprites { texture_id, range } => {
671 format!(
672 "polychrome sprites ({}) on atlas {}",
673 range.len(),
674 texture_id.index
675 )
676 }
677 Self::Surfaces(range) => format!("surfaces ({})", range.len()),
678 Self::BackdropFilters(range) => format!("backdrop filters ({})", range.len()),
679 Self::FilterBoundary(ix) => format!("filter boundary ({ix})"),
680 }
681 }
682}
683
684#[derive(Default, Debug, Copy, Clone)]
685#[repr(C)]
686#[expect(missing_docs)]
687pub struct Quad {
688 pub order: DrawOrder,
689 pub border_style: BorderStyle,
690 pub bounds: Bounds<ScaledPixels>,
691 pub content_mask: ContentMask<ScaledPixels>,
692 pub background: Background,
693 pub border_color: SceneHsla,
694 pub corner_radii: Corners<ScaledPixels>,
695 pub border_widths: Edges<ScaledPixels>,
696}
697
698impl From<Quad> for Primitive {
699 fn from(quad: Quad) -> Self {
700 Primitive::Quad(quad)
701 }
702}
703
704#[derive(Debug, Copy, Clone)]
705#[repr(C)]
706#[expect(missing_docs)]
707pub struct Underline {
708 pub order: DrawOrder,
709 pub pad: u32, pub bounds: Bounds<ScaledPixels>,
711 pub content_mask: ContentMask<ScaledPixels>,
712 pub color: SceneHsla,
713 pub thickness: ScaledPixels,
714 pub wavy: PaddedBool32,
715}
716
717impl From<Underline> for Primitive {
718 fn from(underline: Underline) -> Self {
719 Primitive::Underline(underline)
720 }
721}
722
723#[derive(Debug, Copy, Clone)]
724#[repr(C)]
725#[expect(missing_docs)]
726pub struct Shadow {
727 pub order: DrawOrder,
728 pub blur_radius: ScaledPixels,
729 pub bounds: Bounds<ScaledPixels>,
730 pub corner_radii: Corners<ScaledPixels>,
731 pub content_mask: ContentMask<ScaledPixels>,
732 pub color: SceneHsla,
733 pub element_bounds: Bounds<ScaledPixels>,
734 pub element_corner_radii: Corners<ScaledPixels>,
735 pub inset: u32,
737 pub pad: u32, }
739
740impl From<Shadow> for Primitive {
741 fn from(shadow: Shadow) -> Self {
742 Primitive::Shadow(shadow)
743 }
744}
745
746#[derive(Default, Debug, Clone)]
750#[expect(missing_docs)]
751pub struct BackdropFilter {
752 pub order: DrawOrder,
753 pub bounds: Bounds<ScaledPixels>,
754 pub content_mask: ContentMask<ScaledPixels>,
755 pub corner_radii: Corners<ScaledPixels>,
756 pub filters: SmallVec<[ScaledFilter; 4]>,
763 pub opacity: f32,
765}
766
767impl From<BackdropFilter> for Primitive {
768 fn from(filter: BackdropFilter) -> Self {
769 Primitive::BackdropFilter(filter)
770 }
771}
772
773#[derive(Debug, Clone)]
778#[expect(missing_docs)]
779pub struct FilterBoundary {
780 pub order: DrawOrder,
781 pub bounds: Bounds<ScaledPixels>,
782 pub content_mask: ContentMask<ScaledPixels>,
783 pub corner_radii: Corners<ScaledPixels>,
784 pub filters: SmallVec<[ScaledFilter; 4]>,
788 pub opacity: f32,
789 pub is_start: bool,
791}
792
793impl From<FilterBoundary> for Primitive {
794 fn from(boundary: FilterBoundary) -> Self {
795 Primitive::FilterBoundary(boundary)
796 }
797}
798
799#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
801#[repr(C)]
802pub enum BorderStyle {
803 #[default]
805 Solid = 0,
806 Dashed = 1,
808}
809
810#[derive(Debug, Clone, Copy, PartialEq)]
812#[repr(C)]
813pub struct TransformationMatrix {
814 pub rotation_scale: [[f32; 2]; 2],
817 pub translation: [f32; 2],
819}
820
821impl Eq for TransformationMatrix {}
822
823impl TransformationMatrix {
824 pub fn unit() -> Self {
826 Self {
827 rotation_scale: [[1.0, 0.0], [0.0, 1.0]],
828 translation: [0.0, 0.0],
829 }
830 }
831
832 pub fn translate(mut self, point: Point<ScaledPixels>) -> Self {
834 self.compose(Self {
835 rotation_scale: [[1.0, 0.0], [0.0, 1.0]],
836 translation: [point.x.0, point.y.0],
837 })
838 }
839
840 pub fn rotate(self, angle: Radians) -> Self {
842 self.compose(Self {
843 rotation_scale: [
844 [angle.0.cos(), -angle.0.sin()],
845 [angle.0.sin(), angle.0.cos()],
846 ],
847 translation: [0.0, 0.0],
848 })
849 }
850
851 pub fn scale(self, size: Size<f32>) -> Self {
853 self.compose(Self {
854 rotation_scale: [[size.width, 0.0], [0.0, size.height]],
855 translation: [0.0, 0.0],
856 })
857 }
858
859 #[inline]
863 pub fn compose(self, other: TransformationMatrix) -> TransformationMatrix {
864 if other == Self::unit() {
865 return self;
866 }
867 TransformationMatrix {
869 rotation_scale: [
870 [
871 self.rotation_scale[0][0] * other.rotation_scale[0][0]
872 + self.rotation_scale[0][1] * other.rotation_scale[1][0],
873 self.rotation_scale[0][0] * other.rotation_scale[0][1]
874 + self.rotation_scale[0][1] * other.rotation_scale[1][1],
875 ],
876 [
877 self.rotation_scale[1][0] * other.rotation_scale[0][0]
878 + self.rotation_scale[1][1] * other.rotation_scale[1][0],
879 self.rotation_scale[1][0] * other.rotation_scale[0][1]
880 + self.rotation_scale[1][1] * other.rotation_scale[1][1],
881 ],
882 ],
883 translation: [
884 self.translation[0]
885 + self.rotation_scale[0][0] * other.translation[0]
886 + self.rotation_scale[0][1] * other.translation[1],
887 self.translation[1]
888 + self.rotation_scale[1][0] * other.translation[0]
889 + self.rotation_scale[1][1] * other.translation[1],
890 ],
891 }
892 }
893
894 pub fn apply(&self, point: Point<Pixels>) -> Point<Pixels> {
896 let input = [point.x.0, point.y.0];
897 let mut output = self.translation;
898 for (i, output_cell) in output.iter_mut().enumerate() {
899 for (k, input_cell) in input.iter().enumerate() {
900 *output_cell += self.rotation_scale[i][k] * *input_cell;
901 }
902 }
903 Point::new(output[0].into(), output[1].into())
904 }
905}
906
907impl Default for TransformationMatrix {
908 fn default() -> Self {
909 Self::unit()
910 }
911}
912
913#[derive(Copy, Clone, Debug)]
914#[repr(C)]
915#[expect(missing_docs)]
916pub struct MonochromeSprite {
917 pub order: DrawOrder,
918 pub pad: u32,
919 pub bounds: Bounds<ScaledPixels>,
920 pub content_mask: ContentMask<ScaledPixels>,
921 pub color: SceneHsla,
922 pub tile: AtlasTile,
923 pub transformation: TransformationMatrix,
924}
925
926impl From<MonochromeSprite> for Primitive {
927 fn from(sprite: MonochromeSprite) -> Self {
928 Primitive::MonochromeSprite(sprite)
929 }
930}
931
932#[derive(Copy, Clone, Debug)]
933#[repr(C)]
934#[expect(missing_docs)]
935pub struct SubpixelSprite {
936 pub order: DrawOrder,
937 pub pad: u32, pub bounds: Bounds<ScaledPixels>,
939 pub content_mask: ContentMask<ScaledPixels>,
940 pub color: SceneHsla,
941 pub tile: AtlasTile,
942 pub transformation: TransformationMatrix,
943}
944
945impl From<SubpixelSprite> for Primitive {
946 fn from(sprite: SubpixelSprite) -> Self {
947 Primitive::SubpixelSprite(sprite)
948 }
949}
950
951#[derive(Copy, Clone, Debug)]
952#[repr(C)]
953#[expect(missing_docs)]
954pub struct PolychromeSprite {
955 pub order: DrawOrder,
956 pub pad: u32,
957 pub grayscale: PaddedBool32,
958 pub opacity: f32,
959 pub bounds: Bounds<ScaledPixels>,
960 pub content_mask: ContentMask<ScaledPixels>,
961 pub corner_radii: Corners<ScaledPixels>,
962 pub tile: AtlasTile,
963}
964
965impl From<PolychromeSprite> for Primitive {
966 fn from(sprite: PolychromeSprite) -> Self {
967 Primitive::PolychromeSprite(sprite)
968 }
969}
970
971#[derive(Clone, Debug)]
972#[allow(missing_docs)]
973pub struct PaintSurface {
974 pub order: DrawOrder,
975 pub bounds: Bounds<ScaledPixels>,
976 pub content_mask: ContentMask<ScaledPixels>,
977 #[cfg(target_os = "macos")]
978 pub image_buffer: core_video::pixel_buffer::CVPixelBuffer,
979 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
980 pub texture: std::sync::Arc<dyn std::any::Any + Send + Sync>,
981 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
982 pub texture_size: Size<crate::DevicePixels>,
983}
984
985impl From<PaintSurface> for Primitive {
986 fn from(surface: PaintSurface) -> Self {
987 Primitive::Surface(surface)
988 }
989}
990
991#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
992#[expect(missing_docs)]
993pub struct PathId(pub usize);
994
995#[derive(Clone, Debug)]
997#[expect(missing_docs)]
998pub struct Path<P: Clone + Debug + Default + PartialEq> {
999 pub id: PathId,
1000 pub order: DrawOrder,
1001 pub bounds: Bounds<P>,
1002 pub content_mask: ContentMask<P>,
1003 pub vertices: Vec<PathVertex<P>>,
1004 pub color: Background,
1005 start: Point<P>,
1006 current: Point<P>,
1007 contour_count: usize,
1008}
1009
1010impl Path<Pixels> {
1011 pub fn new(start: Point<Pixels>) -> Self {
1013 Self {
1014 id: PathId(0),
1015 order: DrawOrder::default(),
1016 vertices: Vec::new(),
1017 start,
1018 current: start,
1019 bounds: Bounds {
1020 origin: start,
1021 size: Default::default(),
1022 },
1023 content_mask: Default::default(),
1024 color: Default::default(),
1025 contour_count: 0,
1026 }
1027 }
1028
1029 pub fn scale(&self, factor: f32) -> Path<ScaledPixels> {
1031 Path {
1032 id: self.id,
1033 order: self.order,
1034 bounds: self.bounds.scale(factor),
1035 content_mask: self.content_mask.scale(factor),
1036 vertices: self
1037 .vertices
1038 .iter()
1039 .map(|vertex| vertex.scale(factor))
1040 .collect(),
1041 start: self.start.map(|start| start.scale(factor)),
1042 current: self.current.scale(factor),
1043 contour_count: self.contour_count,
1044 color: self.color,
1045 }
1046 }
1047
1048 pub fn move_to(&mut self, to: Point<Pixels>) {
1050 self.contour_count += 1;
1051 self.start = to;
1052 self.current = to;
1053 }
1054
1055 pub fn line_to(&mut self, to: Point<Pixels>) {
1057 self.contour_count += 1;
1058 if self.contour_count > 1 {
1059 self.push_triangle(
1060 (self.start, self.current, to),
1061 (point(0., 1.), point(0., 1.), point(0., 1.)),
1062 );
1063 }
1064 self.current = to;
1065 }
1066
1067 pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
1069 self.contour_count += 1;
1070 if self.contour_count > 1 {
1071 self.push_triangle(
1072 (self.start, self.current, to),
1073 (point(0., 1.), point(0., 1.), point(0., 1.)),
1074 );
1075 }
1076
1077 self.push_triangle(
1078 (self.current, ctrl, to),
1079 (point(0., 0.), point(0.5, 0.), point(1., 1.)),
1080 );
1081 self.current = to;
1082 }
1083
1084 pub fn push_triangle(
1086 &mut self,
1087 xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>),
1088 st: (Point<f32>, Point<f32>, Point<f32>),
1089 ) {
1090 self.bounds = self
1091 .bounds
1092 .union(&Bounds {
1093 origin: xy.0,
1094 size: Default::default(),
1095 })
1096 .union(&Bounds {
1097 origin: xy.1,
1098 size: Default::default(),
1099 })
1100 .union(&Bounds {
1101 origin: xy.2,
1102 size: Default::default(),
1103 });
1104
1105 self.vertices.push(PathVertex {
1106 xy_position: xy.0,
1107 st_position: st.0,
1108 content_mask: Default::default(),
1109 });
1110 self.vertices.push(PathVertex {
1111 xy_position: xy.1,
1112 st_position: st.1,
1113 content_mask: Default::default(),
1114 });
1115 self.vertices.push(PathVertex {
1116 xy_position: xy.2,
1117 st_position: st.2,
1118 content_mask: Default::default(),
1119 });
1120 }
1121}
1122
1123impl<T> Path<T>
1124where
1125 T: Clone + Debug + Default + PartialEq + PartialOrd + Add<T, Output = T> + Sub<Output = T>,
1126{
1127 #[allow(unused)]
1128 #[expect(missing_docs)]
1129 pub fn clipped_bounds(&self) -> Bounds<T> {
1130 self.bounds.intersect(&self.content_mask.bounds)
1131 }
1132}
1133
1134impl From<Path<ScaledPixels>> for Primitive {
1135 fn from(path: Path<ScaledPixels>) -> Self {
1136 Primitive::Path(path)
1137 }
1138}
1139
1140#[derive(Clone, Debug)]
1141#[repr(C)]
1142#[expect(missing_docs)]
1143pub struct PathVertex<P: Clone + Debug + Default + PartialEq> {
1144 pub xy_position: Point<P>,
1145 pub st_position: Point<f32>,
1146 pub content_mask: ContentMask<P>,
1147}
1148
1149#[expect(missing_docs)]
1150impl PathVertex<Pixels> {
1151 pub fn scale(&self, factor: f32) -> PathVertex<ScaledPixels> {
1152 PathVertex {
1153 xy_position: self.xy_position.scale(factor),
1154 st_position: self.st_position,
1155 content_mask: self.content_mask.scale(factor),
1156 }
1157 }
1158}
1159
1160#[cfg(test)]
1161mod tests {
1162 use super::*;
1163 use crate::{Point, Size};
1164
1165 fn sp(value: f32) -> ScaledPixels {
1166 ScaledPixels(value)
1167 }
1168
1169 fn full_bounds() -> Bounds<ScaledPixels> {
1172 Bounds {
1173 origin: Point {
1174 x: sp(0.0),
1175 y: sp(0.0),
1176 },
1177 size: Size {
1178 width: sp(100.0),
1179 height: sp(100.0),
1180 },
1181 }
1182 }
1183
1184 fn mask() -> ContentMask<ScaledPixels> {
1185 ContentMask {
1186 bounds: full_bounds(),
1187 }
1188 }
1189
1190 fn quad() -> Quad {
1191 Quad {
1192 bounds: full_bounds(),
1193 content_mask: mask(),
1194 ..Default::default()
1195 }
1196 }
1197
1198 fn detached_quad() -> Quad {
1201 let bounds = Bounds {
1202 origin: Point {
1203 x: sp(200.0),
1204 y: sp(200.0),
1205 },
1206 size: Size {
1207 width: sp(100.0),
1208 height: sp(100.0),
1209 },
1210 };
1211 Quad {
1212 bounds,
1213 content_mask: ContentMask { bounds },
1214 ..Default::default()
1215 }
1216 }
1217
1218 fn boundary(is_start: bool) -> FilterBoundary {
1219 FilterBoundary {
1220 order: 0,
1221 bounds: full_bounds(),
1222 content_mask: mask(),
1223 corner_radii: Corners::default(),
1224 filters: smallvec::smallvec![ScaledFilter::Blur(sp(8.0))],
1225 opacity: 1.0,
1226 is_start,
1227 }
1228 }
1229
1230 fn backdrop() -> BackdropFilter {
1231 BackdropFilter {
1232 bounds: full_bounds(),
1233 content_mask: mask(),
1234 corner_radii: Corners::default(),
1235 filters: smallvec::smallvec![ScaledFilter::Blur(sp(20.0))],
1236 opacity: 1.0,
1237 ..Default::default()
1238 }
1239 }
1240
1241 fn batch_kinds(scene: &mut Scene) -> Vec<&'static str> {
1242 scene.finish();
1243 scene
1244 .batches()
1245 .map(|batch| match batch {
1246 PrimitiveBatch::Quads(_) => "quad",
1247 PrimitiveBatch::BackdropFilters(_) => "backdrop",
1248 PrimitiveBatch::FilterBoundary(ix) => {
1249 if scene.filter_boundaries[ix].is_start {
1250 "start"
1251 } else {
1252 "end"
1253 }
1254 }
1255 _ => "other",
1256 })
1257 .collect()
1258 }
1259
1260 #[test]
1261 fn content_filter_group_brackets_its_children() {
1262 let mut scene = Scene::default();
1263 scene.insert_primitive(quad());
1265 scene.insert_primitive(boundary(true));
1267 scene.insert_primitive(quad());
1268 scene.insert_primitive(boundary(false));
1269
1270 assert_eq!(
1273 batch_kinds(&mut scene),
1274 vec!["quad", "start", "quad", "end"]
1275 );
1276 }
1277
1278 #[test]
1282 fn nested_content_filters_emit_well_nested_ordering() {
1283 let mut scene = Scene::default();
1284 scene.insert_primitive(boundary(true)); scene.insert_primitive(quad()); scene.insert_primitive(boundary(true)); scene.insert_primitive(quad()); scene.insert_primitive(boundary(false)); scene.insert_primitive(boundary(false)); assert_eq!(
1292 batch_kinds(&mut scene),
1293 vec!["start", "quad", "start", "quad", "end", "end"]
1294 );
1295 }
1296
1297 #[test]
1298 fn content_after_a_filter_group_sorts_above_it() {
1299 let mut scene = Scene::default();
1300 scene.insert_primitive(boundary(true));
1302 scene.insert_primitive(quad());
1303 scene.insert_primitive(boundary(false));
1304 scene.insert_primitive(detached_quad());
1308
1309 assert_eq!(
1310 batch_kinds(&mut scene),
1311 vec!["start", "quad", "end", "quad"]
1312 );
1313 }
1314
1315 #[test]
1316 fn backdrop_filter_sorts_before_a_later_overlapping_quad() {
1317 let mut scene = Scene::default();
1318 scene.insert_primitive(quad());
1320 scene.insert_primitive(backdrop());
1322 scene.insert_primitive(quad());
1323
1324 assert_eq!(batch_kinds(&mut scene), vec!["quad", "backdrop", "quad"]);
1325 }
1326}