Take clip_rect into account when storing widget rects (#4020)

* Bug introduced in https://github.com/emilk/egui/pull/4013
* Closes https://github.com/emilk/egui/issues/4017

Unfortunately this is a breaking change, since it changes the fields of
`Response`, so can't do a patch-release with this.
This commit is contained in:
Emil Ernerfeldt 2024-02-10 17:28:38 +01:00 committed by GitHub
parent 132d0ec430
commit 407224746d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 70 additions and 19 deletions

View File

@ -199,7 +199,9 @@ impl ContextImpl {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WidgetRect {
/// Where the widget is.
pub rect: Rect,
///
/// This is after clipping with the parent ui clip rect.
pub interact_rect: Rect,
/// The globally unique widget id.
///
@ -234,13 +236,17 @@ impl WidgetRects {
/// Insert the given widget rect in the given layer.
pub fn insert(&mut self, layer_id: LayerId, widget_rect: WidgetRect) {
if !widget_rect.interact_rect.is_positive() {
return;
}
let layer_widgets = self.by_layer.entry(layer_id).or_default();
if let Some(last) = layer_widgets.last_mut() {
if last.id == widget_rect.id {
// e.g. calling `response.interact(…)` right after interacting.
last.sense |= widget_rect.sense;
last.rect = last.rect.union(widget_rect.rect);
last.interact_rect = last.interact_rect.union(widget_rect.interact_rect);
return;
}
}
@ -1046,15 +1052,25 @@ impl Context {
);
}
self.interact_with_hovered(layer_id, id, rect, sense, enabled, contains_pointer)
self.interact_with_hovered(
layer_id,
id,
rect,
interact_rect,
sense,
enabled,
contains_pointer,
)
}
/// You specify if a thing is hovered, and the function gives a [`Response`].
#[allow(clippy::too_many_arguments)]
pub(crate) fn interact_with_hovered(
&self,
layer_id: LayerId,
id: Id,
rect: Rect,
interact_rect: Rect,
sense: Sense,
enabled: bool,
contains_pointer: bool,
@ -1065,6 +1081,7 @@ impl Context {
layer_id,
id,
rect,
interact_rect,
sense,
enabled,
contains_pointer,
@ -1108,9 +1125,14 @@ impl Context {
// We add all widgets here, even non-interactive ones,
// because we need this list not only for checking for blocking widgets,
// but also to know when we have reached the widget we are checking for cover.
viewport
.layer_rects_this_frame
.insert(layer_id, WidgetRect { id, rect, sense });
viewport.layer_rects_this_frame.insert(
layer_id,
WidgetRect {
id,
interact_rect,
sense,
},
);
let input = &viewport.input;
let memory = &mut ctx.memory;
@ -1857,7 +1879,7 @@ impl Context {
} else {
(Color32::from_rgb(0, 0, 0x88), "hover")
};
painter.debug_rect(rect.rect, color, text);
painter.debug_rect(rect.interact_rect, color, text);
}
}
}
@ -2314,13 +2336,13 @@ impl Context {
layer_id: LayerId,
id: Id,
sense: Sense,
rect: Rect,
interact_rect: Rect,
) -> bool {
if !rect.is_positive() {
if !interact_rect.is_positive() {
return false; // don't even remember this widget
}
let contains_pointer = self.rect_contains_pointer(layer_id, rect);
let contains_pointer = self.rect_contains_pointer(layer_id, interact_rect);
let mut blocking_widget = None;
@ -2330,9 +2352,14 @@ impl Context {
// We add all widgets here, even non-interactive ones,
// because we need this list not only for checking for blocking widgets,
// but also to know when we have reached the widget we are checking for cover.
viewport
.layer_rects_this_frame
.insert(layer_id, WidgetRect { id, rect, sense });
viewport.layer_rects_this_frame.insert(
layer_id,
WidgetRect {
id,
interact_rect,
sense,
},
);
// Check if any other widget is covering us.
// Whichever widget is added LAST (=on top) gets the input.
@ -2347,7 +2374,7 @@ impl Context {
// which means there are no widgets covering us.
break;
}
if !blocking.rect.contains(pointer_pos) {
if !blocking.interact_rect.contains(pointer_pos) {
continue;
}
if sense.interactive() && !blocking.sense.interactive() {
@ -2369,7 +2396,7 @@ impl Context {
if blocking.sense.interactive() {
// Another widget is covering us at the pointer position
blocking_widget = Some(blocking.rect);
blocking_widget = Some(blocking.interact_rect);
break;
}
}
@ -2382,7 +2409,7 @@ impl Context {
if let Some(blocking_rect) = blocking_widget {
if sense.interactive() && self.memory(|m| m.options.style.debug.show_blocking_widget) {
Self::layer_painter(self, LayerId::debug()).debug_rect(
rect,
interact_rect,
Color32::GREEN,
"Covered",
);

View File

@ -32,6 +32,15 @@ pub struct Response {
/// The area of the screen we are talking about.
pub rect: Rect,
/// The rectangle sensing interaction.
///
/// This is sometimes smaller than [`Self::rect`] because of clipping
/// (e.g. when inside a scroll area).
///
/// The interact rect may also be slightly larger than the widget rect,
/// because egui adds half if the item spacing to make the interact rect easier to hit.
pub interact_rect: Rect,
/// The senses (click and/or drag) that the widget was interested in (if any).
pub sense: Sense,
@ -605,6 +614,7 @@ impl Response {
self.layer_id,
self.id,
self.rect,
self.interact_rect,
sense,
self.enabled,
self.contains_pointer,
@ -799,6 +809,7 @@ impl Response {
layer_id: self.layer_id,
id: self.id,
rect: self.rect.union(other.rect),
interact_rect: self.interact_rect.union(other.interact_rect),
sense: self.sense.union(other.sense),
enabled: self.enabled || other.enabled,
contains_pointer: self.contains_pointer || other.contains_pointer,

View File

@ -650,10 +650,12 @@ impl Ui {
id: Id,
sense: Sense,
) -> Response {
let interact_rect = rect.intersect(self.clip_rect());
self.ctx().interact_with_hovered(
self.layer_id(),
id,
rect,
interact_rect,
sense,
self.enabled,
contains_pointer,

View File

@ -49,9 +49,20 @@ impl eframe::App for MyApp {
}
ui.label(format!("Hello '{}', age {}", self.name, self.age));
ui.image(egui::include_image!(
"../../../crates/egui/assets/ferris.png"
));
// ui.image(egui::include_image!(
// "../../../crates/egui/assets/ferris.png"
// ));
let (response, painter) =
ui.allocate_painter(egui::vec2(300.0, 150.0), egui::Sense::click());
painter.rect_filled(response.rect, 2.0, egui::Color32::BLACK);
let clicked_pos = response.interact_pointer_pos();
if response.clicked() {
eprintln!("clicked_pos: {clicked_pos:?}");
}
if response.is_pointer_button_down_on() {
eprintln!("down_pos: {clicked_pos:?}");
}
});
}
}