diff --git a/core/src/event/manager.rs b/core/src/event/manager.rs index 9c32c12..6b4b969 100644 --- a/core/src/event/manager.rs +++ b/core/src/event/manager.rs @@ -135,19 +135,16 @@ impl TypeEventManager { )); } - /// What this widget registered, without running any of it. - pub fn registered(&self, id: WidgetId) -> impl Iterator { - self.map.get(&id).into_iter().flatten().map(|(e, _)| e) - } - pub fn run_fn<'a>( &mut self, id: impl IdLike, - ) -> impl for<'b> FnOnce(EventCtx<'_, Rsc, E::Data<'b>>, &mut Rsc) + 'a { + ) -> impl for<'b> FnOnce(EventCtx<'_, Rsc, E::Data<'b>>, &mut Rsc) -> bool + 'a { let fs = self.map.get(&id.id()).cloned().unwrap_or_default(); move |ctx, rsc| { + let mut consumed = false; for (e, f) in fs { if let Some(data) = e.should_run(&ctx.data) { + consumed |= e.consumes(&data); f( EventCtx { state: ctx.state, @@ -157,6 +154,7 @@ impl TypeEventManager { ) } } + consumed } } } diff --git a/core/src/event/mod.rs b/core/src/event/mod.rs index 7b038f4..c3ce00a 100644 --- a/core/src/event/mod.rs +++ b/core/src/event/mod.rs @@ -13,6 +13,14 @@ pub trait Event: Sized + 'static + Clone { fn should_run<'a>(&self, data: &Self::Data<'a>) -> Option> { Some(data.clone()) } + + /// Whether having run on this data uses up whatever triggered it, so + /// nothing further should see it. `run_event` reports back the `or` of + /// this across everything that ran. + #[allow(unused_variables)] + fn consumes(&self, data: &Self::Data<'_>) -> bool { + false + } } pub trait EventLike { diff --git a/core/src/event/rsc.rs b/core/src/event/rsc.rs index 4824a99..123ae1b 100644 --- a/core/src/event/rsc.rs +++ b/core/src/event/rsc.rs @@ -21,12 +21,13 @@ pub trait HasEvents: Sized + UiRsc + HasState { } pub trait RunEvents: HasEvents { + /// Whether anything that ran consumed the input; see `Event::consumes`. fn run_event( &mut self, id: impl IdLike, data: ::Data<'_>, state: &mut Self::State, - ) { + ) -> bool { let f = self.events_mut().get_type::().run_fn(id); f(EventCtx { state, data }, self) } diff --git a/src/default/sense.rs b/src/default/sense.rs index b81bec6..f5bfc5b 100644 --- a/src/default/sense.rs +++ b/src/default/sense.rs @@ -37,19 +37,11 @@ impl Event for CursorSenses { None } } -} -impl CursorSenses { - /// Whether a widget with these senses stops the input reaching the layer - /// below it. A cursor that is only somewhere stops at the first layer it - /// is over. A cursor doing something -- a press, a scroll -- stops only - /// where something answers to it, so a button drawn over a list does not - /// swallow the list's scrolling. - fn consumes(&self, cursor: &CursorState, hover: ActivationState) -> bool { - if cursor.position_only() { - return true; - } - should_run(self, cursor, hover).is_some_and(|sense| !sense.position_only()) + /// A press or a scroll is used up by whatever answered it, so it stops + /// there. Hovering is not: a cursor resting somewhere goes on resting. + fn consumes(&self, data: &Self::Data<'_>) -> bool { + !data.sense.position_only() } } @@ -190,6 +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(); for layer in self.layers.indices().rev() { let mut consumed = false; for (id, sensor) in active.get_mut(&layer).into_flat_iter() { @@ -200,12 +193,6 @@ impl SensorUi for UiRenderState { if sensor.hover == ActivationState::Off { continue; } - consumed |= rsc - .events_mut() - .get_type::() - .registered(*id) - .any(|senses| senses.consumes(&cursor, sensor.hover)); - let cursor = cursor.clone(); let data = CursorData { @@ -219,7 +206,13 @@ impl SensorUi for UiRenderState { sense: CursorSense::Hovering, render: self, }; - rsc.run_event::(*id, data, state); + // 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. + let answered = rsc.run_event::(*id, data, state); + consumed |= answered || (resting && in_shape); } if consumed { break; diff --git a/tests/pointer_routing.rs b/tests/pointer_routing.rs index ee38517..2d83a2e 100644 --- a/tests/pointer_routing.rs +++ b/tests/pointer_routing.rs @@ -232,3 +232,34 @@ fn leaving_a_widget_still_ends_its_hover() { 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" + ); +}