mod common;
use common::Harness;
use guido::prelude::*;
use guido::tree::LayoutCtx;
use guido::widget_prelude::{Constraints, Layout, WidgetId};
const ROWS: usize = 200;
const ROW_WIDTH: f32 = 120.0;
const ROW_HEIGHT: f32 = 24.0;
const SPACING: f32 = 8.0;
const VIEWPORT: f32 = 200.0;
const SCATTERED: [(f32, f32); 8] = [
(0.0, 0.0),
(300.0, 400.0),
(300.0, 400.0),
(300.0, 400.0),
(300.0, 400.0),
(300.0, 400.0),
(300.0, 400.0),
(0.0, 40.0),
];
fn painted_rows(widget: impl Widget + 'static, viewport: f32) -> usize {
Harness::laid_out(widget, 400.0, viewport)
.painted_rects()
.iter()
.filter(|r| (r.width - ROW_WIDTH).abs() < 0.01 && (r.height - ROW_HEIGHT).abs() < 0.01)
.count()
}
fn rows(n: usize) -> Vec<guido::widgets::Container> {
(0..n)
.map(|_| {
container()
.width(ROW_WIDTH)
.height(ROW_HEIGHT)
.background(Color::rgb(0.25, 0.25, 0.35))
})
.collect()
}
#[test]
fn a_wrapped_list_paints_the_rows_an_unwrapped_one_paints() {
let direct = painted_rows(
container()
.width(200.0)
.height(VIEWPORT)
.scroll(Scroll::vertical())
.layout(Flex::column().spacing(SPACING))
.children(rows(ROWS)),
VIEWPORT,
);
let wrapped = painted_rows(
container()
.width(200.0)
.height(VIEWPORT)
.scroll(Scroll::vertical())
.child(
container()
.layout(Flex::column().spacing(SPACING))
.children(rows(ROWS)),
),
VIEWPORT,
);
assert!(
direct <= 12,
"the direct form is no longer windowed: {direct} of {ROWS} rows painted"
);
assert_eq!(
wrapped, direct,
"wrapping the rows cost the list its window: {wrapped} rows painted where the same rows \
as direct children paint {direct}"
);
}
#[test]
fn a_scroller_whose_children_are_ordered_along_neither_axis_keeps_the_visible_ones() {
let painted = painted_rows(
container()
.width(200.0)
.height(100.0)
.scroll(Scroll::vertical())
.layout(Scatter(SCATTERED.to_vec()))
.children(rows(SCATTERED.len())),
100.0,
);
let in_the_viewport = SCATTERED.iter().filter(|(_, y)| *y < 100.0).count();
assert_eq!(
painted,
in_the_viewport,
"a scroller binary-searched bounds that are not partitioned along its axis and dropped a \
child sitting in the viewport: {painted} of {in_the_viewport} visible rows painted, out \
of {} scattered",
SCATTERED.len()
);
}
struct Scatter(Vec<(f32, f32)>);
impl Layout for Scatter {
fn layout(
&mut self,
ctx: &mut LayoutCtx,
children: &[WidgetId],
constraints: Constraints,
origin: (f32, f32),
) -> Size {
let mut size = Size::zero();
for (&child, &(x, y)) in children.iter().zip(&self.0) {
let child_size = ctx.layout_child(child, constraints).unwrap_or_default();
ctx.tree().set_origin(child, origin.0 + x, origin.1 + y);
size.width = size.width.max(x + child_size.width);
size.height = size.height.max(y + child_size.height);
}
size
}
}
#[cfg(feature = "render-stats")]
#[test]
fn the_counter_separates_a_narrowed_window_from_one_that_could_not_narrow() {
use guido::render_stats::{get_stats, reset_stats};
reset_stats();
painted_rows(
container()
.width(200.0)
.height(VIEWPORT)
.scroll(Scroll::vertical())
.layout(Flex::column().spacing(SPACING))
.children(rows(ROWS)),
VIEWPORT,
);
let narrowed = get_stats();
assert_eq!(
narrowed.window_children_total, ROWS as u64,
"the window was not offered the rows"
);
assert!(
narrowed.window_children_iterated < ROWS as u64,
"the window let every row through: {} of {ROWS}",
narrowed.window_children_iterated
);
assert_eq!(
narrowed.window_declined_containers, 0,
"a column of rows is ordered along an axis and nothing should have declined"
);
reset_stats();
painted_rows(
container()
.width(200.0)
.height(100.0)
.scroll(Scroll::vertical())
.layout(Scatter(SCATTERED.to_vec()))
.children(rows(8)),
100.0,
);
let declined = get_stats();
assert_eq!(
declined.window_declined_containers, 1,
"a container that could not narrow went uncounted"
);
assert_eq!(
declined.window_declined_children, 8,
"the children it had to examine one at a time went uncounted"
);
}