// todo("windows"): remove #![cfg_attr(windows, allow(dead_code))] use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use crate::{ AtlasTextureId, AtlasTile, Background, Bounds, ContentMask, Corners, Edges, Hsla, Pixels, Point, Radians, ScaledFilter, ScaledPixels, Size, bounds_tree::BoundsTree, point, }; use smallvec::SmallVec; use std::{ fmt::Debug, iter::Peekable, ops::{Add, Range, Sub}, slice, }; #[allow(non_camel_case_types, unused)] #[expect(missing_docs)] pub type PathVertex_ScaledPixels = PathVertex; #[expect(missing_docs)] pub type DrawOrder = u32; #[derive(Default)] #[expect(missing_docs)] pub struct Scene { pub(crate) paint_operations: Vec, primitive_bounds: BoundsTree, layer_stack: Vec, pub shadows: Vec, pub quads: Vec, pub paths: Vec>, pub underlines: Vec, pub monochrome_sprites: Vec, pub subpixel_sprites: Vec, pub polychrome_sprites: Vec, pub surfaces: Vec, pub backdrop_filters: Vec, pub filter_boundaries: Vec, } #[expect(missing_docs)] impl Scene { pub fn clear(&mut self) { self.paint_operations.clear(); self.primitive_bounds.clear(); self.layer_stack.clear(); self.paths.clear(); self.shadows.clear(); self.quads.clear(); self.underlines.clear(); self.monochrome_sprites.clear(); self.subpixel_sprites.clear(); self.polychrome_sprites.clear(); self.surfaces.clear(); self.backdrop_filters.clear(); self.filter_boundaries.clear(); } pub fn len(&self) -> usize { self.paint_operations.len() } pub fn push_layer(&mut self, bounds: Bounds) { let order = self.primitive_bounds.insert(bounds); self.layer_stack.push(order); self.paint_operations .push(PaintOperation::StartLayer(bounds)); } pub fn pop_layer(&mut self) { self.layer_stack.pop(); self.paint_operations.push(PaintOperation::EndLayer); } /// Raise the draw-order floor so every primitive inserted afterwards sorts above everything /// inserted before. Called before painting deferred draws so overlays (tooltips, popovers, /// drag images) sort above the main scene — and a deferred backdrop's order can't fall inside /// a content-filter (`filter`) order range left behind by the main scene. pub fn raise_order_floor(&mut self) { let floor = self.primitive_bounds.max_order() + 1; self.primitive_bounds.set_order_floor(floor); } pub fn insert_primitive(&mut self, primitive: impl Into) { let mut primitive = primitive.into(); let clipped_bounds = primitive .bounds() .intersect(&primitive.content_mask().bounds); // Content-filter boundaries must always be inserted as matched pairs — dropping one // (e.g. for an empty clipped region) would orphan its partner and corrupt the renderer's // target stack. Each marker takes an order strictly above ALL prior content, so the start // sorts after everything painted before it and the element's own children (which overlap // the marker bounds) sort strictly above the start. This keeps a marker's order range from // colliding with unrelated non-overlapping content that reuses low orderings (e.g. a // background grid), which would otherwise sweep that content into the group. Content // painted *after* the group is held above it by raising the order floor when the end // marker is inserted (see below) — otherwise a later non-overlapping sibling could reuse a // low order that lands inside the start..end range and be swept into the group. let is_filter_boundary = matches!(primitive, Primitive::FilterBoundary(_)); if clipped_bounds.is_empty() && !is_filter_boundary { return; } let order = if is_filter_boundary { let order_bounds = if clipped_bounds.is_empty() { *primitive.bounds() } else { clipped_bounds }; self.primitive_bounds.insert_above_all(order_bounds) } else { self.layer_stack .last() .copied() .unwrap_or_else(|| self.primitive_bounds.insert(clipped_bounds)) }; match &mut primitive { Primitive::Shadow(shadow) => { shadow.order = order; self.shadows.push(*shadow); } Primitive::Quad(quad) => { quad.order = order; self.quads.push(*quad); } Primitive::Path(path) => { path.order = order; path.id = PathId(self.paths.len()); self.paths.push(path.clone()); } Primitive::Underline(underline) => { underline.order = order; self.underlines.push(*underline); } Primitive::MonochromeSprite(sprite) => { sprite.order = order; self.monochrome_sprites.push(*sprite); } Primitive::SubpixelSprite(sprite) => { sprite.order = order; self.subpixel_sprites.push(*sprite); } Primitive::PolychromeSprite(sprite) => { sprite.order = order; self.polychrome_sprites.push(*sprite); } Primitive::Surface(surface) => { surface.order = order; self.surfaces.push(surface.clone()); } Primitive::BackdropFilter(filter) => { filter.order = order; self.backdrop_filters.push(filter.clone()); } Primitive::FilterBoundary(boundary) => { boundary.order = order; if !boundary.is_start { // A closed content-filter group is a draw-order barrier: everything painted // afterwards must sort above the group's end marker so it can't fall back // inside the group's order range (subsequent non-overlapping content otherwise // reuses a low order). Mirrors the floor raised before deferred draws in // `raise_order_floor`. self.primitive_bounds.set_order_floor(order + 1); } self.filter_boundaries.push(boundary.clone()); } } self.paint_operations .push(PaintOperation::Primitive(primitive)); } pub fn replay(&mut self, range: Range, prev_scene: &Scene) { for operation in &prev_scene.paint_operations[range] { match operation { PaintOperation::Primitive(primitive) => self.insert_primitive(primitive.clone()), PaintOperation::StartLayer(bounds) => self.push_layer(*bounds), PaintOperation::EndLayer => self.pop_layer(), } } } pub fn finish(&mut self) { self.shadows.sort_by_key(|shadow| shadow.order); self.quads.sort_by_key(|quad| quad.order); self.paths.sort_by_key(|path| path.order); self.underlines.sort_by_key(|underline| underline.order); self.monochrome_sprites .sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id)); self.subpixel_sprites .sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id)); self.polychrome_sprites .sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id)); self.surfaces.sort_by_key(|surface| surface.order); self.backdrop_filters.sort_by_key(|filter| filter.order); // Markers normally get distinct, monotonically-increasing orders (children overlap // their group bounds and so sort strictly between the start and end). The `!is_start` // tiebreak only matters for a degenerate empty group whose start and end tie: it keeps // the start (false = 0) ahead of the end (true = 1) so the pair stays well-formed. self.filter_boundaries .sort_by_key(|boundary| (boundary.order, !boundary.is_start)); } #[cfg_attr( all( any(target_os = "linux", target_os = "freebsd"), not(any(feature = "x11", feature = "wayland")) ), allow(dead_code) )] pub fn batches(&self) -> impl Iterator + '_ { BatchIterator { shadows_start: 0, shadows_iter: self.shadows.iter().peekable(), quads_start: 0, quads_iter: self.quads.iter().peekable(), paths_start: 0, paths_iter: self.paths.iter().peekable(), underlines_start: 0, underlines_iter: self.underlines.iter().peekable(), monochrome_sprites_start: 0, monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(), subpixel_sprites_start: 0, subpixel_sprites_iter: self.subpixel_sprites.iter().peekable(), polychrome_sprites_start: 0, polychrome_sprites_iter: self.polychrome_sprites.iter().peekable(), surfaces_start: 0, surfaces_iter: self.surfaces.iter().peekable(), backdrop_filters_start: 0, backdrop_filters_iter: self.backdrop_filters.iter().peekable(), filter_boundaries_start: 0, filter_boundaries_iter: self.filter_boundaries.iter().peekable(), } } } #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)] #[cfg_attr( all( any(target_os = "linux", target_os = "freebsd"), not(any(feature = "x11", feature = "wayland")) ), allow(dead_code) )] pub(crate) enum PrimitiveKind { // Lowest discriminant: at an equal order, a content-filter group-start is emitted before // the group's own content so the renderer redirects rendering before any child draws. FilterBoundaryStart, Shadow, #[default] Quad, Path, Underline, MonochromeSprite, SubpixelSprite, PolychromeSprite, Surface, BackdropFilter, // Highest discriminant: at an equal order, a group-end is emitted after the group's content // so the renderer composites the filtered group only once every child has been drawn. FilterBoundaryEnd, } pub(crate) enum PaintOperation { Primitive(Primitive), StartLayer(Bounds), EndLayer, } #[derive(Clone)] #[expect(missing_docs)] pub enum Primitive { Shadow(Shadow), Quad(Quad), Path(Path), Underline(Underline), MonochromeSprite(MonochromeSprite), SubpixelSprite(SubpixelSprite), PolychromeSprite(PolychromeSprite), Surface(PaintSurface), BackdropFilter(BackdropFilter), FilterBoundary(FilterBoundary), } #[expect(missing_docs)] impl Primitive { pub fn bounds(&self) -> &Bounds { match self { Primitive::Shadow(shadow) => &shadow.bounds, Primitive::Quad(quad) => &quad.bounds, Primitive::Path(path) => &path.bounds, Primitive::Underline(underline) => &underline.bounds, Primitive::MonochromeSprite(sprite) => &sprite.bounds, Primitive::SubpixelSprite(sprite) => &sprite.bounds, Primitive::PolychromeSprite(sprite) => &sprite.bounds, Primitive::Surface(surface) => &surface.bounds, Primitive::BackdropFilter(filter) => &filter.bounds, Primitive::FilterBoundary(boundary) => &boundary.bounds, } } pub fn content_mask(&self) -> &ContentMask { match self { Primitive::Shadow(shadow) => &shadow.content_mask, Primitive::Quad(quad) => &quad.content_mask, Primitive::Path(path) => &path.content_mask, Primitive::Underline(underline) => &underline.content_mask, Primitive::MonochromeSprite(sprite) => &sprite.content_mask, Primitive::SubpixelSprite(sprite) => &sprite.content_mask, Primitive::PolychromeSprite(sprite) => &sprite.content_mask, Primitive::Surface(surface) => &surface.content_mask, Primitive::BackdropFilter(filter) => &filter.content_mask, Primitive::FilterBoundary(boundary) => &boundary.content_mask, } } } #[cfg_attr( all( any(target_os = "linux", target_os = "freebsd"), not(any(feature = "x11", feature = "wayland")) ), allow(dead_code) )] struct BatchIterator<'a> { shadows_start: usize, shadows_iter: Peekable>, quads_start: usize, quads_iter: Peekable>, paths_start: usize, paths_iter: Peekable>>, underlines_start: usize, underlines_iter: Peekable>, monochrome_sprites_start: usize, monochrome_sprites_iter: Peekable>, subpixel_sprites_start: usize, subpixel_sprites_iter: Peekable>, polychrome_sprites_start: usize, polychrome_sprites_iter: Peekable>, surfaces_start: usize, surfaces_iter: Peekable>, backdrop_filters_start: usize, backdrop_filters_iter: Peekable>, filter_boundaries_start: usize, filter_boundaries_iter: Peekable>, } impl<'a> Iterator for BatchIterator<'a> { type Item = PrimitiveBatch; fn next(&mut self) -> Option { let mut orders_and_kinds = [ ( self.shadows_iter.peek().map(|s| s.order), PrimitiveKind::Shadow, ), (self.quads_iter.peek().map(|q| q.order), PrimitiveKind::Quad), (self.paths_iter.peek().map(|q| q.order), PrimitiveKind::Path), ( self.underlines_iter.peek().map(|u| u.order), PrimitiveKind::Underline, ), ( self.monochrome_sprites_iter.peek().map(|s| s.order), PrimitiveKind::MonochromeSprite, ), ( self.subpixel_sprites_iter.peek().map(|s| s.order), PrimitiveKind::SubpixelSprite, ), ( self.polychrome_sprites_iter.peek().map(|s| s.order), PrimitiveKind::PolychromeSprite, ), ( self.surfaces_iter.peek().map(|s| s.order), PrimitiveKind::Surface, ), ( self.backdrop_filters_iter.peek().map(|f| f.order), PrimitiveKind::BackdropFilter, ), ( self.filter_boundaries_iter.peek().map(|b| b.order), // The same vec yields both start and end markers; the discriminant decides // where the next marker sorts relative to draw batches at an equal order // (start before content, end after). match self.filter_boundaries_iter.peek() { Some(boundary) if boundary.is_start => PrimitiveKind::FilterBoundaryStart, _ => PrimitiveKind::FilterBoundaryEnd, }, ), ]; orders_and_kinds.sort_by_key(|(order, kind)| (order.unwrap_or(u32::MAX), *kind)); let first = orders_and_kinds[0]; let second = orders_and_kinds[1]; let (batch_kind, max_order_and_kind) = if first.0.is_some() { (first.1, (second.0.unwrap_or(u32::MAX), second.1)) } else { return None; }; match batch_kind { PrimitiveKind::Shadow => { let shadows_start = self.shadows_start; let mut shadows_end = shadows_start + 1; self.shadows_iter.next(); while self .shadows_iter .next_if(|shadow| (shadow.order, batch_kind) < max_order_and_kind) .is_some() { shadows_end += 1; } self.shadows_start = shadows_end; Some(PrimitiveBatch::Shadows(shadows_start..shadows_end)) } PrimitiveKind::Quad => { let quads_start = self.quads_start; let mut quads_end = quads_start + 1; self.quads_iter.next(); while self .quads_iter .next_if(|quad| (quad.order, batch_kind) < max_order_and_kind) .is_some() { quads_end += 1; } self.quads_start = quads_end; Some(PrimitiveBatch::Quads(quads_start..quads_end)) } PrimitiveKind::Path => { let paths_start = self.paths_start; let mut paths_end = paths_start + 1; self.paths_iter.next(); while self .paths_iter .next_if(|path| (path.order, batch_kind) < max_order_and_kind) .is_some() { paths_end += 1; } self.paths_start = paths_end; Some(PrimitiveBatch::Paths(paths_start..paths_end)) } PrimitiveKind::Underline => { let underlines_start = self.underlines_start; let mut underlines_end = underlines_start + 1; self.underlines_iter.next(); while self .underlines_iter .next_if(|underline| (underline.order, batch_kind) < max_order_and_kind) .is_some() { underlines_end += 1; } self.underlines_start = underlines_end; Some(PrimitiveBatch::Underlines(underlines_start..underlines_end)) } PrimitiveKind::MonochromeSprite => { let texture_id = self.monochrome_sprites_iter.peek().unwrap().tile.texture_id; let sprites_start = self.monochrome_sprites_start; let mut sprites_end = sprites_start + 1; self.monochrome_sprites_iter.next(); while self .monochrome_sprites_iter .next_if(|sprite| { (sprite.order, batch_kind) < max_order_and_kind && sprite.tile.texture_id == texture_id }) .is_some() { sprites_end += 1; } self.monochrome_sprites_start = sprites_end; Some(PrimitiveBatch::MonochromeSprites { texture_id, range: sprites_start..sprites_end, }) } PrimitiveKind::SubpixelSprite => { let texture_id = self.subpixel_sprites_iter.peek().unwrap().tile.texture_id; let sprites_start = self.subpixel_sprites_start; let mut sprites_end = sprites_start + 1; self.subpixel_sprites_iter.next(); while self .subpixel_sprites_iter .next_if(|sprite| { (sprite.order, batch_kind) < max_order_and_kind && sprite.tile.texture_id == texture_id }) .is_some() { sprites_end += 1; } self.subpixel_sprites_start = sprites_end; Some(PrimitiveBatch::SubpixelSprites { texture_id, range: sprites_start..sprites_end, }) } PrimitiveKind::PolychromeSprite => { let texture_id = self.polychrome_sprites_iter.peek().unwrap().tile.texture_id; let sprites_start = self.polychrome_sprites_start; let mut sprites_end = sprites_start + 1; self.polychrome_sprites_iter.next(); while self .polychrome_sprites_iter .next_if(|sprite| { (sprite.order, batch_kind) < max_order_and_kind && sprite.tile.texture_id == texture_id }) .is_some() { sprites_end += 1; } self.polychrome_sprites_start = sprites_end; Some(PrimitiveBatch::PolychromeSprites { texture_id, range: sprites_start..sprites_end, }) } PrimitiveKind::Surface => { let surfaces_start = self.surfaces_start; let mut surfaces_end = surfaces_start + 1; self.surfaces_iter.next(); while self .surfaces_iter .next_if(|surface| (surface.order, batch_kind) < max_order_and_kind) .is_some() { surfaces_end += 1; } self.surfaces_start = surfaces_end; Some(PrimitiveBatch::Surfaces(surfaces_start..surfaces_end)) } PrimitiveKind::BackdropFilter => { let backdrop_filters_start = self.backdrop_filters_start; let mut backdrop_filters_end = backdrop_filters_start + 1; self.backdrop_filters_iter.next(); while self .backdrop_filters_iter .next_if(|filter| (filter.order, batch_kind) < max_order_and_kind) .is_some() { backdrop_filters_end += 1; } self.backdrop_filters_start = backdrop_filters_end; Some(PrimitiveBatch::BackdropFilters( backdrop_filters_start..backdrop_filters_end, )) } // Boundaries are emitted one at a time (never merged) so the renderer can switch // render targets at exactly the right point in the batch stream. PrimitiveKind::FilterBoundaryStart | PrimitiveKind::FilterBoundaryEnd => { let index = self.filter_boundaries_start; self.filter_boundaries_iter.next(); self.filter_boundaries_start = index + 1; Some(PrimitiveBatch::FilterBoundary(index)) } } } } #[derive(Debug)] #[cfg_attr( all( any(target_os = "linux", target_os = "freebsd"), not(any(feature = "x11", feature = "wayland")) ), allow(dead_code) )] #[allow(missing_docs)] pub enum PrimitiveBatch { Shadows(Range), Quads(Range), Paths(Range), Underlines(Range), MonochromeSprites { texture_id: AtlasTextureId, range: Range, }, #[cfg_attr(target_os = "macos", allow(dead_code))] SubpixelSprites { texture_id: AtlasTextureId, range: Range, }, PolychromeSprites { texture_id: AtlasTextureId, range: Range, }, Surfaces(Range), BackdropFilters(Range), /// A single content-filter group boundary; index into [`Scene::filter_boundaries`]. Read /// `is_start` to tell whether this opens the group (switch render target) or closes it /// (filter the offscreen target and composite it back). FilterBoundary(usize), } #[derive(Default, Debug, Copy, Clone)] #[repr(C)] #[expect(missing_docs)] pub struct Quad { pub order: DrawOrder, pub border_style: BorderStyle, pub bounds: Bounds, pub content_mask: ContentMask, pub background: Background, pub border_color: Hsla, pub corner_radii: Corners, pub border_widths: Edges, } impl From for Primitive { fn from(quad: Quad) -> Self { Primitive::Quad(quad) } } #[derive(Debug, Copy, Clone)] #[repr(C)] #[expect(missing_docs)] pub struct Underline { pub order: DrawOrder, pub pad: u32, // align to 8 bytes pub bounds: Bounds, pub content_mask: ContentMask, pub color: Hsla, pub thickness: ScaledPixels, pub wavy: u32, } impl From for Primitive { fn from(underline: Underline) -> Self { Primitive::Underline(underline) } } #[derive(Debug, Copy, Clone)] #[repr(C)] #[expect(missing_docs)] pub struct Shadow { pub order: DrawOrder, pub blur_radius: ScaledPixels, pub bounds: Bounds, pub corner_radii: Corners, pub content_mask: ContentMask, pub color: Hsla, pub element_bounds: Bounds, pub element_corner_radii: Corners, /// 0 = drop shadow (rendered outside the element), 1 = inset shadow (rendered inside). pub inset: u32, pub pad: u32, // align to 8 bytes } impl From for Primitive { fn from(shadow: Shadow) -> Self { Primitive::Shadow(shadow) } } /// A backdrop filter blurs (and may otherwise filter) the content already rendered behind /// `bounds`, compositing the result into a rounded rectangle — the frosted-glass effect. /// Emitted by [`crate::Window::paint_backdrop_filter`]; produces the CSS `backdrop-filter` effect. #[derive(Default, Debug, Clone)] #[expect(missing_docs)] pub struct BackdropFilter { pub order: DrawOrder, pub bounds: Bounds, pub content_mask: ContentMask, pub corner_radii: Corners, /// The filter chain applied to the backdrop, in scene (device-pixel) space. Identity filters /// are dropped at paint time, so a `BackdropFilter` is only emitted when this is non-empty. /// /// Inline capacity is 4: a `SmallVec<[ScaledFilter; 4]>` is the same size as capacity 1 here /// (the heap repr already occupies that space), so chains up to 4 filters avoid allocating /// at no extra struct size. pub filters: SmallVec<[ScaledFilter; 4]>, /// Element opacity captured at paint time, multiplied into the composited result. pub opacity: f32, } impl From for Primitive { fn from(filter: BackdropFilter) -> Self { Primitive::BackdropFilter(filter) } } /// The start or end marker of a content-filter (`filter`) isolation group. The element's /// subtree is painted between a matched start/end pair; the renderer redirects that span into /// an offscreen target, filters it, and composites it back at `bounds`. Produces the CSS /// `filter` effect (e.g. blurring the element and its children as a single group). #[derive(Debug, Clone)] #[expect(missing_docs)] pub struct FilterBoundary { pub order: DrawOrder, pub bounds: Bounds, pub content_mask: ContentMask, pub corner_radii: Corners, /// The filter chain applied to the isolated group, in scene (device-pixel) space. Identity /// filters are dropped at paint time, so a `FilterBoundary` is only emitted when non-empty. /// Inline capacity 4 (same struct size as 1 here — see [`BackdropFilter::filters`]). pub filters: SmallVec<[ScaledFilter; 4]>, pub opacity: f32, /// `true` for the start marker (opens the group), `false` for the end marker (closes it). pub is_start: bool, } impl From for Primitive { fn from(boundary: FilterBoundary) -> Self { Primitive::FilterBoundary(boundary) } } /// The style of a border. #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)] #[repr(C)] pub enum BorderStyle { /// A solid border. #[default] Solid = 0, /// A dashed border. Dashed = 1, } /// A data type representing a 2 dimensional transformation that can be applied to an element. #[derive(Debug, Clone, Copy, PartialEq)] #[repr(C)] pub struct TransformationMatrix { /// 2x2 matrix containing rotation and scale, /// stored row-major pub rotation_scale: [[f32; 2]; 2], /// translation vector pub translation: [f32; 2], } impl Eq for TransformationMatrix {} impl TransformationMatrix { /// The unit matrix, has no effect. pub fn unit() -> Self { Self { rotation_scale: [[1.0, 0.0], [0.0, 1.0]], translation: [0.0, 0.0], } } /// Move the origin by a given point pub fn translate(mut self, point: Point) -> Self { self.compose(Self { rotation_scale: [[1.0, 0.0], [0.0, 1.0]], translation: [point.x.0, point.y.0], }) } /// Clockwise rotation in radians around the origin pub fn rotate(self, angle: Radians) -> Self { self.compose(Self { rotation_scale: [ [angle.0.cos(), -angle.0.sin()], [angle.0.sin(), angle.0.cos()], ], translation: [0.0, 0.0], }) } /// Scale around the origin pub fn scale(self, size: Size) -> Self { self.compose(Self { rotation_scale: [[size.width, 0.0], [0.0, size.height]], translation: [0.0, 0.0], }) } /// Perform matrix multiplication with another transformation /// to produce a new transformation that is the result of /// applying both transformations: first, `other`, then `self`. #[inline] pub fn compose(self, other: TransformationMatrix) -> TransformationMatrix { if other == Self::unit() { return self; } // Perform matrix multiplication TransformationMatrix { rotation_scale: [ [ self.rotation_scale[0][0] * other.rotation_scale[0][0] + self.rotation_scale[0][1] * other.rotation_scale[1][0], self.rotation_scale[0][0] * other.rotation_scale[0][1] + self.rotation_scale[0][1] * other.rotation_scale[1][1], ], [ self.rotation_scale[1][0] * other.rotation_scale[0][0] + self.rotation_scale[1][1] * other.rotation_scale[1][0], self.rotation_scale[1][0] * other.rotation_scale[0][1] + self.rotation_scale[1][1] * other.rotation_scale[1][1], ], ], translation: [ self.translation[0] + self.rotation_scale[0][0] * other.translation[0] + self.rotation_scale[0][1] * other.translation[1], self.translation[1] + self.rotation_scale[1][0] * other.translation[0] + self.rotation_scale[1][1] * other.translation[1], ], } } /// Apply transformation to a point, mainly useful for debugging pub fn apply(&self, point: Point) -> Point { let input = [point.x.0, point.y.0]; let mut output = self.translation; for (i, output_cell) in output.iter_mut().enumerate() { for (k, input_cell) in input.iter().enumerate() { *output_cell += self.rotation_scale[i][k] * *input_cell; } } Point::new(output[0].into(), output[1].into()) } } impl Default for TransformationMatrix { fn default() -> Self { Self::unit() } } #[derive(Copy, Clone, Debug)] #[repr(C)] #[expect(missing_docs)] pub struct MonochromeSprite { pub order: DrawOrder, pub pad: u32, pub bounds: Bounds, pub content_mask: ContentMask, pub color: Hsla, pub tile: AtlasTile, pub transformation: TransformationMatrix, } impl From for Primitive { fn from(sprite: MonochromeSprite) -> Self { Primitive::MonochromeSprite(sprite) } } #[derive(Copy, Clone, Debug)] #[repr(C)] #[expect(missing_docs)] pub struct SubpixelSprite { pub order: DrawOrder, pub pad: u32, // align to 8 bytes pub bounds: Bounds, pub content_mask: ContentMask, pub color: Hsla, pub tile: AtlasTile, pub transformation: TransformationMatrix, } impl From for Primitive { fn from(sprite: SubpixelSprite) -> Self { Primitive::SubpixelSprite(sprite) } } #[derive(Copy, Clone, Debug)] #[repr(C)] #[expect(missing_docs)] pub struct PolychromeSprite { pub order: DrawOrder, pub pad: u32, pub grayscale: bool, pub opacity: f32, pub bounds: Bounds, pub content_mask: ContentMask, pub corner_radii: Corners, pub tile: AtlasTile, } impl From for Primitive { fn from(sprite: PolychromeSprite) -> Self { Primitive::PolychromeSprite(sprite) } } #[derive(Clone, Debug)] #[allow(missing_docs)] pub struct PaintSurface { pub order: DrawOrder, pub bounds: Bounds, pub content_mask: ContentMask, #[cfg(target_os = "macos")] pub image_buffer: core_video::pixel_buffer::CVPixelBuffer, #[cfg(any(target_os = "linux", target_os = "freebsd"))] pub texture: std::sync::Arc, #[cfg(any(target_os = "linux", target_os = "freebsd"))] pub texture_size: Size, } impl From for Primitive { fn from(surface: PaintSurface) -> Self { Primitive::Surface(surface) } } #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] #[expect(missing_docs)] pub struct PathId(pub usize); /// A line made up of a series of vertices and control points. #[derive(Clone, Debug)] #[expect(missing_docs)] pub struct Path { pub id: PathId, pub order: DrawOrder, pub bounds: Bounds

