use std::{cell::RefCell, rc::Rc};
use cranpose_app_shell::AppShell;
use cranpose_core::{MutableState, location_key, rememberMutableStateOf};
use cranpose_macros::composable;
use cranpose_render_pixels::{Picture, PixelsRenderer};
use cranpose_ui::{Box, BoxSpec, Modifier};
use cranpose_ui_graphics::{Color, GraphicsLayer};
const WIDTH: u32 = 120;
const HEIGHT: u32 = 80;
const STEPS: u32 = 6;
type Step = Rc<RefCell<Option<MutableState<u32>>>>;
#[composable]
fn FadingParent(start: u32, captured: Step) {
let step = rememberMutableStateOf(move || start);
*captured.borrow_mut() = Some(step);
Box(
Modifier::empty()
.offset(20.0, 20.0)
.width(80.0)
.height(40.0)
.graphics_layer(move || GraphicsLayer {
alpha: 0.2 + (step.value() % 5) as f32 * 0.2,
..GraphicsLayer::default()
})
.background(Color::from_rgb_u8(30, 40, 50)),
BoxSpec::default(),
|| {
Box(
Modifier::empty()
.offset(10.0, 10.0)
.width(40.0)
.height(16.0)
.background(Color::from_rgb_u8(200, 120, 40)),
BoxSpec::default(),
|| {},
);
},
);
}
fn shell(start: u32) -> (AppShell<PixelsRenderer>, Step) {
let captured: Step = Rc::default();
let content = Rc::clone(&captured);
let shell = AppShell::new_with_size_and_density(
PixelsRenderer::new(),
location_key(file!(), line!(), column!()),
move || FadingParent(start, Rc::clone(&content)),
(WIDTH, HEIGHT),
(WIDTH as f32, HEIGHT as f32),
1.0,
);
(shell, captured)
}
fn draw(shell: &mut AppShell<PixelsRenderer>) -> Vec<u8> {
let mut pixels = vec![0u8; (WIDTH * HEIGHT * 4) as usize];
shell
.primary()
.with_renderer(|renderer| renderer.draw_scaled(&mut pixels, WIDTH, HEIGHT, 1.0));
pixels
}
fn redraw_changed(shell: &mut AppShell<PixelsRenderer>, picture: &mut Picture, kept: &mut [u8]) {
let mut areas = Vec::new();
let mut area_pixels = Vec::new();
shell.primary().with_renderer(|renderer| {
renderer.update_picture(picture, WIDTH, HEIGHT, 1.0, &mut areas);
for area in &areas {
area_pixels.resize(area.width as usize * area.height as usize * 4, 0);
renderer.draw_picture_area(picture, *area, &mut area_pixels);
let row_bytes = area.width as usize * 4;
for (row, source) in area_pixels.chunks_exact(row_bytes).enumerate() {
let start = ((area.y as usize + row) * WIDTH as usize + area.x as usize) * 4;
kept[start..start + row_bytes].copy_from_slice(source);
}
}
});
}
#[test]
fn each_step_a_layer_block_reads_paints_as_a_shell_made_at_that_step() {
let (mut kept_shell, captured) = shell(0);
let mut picture = Picture::default();
let mut kept = vec![0u8; (WIDTH * HEIGHT * 4) as usize];
kept_shell.update();
redraw_changed(&mut kept_shell, &mut picture, &mut kept);
for step in 1..STEPS {
(*captured.borrow())
.expect("the composition remembered the step")
.set(step);
kept_shell.update();
redraw_changed(&mut kept_shell, &mut picture, &mut kept);
let (mut made, _) = shell(step);
made.update();
let made = draw(&mut made);
assert!(
draw(&mut kept_shell) == made,
"step {step}: the scene updated in place differs from one made at that step"
);
assert!(
kept == made,
"step {step}: the picture's changed areas leave a frame unlike one made at that step"
);
}
}
#[test]
fn a_layer_block_change_reaches_its_shell_while_another_shell_lives() {
let (mut first, captured) = shell(0);
first.update();
let (mut second, _) = shell(0);
second.update();
for step in 1..STEPS {
(*captured.borrow())
.expect("the composition remembered the step")
.set(step);
first.update();
let (mut made, _) = shell(step);
made.update();
assert!(
draw(&mut first) == draw(&mut made),
"step {step}: the first shell missed a change while a second shell lives"
);
}
}