From 8cac92743845d1a073a70091fa44487f9e4f81ec Mon Sep 17 00:00:00 2001 From: iris <2+iris@noreply.localhost> Date: Sun, 13 Sep 2026 21:07:48 -0400 Subject: [PATCH] Pin the hover-then-scroll case, and say what the line means A wheel makes `position_only` false, so a button already hovered in a layer above does not consume it -- but the line read as though it might. `hovering_a_button_above_does_not_stop_a_later_scroll` is that case in the two frames a window actually delivers it in, and the comment now leads with it. `resting` is renamed to `position_only`, so the same word is used throughout. --- src/default/sense.rs | 14 +++++++------- tests/pointer_routing.rs | 25 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/default/sense.rs b/src/default/sense.rs index f5bfc5b..553409c 100644 --- a/src/default/sense.rs +++ b/src/default/sense.rs @@ -182,7 +182,7 @@ impl SensorUi for UiRenderState { // state like thing, but local to render state, and is passed to UiRsc events so you can // update it there? let mut active = std::mem::take(&mut rsc.events_mut().get_type::().active); - let resting = cursor.position_only(); + let position_only = cursor.position_only(); for layer in self.layers.indices().rev() { let mut consumed = false; for (id, sensor) in active.get_mut(&layer).into_flat_iter() { @@ -206,13 +206,13 @@ impl SensorUi for UiRenderState { sense: CursorSense::Hovering, render: self, }; - // A cursor that is only resting stops at the layer it is - // over, answered or not -- but not at a widget it has just - // left, which is here only to end its hover. An action stops - // where something answered it, so a button over a list does - // not swallow the list's scrolling. + // A press or a scroll stops where something answered it, so a + // button over a list does not swallow the list's scrolling. A + // cursor doing neither stops at the widget it is over, so + // hovering does not reach through one -- but not at a widget + // it has just left, which is here only to end its hover. let answered = rsc.run_event::(*id, data, state); - consumed |= answered || (resting && in_shape); + consumed |= answered || (position_only && in_shape); } if consumed { break; diff --git a/tests/pointer_routing.rs b/tests/pointer_routing.rs index 2d83a2e..b7c4da9 100644 --- a/tests/pointer_routing.rs +++ b/tests/pointer_routing.rs @@ -263,3 +263,28 @@ fn leaving_a_widget_does_not_block_the_layer_below() { "ending a hover above must not stop the hover 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(), []); +}