Skip to content

Commit

Permalink
Fix buggy interaction with widgets outside of clip rect (emilk#4675)
Browse files Browse the repository at this point in the history
This fixes a bug which sometimes would make it possible to interact with
widgets that were outside the parent clip_rect.

Interaction with a widget is done with the `interact_rect`, which is the
intersection of the widget rect and the parent clip rect. If these
rectangles are disjoint (the widget is outside the parent clip rect),
this results in a _negative rectangle_ (a rectangle with a negative
width and/or height). The distance tests for negative rectangles were
broken, causing the bug.

* This is part of solving emilk#4475
* It is also likely this would have solved
emilk#4349 (which now has another fix for
it)


### Breaking changes
`Rect::distance_to_pos`, `distance_sq_to_pos`, `signed_distance_to_pos`
now all return `f32::INFINITY` if the rectangle is negative.
  • Loading branch information
emilk authored and valadaptive committed Jun 20, 2024
1 parent 014327e commit affcac7
Show file tree
Hide file tree
Showing 2 changed files with 22 additions and 0 deletions.
8 changes: 8 additions & 0 deletions crates/egui/src/hit_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ pub fn hit_test(
.filter(|layer| layer.order.allow_interaction())
.flat_map(|&layer_id| widgets.get_layer(layer_id))
.filter(|&w| {
if w.interact_rect.is_negative() {
return false;
}

let pos_in_layer = pos_in_layers.get(&w.layer_id).copied().unwrap_or(pos);
let dist_sq = w.interact_rect.distance_sq_to_pos(pos_in_layer);

Expand Down Expand Up @@ -311,6 +315,10 @@ fn find_closest(widgets: impl Iterator<Item = WidgetRect>, pos: Pos2) -> Option<
let mut closest = None;
let mut closest_dist_sq = f32::INFINITY;
for widget in widgets {
if widget.interact_rect.is_negative() {
continue;
}

let dist_sq = widget.interact_rect.distance_sq_to_pos(pos);

// In case of a tie, take the last one = the one on top.
Expand Down
14 changes: 14 additions & 0 deletions crates/emath/src/rect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,8 @@ impl Rect {
/// The distance from the rect to the position.
///
/// The distance is zero when the position is in the interior of the rectangle.
///
/// [Negative rectangles](Self::is_negative) always return [`f32::INFINITY`].
#[inline]
pub fn distance_to_pos(&self, pos: Pos2) -> f32 {
self.distance_sq_to_pos(pos).sqrt()
Expand All @@ -368,8 +370,14 @@ impl Rect {
/// The distance from the rect to the position, squared.
///
/// The distance is zero when the position is in the interior of the rectangle.
///
/// [Negative rectangles](Self::is_negative) always return [`f32::INFINITY`].
#[inline]
pub fn distance_sq_to_pos(&self, pos: Pos2) -> f32 {
if self.is_negative() {
return f32::INFINITY;
}

let dx = if self.min.x > pos.x {
self.min.x - pos.x
} else if pos.x > self.max.x {
Expand All @@ -393,6 +401,8 @@ impl Rect {
///
/// Negative inside the box.
///
/// [Negative rectangles](Self::is_negative) always return [`f32::INFINITY`].
///
/// ```
/// # use emath::{pos2, Rect};
/// let rect = Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0));
Expand All @@ -401,6 +411,10 @@ impl Rect {
/// assert_eq!(rect.signed_distance_to_pos(pos2(1.50, 0.50)), 0.50);
/// ```
pub fn signed_distance_to_pos(&self, pos: Pos2) -> f32 {
if self.is_negative() {
return f32::INFINITY;
}

let edge_distances = (pos - self.center()).abs() - self.size() * 0.5;
let inside_dist = edge_distances.max_elem().min(0.0);
let outside_dist = edge_distances.max(Vec2::ZERO).length();
Expand Down

0 comments on commit affcac7

Please sign in to comment.