use std::fmt;
use std::sync::Arc;
use super::Scene;
use super::layer::{
ContainerLayer, DisplayListLayer, Effect, EmbeddedLayer, EngineLayer, Layer, VideoLayer,
};
use crate::composition::CompositionLayer;
use crate::geometry::{Clip, Matrix4, Offset, RRect, Rect};
use crate::media::VideoFrameReader;
use crate::painting::{BlendMode, ImageFilter, Path, Picture};
pub struct SceneBuilder {
layer_stack: Vec<ContainerLayerBuilder>,
}
struct ContainerLayerBuilder {
effect: Effect,
children: Vec<Layer>,
}
impl ContainerLayerBuilder {
fn new(effect: Effect) -> ContainerLayerBuilder {
ContainerLayerBuilder {
effect,
children: Vec::new(),
}
}
fn build(self) -> ContainerLayer {
ContainerLayer {
effect: self.effect,
children: self.children,
}
}
}
impl fmt::Debug for SceneBuilder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SceneBuilder")
.field("open_pushes", &(self.layer_stack.len() - 1))
.finish_non_exhaustive()
}
}
impl Default for SceneBuilder {
fn default() -> SceneBuilder {
SceneBuilder::new()
}
}
impl SceneBuilder {
pub fn new() -> SceneBuilder {
SceneBuilder {
layer_stack: vec![ContainerLayerBuilder::new(Effect::None)],
}
}
pub fn push_transform(&mut self, matrix4: &Matrix4) {
self.push_layer(Effect::Transform(*matrix4));
}
pub fn push_offset(&mut self, dx: f64, dy: f64) {
self.push_layer(Effect::Transform(Matrix4::translation(
dx as f32, dy as f32,
)));
}
pub fn push_clip_rect(&mut self, rect: Rect, clip_behavior: Clip) {
self.push_layer(Effect::ClipRect {
rect,
clip_behavior,
});
}
pub fn push_clip_rrect(&mut self, rrect: RRect, clip_behavior: Clip) {
self.push_layer(Effect::ClipRRect {
rrect,
clip_behavior,
});
}
pub fn push_clip_path(&mut self, path: &Arc<Path>, clip_behavior: Clip) {
self.push_layer(Effect::ClipPath {
path: Arc::clone(path),
clip_behavior,
});
}
pub fn push_opacity(&mut self, alpha: i32, offset: Offset) {
self.push_layer(Effect::Opacity {
alpha: alpha.clamp(0, 255) as u8,
offset,
});
}
pub fn push_backdrop_filter(
&mut self,
filter: &ImageFilter,
blend_mode: BlendMode,
backdrop_id: Option<u64>,
) {
self.push_layer(Effect::BackdropFilter {
filter: filter.clone(),
blend_mode,
backdrop_id,
});
}
pub fn pop(&mut self) -> EngineLayer {
assert!(
self.layer_stack.len() > 1,
"SceneBuilder::pop without a push"
);
let built = self.layer_stack.pop().expect("a push is open").build();
let layer = Arc::new(built);
self.add_layer(Layer::Container(Arc::clone(&layer)));
layer
}
pub fn add_embedded_layer(&mut self, layer: Arc<dyn CompositionLayer>, rect: Rect) {
self.add_layer(Layer::Embedded(EmbeddedLayer { layer, rect }));
}
pub fn add_video(&mut self, reader: Arc<dyn VideoFrameReader>, rect: Rect) {
self.add_layer(Layer::Video(VideoLayer { reader, rect }));
}
pub fn add_retained(&mut self, retained_layer: &EngineLayer) {
self.add_layer(Layer::Container(Arc::clone(retained_layer)));
}
pub fn add_picture(
&mut self,
offset: Offset,
picture: &Arc<Picture>,
is_complex_hint: bool,
will_change_hint: bool,
) {
let _ = (is_complex_hint, will_change_hint);
self.add_layer(Layer::DisplayList(DisplayListLayer {
offset,
picture: Arc::clone(picture),
}));
}
pub fn build(mut self) -> Scene {
debug_assert!(
self.layer_stack.len() == 1,
"SceneBuilder::build with an open push"
);
while self.layer_stack.len() > 1 {
self.pop();
}
let root = self.layer_stack.pop().expect("the root is being built");
Scene { root: root.build() }
}
fn push_layer(&mut self, effect: Effect) {
self.layer_stack.push(ContainerLayerBuilder::new(effect));
}
fn add_layer(&mut self, layer: Layer) {
self.layer_stack
.last_mut()
.expect("the root is built only by build")
.children
.push(layer);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Canvas, Paint};
fn square() -> Arc<Picture> {
let mut canvas = Canvas::new();
canvas.draw_rect(Rect::from_ltwh(0.0, 0.0, 10.0, 10.0), &Paint::default());
Arc::new(canvas.build())
}
fn container(layer: &Layer) -> &EngineLayer {
match layer {
Layer::Container(container) => container,
other => panic!("not a container: {other:?}"),
}
}
#[test]
fn a_popped_container_is_a_node_of_its_parent() {
let picture = square();
let mut builder = SceneBuilder::new();
builder.push_opacity(128, Offset::new(1.0, 2.0));
builder.push_transform(&Matrix4::scale(2.0, 2.0));
builder.add_picture(Offset::new(3.0, 4.0), &picture, false, false);
let inner = builder.pop();
let outer = builder.pop();
let scene = builder.build();
assert_eq!(scene.root.effect, Effect::None);
let [only] = scene.root.children.as_slice() else {
panic!("one child: {:?}", scene.root.children);
};
assert!(Arc::ptr_eq(container(only), &outer));
assert_eq!(
outer.effect,
Effect::Opacity {
alpha: 128,
offset: Offset::new(1.0, 2.0)
}
);
assert!(Arc::ptr_eq(container(&outer.children[0]), &inner));
assert_eq!(inner.effect, Effect::Transform(Matrix4::scale(2.0, 2.0)));
assert_eq!(
inner.children,
[Layer::DisplayList(DisplayListLayer {
offset: Offset::new(3.0, 4.0),
picture,
})]
);
}
#[test]
fn a_retained_container_is_the_same_node_in_the_next_scene() {
let mut builder = SceneBuilder::new();
builder.push_clip_rect(Rect::from_ltwh(0.0, 0.0, 5.0, 5.0), Clip::HardEdge);
builder.add_picture(Offset::ZERO, &square(), false, false);
let retained = builder.pop();
let _first = builder.build();
let mut builder = SceneBuilder::new();
builder.push_offset(10.0, 0.0);
builder.add_retained(&retained);
builder.pop();
let next = builder.build();
let moved = container(&next.root.children[0]);
assert!(Arc::ptr_eq(container(&moved.children[0]), &retained));
}
#[test]
fn an_opacity_is_kept_between_none_and_all() {
let mut builder = SceneBuilder::new();
builder.push_opacity(300, Offset::ZERO);
let all = builder.pop();
builder.push_opacity(-1, Offset::ZERO);
let none = builder.pop();
let alpha = |layer: &EngineLayer| match layer.effect {
Effect::Opacity { alpha, .. } => alpha,
_ => unreachable!(),
};
assert_eq!((alpha(&all), alpha(&none)), (255, 0));
}
#[test]
fn a_composition_layer_is_a_leaf_at_its_place_in_paint_order() {
let layer = super::super::idle_layer();
let rect = Rect::from_ltwh(1.0, 2.0, 3.0, 4.0);
let mut builder = SceneBuilder::new();
builder.add_picture(Offset::ZERO, &square(), false, false);
builder.add_embedded_layer(Arc::clone(&layer), rect);
let scene = builder.build();
assert!(matches!(scene.root.children[0], Layer::DisplayList(_)));
assert_eq!(
scene.root.children[1],
Layer::Embedded(EmbeddedLayer { layer, rect })
);
}
#[test]
fn a_scene_without_layers_has_an_empty_root() {
let scene = SceneBuilder::new().build();
assert!(scene.root.children.is_empty());
}
#[test]
#[should_panic(expected = "open push")]
fn build_with_an_open_push_panics() {
let mut builder = SceneBuilder::new();
builder.push_offset(0.0, 0.0);
let _scene = builder.build();
}
#[test]
#[should_panic(expected = "without a push")]
fn pop_without_a_push_panics() {
SceneBuilder::new().pop();
}
}