From 4c8b95f365983582ccb7a97a29bafeecd7f4d6bc Mon Sep 17 00:00:00 2001 From: lucasmerlin Date: Mon, 15 Jan 2024 10:08:41 +0100 Subject: [PATCH] Fix clickable widgets blocking scrolling on touch screens (#3815) Currently, when you try dragging a ScrollArea and start over a button, the button blocks the ScrollArea from receiving the drag event. This PR updates layer_rects_this_frame and layer_rects_prev_frame to include the Sense and will use it to check if the previous layer has a sense that would be a conflict.
Videos

(In both videos I'm clicking and dragging) Before: https://github.com/emilk/egui/assets/8009393/83cc5a8a-2283-46d6-9337-164e1289f701 After (I modified the WidgetGallery to be scrollable, as you can see you can now scroll over buttons and dragging the slider still blocks the scroll): https://github.com/emilk/egui/assets/8009393/be634cf8-3af2-4520-ba1c-bb8bfcc82997 Scrolling in the sidebar also works now: https://github.com/emilk/egui/assets/8009393/d4309bec-22e1-4fb4-a425-7b029237849b

--------- Co-authored-by: Emil Ernerfeldt --- crates/egui/src/context.rs | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 10eb10415d6..939198058be 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -112,6 +112,14 @@ impl ContextImpl { // ---------------------------------------------------------------------------- +/// Used to store each widgets [Id], [Rect] and [Sense] each frame. +/// Used to check for overlaps between widgets when handling events. +struct WidgetRect { + id: Id, + rect: Rect, + sense: Sense, +} + /// State stored per viewport #[derive(Default)] struct ViewportState { @@ -138,10 +146,10 @@ struct ViewportState { used: bool, /// Written to during the frame. - layer_rects_this_frame: HashMap>, + layer_rects_this_frame: HashMap>, /// Read - layer_rects_prev_frame: HashMap>, + layer_rects_prev_frame: HashMap>, /// State related to repaint scheduling. repaint: ViewportRepaintInfo, @@ -865,17 +873,29 @@ impl Context { .layer_rects_this_frame .entry(layer_id) .or_default() - .push((id, interact_rect)); + .push(WidgetRect { + id, + rect: interact_rect, + sense, + }); if hovered { let pointer_pos = viewport.input.pointer.interact_pos(); if let Some(pointer_pos) = pointer_pos { if let Some(rects) = viewport.layer_rects_prev_frame.get(&layer_id) { - for &(prev_id, prev_rect) in rects.iter().rev() { + for &WidgetRect { + id: prev_id, + rect: prev_rect, + sense: prev_sense, + } in rects.iter().rev() + { if prev_id == id { break; // there is no other interactive widget covering us at the pointer position. } - if prev_rect.contains(pointer_pos) { + // We don't want a click-only button to block drag-events to a `ScrollArea`: + let has_conflicting_sense = (prev_sense.click && sense.click) + || (prev_sense.drag && sense.drag); + if prev_rect.contains(pointer_pos) && has_conflicting_sense { // Another interactive widget is covering us at the pointer position, // so we aren't hovered.