diff --git a/src/default/sense.rs b/src/default/sense.rs index 553409c..980cd5f 100644 --- a/src/default/sense.rs +++ b/src/default/sense.rs @@ -183,12 +183,16 @@ impl SensorUi for UiRenderState { // update it there? let mut active = std::mem::take(&mut rsc.events_mut().get_type::().active); let position_only = cursor.position_only(); + let mut consumed = false; for layer in self.layers.indices().rev() { - let mut consumed = false; + let mut consumed_here = false; for (id, sensor) in active.get_mut(&layer).into_flat_iter() { let shape = self.active.get(id).unwrap().region; let region = shape.to_px(window_size); - let in_shape = cursor.exists && region.contains(cursor.pos); + // Once a layer above has taken the input, everything under it + // is covered rather than skipped: it is not hovered, so a + // widget that was gets to end its hover. + let in_shape = !consumed && cursor.exists && region.contains(cursor.pos); sensor.hover.update(in_shape); if sensor.hover == ActivationState::Off { continue; @@ -212,11 +216,11 @@ impl SensorUi for UiRenderState { // 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 || (position_only && in_shape); - } - if consumed { - break; + consumed_here |= answered || (position_only && in_shape); } + // Applied after the layer, never during it: senses on one layer + // do not block each other. + consumed |= consumed_here; } rsc.events_mut().get_type::().active = active; } diff --git a/tests/pointer_routing.rs b/tests/pointer_routing.rs index b7c4da9..1a653f5 100644 --- a/tests/pointer_routing.rs +++ b/tests/pointer_routing.rs @@ -288,3 +288,33 @@ fn hovering_a_button_above_does_not_stop_a_later_scroll() { ); assert_eq!(clicked.take(), []); } + +#[test] +fn covering_a_widget_ends_its_hover() { + let ui = &mut Ui::new(); + let below = full(ui); + // Only the left half of the layer above is a widget, so the cursor can + // start beside it and then move onto it. + let (above, gap) = (full(ui), full(ui)); + let below_hover = ui.listen(&below, CursorSense::HoverStart | CursorSense::HoverEnd); + let above_hover = ui.listen(&above, CursorSense::HoverStart); + 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 beside_it = ui.cursor((80.0, 50.0)); + ui.run(beside_it); + assert_eq!(below_hover.take(), [CursorSense::HoverStart]); + + let onto_above = ui.cursor((20.0, 50.0)); + ui.run(onto_above); + assert_eq!(above_hover.take(), [CursorSense::HoverStart]); + assert_eq!( + below_hover.take(), + [CursorSense::HoverEnd], + "a widget covered by one that took the input is no longer hovered" + ); +}