Small, and disjoint from #12 — this touches `task.rs`, `harness.rs` and `render_state.rs`, none of which #12 goes near. `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 also moves from *the task ended* to *an update was sent*, which is when there is actually something for the host to apply. A task that keeps running after sending one no longer holds it until it finishes, and a task that sends none no longer asks for a frame nothing needs. `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` covers span layout, resize relayout, press routing, hover start and end, wheel scrolling with its clamp, and a task update reaching the tree. None of them could be written before, since the only way into layout was a window. It does not draw. A claim about pixels still needs a real surface — I checked this one against the rig rather than asserting it: `examples/task` under headless sway, centre pixel `ff0000` before the click and `0000ff` after, so the windowed path still applies task updates under the new wake. The only core change is `UiRenderState::output_size()`, so that a host reading back the size it set does not have to keep a second copy. --------- Co-authored-by: iris <2+iris@noreply.localhost> Reviewed-on: iris/iris#15 Reviewed-by: iris <2+iris@noreply.localhost> Co-authored-by: AIris <4+iris-ai@noreply.localhost>
61 lines
1.7 KiB
Rust
61 lines
1.7 KiB
Rust
//! Which widget an input reaches.
|
|
|
|
use std::{cell::RefCell, rc::Rc};
|
|
|
|
use iris::harness::Harness;
|
|
use iris::prelude::*;
|
|
|
|
#[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));
|
|
}
|