Route pointer input per kind, so a scroll falls through a hovered button
`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".
This commit is contained in:
1 parent
0f6a28b4dd
commit
028521b419
3 files changed
+170
-1
No files matched your search
@@ -0,0 +1,121 @@
|
||||
//! 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"
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user