diff --git a/crates/egui/src/input_state/mod.rs b/crates/egui/src/input_state/mod.rs index 0c99b6ac3..d93ec67cb 100644 --- a/crates/egui/src/input_state/mod.rs +++ b/crates/egui/src/input_state/mod.rs @@ -909,7 +909,7 @@ impl InputState { /// to trigger a secondary click (context menu). /// /// Returns `true` only on one frame. - pub(crate) fn is_long_touch(&self) -> bool { + pub fn is_long_touch(&self) -> bool { self.any_touches() && self.pointer.is_long_press() } } @@ -1481,7 +1481,7 @@ impl PointerState { /// to trigger a secondary click (context menu). /// /// Returns `true` only on one frame. - pub(crate) fn is_long_press(&self) -> bool { + pub fn is_long_press(&self) -> bool { self.started_decidedly_dragging && !self.has_moved_too_much_for_a_click && self.button_down(PointerButton::Primary) diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index e89cb5252..e43221c03 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -1,10 +1,10 @@ use std::{any::Any, sync::Arc}; use crate::{ - Context, CursorIcon, Id, LayerId, PointerButton, Popup, PopupKind, Sense, Tooltip, Ui, - WidgetRect, WidgetText, - emath::{Align, Pos2, Rect, Vec2}, - pass_state, + emath::{Align, Pos2, Rect, Vec2}, pass_state, Context, CursorIcon, Id, LayerId, PointerButton, Popup, PopupKind, Sense, + Tooltip, Ui, + WidgetRect, + WidgetText, }; // ---------------------------------------------------------------------------- @@ -282,13 +282,34 @@ impl Response { /// This means it is useful for styling things like drag-and-drop targets. /// `contains_pointer` can also be `true` for disabled widgets. /// - /// This is slightly different from [`Ui::rect_contains_pointer`] and [`Context::rect_contains_pointer`], in that - /// [`Self::contains_pointer`] also checks that no other widget is covering this response rectangle. + /// This is slightly different from [`Response::container_contains_pointer`], + /// [`Ui::rect_contains_pointer`] and [`Context::rect_contains_pointer`], in that + /// [`Response::contains_pointer`] also checks that no other widget is covering this response rectangle. #[inline(always)] pub fn contains_pointer(&self) -> bool { self.flags.contains(Flags::CONTAINS_POINTER) } + /// Is this response or any child widgets hovered? + /// + /// Will return false if some other area is covering the given layer, or if anything is being + /// dragged. + /// + /// This calls [`Context::rect_contains_pointer`]. See also [`Response::hovered`]. + pub fn container_hovered(&self) -> bool { + self.ctx.dragged_id().is_none() && self.container_contains_pointer() + } + + /// Does this response or any child widgets contain the mouse pointer? + /// + /// Will return false if some other area is covering the given layer. + /// + /// This calls [`Context::rect_contains_pointer`]. See also [`Response::contains_pointer`]. + pub fn container_contains_pointer(&self) -> bool { + self.ctx + .rect_contains_pointer(self.layer_id, self.interact_rect) + } + /// The widget is highlighted via a call to [`Self::highlight`] or [`Context::highlight_widget`]. #[doc(hidden)] #[inline(always)] @@ -1006,6 +1027,47 @@ impl Response { intrinsic_size: None, } } + + /// Calls [`Self::union`] with all widgets on the same layer fully contained within this + /// response's rect. + /// + /// Note that this is an expensive call if there are many widgets on the same layer. + /// Consider using [`Response::container_clicked`] or [`Response::container_secondary_clicked`] + /// which are optimized. + /// + /// This is useful if you e.g. want to sense right clicks on some [`Ui`] that contains + /// widgets that also sense for clicks. + pub fn union_children(&self) -> Self { + let child_widgets = self.ctx.pass_state(|pass| { + pass.widgets + .get_layer(self.layer_id) + .filter(|r| self.rect.contains_rect(r.rect)) + .copied() + .collect::>() + }); + let mut result = self.clone(); + for widget in child_widgets { + let child_response = self.ctx.get_response(widget); + result = result.union(child_response); + } + result + } + + /// Optimized version of `response.union_children().clicked()`. + pub fn container_clicked(&self) -> bool { + self.container_contains_pointer() + && self.ctx.input(|i| i.pointer.primary_clicked()) + && self.union_children().clicked() + } + + /// Optimized version of `response.union_children().secondary_clicked()`. + pub fn container_secondary_clicked(&self) -> bool { + self.container_contains_pointer() + && self + .ctx + .input(|i| i.pointer.secondary_clicked() || i.is_long_touch()) + && self.union_children().secondary_clicked() + } } impl Response {