`Tasks` held an `Arc<Window>` only to call `request_redraw` when a task finished, which made the task queue -- and so `DefaultRsc` -- impossible to build without a window. It now takes an `Arc<dyn WakeTaskQueue>`, and `Window` implements it. Waking moves from "the task ended" to "an update was sent", which is when there is something for the host to apply: a task that keeps running after sending one no longer holds its update until it finishes, and a task that sends none no longer asks for a frame it does not need. `iris::harness` is what that buys. `UiRenderState` already does layout, hit testing and primitive building with no surface, so a test can build a tree, run frames, move a pointer and read back where widgets landed. `tests/harness.rs` does each of those; none of them could be written before, since the only entry point to layout was a window. It does not draw. A claim about pixels still needs a real surface.
141 lines
4.1 KiB
Rust
141 lines
4.1 KiB
Rust
//! Layout, hit testing and task updates, driven without a window.
|
|
|
|
use std::{cell::RefCell, rc::Rc, time::Duration};
|
|
|
|
use iris::harness::Harness;
|
|
use iris::prelude::*;
|
|
|
|
/// `PixelRegion` neither compares nor prints.
|
|
fn corners(h: &Harness, id: &impl IdLike) -> (f32, f32, f32, f32) {
|
|
let region = h.region(id).expect("widget drew nothing");
|
|
(
|
|
region.top_left.x,
|
|
region.top_left.y,
|
|
region.bot_right.x,
|
|
region.bot_right.y,
|
|
)
|
|
}
|
|
|
|
/// A fixed 100 wide, and the rest of the 400 to its neighbour.
|
|
fn two_rects(h: &mut Harness) -> (WidgetId, WidgetId) {
|
|
let left = rect(Color::RED).width(100).add(&mut h.rsc);
|
|
let right = rect(Color::BLUE).add(&mut h.rsc);
|
|
h.set_root((left, right).span(Dir::RIGHT));
|
|
(left.id(), right.id())
|
|
}
|
|
|
|
#[test]
|
|
fn a_span_gives_each_child_the_width_it_asked_for() {
|
|
let mut h = Harness::new((400, 200));
|
|
let (left, right) = two_rects(&mut h);
|
|
|
|
assert_eq!(corners(&h, &left), (0.0, 0.0, 100.0, 200.0));
|
|
assert_eq!(corners(&h, &right), (100.0, 0.0, 400.0, 200.0));
|
|
}
|
|
|
|
#[test]
|
|
fn resizing_relays_out_against_the_new_output() {
|
|
let mut h = Harness::new((400, 200));
|
|
let (left, right) = two_rects(&mut h);
|
|
|
|
h.resize((800, 100));
|
|
assert!(h.needs_redraw());
|
|
h.frame();
|
|
|
|
assert_eq!(corners(&h, &left), (0.0, 0.0, 100.0, 100.0));
|
|
assert_eq!(corners(&h, &right), (100.0, 0.0, 800.0, 100.0));
|
|
}
|
|
|
|
#[test]
|
|
fn a_press_reaches_only_the_widget_under_the_cursor() {
|
|
let mut h = Harness::new((400, 200));
|
|
let clicks = Rc::new(RefCell::new(Vec::new()));
|
|
|
|
let (on_left, on_right) = (clicks.clone(), clicks.clone());
|
|
let left = rect(Color::RED)
|
|
.width(100)
|
|
.on(CursorSense::click(), move |_, _| {
|
|
on_left.borrow_mut().push("left")
|
|
})
|
|
.add(&mut h.rsc);
|
|
let right = rect(Color::BLUE)
|
|
.on(CursorSense::click(), move |_, _| {
|
|
on_right.borrow_mut().push("right")
|
|
})
|
|
.add(&mut h.rsc);
|
|
h.set_root((left, right).span(Dir::RIGHT));
|
|
|
|
h.click((50, 100));
|
|
assert_eq!(*clicks.borrow(), ["left"]);
|
|
|
|
h.click((300, 100));
|
|
assert_eq!(*clicks.borrow(), ["left", "right"]);
|
|
}
|
|
|
|
#[test]
|
|
fn hover_ends_when_the_cursor_leaves_the_window() {
|
|
let mut h = Harness::new((400, 200));
|
|
let hovered = Rc::new(RefCell::new(0));
|
|
let ended = Rc::new(RefCell::new(0));
|
|
|
|
let (h_count, e_count) = (hovered.clone(), ended.clone());
|
|
let widget = rect(Color::RED)
|
|
.on(CursorSense::HoverStart, move |_, _| {
|
|
*h_count.borrow_mut() += 1
|
|
})
|
|
.on(CursorSense::HoverEnd, move |_, _| {
|
|
*e_count.borrow_mut() += 1
|
|
})
|
|
.add(&mut h.rsc);
|
|
h.set_root(widget);
|
|
|
|
h.move_to((200, 100));
|
|
assert_eq!((*hovered.borrow(), *ended.borrow()), (1, 0));
|
|
|
|
// A second sample inside the same widget is not a second hover.
|
|
h.move_to((210, 100));
|
|
assert_eq!((*hovered.borrow(), *ended.borrow()), (1, 0));
|
|
|
|
h.leave();
|
|
assert_eq!((*hovered.borrow(), *ended.borrow()), (1, 1));
|
|
}
|
|
|
|
#[test]
|
|
fn a_task_update_reaches_the_tree() {
|
|
let mut h = Harness::new((400, 200));
|
|
let widget = rect(Color::RED).add(&mut h.rsc);
|
|
h.set_root(widget.task_on(CursorSense::click(), async move |mut ctx| {
|
|
ctx.update(move |_, rsc| widget(rsc).color = Color::BLUE);
|
|
}));
|
|
|
|
h.click((200, 100));
|
|
|
|
assert!(
|
|
h.await_update(Duration::from_secs(5)),
|
|
"the task sent no update"
|
|
);
|
|
assert_eq!(h.rsc[widget].color, Color::BLUE);
|
|
}
|
|
|
|
#[test]
|
|
fn a_wheel_scrolls_the_content_and_stops_at_its_end() {
|
|
let mut h = Harness::new((400, 200));
|
|
// Twice the window's height, so there is 200 to scroll.
|
|
let top = rect(Color::RED).height(200).add(&mut h.rsc);
|
|
let bottom = rect(Color::BLUE).height(200).add(&mut h.rsc);
|
|
h.set_root((top, bottom).span(Dir::DOWN).scrollable());
|
|
h.move_to((200, 100));
|
|
|
|
// `Scroll` starts snapped to the end.
|
|
assert_eq!(corners(&h, &top).1, -200.0);
|
|
|
|
// The handler scales a wheel line by 50.
|
|
h.scroll((0, 1));
|
|
h.frame();
|
|
assert_eq!(corners(&h, &top).1, -150.0);
|
|
|
|
h.scroll((0, 10));
|
|
h.frame();
|
|
assert_eq!(corners(&h, &top).1, 0.0);
|
|
}
|