use crate::compositor::LayerId;
use crate::effects::{BlendMode, ColorFilter, DropShadowEffect, GaussianBlur};
use crate::geometry::RRect;
use crate::glyphs::GlyphRun;
use crate::gradients::Brush;
use crate::paint::{Border, FillRule, Paint, PaintStyle, Stroke};
use crate::paths::Path;
use incular_core::{Color, Rect, Size, Transform};
use incular_image::ImageHandle;
use std::sync::{
Arc,
atomic::{AtomicU64, Ordering},
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct BackdropKey(u64);
impl BackdropKey {
#[must_use]
pub fn new() -> Self {
static NEXT: AtomicU64 = AtomicU64::new(1);
Self(NEXT.fetch_add(1, Ordering::Relaxed))
}
}
impl Default for BackdropKey {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct SurfacePartitionId {
index: u32,
generation: u32,
}
impl SurfacePartitionId {
#[must_use]
pub const fn from_parts(index: u32, generation: u32) -> Self {
Self { index, generation }
}
#[must_use]
pub const fn index(self) -> u32 {
self.index
}
#[must_use]
pub const fn generation(self) -> u32 {
self.generation
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum PaintCommand {
PushSurfacePartition {
id: SurfacePartitionId,
},
PopSurfacePartition,
Rect {
rect: Rect,
color: Color,
},
RRect {
rrect: RRect,
brush: Brush,
},
Border {
rrect: RRect,
border: Border,
},
FillPath {
path: Arc<Path>,
brush: Brush,
fill_rule: FillRule,
},
StrokePath {
path: Arc<Path>,
brush: Brush,
stroke: Stroke,
},
Image {
image: ImageHandle,
source: Rect,
destination: Rect,
sampling: ImageSampling,
},
GlyphRun {
run: Arc<GlyphRun>,
color: Color,
},
PushClip {
rect: Rect,
},
PushClipRRect {
rrect: RRect,
},
PushClipOval {
rect: Rect,
},
PushClipPath {
path: Arc<Path>,
fill_rule: FillRule,
},
PopClip,
PushTransform {
transform: Transform,
},
PopTransform,
PushOpacity {
layer: LayerId,
alpha: f32,
generation: u64,
bounds: Rect,
},
PopOpacity,
PushBlur {
layer: LayerId,
blur: GaussianBlur,
generation: u64,
bounds: Rect,
},
PushDropShadow {
layer: LayerId,
shadow: DropShadowEffect,
generation: u64,
bounds: Rect,
},
PushColorFilter {
layer: LayerId,
filter: ColorFilter,
generation: u64,
bounds: Rect,
},
PushBlend {
layer: LayerId,
mode: BlendMode,
generation: u64,
bounds: Rect,
},
PushShaderMask {
layer: LayerId,
shader: Brush,
blend_mode: BlendMode,
mask_size: Size,
mask_transform: Transform,
generation: u64,
bounds: Rect,
},
PushBackdropFilter {
layer: LayerId,
blur: GaussianBlur,
blend_mode: BlendMode,
enabled: bool,
matrix: Option<Transform>,
backdrop_key: Option<BackdropKey>,
generation: u64,
bounds: Rect,
},
PopEffect,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum ImageSampling {
#[default]
Linear,
Nearest,
}
#[derive(Clone, Default)]
pub struct DisplayList {
commands: Option<std::sync::Arc<Vec<PaintCommand>>>,
}
impl PartialEq for DisplayList {
fn eq(&self, other: &Self) -> bool {
self.commands() == other.commands()
}
}
impl std::fmt::Debug for DisplayList {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DisplayList")
.field("commands", &self.commands())
.finish()
}
}
impl DisplayList {
#[must_use]
pub const fn new() -> Self {
Self { commands: None }
}
fn commands_mut(&mut self) -> &mut Vec<PaintCommand> {
std::sync::Arc::make_mut(
self.commands
.get_or_insert_with(|| std::sync::Arc::new(Vec::new())),
)
}
pub(crate) fn deep_copy(&self) -> Self {
Self {
commands: Some(std::sync::Arc::new(self.commands().to_vec())),
}
}
pub fn push(&mut self, command: PaintCommand) {
let commands = self.commands_mut();
if commands.len() == commands.capacity() {
commands.reserve_exact(commands.len().max(1));
}
commands.push(command);
}
pub fn extend_from(&mut self, other: &Self) {
if other.is_empty() {
return;
}
self.commands_mut().extend(other.commands().iter().cloned());
}
#[must_use]
pub fn detach_surface_partitions(
&self,
selected: &std::collections::HashSet<SurfacePartitionId>,
) -> (Self, std::collections::HashMap<SurfacePartitionId, Self>) {
let mut parent = Self::new();
let mut detached = std::collections::HashMap::<SurfacePartitionId, Self>::new();
let mut stack = Vec::<SurfacePartitionId>::new();
for command in self.commands() {
match command {
PaintCommand::PushSurfacePartition { id } => stack.push(*id),
PaintCommand::PopSurfacePartition => {
let _ = stack.pop();
}
command => {
let target = stack.iter().rev().copied().find(|id| selected.contains(id));
if let Some(id) = target {
detached.entry(id).or_default().push(command.clone());
} else {
parent.push(command.clone());
}
}
}
}
(parent, detached)
}
#[must_use]
pub fn translated(&self, offset: incular_core::Offset) -> Self {
if self.is_empty() || offset == incular_core::Offset::ZERO {
return self.clone();
}
let mut output = Self::new();
output.push(PaintCommand::PushTransform {
transform: Transform::translation(offset),
});
output.extend_from(self);
output.push(PaintCommand::PopTransform);
output
}
#[must_use]
pub fn begins_with_opaque_surface_rect(&self, surface_size: Size) -> bool {
if !surface_size.width.is_finite()
|| !surface_size.height.is_finite()
|| surface_size.width <= 0.0
|| surface_size.height <= 0.0
{
return false;
}
let mut transforms = vec![Transform::IDENTITY];
for command in self.commands() {
match command {
PaintCommand::PushTransform { transform } => {
let next = transforms
.last()
.copied()
.expect("root transform")
.then(*transform);
if !next.is_translation() {
return false;
}
transforms.push(next);
}
PaintCommand::PopTransform => {
if transforms.len() <= 1 {
return false;
}
transforms.pop();
}
PaintCommand::PushSurfacePartition { .. } | PaintCommand::PopSurfacePartition => {}
PaintCommand::Rect { rect, color } => {
if color.alpha != u8::MAX {
return false;
}
let world = transforms
.last()
.expect("root transform")
.transform_rect_bbox(*rect);
const EPSILON: f32 = 0.01;
return world.origin.x <= EPSILON
&& world.origin.y <= EPSILON
&& world.origin.x + world.size.width >= surface_size.width - EPSILON
&& world.origin.y + world.size.height >= surface_size.height - EPSILON;
}
_ => return false,
}
}
false
}
#[must_use]
pub fn commands(&self) -> &[PaintCommand] {
self.commands.as_deref().map_or(&[], Vec::as_slice)
}
#[must_use]
pub fn len(&self) -> usize {
self.commands().len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.commands().is_empty()
}
pub fn clear(&mut self) {
if let Some(commands) = self.commands.as_mut() {
match std::sync::Arc::get_mut(commands) {
Some(unique) => unique.clear(),
None => self.commands = None,
}
}
}
}
#[derive(Default)]
pub struct Canvas {
list: DisplayList,
saves: Vec<SaveKind>,
}
#[derive(Clone, Copy)]
enum SaveKind {
Transform,
Clip,
}
impl Canvas {
pub fn rect(&mut self, rect: Rect, color: Color) {
self.list.push(PaintCommand::Rect { rect, color });
}
pub fn rrect(&mut self, rrect: RRect, brush: impl Into<Brush>) {
self.list.push(PaintCommand::RRect {
rrect,
brush: brush.into(),
});
}
pub fn border(&mut self, rrect: RRect, border: Border) {
self.list.push(PaintCommand::Border { rrect, border });
}
pub fn fill_path(&mut self, path: Arc<Path>, brush: impl Into<Brush>, fill_rule: FillRule) {
self.list.push(PaintCommand::FillPath {
path,
brush: brush.into(),
fill_rule,
});
}
pub fn stroke_path(&mut self, path: Arc<Path>, brush: impl Into<Brush>, stroke: Stroke) {
self.list.push(PaintCommand::StrokePath {
path,
brush: brush.into(),
stroke,
});
}
pub fn draw_path(&mut self, path: Arc<Path>, paint: &Paint, fill_rule: FillRule) {
match paint.style_value() {
PaintStyle::Fill => self.fill_path(path, paint.brush_value().clone(), fill_rule),
PaintStyle::Stroke => {
self.stroke_path(path, paint.brush_value().clone(), paint.stroke_value())
}
}
}
pub fn image(&mut self, image: ImageHandle, source: Rect, destination: Rect) {
self.image_with_sampling(image, source, destination, ImageSampling::Linear);
}
pub fn image_with_sampling(
&mut self,
image: ImageHandle,
source: Rect,
destination: Rect,
sampling: ImageSampling,
) {
self.list.push(PaintCommand::Image {
image,
source,
destination,
sampling,
});
}
pub fn glyph_run(&mut self, run: Arc<GlyphRun>, color: Color) {
self.list.push(PaintCommand::GlyphRun { run, color });
}
pub fn save_transform(&mut self, transform: Transform) {
self.saves.push(SaveKind::Transform);
self.list.push(PaintCommand::PushTransform { transform });
}
pub fn save_clip(&mut self, rect: Rect) {
self.saves.push(SaveKind::Clip);
self.list.push(PaintCommand::PushClip { rect });
}
pub fn save_clip_rrect(&mut self, rrect: RRect) {
self.saves.push(SaveKind::Clip);
self.list.push(PaintCommand::PushClipRRect { rrect });
}
pub fn save_clip_oval(&mut self, rect: Rect) {
self.saves.push(SaveKind::Clip);
self.list.push(PaintCommand::PushClipOval { rect });
}
pub fn save_clip_path(&mut self, path: Arc<Path>, fill_rule: FillRule) {
self.saves.push(SaveKind::Clip);
self.list
.push(PaintCommand::PushClipPath { path, fill_rule });
}
pub fn restore(&mut self) {
match self.saves.pop().expect("unbalanced canvas restore") {
SaveKind::Transform => self.list.push(PaintCommand::PopTransform),
SaveKind::Clip => self.list.push(PaintCommand::PopClip),
}
}
#[must_use]
pub fn finish(self) -> DisplayList {
assert!(self.saves.is_empty(), "unbalanced display list");
self.list
}
}