use std::{
cell::{Cell, RefCell},
rc::Rc,
};
use cranpose_core::{MutableState, NodeId};
use cranpose_render_common::{
graph::{LayerNode, PrimitiveNode, ProjectiveTransform, RenderGraph, RenderNode},
scene_builder::{build_graph_from_applier, clear_command_recordings_for_tests},
};
use cranpose_ui::{
Box, BoxSpec, Brush, Canvas, Color, Column, ColumnSpec, GraphicsLayer, LayoutEngine, Modifier,
Size, TestComposition, Text, TextStyle, composable, run_test_composition,
};
use cranpose_ui_graphics::{DrawPrimitive, Rect};
use crate::scene_probe::fresh_graph;
const VIEWPORT: Size = Size::new(240.0, 240.0);
#[derive(Default)]
struct Probe {
spacer_height: Option<MutableState<f32>>,
canvas_color: Option<MutableState<Color>>,
canvas_angle: Option<MutableState<f32>>,
canvas: Option<NodeId>,
label_window_rect: Option<MutableState<Rect>>,
}
#[composable]
fn Spacer(height: MutableState<f32>) {
Box(
Modifier::empty().size(Size::new(40.0, height.get())),
BoxSpec::default(),
|| {},
);
}
fn moving_column(probe: Rc<RefCell<Probe>>, draws: Rc<Cell<usize>>) -> TestComposition {
run_test_composition(move || {
let probe = Rc::clone(&probe);
let draws = Rc::clone(&draws);
Column(
Modifier::empty().size(Size::new(200.0, 200.0)),
ColumnSpec::default(),
move || {
let height = cranpose_core::rememberMutableStateOf(|| 20.0_f32);
let color = cranpose_core::rememberMutableStateOf(|| Color::RED);
let angle = cranpose_core::rememberMutableStateOf(|| 0.0_f32);
let window_rect =
cranpose_core::rememberMutableStateOf(|| Rect::from_size(Size::ZERO));
Spacer(height);
let draws = Rc::clone(&draws);
let canvas = Canvas(
Modifier::empty()
.size(Size::new(40.0, 24.0))
.graphics_layer(move || GraphicsLayer {
rotation_z: angle.get(),
..Default::default()
}),
move |scope| {
draws.set(draws.get() + 1);
scope.draw_rect(Brush::solid(color.get()));
},
);
Text(
"moved label",
Modifier::empty()
.size(Size::new(80.0, 20.0))
.report_window_rect_state(window_rect),
TextStyle::default(),
);
*probe.borrow_mut() = Probe {
spacer_height: Some(height),
canvas_color: Some(color),
canvas_angle: Some(angle),
canvas: Some(canvas),
label_window_rect: Some(window_rect),
};
},
);
})
}
fn initial_graph(composition: &mut TestComposition, root: NodeId) -> RenderGraph {
crate::scene_probe::with_applier(composition, |applier| {
applier.compute_layout(root, VIEWPORT).expect("layout");
let graph = build_graph_from_applier(applier, root, 1.0).expect("initial graph");
let _ = cranpose_ui::take_geometry_scene_nodes();
let _ = cranpose_ui::take_draw_repass_nodes();
let _ = applier.take_structural_change_parents_attached_to(root);
graph
})
}
fn update_frame(
composition: &mut TestComposition,
root: NodeId,
graph: &mut RenderGraph,
) -> Vec<NodeId> {
crate::scene_probe::update_scene(composition, root, VIEWPORT, graph)
}
fn painted(layer: &LayerNode, parent: ProjectiveTransform, out: &mut Vec<(Rect, String)>) {
let transform = layer.transform_to_parent.then(parent);
for child in &layer.children {
match child {
RenderNode::Layer(child) => painted(child, transform, out),
RenderNode::DrawRun(run) => {
out.extend(run.primitives().filter_map(|primitive| match primitive {
DrawPrimitive::Rect {
rect,
brush: Brush::Solid(color),
..
} => Some((transform.bounds_for_rect(rect), format!("{color:?}"))),
_ => None,
}));
}
RenderNode::Primitive(entry) => match &entry.node {
PrimitiveNode::Draw(draw) => {
if let DrawPrimitive::Rect {
rect,
brush: Brush::Solid(color),
..
} = &draw.primitive
{
out.push((transform.bounds_for_rect(*rect), format!("{color:?}")));
}
}
PrimitiveNode::Text(text) => {
out.push((transform.bounds_for_rect(text.rect), text.text.text.clone()));
}
},
}
}
}
fn painted_output(graph: &RenderGraph) -> Vec<(Rect, String)> {
let mut out = Vec::new();
painted(&graph.root, ProjectiveTransform::identity(), &mut out);
out
}
#[test]
fn a_node_that_only_moved_keeps_what_it_drew() {
clear_command_recordings_for_tests();
let probe = Rc::new(RefCell::new(Probe::default()));
let draws = Rc::new(Cell::new(0));
let mut composition = moving_column(Rc::clone(&probe), Rc::clone(&draws));
let root = composition.root().expect("root");
let mut graph = initial_graph(&mut composition, root);
let (height, canvas, window_rect) = {
let probe = probe.borrow();
(
probe.spacer_height.expect("spacer height"),
probe.canvas.expect("canvas"),
probe.label_window_rect.expect("label window rect"),
)
};
let drawn = draws.get();
let label_before = window_rect.get();
height.set(50.0);
let moved = update_frame(&mut composition, root, &mut graph);
assert!(moved.contains(&canvas), "the canvas only moved: {moved:?}");
assert_eq!(draws.get(), drawn, "a moved canvas keeps its recording");
let label_after = window_rect.get();
assert_eq!(
label_after.y - label_before.y,
30.0,
"the moved label publishes its new window rect"
);
let fresh = fresh_graph(&mut composition, root);
assert_eq!(window_rect.get(), label_after, "a fresh scene agrees");
assert_eq!(
painted_output(&graph),
painted_output(&fresh),
"the moved scene paints what a fresh one does"
);
}
#[test]
fn a_node_that_moved_and_changed_what_it_draws_draws_again() {
clear_command_recordings_for_tests();
let probe = Rc::new(RefCell::new(Probe::default()));
let draws = Rc::new(Cell::new(0));
let mut composition = moving_column(Rc::clone(&probe), Rc::clone(&draws));
let root = composition.root().expect("root");
let mut graph = initial_graph(&mut composition, root);
let (height, color) = {
let probe = probe.borrow();
(
probe.spacer_height.expect("spacer height"),
probe.canvas_color.expect("canvas color"),
)
};
let drawn = draws.get();
height.set(50.0);
color.set(Color::GREEN);
update_frame(&mut composition, root, &mut graph);
assert_eq!(draws.get(), drawn + 1, "the canvas draws its new color");
let fresh = fresh_graph(&mut composition, root);
assert_eq!(
painted_output(&graph),
painted_output(&fresh),
"the scene paints the moved canvas in its new color"
);
}
#[test]
fn a_node_that_moved_and_turned_keeps_what_it_drew() {
clear_command_recordings_for_tests();
let probe = Rc::new(RefCell::new(Probe::default()));
let draws = Rc::new(Cell::new(0));
let mut composition = moving_column(Rc::clone(&probe), Rc::clone(&draws));
let root = composition.root().expect("root");
let mut graph = initial_graph(&mut composition, root);
let (height, angle, canvas) = {
let probe = probe.borrow();
(
probe.spacer_height.expect("spacer height"),
probe.canvas_angle.expect("canvas angle"),
probe.canvas.expect("canvas"),
)
};
let drawn = draws.get();
height.set(50.0);
angle.set(30.0);
let moved = update_frame(&mut composition, root, &mut graph);
assert!(moved.contains(&canvas), "the canvas moved: {moved:?}");
assert_eq!(
draws.get(),
drawn,
"a canvas that moved and turned keeps its recording"
);
let fresh = fresh_graph(&mut composition, root);
assert_eq!(
painted_output(&graph),
painted_output(&fresh),
"the scene paints the canvas moved and turned"
);
}