Skip to content

Commit

Permalink
Take clip_rect into account when storing widget rects (emilk#4020)
Browse files Browse the repository at this point in the history
* Bug introduced in emilk#4013
* Closes emilk#4017

Unfortunately this is a breaking change, since it changes the fields of
`Response`, so can't do a patch-release with this.
  • Loading branch information
emilk authored and hacknus committed Oct 30, 2024
1 parent a19186d commit c963f0a
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 19 deletions.
59 changes: 43 additions & 16 deletions crates/egui/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -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;
}
}
Expand Down Expand Up @@ -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,
Expand All @@ -1065,6 +1081,7 @@ impl Context {
layer_id,
id,
rect,
interact_rect,
sense,
enabled,
contains_pointer,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}
}
Expand Down Expand Up @@ -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;

Expand All @@ -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.
Expand All @@ -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() {
Expand All @@ -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;
}
}
Expand All @@ -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",
);
Expand Down
11 changes: 11 additions & 0 deletions crates/egui/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand Down Expand Up @@ -605,6 +614,7 @@ impl Response {
self.layer_id,
self.id,
self.rect,
self.interact_rect,
sense,
self.enabled,
self.contains_pointer,
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions crates/egui/src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
17 changes: 14 additions & 3 deletions examples/hello_world/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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:?}");
}
});
}
}

0 comments on commit c963f0a

Please sign in to comment.