, pub content_mask: ContentMask

, pub vertices: Vec>, pub color: Background, start: Point

, current: Point

, contour_count: usize, } impl Path { /// Create a new path with the given starting point. pub fn new(start: Point) -> Self { Self { id: PathId(0), order: DrawOrder::default(), vertices: Vec::new(), start, current: start, bounds: Bounds { origin: start, size: Default::default(), }, content_mask: Default::default(), color: Default::default(), contour_count: 0, } } /// Scale this path by the given factor. pub fn scale(&self, factor: f32) -> Path { Path { id: self.id, order: self.order, bounds: self.bounds.scale(factor), content_mask: self.content_mask.scale(factor), vertices: self .vertices .iter() .map(|vertex| vertex.scale(factor)) .collect(), start: self.start.map(|start| start.scale(factor)), current: self.current.scale(factor), contour_count: self.contour_count, color: self.color, } } /// Move the start, current point to the given point. pub fn move_to(&mut self, to: Point) { self.contour_count += 1; self.start = to; self.current = to; } /// Draw a straight line from the current point to the given point. pub fn line_to(&mut self, to: Point) { self.contour_count += 1; if self.contour_count > 1 { self.push_triangle( (self.start, self.current, to), (point(0., 1.), point(0., 1.), point(0., 1.)), ); } self.current = to; } /// Draw a curve from the current point to the given point, using the given control point. pub fn curve_to(&mut self, to: Point, ctrl: Point) { self.contour_count += 1; if self.contour_count > 1 { self.push_triangle( (self.start, self.current, to), (point(0., 1.), point(0., 1.), point(0., 1.)), ); } self.push_triangle( (self.current, ctrl, to), (point(0., 0.), point(0.5, 0.), point(1., 1.)), ); self.current = to; } /// Push a triangle to the Path. pub fn push_triangle( &mut self, xy: (Point, Point, Point), st: (Point, Point, Point), ) { self.bounds = self .bounds .union(&Bounds { origin: xy.0, size: Default::default(), }) .union(&Bounds { origin: xy.1, size: Default::default(), }) .union(&Bounds { origin: xy.2, size: Default::default(), }); self.vertices.push(PathVertex { xy_position: xy.0, st_position: st.0, content_mask: Default::default(), }); self.vertices.push(PathVertex { xy_position: xy.1, st_position: st.1, content_mask: Default::default(), }); self.vertices.push(PathVertex { xy_position: xy.2, st_position: st.2, content_mask: Default::default(), }); } } impl Path where T: Clone + Debug + Default + PartialEq + PartialOrd + Add + Sub, { #[allow(unused)] #[expect(missing_docs)] pub fn clipped_bounds(&self) -> Bounds { self.bounds.intersect(&self.content_mask.bounds) } } impl From> for Primitive { fn from(path: Path) -> Self { Primitive::Path(path) } } #[derive(Clone, Debug)] #[repr(C)] #[expect(missing_docs)] pub struct PathVertex { pub xy_position: Point

, pub st_position: Point, pub content_mask: ContentMask

, } #[expect(missing_docs)] impl PathVertex { pub fn scale(&self, factor: f32) -> PathVertex { PathVertex { xy_position: self.xy_position.scale(factor), st_position: self.st_position, content_mask: self.content_mask.scale(factor), } } } #[cfg(test)] mod tests { use super::*; use crate::{Point, Size}; fn sp(value: f32) -> ScaledPixels { ScaledPixels(value) } /// All test primitives cover the same region so the bounds tree assigns strictly /// increasing orders in insertion order — making the expected batch order deterministic. fn full_bounds() -> Bounds { Bounds { origin: Point { x: sp(0.0), y: sp(0.0), }, size: Size { width: sp(100.0), height: sp(100.0), }, } } fn mask() -> ContentMask { ContentMask { bounds: full_bounds(), } } fn quad() -> Quad { Quad { bounds: full_bounds(), content_mask: mask(), ..Default::default() } } /// A 100x100 quad whose bounds don't overlap `full_bounds()` (used to exercise the /// order-reuse path: non-overlapping content reuses low draw-orders). fn detached_quad() -> Quad { let bounds = Bounds { origin: Point { x: sp(200.0), y: sp(200.0), }, size: Size { width: sp(100.0), height: sp(100.0), }, }; Quad { bounds, content_mask: ContentMask { bounds }, ..Default::default() } } fn boundary(is_start: bool) -> FilterBoundary { FilterBoundary { order: 0, bounds: full_bounds(), content_mask: mask(), corner_radii: Corners::default(), filters: smallvec::smallvec![ScaledFilter::Blur(sp(8.0))], opacity: 1.0, is_start, } } fn backdrop() -> BackdropFilter { BackdropFilter { bounds: full_bounds(), content_mask: mask(), corner_radii: Corners::default(), filters: smallvec::smallvec![ScaledFilter::Blur(sp(20.0))], opacity: 1.0, ..Default::default() } } fn batch_kinds(scene: &mut Scene) -> Vec<&'static str> { scene.finish(); scene .batches() .map(|batch| match batch { PrimitiveBatch::Quads(_) => "quad", PrimitiveBatch::BackdropFilters(_) => "backdrop", PrimitiveBatch::FilterBoundary(ix) => { if scene.filter_boundaries[ix].is_start { "start" } else { "end" } } _ => "other", }) .collect() } #[test] fn content_filter_group_brackets_its_children() { let mut scene = Scene::default(); // Background painted before the filtered element. scene.insert_primitive(quad()); // A content-filtered element: start marker, its child, end marker. scene.insert_primitive(boundary(true)); scene.insert_primitive(quad()); scene.insert_primitive(boundary(false)); // The start must precede the group's child and the end must follow it, so the // renderer can redirect rendering for exactly the group's span. assert_eq!( batch_kinds(&mut scene), vec!["quad", "start", "quad", "end"] ); } // Note: this validates only the *scene ordering* of nested filter boundaries (start/child/ // end interleaving), not that a renderer actually isolates both levels — that depends on the // backend's group-texture pool (see MAX_FILTER_DEPTH) and is exercised by the `blur` example. #[test] fn nested_content_filters_emit_well_nested_ordering() { let mut scene = Scene::default(); scene.insert_primitive(boundary(true)); // outer start scene.insert_primitive(quad()); // outer child scene.insert_primitive(boundary(true)); // inner start scene.insert_primitive(quad()); // inner child scene.insert_primitive(boundary(false)); // inner end scene.insert_primitive(boundary(false)); // outer end assert_eq!( batch_kinds(&mut scene), vec!["start", "quad", "start", "quad", "end", "end"] ); } #[test] fn content_after_a_filter_group_sorts_above_it() { let mut scene = Scene::default(); // A content-filtered element: start marker, its child, end marker. scene.insert_primitive(boundary(true)); scene.insert_primitive(quad()); scene.insert_primitive(boundary(false)); // A sibling painted after the group that does NOT overlap it. Without the close-time // order-floor it would reuse the lowest order, tie with the start marker, and be swept // into the group (start, quad, quad, end); it must instead sort after the end marker. scene.insert_primitive(detached_quad()); assert_eq!( batch_kinds(&mut scene), vec!["start", "quad", "end", "quad"] ); } #[test] fn backdrop_filter_sorts_before_a_later_overlapping_quad() { let mut scene = Scene::default(); // Content behind the frosted panel. scene.insert_primitive(quad()); // The panel: its backdrop snapshot, then its (translucent) background quad on top. scene.insert_primitive(backdrop()); scene.insert_primitive(quad()); assert_eq!(batch_kinds(&mut scene), vec!["quad", "backdrop", "quad"]); } }