diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 3566042d..ea51532a 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -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", ); diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 2cef7849..073ab561 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -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, diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 559711a2..1d65253e 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -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, diff --git a/examples/hello_world/src/main.rs b/examples/hello_world/src/main.rs index b3fda5a5..abe3838a 100644 --- a/examples/hello_world/src/main.rs +++ b/examples/hello_world/src/main.rs @@ -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:?}"); + } }); } }