Add response.interact(sense), e.g. to check for clicks on labels

This commit is contained in:
Emil Ernerfeldt 2020-12-26 22:05:56 +01:00
parent de614153b5
commit db3fdbe6d3
6 changed files with 78 additions and 26 deletions

View file

@ -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. * Add `egui::math::Rot2`: rotation helper.
* `Response` now contains the `Id` of the widget it pertains to. * `Response` now contains the `Id` of the widget it pertains to.
* `ui.allocate_response` that allocates space and checks for interactions. * `ui.allocate_response` that allocates space and checks for interactions.
* Add response.interact(sense), e.g. to check for clicks on labels.
### Changed 🔧 ### Changed 🔧

View file

@ -180,11 +180,11 @@ impl Prepared {
}; };
let move_response = ctx.interact( let move_response = ctx.interact(
layer_id,
Rect::everything(), Rect::everything(),
ctx.style().spacing.item_spacing, ctx.style().spacing.item_spacing,
state.rect(), layer_id,
interact_id, interact_id,
state.rect(),
sense, sense,
); );

View file

@ -198,7 +198,7 @@ impl Prepared {
} }
// TODO: check that nothing else is being interacted with // 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; state.offset.y -= ui.input().scroll_delta.y;
} }

View file

@ -29,7 +29,7 @@ struct Options {
pub(crate) struct FrameState { pub(crate) struct FrameState {
/// Starts off as the screen_rect, shrinks as panels are added. /// Starts off as the screen_rect, shrinks as panels are added.
/// The `CentralPanel` does not change this. /// 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, available_rect: Rect,
/// Starts off as the screen_rect, shrinks as panels are added. /// 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) { pub(crate) fn allocate_central_panel(&mut self, panel_rect: Rect) {
// Note: we do not shrink `available_rect`, because // 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.unused_rect = Rect::nothing(); // Nothing left unused after this
self.used_by_panels = self.used_by_panels.union(panel_rect); self.used_by_panels = self.used_by_panels.union(panel_rect);
} }
@ -196,15 +196,27 @@ impl CtxRef {
/// Use `ui.interact` instead /// Use `ui.interact` instead
pub(crate) fn interact( pub(crate) fn interact(
&self, &self,
layer_id: LayerId,
clip_rect: Rect, clip_rect: Rect,
item_spacing: Vec2, item_spacing: Vec2,
rect: Rect, layer_id: LayerId,
id: Id, id: Id,
rect: Rect,
sense: Sense, sense: Sense,
) -> Response { ) -> Response {
let interact_rect = rect.expand2((0.5 * item_spacing).min(Vec2::splat(5.0))); // make it easier to click 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); let has_kb_focus = self.memory().has_kb_focus(id);
// If the the focus is lost after the call to interact, // If the the focus is lost after the call to interact,
@ -215,6 +227,7 @@ impl CtxRef {
// Not interested or allowed input: // Not interested or allowed input:
return Response { return Response {
ctx: self.clone(), ctx: self.clone(),
layer_id,
id, id,
rect, rect,
sense, sense,
@ -241,6 +254,7 @@ impl CtxRef {
if hovered { if hovered {
let mut response = Response { let mut response = Response {
ctx: self.clone(), ctx: self.clone(),
layer_id,
id, id,
rect, rect,
sense, sense,
@ -273,6 +287,7 @@ impl CtxRef {
// miss // miss
Response { Response {
ctx: self.clone(), ctx: self.clone(),
layer_id,
id, id,
rect, rect,
sense, sense,
@ -288,6 +303,7 @@ impl CtxRef {
let clicked = hovered && active && self.input.mouse.could_be_click; let clicked = hovered && active && self.input.mouse.could_be_click;
Response { Response {
ctx: self.clone(), ctx: self.clone(),
layer_id,
id, id,
rect, rect,
sense, sense,
@ -301,6 +317,7 @@ impl CtxRef {
} else if self.input.mouse.down { } else if self.input.mouse.down {
Response { Response {
ctx: self.clone(), ctx: self.clone(),
layer_id,
id, id,
rect, rect,
sense, sense,
@ -314,6 +331,7 @@ impl CtxRef {
} else { } else {
Response { Response {
ctx: self.clone(), ctx: self.clone(),
layer_id,
id, id,
rect, rect,
sense, sense,
@ -652,8 +670,7 @@ impl Context {
self.memory().layer_id_at(pos, resize_grab_radius_side) self.memory().layer_id_at(pos, resize_grab_radius_side)
} }
pub fn contains_mouse(&self, layer_id: LayerId, clip_rect: Rect, rect: Rect) -> bool { pub(crate) fn rect_contains_mouse(&self, layer_id: LayerId, rect: Rect) -> bool {
let rect = rect.intersect(clip_rect);
if let Some(mouse_pos) = self.input.mouse.pos { if let Some(mouse_pos) = self.input.mouse.pos {
rect.contains(mouse_pos) && self.layer_id_at(mouse_pos) == Some(layer_id) rect.contains(mouse_pos) && self.layer_id_at(mouse_pos) == Some(layer_id)
} else { } else {

View file

@ -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)] #[derive(Clone)]
pub struct Response { pub struct Response {
// CONTEXT: // CONTEXT:
/// Used for optionally showing a tooltip /// Used for optionally showing a tooltip and checking for more interactions.
pub ctx: CtxRef, pub ctx: CtxRef,
// IN: // IN:
/// Which layer the widget is part of.
pub layer_id: LayerId,
/// The `Id` of the widget/area this response pertains. /// The `Id` of the widget/area this response pertains.
pub id: Id, pub id: Id,
/// The area of the screen we are talking about /// The area of the screen we are talking about.
pub rect: Rect, pub rect: Rect,
/// The senses (click or drag) that the widget is interested in (if any). /// The senses (click or drag) that the widget is interested in (if any).
pub sense: Sense, pub sense: Sense,
// OUT: // OUT:
/// The mouse is hovering above this /// The mouse is hovering above this.
pub hovered: bool, pub hovered: bool,
/// The mouse clicked this thing this frame /// The mouse clicked this thing this frame.
pub clicked: bool, pub clicked: bool,
/// The thing was double-clicked /// The thing was double-clicked.
pub double_clicked: bool, 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, 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, pub has_kb_focus: bool,
/// The widget had keyboard focus and lost it, /// 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 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { let Self {
ctx: _, ctx: _,
layer_id,
id, id,
rect, rect,
sense, sense,
@ -105,6 +109,7 @@ impl std::fmt::Debug for Response {
lost_kb_focus, lost_kb_focus,
} = self; } = self;
f.debug_struct("Response") f.debug_struct("Response")
.field("layer_id", layer_id)
.field("id", id) .field("id", id)
.field("rect", rect) .field("rect", rect)
.field("sense", sense) .field("sense", sense)
@ -140,6 +145,20 @@ impl Response {
pub fn tooltip_text(self, text: impl Into<String>) -> Self { pub fn tooltip_text(self, text: impl Into<String>) -> Self {
self.on_hover_text(text) 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 { impl Response {
@ -147,8 +166,13 @@ impl Response {
/// For instance `a.union(b).hovered` means "was either a or b hovered?". /// For instance `a.union(b).hovered` means "was either a or b hovered?".
pub fn union(&self, other: Self) -> Self { pub fn union(&self, other: Self) -> Self {
assert!(self.ctx == other.ctx); 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 { Self {
ctx: other.ctx, ctx: other.ctx,
layer_id: self.layer_id,
id: self.id, id: self.id,
rect: self.rect.union(other.rect), rect: self.rect.union(other.rect),
sense: self.sense.union(other.sense), sense: self.sense.union(other.sense),

View file

@ -382,30 +382,40 @@ impl Ui {
impl Ui { impl Ui {
pub fn interact(&self, rect: Rect, id: Id, sense: Sense) -> Response { pub fn interact(&self, rect: Rect, id: Id, sense: Sense) -> Response {
self.ctx().interact( self.ctx().interact(
self.layer_id(),
self.clip_rect(), self.clip_rect(),
self.style().spacing.item_spacing, self.style().spacing.item_spacing,
rect, self.layer_id(),
id, id,
rect,
sense, 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())"] #[deprecated = "Use: interact(rect, id, Sense::hover())"]
pub fn interact_hover(&self, rect: Rect) -> Response { pub fn interact_hover(&self, rect: Rect) -> Response {
self.interact(rect, self.auto_id_with("hover_rect"), Sense::hover()) 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 { pub fn hovered(&self, rect: Rect) -> bool {
self.interact(rect, self.id, Sense::hover()).hovered 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! // Stuff that moves the cursor, i.e. allocates space in this ui!