
Fixes a regression introduced in https://github.com/zed-industries/zed/pull/34992 Paths are rendered first to an intermediate MSAA texture, and then copied to the final drawable. Because paths can have transparency, it's important that pixels are not copied repeatedly if paths have overlapping bounding boxes. When N paths have the same draw order, we infer that they must have disjoint bounding boxes, so that we can copy them each individually (as opposed to copying a single rect that contains them all). Previously, the bounding box that we were using to copy paths was not accounting for the path's content mask (but it is accounted for in the bounds tree that determines their draw order). This cause bugs like this, where certain path pixels spuriously had their opacity doubled: https://github.com/user-attachments/assets/d792e60c-790b-49ad-b435-6695daba430f This PR fixes that bug. * [x] mac * [x] linux * [x] windows Release Notes: - Fixed a bug where a selection's opacity was computed incorrectly when it overlapped with another editor's selections in a certain way.
833 lines
27 KiB
Rust
833 lines
27 KiB
Rust
// 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, ScaledPixels, Size, bounds_tree::BoundsTree, point,
|
|
};
|
|
use std::{
|
|
fmt::Debug,
|
|
iter::Peekable,
|
|
ops::{Add, Range, Sub},
|
|
slice,
|
|
};
|
|
|
|
#[allow(non_camel_case_types, unused)]
|
|
pub(crate) type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
|
|
|
|
pub(crate) type DrawOrder = u32;
|
|
|
|
#[derive(Default)]
|
|
pub(crate) struct Scene {
|
|
pub(crate) paint_operations: Vec<PaintOperation>,
|
|
primitive_bounds: BoundsTree<ScaledPixels>,
|
|
layer_stack: Vec<DrawOrder>,
|
|
pub(crate) shadows: Vec<Shadow>,
|
|
pub(crate) quads: Vec<Quad>,
|
|
pub(crate) paths: Vec<Path<ScaledPixels>>,
|
|
pub(crate) underlines: Vec<Underline>,
|
|
pub(crate) monochrome_sprites: Vec<MonochromeSprite>,
|
|
pub(crate) polychrome_sprites: Vec<PolychromeSprite>,
|
|
pub(crate) surfaces: Vec<PaintSurface>,
|
|
}
|
|
|
|
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.polychrome_sprites.clear();
|
|
self.surfaces.clear();
|
|
}
|
|
|
|
pub fn len(&self) -> usize {
|
|
self.paint_operations.len()
|
|
}
|
|
|
|
pub fn push_layer(&mut self, bounds: Bounds<ScaledPixels>) {
|
|
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);
|
|
}
|
|
|
|
pub fn insert_primitive(&mut self, primitive: impl Into<Primitive>) {
|
|
let mut primitive = primitive.into();
|
|
let clipped_bounds = primitive
|
|
.bounds()
|
|
.intersect(&primitive.content_mask().bounds);
|
|
|
|
if clipped_bounds.is_empty() {
|
|
return;
|
|
}
|
|
|
|
let order = 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.clone());
|
|
}
|
|
Primitive::Quad(quad) => {
|
|
quad.order = order;
|
|
self.quads.push(quad.clone());
|
|
}
|
|
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.clone());
|
|
}
|
|
Primitive::MonochromeSprite(sprite) => {
|
|
sprite.order = order;
|
|
self.monochrome_sprites.push(sprite.clone());
|
|
}
|
|
Primitive::PolychromeSprite(sprite) => {
|
|
sprite.order = order;
|
|
self.polychrome_sprites.push(sprite.clone());
|
|
}
|
|
Primitive::Surface(surface) => {
|
|
surface.order = order;
|
|
self.surfaces.push(surface.clone());
|
|
}
|
|
}
|
|
self.paint_operations
|
|
.push(PaintOperation::Primitive(primitive));
|
|
}
|
|
|
|
pub fn replay(&mut self, range: Range<usize>, 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.polychrome_sprites
|
|
.sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
|
|
self.surfaces.sort_by_key(|surface| surface.order);
|
|
}
|
|
|
|
#[cfg_attr(
|
|
all(
|
|
any(target_os = "linux", target_os = "freebsd"),
|
|
not(any(feature = "x11", feature = "wayland"))
|
|
),
|
|
allow(dead_code)
|
|
)]
|
|
pub(crate) fn batches(&self) -> impl Iterator<Item = PrimitiveBatch<'_>> {
|
|
BatchIterator {
|
|
shadows: &self.shadows,
|
|
shadows_start: 0,
|
|
shadows_iter: self.shadows.iter().peekable(),
|
|
quads: &self.quads,
|
|
quads_start: 0,
|
|
quads_iter: self.quads.iter().peekable(),
|
|
paths: &self.paths,
|
|
paths_start: 0,
|
|
paths_iter: self.paths.iter().peekable(),
|
|
underlines: &self.underlines,
|
|
underlines_start: 0,
|
|
underlines_iter: self.underlines.iter().peekable(),
|
|
monochrome_sprites: &self.monochrome_sprites,
|
|
monochrome_sprites_start: 0,
|
|
monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(),
|
|
polychrome_sprites: &self.polychrome_sprites,
|
|
polychrome_sprites_start: 0,
|
|
polychrome_sprites_iter: self.polychrome_sprites.iter().peekable(),
|
|
surfaces: &self.surfaces,
|
|
surfaces_start: 0,
|
|
surfaces_iter: self.surfaces.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 {
|
|
Shadow,
|
|
#[default]
|
|
Quad,
|
|
Path,
|
|
Underline,
|
|
MonochromeSprite,
|
|
PolychromeSprite,
|
|
Surface,
|
|
}
|
|
|
|
pub(crate) enum PaintOperation {
|
|
Primitive(Primitive),
|
|
StartLayer(Bounds<ScaledPixels>),
|
|
EndLayer,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub(crate) enum Primitive {
|
|
Shadow(Shadow),
|
|
Quad(Quad),
|
|
Path(Path<ScaledPixels>),
|
|
Underline(Underline),
|
|
MonochromeSprite(MonochromeSprite),
|
|
PolychromeSprite(PolychromeSprite),
|
|
Surface(PaintSurface),
|
|
}
|
|
|
|
impl Primitive {
|
|
pub fn bounds(&self) -> &Bounds<ScaledPixels> {
|
|
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::PolychromeSprite(sprite) => &sprite.bounds,
|
|
Primitive::Surface(surface) => &surface.bounds,
|
|
}
|
|
}
|
|
|
|
pub fn content_mask(&self) -> &ContentMask<ScaledPixels> {
|
|
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::PolychromeSprite(sprite) => &sprite.content_mask,
|
|
Primitive::Surface(surface) => &surface.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: &'a [Shadow],
|
|
shadows_start: usize,
|
|
shadows_iter: Peekable<slice::Iter<'a, Shadow>>,
|
|
quads: &'a [Quad],
|
|
quads_start: usize,
|
|
quads_iter: Peekable<slice::Iter<'a, Quad>>,
|
|
paths: &'a [Path<ScaledPixels>],
|
|
paths_start: usize,
|
|
paths_iter: Peekable<slice::Iter<'a, Path<ScaledPixels>>>,
|
|
underlines: &'a [Underline],
|
|
underlines_start: usize,
|
|
underlines_iter: Peekable<slice::Iter<'a, Underline>>,
|
|
monochrome_sprites: &'a [MonochromeSprite],
|
|
monochrome_sprites_start: usize,
|
|
monochrome_sprites_iter: Peekable<slice::Iter<'a, MonochromeSprite>>,
|
|
polychrome_sprites: &'a [PolychromeSprite],
|
|
polychrome_sprites_start: usize,
|
|
polychrome_sprites_iter: Peekable<slice::Iter<'a, PolychromeSprite>>,
|
|
surfaces: &'a [PaintSurface],
|
|
surfaces_start: usize,
|
|
surfaces_iter: Peekable<slice::Iter<'a, PaintSurface>>,
|
|
}
|
|
|
|
impl<'a> Iterator for BatchIterator<'a> {
|
|
type Item = PrimitiveBatch<'a>;
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
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.polychrome_sprites_iter.peek().map(|s| s.order),
|
|
PrimitiveKind::PolychromeSprite,
|
|
),
|
|
(
|
|
self.surfaces_iter.peek().map(|s| s.order),
|
|
PrimitiveKind::Surface,
|
|
),
|
|
];
|
|
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(
|
|
&self.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(&self.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(&self.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(
|
|
&self.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,
|
|
sprites: &self.monochrome_sprites[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 = self.polychrome_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,
|
|
sprites: &self.polychrome_sprites[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(
|
|
&self.surfaces[surfaces_start..surfaces_end],
|
|
))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
#[cfg_attr(
|
|
all(
|
|
any(target_os = "linux", target_os = "freebsd"),
|
|
not(any(feature = "x11", feature = "wayland"))
|
|
),
|
|
allow(dead_code)
|
|
)]
|
|
pub(crate) enum PrimitiveBatch<'a> {
|
|
Shadows(&'a [Shadow]),
|
|
Quads(&'a [Quad]),
|
|
Paths(&'a [Path<ScaledPixels>]),
|
|
Underlines(&'a [Underline]),
|
|
MonochromeSprites {
|
|
texture_id: AtlasTextureId,
|
|
sprites: &'a [MonochromeSprite],
|
|
},
|
|
PolychromeSprites {
|
|
texture_id: AtlasTextureId,
|
|
sprites: &'a [PolychromeSprite],
|
|
},
|
|
Surfaces(&'a [PaintSurface]),
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone)]
|
|
#[repr(C)]
|
|
pub(crate) struct Quad {
|
|
pub order: DrawOrder,
|
|
pub border_style: BorderStyle,
|
|
pub bounds: Bounds<ScaledPixels>,
|
|
pub content_mask: ContentMask<ScaledPixels>,
|
|
pub background: Background,
|
|
pub border_color: Hsla,
|
|
pub corner_radii: Corners<ScaledPixels>,
|
|
pub border_widths: Edges<ScaledPixels>,
|
|
}
|
|
|
|
impl From<Quad> for Primitive {
|
|
fn from(quad: Quad) -> Self {
|
|
Primitive::Quad(quad)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
#[repr(C)]
|
|
pub(crate) struct Underline {
|
|
pub order: DrawOrder,
|
|
pub pad: u32, // align to 8 bytes
|
|
pub bounds: Bounds<ScaledPixels>,
|
|
pub content_mask: ContentMask<ScaledPixels>,
|
|
pub color: Hsla,
|
|
pub thickness: ScaledPixels,
|
|
pub wavy: bool,
|
|
}
|
|
|
|
impl From<Underline> for Primitive {
|
|
fn from(underline: Underline) -> Self {
|
|
Primitive::Underline(underline)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
#[repr(C)]
|
|
pub(crate) struct Shadow {
|
|
pub order: DrawOrder,
|
|
pub blur_radius: ScaledPixels,
|
|
pub bounds: Bounds<ScaledPixels>,
|
|
pub corner_radii: Corners<ScaledPixels>,
|
|
pub content_mask: ContentMask<ScaledPixels>,
|
|
pub color: Hsla,
|
|
}
|
|
|
|
impl From<Shadow> for Primitive {
|
|
fn from(shadow: Shadow) -> Self {
|
|
Primitive::Shadow(shadow)
|
|
}
|
|
}
|
|
|
|
/// 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<ScaledPixels>) -> 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<f32>) -> 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<Pixels>) -> Point<Pixels> {
|
|
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(Clone, Debug)]
|
|
#[repr(C)]
|
|
pub(crate) struct MonochromeSprite {
|
|
pub order: DrawOrder,
|
|
pub pad: u32, // align to 8 bytes
|
|
pub bounds: Bounds<ScaledPixels>,
|
|
pub content_mask: ContentMask<ScaledPixels>,
|
|
pub color: Hsla,
|
|
pub tile: AtlasTile,
|
|
pub transformation: TransformationMatrix,
|
|
}
|
|
|
|
impl From<MonochromeSprite> for Primitive {
|
|
fn from(sprite: MonochromeSprite) -> Self {
|
|
Primitive::MonochromeSprite(sprite)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
#[repr(C)]
|
|
pub(crate) struct PolychromeSprite {
|
|
pub order: DrawOrder,
|
|
pub pad: u32, // align to 8 bytes
|
|
pub grayscale: bool,
|
|
pub opacity: f32,
|
|
pub bounds: Bounds<ScaledPixels>,
|
|
pub content_mask: ContentMask<ScaledPixels>,
|
|
pub corner_radii: Corners<ScaledPixels>,
|
|
pub tile: AtlasTile,
|
|
}
|
|
|
|
impl From<PolychromeSprite> for Primitive {
|
|
fn from(sprite: PolychromeSprite) -> Self {
|
|
Primitive::PolychromeSprite(sprite)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub(crate) struct PaintSurface {
|
|
pub order: DrawOrder,
|
|
pub bounds: Bounds<ScaledPixels>,
|
|
pub content_mask: ContentMask<ScaledPixels>,
|
|
#[cfg(target_os = "macos")]
|
|
pub image_buffer: core_video::pixel_buffer::CVPixelBuffer,
|
|
}
|
|
|
|
impl From<PaintSurface> for Primitive {
|
|
fn from(surface: PaintSurface) -> Self {
|
|
Primitive::Surface(surface)
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
|
pub(crate) struct PathId(pub(crate) usize);
|
|
|
|
/// A line made up of a series of vertices and control points.
|
|
#[derive(Clone, Debug)]
|
|
pub struct Path<P: Clone + Debug + Default + PartialEq> {
|
|
pub(crate) id: PathId,
|
|
pub(crate) order: DrawOrder,
|
|
pub(crate) bounds: Bounds<P>,
|
|
pub(crate) content_mask: ContentMask<P>,
|
|
pub(crate) vertices: Vec<PathVertex<P>>,
|
|
pub(crate) color: Background,
|
|
start: Point<P>,
|
|
current: Point<P>,
|
|
contour_count: usize,
|
|
}
|
|
|
|
impl Path<Pixels> {
|
|
/// Create a new path with the given starting point.
|
|
pub fn new(start: Point<Pixels>) -> 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<ScaledPixels> {
|
|
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<Pixels>) {
|
|
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<Pixels>) {
|
|
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<Pixels>, ctrl: Point<Pixels>) {
|
|
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<Pixels>, Point<Pixels>, Point<Pixels>),
|
|
st: (Point<f32>, Point<f32>, Point<f32>),
|
|
) {
|
|
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<T> Path<T>
|
|
where
|
|
T: Clone + Debug + Default + PartialEq + PartialOrd + Add<T, Output = T> + Sub<Output = T>,
|
|
{
|
|
#[allow(unused)]
|
|
pub(crate) fn clipped_bounds(&self) -> Bounds<T> {
|
|
self.bounds.intersect(&self.content_mask.bounds)
|
|
}
|
|
}
|
|
|
|
impl From<Path<ScaledPixels>> for Primitive {
|
|
fn from(path: Path<ScaledPixels>) -> Self {
|
|
Primitive::Path(path)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
#[repr(C)]
|
|
pub(crate) struct PathVertex<P: Clone + Debug + Default + PartialEq> {
|
|
pub(crate) xy_position: Point<P>,
|
|
pub(crate) st_position: Point<f32>,
|
|
pub(crate) content_mask: ContentMask<P>,
|
|
}
|
|
|
|
impl PathVertex<Pixels> {
|
|
pub fn scale(&self, factor: f32) -> PathVertex<ScaledPixels> {
|
|
PathVertex {
|
|
xy_position: self.xy_position.scale(factor),
|
|
st_position: self.st_position,
|
|
content_mask: self.content_mask.scale(factor),
|
|
}
|
|
}
|
|
}
|