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
+40
-1
@@ -52,6 +52,18 @@ impl CursorSense {
|
||||
pub fn is_dragging(&self) -> bool {
|
||||
matches!(self, CursorSense::Pressing(CursorButton::Left))
|
||||
}
|
||||
|
||||
/// True for a sense that names something happening this frame (a
|
||||
/// button transitioning, a scroll), as opposed to the ambient
|
||||
/// `Hover*` family that is on for as long as the cursor rests there.
|
||||
/// Only a momentary sense can consume an input -- see
|
||||
/// `SensorUi::run_sensors`.
|
||||
pub fn is_momentary(&self) -> bool {
|
||||
!matches!(
|
||||
self,
|
||||
CursorSense::HoverStart | CursorSense::Hovering | CursorSense::HoverEnd
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
@@ -163,6 +175,11 @@ impl SensorUi for UiRenderState {
|
||||
// this would probably be done through a generic parameter that adds yet another rsc /
|
||||
// state like thing, but local to render state, and is passed to UiRsc events so you can
|
||||
// update it there?
|
||||
// Whether anything momentary is happening this frame at all; see
|
||||
// `consumed` below, which is only judged per kind when it is.
|
||||
let momentary_active =
|
||||
cursor.scroll_delta != Vec2::ZERO || cursor.buttons.iter().any(|(_, a)| !a.is_off());
|
||||
|
||||
let mut active = std::mem::take(&mut rsc.events_mut().get_type::<CursorSense>().active);
|
||||
for layer in self.layers.indices().rev() {
|
||||
let mut sensed = false;
|
||||
@@ -174,7 +191,29 @@ impl SensorUi for UiRenderState {
|
||||
if sensor.hover == ActivationState::Off {
|
||||
continue;
|
||||
}
|
||||
sensed = true;
|
||||
|
||||
// A widget in shape always still runs: a hover-only
|
||||
// highlight must fire on the topmost thing under the
|
||||
// cursor even while a scroll passes through it. What is
|
||||
// judged per input kind is whether it *consumes* that
|
||||
// input, stopping a lower layer from seeing it. With
|
||||
// nothing momentary happening, being in shape is
|
||||
// consumption 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, so a
|
||||
// button registered for `click()` alone cannot block a
|
||||
// scroll meant for the list behind it.
|
||||
let consumed = if momentary_active {
|
||||
rsc.events_mut()
|
||||
.get_type::<CursorSense>()
|
||||
.registered(*id)
|
||||
.any(|senses| {
|
||||
matches!(should_run(senses, &cursor, sensor.hover), Some(s) if s.is_momentary())
|
||||
})
|
||||
} else {
|
||||
true
|
||||
};
|
||||
sensed |= consumed;
|
||||
|
||||
let cursor = cursor.clone();
|
||||
|
||||
|
||||
Reference in new issue
Block a user