`run_sensors` decided that a widget had consumed the frame's input from hover alone: if the cursor was inside its shape, no lower layer saw anything. So a button sitting over a list swallowed the list's scroll, having registered nothing but `click()`. Being in shape still runs a widget -- a hover highlight has to fire on the topmost thing under the cursor regardless -- but consuming is now judged per input kind. With nothing momentary happening the behaviour is unchanged and the topmost widget wins the hover; with a scroll or a press happening, only a widget that registered a matching momentary sense consumes it. `TypeEventManager::registered` is what makes that askable: what a widget would match is a different question from dispatching to it, and `run_fn` can only answer the second. tests/pointer_routing.rs drives `run_sensors` directly, with no GPU and no window. It fails on the unfixed code with "a scroll over the button must still reach the list underneath it".
122 lines
3.4 KiB
Rust
122 lines
3.4 KiB
Rust
//! A widget that registered only `click()` used to block a `Scroll` meant
|
|
//! for whatever is behind it: `run_sensors` decided "consumed, stop looking
|
|
//! at lower layers" from mere hover rather than from anything actually
|
|
//! matching the input. These drive `run_sensors` directly, which needs no
|
|
//! GPU and no window.
|
|
|
|
use iris::prelude::*;
|
|
use std::{cell::Cell, 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
|
|
}
|
|
}
|
|
|
|
fn cursor_at(pos: Vec2) -> CursorState {
|
|
CursorState {
|
|
pos,
|
|
exists: true,
|
|
buttons: Default::default(),
|
|
scroll_delta: Vec2::ZERO,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn a_button_over_a_list_scrolls_the_list_and_still_clicks() {
|
|
let mut rsc = SenseRsc {
|
|
ui: UiData::default(),
|
|
events: EventManager::default(),
|
|
};
|
|
|
|
// Both cover the whole window: the button "sitting over" the list.
|
|
let list = rsc.ui.widgets.add_strong(Rect::new(UiColor::WHITE));
|
|
let list_weak = list.weak();
|
|
let button = rsc.ui.widgets.add_strong(Rect::new(UiColor::RED));
|
|
let button_weak = button.weak();
|
|
|
|
let scrolled = Rc::new(Cell::new(false));
|
|
let clicked = Rc::new(Cell::new(false));
|
|
{
|
|
let scrolled = scrolled.clone();
|
|
rsc.register_event(list_weak, CursorSense::Scroll, move |_ctx, _rsc| {
|
|
scrolled.set(true);
|
|
});
|
|
}
|
|
{
|
|
let clicked = clicked.clone();
|
|
rsc.register_event(button_weak, CursorSense::click(), move |_ctx, _rsc| {
|
|
clicked.set(true);
|
|
});
|
|
}
|
|
|
|
// A Stack draws its children on separate layers in order, which is
|
|
// exactly the "one thing drawn over another" shape `run_sensors`
|
|
// walks top layer first.
|
|
let root = rsc
|
|
.ui
|
|
.widgets
|
|
.add_strong(Stack {
|
|
children: vec![list.any(), button.any()],
|
|
size: StackSize::default(),
|
|
})
|
|
.any();
|
|
|
|
let mut render = UiRenderState::new();
|
|
render.resize((100.0, 100.0));
|
|
render.update(&root, &mut rsc);
|
|
|
|
let mut state = ();
|
|
let mut scroll_cursor = cursor_at((50.0, 50.0).into());
|
|
scroll_cursor.scroll_delta = (0.0, 10.0).into();
|
|
render.run_sensors(&mut rsc, &mut state, scroll_cursor, (100.0, 100.0).into());
|
|
|
|
assert!(
|
|
scrolled.get(),
|
|
"a scroll over the button must still reach the list underneath it"
|
|
);
|
|
assert!(
|
|
!clicked.get(),
|
|
"a scroll is not a click; the button must not have fired"
|
|
);
|
|
|
|
let mut click_cursor = cursor_at((50.0, 50.0).into());
|
|
click_cursor.buttons.left = ActivationState::Start;
|
|
render.run_sensors(&mut rsc, &mut state, click_cursor, (100.0, 100.0).into());
|
|
|
|
assert!(
|
|
clicked.get(),
|
|
"the button on top must still receive an actual click"
|
|
);
|
|
}
|