use std::cell::Cell;
use std::rc::Rc;
use super::*;
use crate::base::{Point, Rect, Size};
use crate::layout::Style as LayoutStyle;
use crate::theme::default_theme;
use crate::ui::{text, Element, MouseKind, UiTree, ViewId};
use crate::widgets::itest_util::{mount_widget, mouse, settle};
const VIEW_W: i32 = 12;
const VIEW_H: i32 = 4;
const ROWS: i32 = 20;
const DEEP_ROW: i32 = 12;
fn scroll_of_rows(
marked_row: i32,
probe: Rc<Cell<Option<Rect>>>,
culled_probe: bool,
) -> impl FnOnce(crate::reactive::Scope) -> View {
move |cx| {
let t = &default_theme().tokens;
let mut col = Element::new().style(LayoutStyle::column());
for i in 0..ROWS {
if i == marked_row {
let p = probe.clone();
let mut marked = Element::new()
.style(LayoutStyle::column())
.child(text(format!("row {i}")))
.draw(move |_canvas, rect| p.set(Some(rect)));
if culled_probe {
marked = marked.probe_when_culled();
}
col = col.child(marked.build());
} else {
col = col.child(text(format!("row {i}")));
}
}
Scroll::new(col.build())
.content_size(VIEW_W - 1, ROWS)
.element(cx, t)
.build()
}
}
fn hit_row(tree: &UiTree, y: i32) -> Option<ViewId> {
tree.hit_test(Point::new(1, y))
}
#[test]
fn off_screen_child_rect_is_already_public_through_rect_of() {
let size = Size::new(VIEW_W, VIEW_H);
let probe: Rc<Cell<Option<Rect>>> = Rc::new(Cell::new(None));
let (_root, mut tree) = mount_widget(size, scroll_of_rows(0, probe.clone(), false));
settle(&mut tree, size);
let id = hit_row(&tree, 0).expect("row 0 is inside the viewport at mount");
assert_eq!(
tree.rect_of(id).y,
0,
"sanity: the named child is the one at content row 0"
);
let painted_while_visible = probe
.get()
.expect("sanity: a visible child paints, so its probe fires");
assert_eq!(painted_while_visible.y, 0);
for _ in 0..8 {
mouse(&mut tree, MouseKind::ScrollDown, 2, 1);
}
settle(&mut tree, size);
let solved = tree.rect_of(id);
assert!(
solved.y < 0,
"the child really is off the top of the {VIEW_H}-row viewport, \
got {solved:?}"
);
let stale = probe.get().expect("the probe holds its last painted rect");
assert_eq!(
stale.y, painted_while_visible.y,
"the culled child has not repainted, so the paint idiom still \
believes it is at row {}",
painted_while_visible.y
);
assert_ne!(
solved.y, stale.y,
"rect_of and the paint probe now disagree, and rect_of is the \
one telling the truth: the readback 0910 asks for is already \
public — what is missing is the ViewId to pass it"
);
}
#[test]
fn a_below_the_fold_child_cannot_be_named_by_hit_test() {
let size = Size::new(VIEW_W, VIEW_H);
let probe: Rc<Cell<Option<Rect>>> = Rc::new(Cell::new(None));
let (_root, mut tree) = mount_widget(size, scroll_of_rows(DEEP_ROW, probe.clone(), true));
settle(&mut tree, size);
let truth = probe
.get()
.expect("probe_when_culled keeps the off-screen child's paint alive");
assert_eq!(
truth.y, DEEP_ROW,
"engine-side, the child's solved offset is known exactly"
);
let named: Vec<Rect> = (0..VIEW_H)
.filter_map(|y| hit_row(&tree, y))
.map(|id| tree.rect_of(id))
.collect();
assert!(
!named.is_empty(),
"control: hit_test must name SOMETHING inside the viewport, or \
the absence below proves nothing about the deep child"
);
assert!(
named.iter().all(|r| r.y != truth.y),
"no point in the viewport names the child at content row \
{DEEP_ROW}; the app can reach {named:?} and nothing else, so \
it has no ViewId to hand rect_of — CHILD IDENTITY is the \
missing primitive, not the rect"
);
let mut now = truth;
for _ in 0..ROWS {
if (0..VIEW_H).contains(&now.y) {
break;
}
mouse(&mut tree, MouseKind::ScrollDown, 2, 1);
settle(&mut tree, size);
now = probe.get().expect("the probe keeps reporting");
}
assert!(
(0..VIEW_H).contains(&now.y),
"control: the child is inside the viewport after scrolling, \
got {now:?}"
);
let named_now: Vec<Rect> = (0..VIEW_H)
.filter_map(|y| hit_row(&tree, y))
.map(|id| tree.rect_of(id))
.collect();
assert!(
named_now.iter().any(|r| r.y == now.y),
"control: once visible, hit_test DOES name the child — the \
sweep works, so the miss above is about the fold and not \
about the sweep. reachable: {named_now:?}, child at {now:?}"
);
}