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

View File

@ -32,6 +32,15 @@ pub struct Response {
/// The area of the screen we are talking about. /// The area of the screen we are talking about.
pub rect: Rect, 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). /// The senses (click and/or drag) that the widget was interested in (if any).
pub sense: Sense, pub sense: Sense,
@ -605,6 +614,7 @@ impl Response {
self.layer_id, self.layer_id,
self.id, self.id,
self.rect, self.rect,
self.interact_rect,
sense, sense,
self.enabled, self.enabled,
self.contains_pointer, self.contains_pointer,
@ -799,6 +809,7 @@ impl Response {
layer_id: self.layer_id, layer_id: self.layer_id,
id: self.id, id: self.id,
rect: self.rect.union(other.rect), rect: self.rect.union(other.rect),
interact_rect: self.interact_rect.union(other.interact_rect),
sense: self.sense.union(other.sense), sense: self.sense.union(other.sense),
enabled: self.enabled || other.enabled, enabled: self.enabled || other.enabled,
contains_pointer: self.contains_pointer || other.contains_pointer, contains_pointer: self.contains_pointer || other.contains_pointer,

View File

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

View File

@ -49,9 +49,20 @@ impl eframe::App for MyApp {
} }
ui.label(format!("Hello '{}', age {}", self.name, self.age)); ui.label(format!("Hello '{}', age {}", self.name, self.age));
ui.image(egui::include_image!( // ui.image(egui::include_image!(
"../../../crates/egui/assets/ferris.png" // "../../../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:?}");
}
}); });
} }
} }