use valo_geometry::Matrix;
use crate::{Bounds, DisplayList, DisplayListBuilder, Op};
const RESTORE: &Op = &Op::Restore;
pub struct DisplayListWalker<'a> {
lists: Vec<ListPosition<'a>>,
current: Option<CurrentOp<'a>>,
matrix_clip: MatrixClipState,
}
#[derive(Clone, Copy)]
struct ListPosition<'a> {
ops: &'a [Op],
next: usize,
}
struct CurrentOp<'a> {
op: &'a Op,
list: &'a [Op],
index: usize,
skip_scope: bool,
}
pub struct MatrixClipState {
scopes: Vec<Scope>,
}
#[derive(Default)]
struct Scope {
transforms_and_clips: Vec<Op>,
transform_at_open: Matrix,
list_transform: Matrix,
}
impl DisplayList {
pub fn walk(&self) -> DisplayListWalker<'_> {
DisplayListWalker {
lists: vec![ListPosition {
ops: self.ops(),
next: 0,
}],
current: None,
matrix_clip: MatrixClipState {
scopes: vec![Scope::default()],
},
}
}
}
impl<'a> DisplayListWalker<'a> {
pub fn scope(&self) -> &'a [Op] {
let Some(current) = &self.current else {
return &[];
};
match current.op {
Op::Save | Op::SaveLayer { .. } => {
¤t.list[current.index..=scope_end(current.list, current.index)]
}
_ => std::slice::from_ref(current.op),
}
}
pub fn skip_scope(&mut self) {
if let Some(current) = &mut self.current {
current.skip_scope = true;
}
}
pub fn matrix_clip(&self) -> &MatrixClipState {
&self.matrix_clip
}
fn finish_current(&mut self) {
let Some(current) = self.current.take() else {
return;
};
let state = &mut self.matrix_clip;
match (current.op, current.skip_scope) {
(Op::Save | Op::SaveLayer { .. }, true) => {
if let Some(position) = self.lists.last_mut() {
position.next = scope_end(current.list, current.index) + 1;
}
}
(Op::Save | Op::SaveLayer { .. }, false) => {
state.open_scope(state.innermost().list_transform)
}
(Op::Restore, _) => {
state.scopes.pop();
}
(Op::DrawDisplayList { list, .. }, false) => {
state.open_scope(state.transform());
self.lists.push(ListPosition {
ops: list.ops(),
next: 0,
});
}
(Op::Transform(_) | Op::ClipPath { .. }, _) => {
let scope = state.innermost_mut();
scope.transforms_and_clips.push(current.op.clone());
}
_ => {}
}
}
}
impl<'a> Iterator for DisplayListWalker<'a> {
type Item = &'a Op;
fn next(&mut self) -> Option<&'a Op> {
self.finish_current();
let position = *self.lists.last()?;
let (op, list, index) = match position.ops.get(position.next) {
Some(op) => {
if let Some(position) = self.lists.last_mut() {
position.next += 1;
}
(op, position.ops, position.next)
}
None if self.lists.len() > 1 => {
self.lists.pop();
(RESTORE, std::slice::from_ref(RESTORE), 0)
}
None => return None,
};
self.current = Some(CurrentOp {
op,
list,
index,
skip_scope: false,
});
Some(op)
}
}
impl MatrixClipState {
pub fn transform(&self) -> Matrix {
let scope = self.innermost();
scope
.transforms_and_clips
.iter()
.fold(scope.transform_at_open, |transform, op| match op {
Op::Transform(matrix) => transform.then(matrix),
_ => transform,
})
}
pub fn bounds(&self, op: &Op) -> Bounds {
let bounds = match op {
Op::SaveLayer {
paint,
scope_bounds,
clip_bounds,
floods_clip,
backdrop,
..
} => {
if *floods_clip || backdrop.is_some() {
*clip_bounds
} else {
let padding = paint.device_effect_padding(&self.transform());
match scope_bounds.rect() {
Some(rect) => Bounds::of(rect.expand(padding)).intersect(clip_bounds),
None => *scope_bounds,
}
}
}
Op::DrawRect { bounds, .. }
| Op::DrawPath { bounds, .. }
| Op::RRectBlur { bounds, .. }
| Op::DrawImage { bounds, .. }
| Op::GlyphRun { bounds, .. }
| Op::DrawDisplayList { bounds, .. } => Bounds::of(*bounds),
Op::Save | Op::Restore | Op::Transform(_) | Op::ClipPath { .. } => Bounds::Empty,
};
bounds.map(&self.innermost().list_transform)
}
pub fn isolate(&self, ops: &[Op]) -> DisplayList {
let mut b = DisplayListBuilder::new();
for scope in &self.scopes {
b.save();
scope
.transforms_and_clips
.iter()
.for_each(|op| b.replay(op));
}
ops.iter().for_each(|op| b.replay(op));
for _ in &self.scopes {
b.restore();
}
b.build()
}
fn open_scope(&mut self, list_transform: Matrix) {
let transform_at_open = self.transform();
self.scopes.push(Scope {
transforms_and_clips: Vec::new(),
transform_at_open,
list_transform,
});
}
fn innermost(&self) -> &Scope {
self.scopes.last().expect("the walked list's scope is open")
}
fn innermost_mut(&mut self) -> &mut Scope {
self.scopes
.last_mut()
.expect("the walked list's scope is open")
}
}
fn scope_end(ops: &[Op], at: usize) -> usize {
let mut depth = 0usize;
for (index, op) in ops.iter().enumerate().skip(at) {
match op {
Op::Save | Op::SaveLayer { .. } => depth += 1,
Op::Restore => {
depth -= 1;
if depth == 0 {
return index;
}
}
_ => {}
}
}
unreachable!("a built list closes every scope it opens")
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use super::*;
use crate::{ClipOp, Paint};
use valo_geometry::{Color, Point, Rect};
fn kinds<'a>(ops: impl IntoIterator<Item = &'a Op>) -> Vec<&'static str> {
ops.into_iter()
.map(|op| match op {
Op::Save => "save",
Op::SaveLayer { .. } => "layer",
Op::Restore => "restore",
Op::Transform(_) => "transform",
Op::ClipPath { .. } => "clip",
Op::DrawRect { .. } => "rect",
Op::DrawPath { .. } => "path",
Op::RRectBlur { .. } => "blur",
Op::DrawImage { .. } => "image",
Op::GlyphRun { .. } => "glyphs",
Op::DrawDisplayList { .. } => "list",
})
.collect()
}
fn red() -> Paint {
Paint::from_color(Color::rgb(1.0, 0.0, 0.0))
}
#[test]
fn a_walk_gives_each_op_with_the_transform_in_force() {
let mut b = DisplayListBuilder::new();
b.save();
b.translate(10.0, 20.0);
b.draw_rect(Rect::new(0.0, 0.0, 5.0, 5.0), &red());
b.restore();
b.draw_rect(Rect::new(0.0, 0.0, 5.0, 5.0), &red());
let list = b.build();
let mut seen = Vec::new();
let mut ops = list.walk();
while let Some(op) = ops.next() {
if matches!(op, Op::DrawRect { .. }) {
let origin = ops
.matrix_clip()
.transform()
.map_point(Point::from((0.0, 0.0)));
seen.push(origin);
}
}
assert_eq!(
seen,
vec![Point::from((10.0, 20.0)), Point::from((0.0, 0.0))]
);
}
#[test]
fn skipping_a_scope_skips_its_ops_and_its_restore() {
let mut b = DisplayListBuilder::new();
b.save_layer(None, &Paint::from_color(Color::rgba(0.0, 0.0, 0.0, 0.5)));
b.draw_rect(Rect::new(0.0, 0.0, 5.0, 5.0), &red());
b.restore();
b.draw_rect(Rect::new(0.0, 0.0, 5.0, 5.0), &red());
let list = b.build();
let mut visited = Vec::new();
let mut scopes = Vec::new();
let mut ops = list.walk();
while let Some(op) = ops.next() {
visited.push(op);
if matches!(op, Op::SaveLayer { .. }) {
scopes.push(kinds(ops.scope()));
ops.skip_scope();
}
}
assert_eq!(kinds(visited), vec!["layer", "rect"]);
assert_eq!(scopes, vec![vec!["layer", "rect", "restore"]]);
}
#[test]
fn a_nested_list_is_a_scope_a_restore_closes() {
let mut inner = DisplayListBuilder::new();
inner.draw_rect(Rect::new(0.0, 0.0, 5.0, 5.0), &red());
let inner = Arc::new(inner.build());
let mut b = DisplayListBuilder::new();
b.translate(100.0, 0.0);
b.draw_display_list(&inner);
let list = b.build();
let mut visited = Vec::new();
let mut rect_bounds = None;
let mut ops = list.walk();
while let Some(op) = ops.next() {
visited.push(op);
if matches!(op, Op::DrawRect { .. }) {
rect_bounds = ops.matrix_clip().bounds(op).rect();
}
}
assert_eq!(kinds(visited), vec!["transform", "list", "rect", "restore"]);
assert_eq!(rect_bounds, Some(Rect::new(100.0, 0.0, 5.0, 5.0)));
}
#[test]
fn an_isolated_draw_keeps_the_transforms_and_clips_around_it() {
let mut b = DisplayListBuilder::new();
b.save();
b.translate(10.0, 0.0);
b.clip_rect(Rect::new(0.0, 0.0, 4.0, 4.0), ClipOp::Intersect);
b.draw_rect(Rect::new(0.0, 0.0, 8.0, 8.0), &red());
b.draw_rect(Rect::new(20.0, 0.0, 8.0, 8.0), &red());
b.restore();
let list = b.build();
let mut ops = list.walk();
let isolated = loop {
let op = ops.next().expect("the list draws a rect");
if matches!(op, Op::DrawRect { .. }) {
break ops.matrix_clip().isolate(ops.scope());
}
};
assert_eq!(
kinds(isolated.ops()),
vec![
"save",
"save",
"transform",
"clip",
"rect",
"restore",
"restore"
]
);
assert_eq!(
isolated.bounds().rect(),
Some(Rect::new(10.0, 0.0, 4.0, 4.0))
);
}
}