Swap two hover buffers, and drive the tests from the harness
The set of hovered widgets is two vectors that trade places, so an input allocates nothing once they have grown, instead of building a fresh one each time. `run_sensors` no longer takes a `window_size`: `UiRenderState` already holds the output size, and passing it back in was one more thing that could disagree. `tests/pointer_routing.rs` drops its own `Rsc`, event manager and layer scaffolding for `iris::harness`, which is what it was standing in for. Presses now arrive as a move and then a press, and a scroll after the hover that precedes it, because that is what the harness delivers and what a window does.
This commit is contained in:
1 parent
e865467a3f
commit
7dc7614ae6
4 files changed
+116
-252
No files matched your search
+92
-234
@@ -1,45 +1,10 @@
|
||||
//! Input across layers: what stops at a layer, what passes through it, and
|
||||
//! where hovering stops. These drive `run_sensors` directly, which needs no
|
||||
//! GPU and no window.
|
||||
//! where hovering stops.
|
||||
|
||||
use iris::prelude::*;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
struct SenseRsc {
|
||||
ui: UiData,
|
||||
events: EventManager<SenseRsc>,
|
||||
}
|
||||
|
||||
impl UiRsc for SenseRsc {
|
||||
fn ui(&self) -> &UiData {
|
||||
&self.ui
|
||||
}
|
||||
fn ui_mut(&mut self) -> &mut UiData {
|
||||
&mut self.ui
|
||||
}
|
||||
fn on_draw(&mut self, active: &ActiveData) {
|
||||
self.events.draw(active);
|
||||
}
|
||||
fn on_undraw(&mut self, active: &ActiveData) {
|
||||
self.events.undraw(active);
|
||||
}
|
||||
fn on_remove(&mut self, id: WidgetId) {
|
||||
self.events.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
impl HasState for SenseRsc {
|
||||
type State = ();
|
||||
}
|
||||
|
||||
impl HasEvents for SenseRsc {
|
||||
fn events(&self) -> &EventManager<Self> {
|
||||
&self.events
|
||||
}
|
||||
fn events_mut(&mut self) -> &mut EventManager<Self> {
|
||||
&mut self.events
|
||||
}
|
||||
}
|
||||
use iris::harness::Harness;
|
||||
use iris::prelude::*;
|
||||
|
||||
const WINDOW: f32 = 100.0;
|
||||
|
||||
@@ -53,88 +18,36 @@ impl Fired {
|
||||
}
|
||||
}
|
||||
|
||||
struct Ui {
|
||||
rsc: SenseRsc,
|
||||
render: UiRenderState,
|
||||
state: (),
|
||||
/// A widget filling whatever it is given, recording the senses it is sent.
|
||||
fn listener(h: &mut Harness, senses: impl Into<CursorSenses>) -> (WeakWidget<Rect>, Fired) {
|
||||
let fired = Fired::default();
|
||||
let record = fired.clone();
|
||||
let id = rect(Color::WHITE)
|
||||
.on(senses.into(), move |ctx, _| {
|
||||
record.0.borrow_mut().push(ctx.data.sense)
|
||||
})
|
||||
.add(&mut h.rsc);
|
||||
(id, fired)
|
||||
}
|
||||
|
||||
impl Ui {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
rsc: SenseRsc {
|
||||
ui: UiData::default(),
|
||||
events: EventManager::default(),
|
||||
},
|
||||
render: UiRenderState::new(),
|
||||
state: (),
|
||||
}
|
||||
}
|
||||
|
||||
fn listen<W: Widget + ?core::marker::Sized + 'static>(
|
||||
&mut self,
|
||||
widget: &StrongWidget<W>,
|
||||
senses: impl Into<CursorSenses>,
|
||||
) -> Fired {
|
||||
let fired = Fired::default();
|
||||
let sink = fired.clone();
|
||||
self.rsc
|
||||
.register_event(widget.weak(), senses.into(), move |ctx, _rsc| {
|
||||
sink.0.borrow_mut().push(ctx.data.sense)
|
||||
});
|
||||
fired
|
||||
}
|
||||
|
||||
/// Stacks the widgets bottom first, each on its own layer, and lays them
|
||||
/// out in a square window.
|
||||
fn stack(&mut self, children: Vec<StrongWidget>) {
|
||||
let root = self
|
||||
.rsc
|
||||
.ui
|
||||
.widgets
|
||||
.add_strong(Stack {
|
||||
children,
|
||||
size: StackSize::default(),
|
||||
})
|
||||
.any();
|
||||
self.render.resize((WINDOW, WINDOW));
|
||||
self.render.update(&root, &mut self.rsc);
|
||||
}
|
||||
|
||||
fn cursor(&mut self, at: (f32, f32)) -> CursorState {
|
||||
CursorState {
|
||||
pos: at.into(),
|
||||
exists: true,
|
||||
buttons: Default::default(),
|
||||
scroll_delta: Vec2::ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
fn run(&mut self, cursor: CursorState) {
|
||||
self.render.run_sensors(
|
||||
&mut self.rsc,
|
||||
&mut self.state,
|
||||
cursor,
|
||||
(WINDOW, WINDOW).into(),
|
||||
);
|
||||
}
|
||||
/// A widget with no senses of its own, to leave a gap beside one that has.
|
||||
fn blank(h: &mut Harness) -> WeakWidget<Rect> {
|
||||
rect(Color::WHITE).add(&mut h.rsc)
|
||||
}
|
||||
|
||||
fn full(ui: &mut Ui) -> StrongWidget<Rect> {
|
||||
rect(UiColor::WHITE).add_strong(&mut ui.rsc)
|
||||
fn harness() -> Harness {
|
||||
Harness::new((WINDOW, WINDOW))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_stops_at_the_topmost_widget() {
|
||||
let ui = &mut Ui::new();
|
||||
let (bottom, middle, top) = (full(ui), full(ui), full(ui));
|
||||
let bottom_hover = ui.listen(&bottom, CursorSense::HoverStart);
|
||||
let middle_hover = ui.listen(&middle, CursorSense::HoverStart);
|
||||
let top_hover = ui.listen(&top, CursorSense::HoverStart);
|
||||
ui.stack(vec![bottom.any(), middle.any(), top.any()]);
|
||||
let mut h = harness();
|
||||
let (bottom, bottom_hover) = listener(&mut h, CursorSense::HoverStart);
|
||||
let (middle, middle_hover) = listener(&mut h, CursorSense::HoverStart);
|
||||
let (top, top_hover) = listener(&mut h, CursorSense::HoverStart);
|
||||
h.set_root((bottom, middle, top).stack());
|
||||
|
||||
let cursor = ui.cursor((50.0, 50.0));
|
||||
ui.run(cursor);
|
||||
h.move_to((50, 50));
|
||||
|
||||
assert_eq!(top_hover.take(), [CursorSense::HoverStart]);
|
||||
assert_eq!(
|
||||
@@ -147,16 +60,14 @@ fn hover_stops_at_the_topmost_widget() {
|
||||
|
||||
#[test]
|
||||
fn a_scroll_passes_through_every_widget_that_does_not_want_it() {
|
||||
let ui = &mut Ui::new();
|
||||
let (list, button, overlay) = (full(ui), full(ui), full(ui));
|
||||
let scrolled = ui.listen(&list, CursorSense::Scroll);
|
||||
let clicked = ui.listen(&button, CursorSense::click());
|
||||
let overlay_clicked = ui.listen(&overlay, CursorSense::click());
|
||||
ui.stack(vec![list.any(), button.any(), overlay.any()]);
|
||||
let mut h = harness();
|
||||
let (list, scrolled) = listener(&mut h, CursorSense::Scroll);
|
||||
let (button, clicked) = listener(&mut h, CursorSense::click());
|
||||
let (overlay, overlay_clicked) = listener(&mut h, CursorSense::click());
|
||||
h.set_root((list, button, overlay).stack());
|
||||
|
||||
let mut cursor = ui.cursor((50.0, 50.0));
|
||||
cursor.scroll_delta = (0.0, 10.0).into();
|
||||
ui.run(cursor);
|
||||
h.move_to((50, 50));
|
||||
h.scroll((0, 10));
|
||||
|
||||
assert_eq!(
|
||||
scrolled.take(),
|
||||
@@ -168,16 +79,32 @@ fn a_scroll_passes_through_every_widget_that_does_not_want_it() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_the_topmost_listener_takes_a_press() {
|
||||
let ui = &mut Ui::new();
|
||||
let (below, above) = (full(ui), full(ui));
|
||||
let below_clicked = ui.listen(&below, CursorSense::click());
|
||||
let above_clicked = ui.listen(&above, CursorSense::click());
|
||||
ui.stack(vec![below.any(), above.any()]);
|
||||
fn hovering_a_button_above_does_not_stop_a_later_scroll() {
|
||||
let mut h = harness();
|
||||
let (list, scrolled) = listener(&mut h, CursorSense::Scroll);
|
||||
let (button, _clicked) = listener(&mut h, CursorSense::click());
|
||||
h.set_root((list, button).stack());
|
||||
|
||||
let mut cursor = ui.cursor((50.0, 50.0));
|
||||
cursor.buttons.left = ActivationState::Start;
|
||||
ui.run(cursor);
|
||||
// The hover arrives in its own frame, as a window delivers it.
|
||||
h.move_to((50, 50));
|
||||
assert_eq!(scrolled.take(), []);
|
||||
|
||||
h.scroll((0, 10));
|
||||
assert_eq!(
|
||||
scrolled.take(),
|
||||
[CursorSense::Scroll],
|
||||
"a hover already resting on the button must not consume the wheel"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_the_topmost_listener_takes_a_press() {
|
||||
let mut h = harness();
|
||||
let (below, below_clicked) = listener(&mut h, CursorSense::click());
|
||||
let (above, above_clicked) = listener(&mut h, CursorSense::click());
|
||||
h.set_root((below, above).stack());
|
||||
|
||||
h.click((50, 50));
|
||||
|
||||
assert_eq!(above_clicked.take(), [CursorSense::click()]);
|
||||
assert_eq!(below_clicked.take(), [], "one press goes to one widget");
|
||||
@@ -185,29 +112,18 @@ fn only_the_topmost_listener_takes_a_press() {
|
||||
|
||||
#[test]
|
||||
fn a_press_beside_the_button_reaches_the_layer_below() {
|
||||
let ui = &mut Ui::new();
|
||||
let list = full(ui);
|
||||
let mut h = harness();
|
||||
let (list, list_clicked) = listener(&mut h, CursorSense::click());
|
||||
// The row above the list covers it, but only its left half is the button.
|
||||
let button = full(ui);
|
||||
let gap = full(ui);
|
||||
let list_clicked = ui.listen(&list, CursorSense::click());
|
||||
let button_clicked = ui.listen(&button, CursorSense::click());
|
||||
let row = ui.rsc.ui.widgets.add_strong(Span {
|
||||
children: vec![button.any(), gap.any()],
|
||||
dir: Dir::RIGHT,
|
||||
gap: 0.0,
|
||||
});
|
||||
ui.stack(vec![list.any(), row.any()]);
|
||||
let (button, button_clicked) = listener(&mut h, CursorSense::click());
|
||||
let row = (button, blank(&mut h)).span(Dir::RIGHT).add(&mut h.rsc);
|
||||
h.set_root((list, row).stack());
|
||||
|
||||
let mut on_button = ui.cursor((20.0, 50.0));
|
||||
on_button.buttons.left = ActivationState::Start;
|
||||
ui.run(on_button);
|
||||
h.click((20, 50));
|
||||
assert_eq!(button_clicked.take(), [CursorSense::click()]);
|
||||
assert_eq!(list_clicked.take(), []);
|
||||
|
||||
let mut beside_it = ui.cursor((80.0, 50.0));
|
||||
beside_it.buttons.left = ActivationState::Start;
|
||||
ui.run(beside_it);
|
||||
h.click((80, 50));
|
||||
assert_eq!(button_clicked.take(), [], "the cursor is not on the button");
|
||||
assert_eq!(
|
||||
list_clicked.take(),
|
||||
@@ -218,44 +134,32 @@ fn a_press_beside_the_button_reaches_the_layer_below() {
|
||||
|
||||
#[test]
|
||||
fn leaving_a_widget_still_ends_its_hover() {
|
||||
let ui = &mut Ui::new();
|
||||
let widget = full(ui);
|
||||
let hover = ui.listen(&widget, CursorSense::HoverStart | CursorSense::HoverEnd);
|
||||
ui.stack(vec![widget.any()]);
|
||||
let mut h = harness();
|
||||
let (widget, hover) = listener(&mut h, CursorSense::HoverStart | CursorSense::HoverEnd);
|
||||
h.set_root(widget);
|
||||
|
||||
let cursor = ui.cursor((50.0, 50.0));
|
||||
ui.run(cursor);
|
||||
h.move_to((50, 50));
|
||||
assert_eq!(hover.take(), [CursorSense::HoverStart]);
|
||||
|
||||
let mut gone = ui.cursor((50.0, 50.0));
|
||||
gone.exists = false;
|
||||
ui.run(gone);
|
||||
h.leave();
|
||||
assert_eq!(hover.take(), [CursorSense::HoverEnd]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn leaving_a_widget_does_not_block_the_layer_below() {
|
||||
let ui = &mut Ui::new();
|
||||
let below = full(ui);
|
||||
let mut h = harness();
|
||||
let (below, below_hover) = listener(&mut h, CursorSense::HoverStart);
|
||||
// Only the left half of the layer above is a widget, so the cursor can
|
||||
// leave it without leaving the one underneath.
|
||||
let (above, gap) = (full(ui), full(ui));
|
||||
let below_hover = ui.listen(&below, CursorSense::HoverStart);
|
||||
let above_hover = ui.listen(&above, CursorSense::HoverStart | CursorSense::HoverEnd);
|
||||
let row = ui.rsc.ui.widgets.add_strong(Span {
|
||||
children: vec![above.any(), gap.any()],
|
||||
dir: Dir::RIGHT,
|
||||
gap: 0.0,
|
||||
});
|
||||
ui.stack(vec![below.any(), row.any()]);
|
||||
let (above, above_hover) = listener(&mut h, CursorSense::HoverStart | CursorSense::HoverEnd);
|
||||
let row = (above, blank(&mut h)).span(Dir::RIGHT).add(&mut h.rsc);
|
||||
h.set_root((below, row).stack());
|
||||
|
||||
let on_above = ui.cursor((20.0, 50.0));
|
||||
ui.run(on_above);
|
||||
h.move_to((20, 50));
|
||||
assert_eq!(above_hover.take(), [CursorSense::HoverStart]);
|
||||
assert_eq!(below_hover.take(), [], "the layer above is over it");
|
||||
|
||||
let beside_it = ui.cursor((80.0, 50.0));
|
||||
ui.run(beside_it);
|
||||
h.move_to((80, 50));
|
||||
assert_eq!(above_hover.take(), [CursorSense::HoverEnd]);
|
||||
assert_eq!(
|
||||
below_hover.take(),
|
||||
@@ -264,53 +168,18 @@ fn leaving_a_widget_does_not_block_the_layer_below() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hovering_a_button_above_does_not_stop_a_later_scroll() {
|
||||
let ui = &mut Ui::new();
|
||||
let (list, button) = (full(ui), full(ui));
|
||||
let scrolled = ui.listen(&list, CursorSense::Scroll);
|
||||
let clicked = ui.listen(&button, CursorSense::click());
|
||||
ui.stack(vec![list.any(), button.any()]);
|
||||
|
||||
// The hover arrives in its own frame, as a window delivers it.
|
||||
let hover = ui.cursor((50.0, 50.0));
|
||||
ui.run(hover);
|
||||
assert_eq!(scrolled.take(), []);
|
||||
|
||||
let mut wheel = ui.cursor((50.0, 50.0));
|
||||
wheel.scroll_delta = (0.0, 10.0).into();
|
||||
ui.run(wheel);
|
||||
|
||||
assert_eq!(
|
||||
scrolled.take(),
|
||||
[CursorSense::Scroll],
|
||||
"a hover already resting on the button must not consume the wheel"
|
||||
);
|
||||
assert_eq!(clicked.take(), []);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn covering_a_widget_ends_its_hover() {
|
||||
let ui = &mut Ui::new();
|
||||
let below = full(ui);
|
||||
// Only the left half of the layer above is a widget, so the cursor can
|
||||
// start beside it and then move onto it.
|
||||
let (above, gap) = (full(ui), full(ui));
|
||||
let below_hover = ui.listen(&below, CursorSense::HoverStart | CursorSense::HoverEnd);
|
||||
let above_hover = ui.listen(&above, CursorSense::HoverStart);
|
||||
let row = ui.rsc.ui.widgets.add_strong(Span {
|
||||
children: vec![above.any(), gap.any()],
|
||||
dir: Dir::RIGHT,
|
||||
gap: 0.0,
|
||||
});
|
||||
ui.stack(vec![below.any(), row.any()]);
|
||||
let mut h = harness();
|
||||
let (below, below_hover) = listener(&mut h, CursorSense::HoverStart | CursorSense::HoverEnd);
|
||||
let (above, above_hover) = listener(&mut h, CursorSense::HoverStart);
|
||||
let row = (above, blank(&mut h)).span(Dir::RIGHT).add(&mut h.rsc);
|
||||
h.set_root((below, row).stack());
|
||||
|
||||
let beside_it = ui.cursor((80.0, 50.0));
|
||||
ui.run(beside_it);
|
||||
h.move_to((80, 50));
|
||||
assert_eq!(below_hover.take(), [CursorSense::HoverStart]);
|
||||
|
||||
let onto_above = ui.cursor((20.0, 50.0));
|
||||
ui.run(onto_above);
|
||||
h.move_to((20, 50));
|
||||
assert_eq!(above_hover.take(), [CursorSense::HoverStart]);
|
||||
assert_eq!(
|
||||
below_hover.take(),
|
||||
@@ -318,8 +187,7 @@ fn covering_a_widget_ends_its_hover() {
|
||||
"a widget covered by one that took the input is no longer hovered"
|
||||
);
|
||||
|
||||
let off_again = ui.cursor((80.0, 50.0));
|
||||
ui.run(off_again);
|
||||
h.move_to((80, 50));
|
||||
assert_eq!(
|
||||
below_hover.take(),
|
||||
[CursorSense::HoverStart],
|
||||
@@ -329,36 +197,26 @@ fn covering_a_widget_ends_its_hover() {
|
||||
|
||||
#[test]
|
||||
fn hover_starts_and_ends_once_each() {
|
||||
let ui = &mut Ui::new();
|
||||
let mut h = harness();
|
||||
// Only the left half is the widget, so the cursor can leave it without
|
||||
// leaving the window.
|
||||
let (widget, gap) = (full(ui), full(ui));
|
||||
let hover = ui.listen(&widget, CursorSense::HoverStart | CursorSense::HoverEnd);
|
||||
let row = ui.rsc.ui.widgets.add_strong(Span {
|
||||
children: vec![widget.any(), gap.any()],
|
||||
dir: Dir::RIGHT,
|
||||
gap: 0.0,
|
||||
});
|
||||
ui.stack(vec![row.any()]);
|
||||
let (widget, hover) = listener(&mut h, CursorSense::HoverStart | CursorSense::HoverEnd);
|
||||
let row = (widget, blank(&mut h)).span(Dir::RIGHT).add(&mut h.rsc);
|
||||
h.set_root(row);
|
||||
|
||||
let inside = ui.cursor((20.0, 50.0));
|
||||
ui.run(inside);
|
||||
h.move_to((20, 50));
|
||||
assert_eq!(hover.take(), [CursorSense::HoverStart]);
|
||||
|
||||
let further_in = ui.cursor((30.0, 50.0));
|
||||
ui.run(further_in);
|
||||
h.move_to((30, 50));
|
||||
assert_eq!(hover.take(), [], "staying inside is not a second start");
|
||||
|
||||
let outside = ui.cursor((80.0, 50.0));
|
||||
ui.run(outside);
|
||||
h.move_to((80, 50));
|
||||
assert_eq!(hover.take(), [CursorSense::HoverEnd]);
|
||||
|
||||
let further_out = ui.cursor((90.0, 50.0));
|
||||
ui.run(further_out);
|
||||
h.move_to((90, 50));
|
||||
assert_eq!(hover.take(), [], "an ended hover does not end again");
|
||||
|
||||
let back_inside = ui.cursor((20.0, 50.0));
|
||||
ui.run(back_inside);
|
||||
h.move_to((20, 50));
|
||||
assert_eq!(
|
||||
hover.take(),
|
||||
[CursorSense::HoverStart],
|
||||
|
||||
Reference in new issue
Block a user