From db3fdbe6d3c1b2baf9cb71445f8dbbf8aded0e29 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 26 Dec 2020 22:05:56 +0100 Subject: [PATCH] Add response.interact(sense), e.g. to check for clicks on labels --- CHANGELOG.md | 1 + egui/src/containers/area.rs | 4 +-- egui/src/containers/scroll_area.rs | 2 +- egui/src/context.rs | 31 +++++++++++++++++------ egui/src/types.rs | 40 ++++++++++++++++++++++++------ egui/src/ui.rs | 26 +++++++++++++------ 6 files changed, 78 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 652a09ab..3be99636 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). * Add `egui::math::Rot2`: rotation helper. * `Response` now contains the `Id` of the widget it pertains to. * `ui.allocate_response` that allocates space and checks for interactions. +* Add response.interact(sense), e.g. to check for clicks on labels. ### Changed 🔧 diff --git a/egui/src/containers/area.rs b/egui/src/containers/area.rs index 7ef2f23d..142492f9 100644 --- a/egui/src/containers/area.rs +++ b/egui/src/containers/area.rs @@ -180,11 +180,11 @@ impl Prepared { }; let move_response = ctx.interact( - layer_id, Rect::everything(), ctx.style().spacing.item_spacing, - state.rect(), + layer_id, interact_id, + state.rect(), sense, ); diff --git a/egui/src/containers/scroll_area.rs b/egui/src/containers/scroll_area.rs index 252f1aa7..779962b3 100644 --- a/egui/src/containers/scroll_area.rs +++ b/egui/src/containers/scroll_area.rs @@ -198,7 +198,7 @@ impl Prepared { } // TODO: check that nothing else is being interacted with - if ui.contains_mouse(outer_rect) { + if ui.rect_contains_mouse(outer_rect) { state.offset.y -= ui.input().scroll_delta.y; } diff --git a/egui/src/context.rs b/egui/src/context.rs index 8480e099..ce6c2c1b 100644 --- a/egui/src/context.rs +++ b/egui/src/context.rs @@ -29,7 +29,7 @@ struct Options { pub(crate) struct FrameState { /// Starts off as the screen_rect, shrinks as panels are added. /// The `CentralPanel` does not change this. - /// This is the area avilable to Window's. + /// This is the area available to Window's. available_rect: Rect, /// Starts off as the screen_rect, shrinks as panels are added. @@ -93,7 +93,7 @@ impl FrameState { pub(crate) fn allocate_central_panel(&mut self, panel_rect: Rect) { // Note: we do not shrink `available_rect`, because - // we alllow windows to cover the CentralPanel. + // we allow windows to cover the CentralPanel. self.unused_rect = Rect::nothing(); // Nothing left unused after this self.used_by_panels = self.used_by_panels.union(panel_rect); } @@ -196,15 +196,27 @@ impl CtxRef { /// Use `ui.interact` instead pub(crate) fn interact( &self, - layer_id: LayerId, clip_rect: Rect, item_spacing: Vec2, - rect: Rect, + layer_id: LayerId, id: Id, + rect: Rect, sense: Sense, ) -> Response { let interact_rect = rect.expand2((0.5 * item_spacing).min(Vec2::splat(5.0))); // make it easier to click - let hovered = self.contains_mouse(layer_id, clip_rect, interact_rect); + let hovered = self.rect_contains_mouse(layer_id, clip_rect.intersect(interact_rect)); + self.interact_with_hovered(layer_id, id, rect, sense, hovered) + } + + /// You specify if a thing is hovered, and the function gives a `Response`. + pub(crate) fn interact_with_hovered( + &self, + layer_id: LayerId, + id: Id, + rect: Rect, + sense: Sense, + hovered: bool, + ) -> Response { let has_kb_focus = self.memory().has_kb_focus(id); // If the the focus is lost after the call to interact, @@ -215,6 +227,7 @@ impl CtxRef { // Not interested or allowed input: return Response { ctx: self.clone(), + layer_id, id, rect, sense, @@ -241,6 +254,7 @@ impl CtxRef { if hovered { let mut response = Response { ctx: self.clone(), + layer_id, id, rect, sense, @@ -273,6 +287,7 @@ impl CtxRef { // miss Response { ctx: self.clone(), + layer_id, id, rect, sense, @@ -288,6 +303,7 @@ impl CtxRef { let clicked = hovered && active && self.input.mouse.could_be_click; Response { ctx: self.clone(), + layer_id, id, rect, sense, @@ -301,6 +317,7 @@ impl CtxRef { } else if self.input.mouse.down { Response { ctx: self.clone(), + layer_id, id, rect, sense, @@ -314,6 +331,7 @@ impl CtxRef { } else { Response { ctx: self.clone(), + layer_id, id, rect, sense, @@ -652,8 +670,7 @@ impl Context { self.memory().layer_id_at(pos, resize_grab_radius_side) } - pub fn contains_mouse(&self, layer_id: LayerId, clip_rect: Rect, rect: Rect) -> bool { - let rect = rect.intersect(clip_rect); + pub(crate) fn rect_contains_mouse(&self, layer_id: LayerId, rect: Rect) -> bool { if let Some(mouse_pos) = self.input.mouse.pos { rect.contains(mouse_pos) && self.layer_id_at(mouse_pos) == Some(layer_id) } else { diff --git a/egui/src/types.rs b/egui/src/types.rs index 794d5a64..fc5aa7da 100644 --- a/egui/src/types.rs +++ b/egui/src/types.rs @@ -1,4 +1,4 @@ -use crate::{math::Rect, CtxRef, Id, Ui}; +use crate::{math::Rect, CtxRef, Id, LayerId, Ui}; // ---------------------------------------------------------------------------- @@ -54,33 +54,36 @@ impl Default for CursorIcon { #[derive(Clone)] pub struct Response { // CONTEXT: - /// Used for optionally showing a tooltip + /// Used for optionally showing a tooltip and checking for more interactions. pub ctx: CtxRef, // IN: + /// Which layer the widget is part of. + pub layer_id: LayerId, + /// The `Id` of the widget/area this response pertains. pub id: Id, - /// The area of the screen we are talking about + /// The area of the screen we are talking about. pub rect: Rect, /// The senses (click or drag) that the widget is interested in (if any). pub sense: Sense, // OUT: - /// The mouse is hovering above this + /// The mouse is hovering above this. pub hovered: bool, - /// The mouse clicked this thing this frame + /// The mouse clicked this thing this frame. pub clicked: bool, - /// The thing was double-clicked + /// The thing was double-clicked. pub double_clicked: bool, - /// The mouse is interacting with this thing (e.g. dragging it) + /// The mouse is interacting with this thing (e.g. dragging it). pub active: bool, - /// This widget has the keyboard focus (i.e. is receiving key pressed) + /// This widget has the keyboard focus (i.e. is receiving key pressed). pub has_kb_focus: bool, /// The widget had keyboard focus and lost it, @@ -94,6 +97,7 @@ impl std::fmt::Debug for Response { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let Self { ctx: _, + layer_id, id, rect, sense, @@ -105,6 +109,7 @@ impl std::fmt::Debug for Response { lost_kb_focus, } = self; f.debug_struct("Response") + .field("layer_id", layer_id) .field("id", id) .field("rect", rect) .field("sense", sense) @@ -140,6 +145,20 @@ impl Response { pub fn tooltip_text(self, text: impl Into) -> Self { self.on_hover_text(text) } + + /// Check for more interactions (e.g. sense clicks on a `Response` returned from a label). + /// + /// ``` + /// # let mut ui = egui::Ui::__test(); + /// let response = ui.label("hello"); + /// assert!(!response.clicked); // labels don't sense clicks + /// let response = response.interact(egui::Sense::click()); + /// if response.clicked { /* … */ } + /// ``` + pub fn interact(&self, sense: Sense) -> Self { + self.ctx + .interact_with_hovered(self.layer_id, self.id, self.rect, sense, self.hovered) + } } impl Response { @@ -147,8 +166,13 @@ impl Response { /// For instance `a.union(b).hovered` means "was either a or b hovered?". pub fn union(&self, other: Self) -> Self { assert!(self.ctx == other.ctx); + debug_assert_eq!( + self.layer_id, other.layer_id, + "It makes no sense to combine Responses from two different layers" + ); Self { ctx: other.ctx, + layer_id: self.layer_id, id: self.id, rect: self.rect.union(other.rect), sense: self.sense.union(other.sense), diff --git a/egui/src/ui.rs b/egui/src/ui.rs index b2d204e5..ed532257 100644 --- a/egui/src/ui.rs +++ b/egui/src/ui.rs @@ -382,30 +382,40 @@ impl Ui { impl Ui { pub fn interact(&self, rect: Rect, id: Id, sense: Sense) -> Response { self.ctx().interact( - self.layer_id(), self.clip_rect(), self.style().spacing.item_spacing, - rect, + self.layer_id(), id, + rect, sense, ) } + pub fn rect_contains_mouse(&self, rect: Rect) -> bool { + self.ctx() + .rect_contains_mouse(self.layer_id(), self.clip_rect().intersect(rect)) + } + + /// Is the mouse above this `Ui`? + pub fn ui_contains_mouse(&self) -> bool { + if let Some(mouse_pos) = self.input().mouse.pos { + self.clip_rect().contains(mouse_pos) + && self.ctx().layer_id_at(mouse_pos) == Some(self.layer_id()) + } else { + false + } + } + #[deprecated = "Use: interact(rect, id, Sense::hover())"] pub fn interact_hover(&self, rect: Rect) -> Response { self.interact(rect, self.auto_id_with("hover_rect"), Sense::hover()) } - #[deprecated = "Use: contains_mouse()"] + #[deprecated = "Use: ui_contains_mouse()"] pub fn hovered(&self, rect: Rect) -> bool { self.interact(rect, self.id, Sense::hover()).hovered } - pub fn contains_mouse(&self, rect: Rect) -> bool { - self.ctx() - .contains_mouse(self.layer_id(), self.clip_rect(), rect) - } - // ------------------------------------------------------------------------ // Stuff that moves the cursor, i.e. allocates space in this ui!