use std::time::Instant;
use crabshell_ui::{Align, Color, Rect, Row, Stack, Style, Text, Window};
use super::{Buffer, Scene, node_at};
use crate::DamageHistory;
const CARD: Style = Style::new()
.background(Color::hex("#45475a"))
.border(2.0, Color::hex("#fab387"))
.radius(6.0)
.color(Color::hex("#cdd6f4"))
.padding(4, 8);
fn turning(scene: &mut Scene, degrees: crabshell_reactive::Signal<f32>) -> Window {
let panel = scene
.panel()
.style(Style::new().background(Color::rgba(30, 30, 46, 200)))
.child(
Row::new().padding(20, 150).align(Align::Start).child(
Row::new()
.style(move || {
let degrees = degrees.get();
CARD.rotate(degrees).translate(degrees / 40.0, 0.0)
})
.gap(4)
.child(Text::new("turning"))
.child(
Stack::new()
.style(move || Style::new().opacity(0.6).rotate(-2.0 * degrees.get()))
.child(
Row::new().style(Style::new().background(Color::hex("#89b4fa")).padding(6, 6)),
),
),
),
);
scene.window(panel, 400.0, 80.0)
}
#[test]
fn turning_repaints_like_drawing_everything_and_only_where_it_was_and_is() {
for scale in [1.0, 1.25, 1.5, 1.75, 2.0] {
let mut scene = Scene::new();
let degrees = scene.cx.signal(0.0_f32);
let window = turning(&mut scene, degrees);
let mut history = DamageHistory::new();
let mut buffer = Buffer::new(&window, scale);
scene.draw(&window, &mut history, &mut buffer);
for step in [15.0, 40.0, 95.0, 180.0, 333.3] {
let old = node_at(window.view().root(), &[0, 0]).bounds();
degrees.set(step);
scene.frame(&window);
let new = node_at(window.view().root(), &[0, 0]).bounds();
let report = scene.draw(&window, &mut history, &mut buffer);
let full = scene.full(&window, scale);
assert!(
buffer.pixels == full.pixels,
"{step} degrees at {scale}: the repaint differs from a full draw"
);
let both = old.union(new);
let left = (both.x * scale).floor() - 1.0;
let right = (both.right() * scale).ceil() + 1.0;
let top = ((both.y * scale).floor() - 1.0).max(0.0);
let bottom = (both.bottom() * scale).ceil() + 1.0;
assert!(!report.is_empty());
for rect in &report {
assert!(
rect.x as f32 >= left
&& (rect.x + rect.width) as f32 <= right
&& rect.y as f32 >= top
&& (rect.y + rect.height) as f32 <= bottom,
"{step} degrees at {scale}: {rect:?} is outside {both:?}"
);
}
}
}
}
#[test]
fn a_turned_box_shows_no_seams_where_tiles_meet() {
let color = [0x89, 0xb4, 0xfa, 255];
let mut scene = Scene::new();
let panel = scene.panel().child(
Row::new().padding(30, 30).align(Align::Start).child(
Row::new().style(
Style::new()
.background(Color::rgb(color[0], color[1], color[2]))
.width(200.0)
.height(60.0)
.rotate(10.0),
),
),
);
let window = scene.window(panel, 260.0, 120.0);
for scale in [1.0, 1.5, 2.0] {
let buffer = scene.full(&window, scale);
let (sin, cos) = 10.0_f32.to_radians().sin_cos();
let mut inside = 0;
for y in 0..buffer.height {
for x in 0..buffer.width {
let (dx, dy) = (
(x as f32 + 0.5) / scale - 130.0,
(y as f32 + 0.5) / scale - 60.0,
);
let (u, v) = (dx * cos + dy * sin, -dx * sin + dy * cos);
if u.abs() < 100.0 - 2.0 && v.abs() < 30.0 - 2.0 {
inside += 1;
assert_eq!(buffer.pixel(x, y), color, "at ({x}, {y}) at {scale}");
}
}
}
assert!(inside > 10_000, "{inside}");
}
}
#[test]
fn layers_are_made_only_for_a_transform() {
let mut scene = Scene::new();
let turned = scene.cx.signal(false);
let panel = scene.panel().child(
Row::new().align(Align::Start).child(
Row::new()
.style(move || {
let card = CARD.rotate(if turned.get() { 1.0 } else { 0.0 });
card.scale(1.0).translate(0.0, 0.0).origin(0.0, 0.0)
})
.child(Text::new("plain")),
),
);
let window = scene.window(panel, 300.0, 100.0);
scene.full(&window, 2.0);
assert_eq!(scene.renderer.work().0, 0, "no layer without a transform");
turned.set(true);
scene.frame(&window);
scene.full(&window, 1.0);
let bounds = node_at(window.view().root(), &[0, 0]).bounds();
let area = (bounds.width * bounds.height) as u64;
let layers = scene.renderer.work().0;
assert!(
layers > 0 && layers < area * 3 / 2,
"{layers} pixels for {area}"
);
}
#[test]
#[ignore = "a measurement, not a check"]
fn time_a_turning_card() {
let mut scene = Scene::new();
let degrees = scene.cx.signal(0.0_f32);
let panel = scene
.panel()
.style(Style::new().background(Color::hex("#1e1e2e")))
.child(
Row::new().padding(70, 70).align(Align::Start).child(
Row::new()
.style(move || CARD.width(200.0).height(60.0).rotate(degrees.get()))
.child(Text::new("a turning card")),
),
);
let window = scene.window(panel, 340.0, 200.0);
let mut history = DamageHistory::new();
let mut buffer = Buffer::new(&window, 2.0);
scene.draw(&window, &mut history, &mut buffer);
let frames = 360;
let mut drawing = std::time::Duration::ZERO;
for frame in 1..=frames {
degrees.set(frame as f32);
scene.frame(&window);
let start = Instant::now();
scene.draw(&window, &mut history, &mut buffer);
drawing += start.elapsed();
}
let bounds: Rect = node_at(window.view().root(), &[0, 0]).bounds();
println!(
"a frame of the turning card at scale 2 takes {:.3} ms on average over {frames} frames ({bounds:?})",
drawing.as_secs_f64() * 1000.0 / f64::from(frames)
);
}