`Event::consumes` says whether having run uses up what triggered it, defaulting to no. `run_fn` already calls `should_run` per registration, so it ors that across everything that ran and hands it back through `run_event`. `CursorSenses` answers it with the sense it matched: a press or a scroll is used up, hovering is not. That drops `TypeEventManager::registered` and the second pass over a widget's senses -- the match that decides consumption is now the same one that decides whether the handler runs. A cursor that is only resting still stops at the layer it is over, which `run_event` cannot report because nothing need answer for it to be true. It must not stop at a widget it has merely left, though, or ending a hover above blocks the hover below: `leaving_a_widget_does_not_block_the_layer_below` is that case, and it fails on `main` too.
266 lines
7.6 KiB
Rust
266 lines
7.6 KiB
Rust
//! 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.
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
const WINDOW: f32 = 100.0;
|
|
|
|
/// Every sense that has fired on one widget since it was last read.
|
|
#[derive(Default, Clone)]
|
|
struct Fired(Rc<RefCell<Vec<CursorSense>>>);
|
|
|
|
impl Fired {
|
|
fn take(&self) -> Vec<CursorSense> {
|
|
std::mem::take(&mut self.0.borrow_mut())
|
|
}
|
|
}
|
|
|
|
struct Ui {
|
|
rsc: SenseRsc,
|
|
render: UiRenderState,
|
|
state: (),
|
|
}
|
|
|
|
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(),
|
|
);
|
|
}
|
|
}
|
|
|
|
fn full(ui: &mut Ui) -> StrongWidget<Rect> {
|
|
rect(UiColor::WHITE).add_strong(&mut ui.rsc)
|
|
}
|
|
|
|
#[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 cursor = ui.cursor((50.0, 50.0));
|
|
ui.run(cursor);
|
|
|
|
assert_eq!(top_hover.take(), [CursorSense::HoverStart]);
|
|
assert_eq!(
|
|
middle_hover.take(),
|
|
[],
|
|
"hover is not shared with a layer below"
|
|
);
|
|
assert_eq!(bottom_hover.take(), []);
|
|
}
|
|
|
|
#[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 cursor = ui.cursor((50.0, 50.0));
|
|
cursor.scroll_delta = (0.0, 10.0).into();
|
|
ui.run(cursor);
|
|
|
|
assert_eq!(
|
|
scrolled.take(),
|
|
[CursorSense::Scroll],
|
|
"two layers of click-only widgets do not stop a scroll"
|
|
);
|
|
assert_eq!(clicked.take(), []);
|
|
assert_eq!(overlay_clicked.take(), []);
|
|
}
|
|
|
|
#[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()]);
|
|
|
|
let mut cursor = ui.cursor((50.0, 50.0));
|
|
cursor.buttons.left = ActivationState::Start;
|
|
ui.run(cursor);
|
|
|
|
assert_eq!(above_clicked.take(), [CursorSense::click()]);
|
|
assert_eq!(below_clicked.take(), [], "one press goes to one widget");
|
|
}
|
|
|
|
#[test]
|
|
fn a_press_beside_the_button_reaches_the_layer_below() {
|
|
let ui = &mut Ui::new();
|
|
let list = full(ui);
|
|
// 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 mut on_button = ui.cursor((20.0, 50.0));
|
|
on_button.buttons.left = ActivationState::Start;
|
|
ui.run(on_button);
|
|
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);
|
|
assert_eq!(button_clicked.take(), [], "the cursor is not on the button");
|
|
assert_eq!(
|
|
list_clicked.take(),
|
|
[CursorSense::click()],
|
|
"a press beside the button belongs to what is under it"
|
|
);
|
|
}
|
|
|
|
#[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 cursor = ui.cursor((50.0, 50.0));
|
|
ui.run(cursor);
|
|
assert_eq!(hover.take(), [CursorSense::HoverStart]);
|
|
|
|
let mut gone = ui.cursor((50.0, 50.0));
|
|
gone.exists = false;
|
|
ui.run(gone);
|
|
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);
|
|
// 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 on_above = ui.cursor((20.0, 50.0));
|
|
ui.run(on_above);
|
|
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);
|
|
assert_eq!(above_hover.take(), [CursorSense::HoverEnd]);
|
|
assert_eq!(
|
|
below_hover.take(),
|
|
[CursorSense::HoverStart],
|
|
"ending a hover above must not stop the hover below"
|
|
);
|
|
}
|