From 8e3cc883c1a1a91eae910e4975a032cee97f1f24 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Sun, 1 Sep 2024 20:09:04 +0200 Subject: [PATCH] Add Ui::interact_scope --- crates/egui/src/containers/area.rs | 19 ++--- crates/egui/src/containers/window.rs | 19 ++--- crates/egui/src/context.rs | 8 +-- crates/egui/src/response.rs | 19 ++--- crates/egui/src/ui.rs | 104 ++++++++++++++++++++------- crates/egui/src/ui_builder.rs | 9 ++- 6 files changed, 124 insertions(+), 54 deletions(-) diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index 378b0edc8d9e..3a77e2c04574 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -462,14 +462,17 @@ impl Area { } }); - let move_response = ctx.create_widget(WidgetRect { - id: interact_id, - layer_id, - rect: state.rect(), - interact_rect: state.rect(), - sense, - enabled, - }); + let move_response = ctx.create_widget( + WidgetRect { + id: interact_id, + layer_id, + rect: state.rect(), + interact_rect: state.rect(), + sense, + enabled, + }, + false, + ); if movable && move_response.dragged() { if let Some(pivot_pos) = &mut state.pivot_pos { diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index c183a4dc7317..9fc21a67a5fe 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -833,14 +833,17 @@ fn resize_interaction( } let is_dragging = |rect, id| { - let response = ctx.create_widget(WidgetRect { - layer_id, - id, - rect, - interact_rect: rect, - sense: Sense::drag(), - enabled: true, - }); + let response = ctx.create_widget( + WidgetRect { + layer_id, + id, + rect, + interact_rect: rect, + sense: Sense::drag(), + enabled: true, + }, + false, + ); SideResponse { hover: response.hovered(), drag: response.dragged(), diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 7225af6d1caf..a08149b69b1f 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1050,7 +1050,7 @@ impl Context { /// /// If the widget already exists, its state (sense, Rect, etc) will be updated. #[allow(clippy::too_many_arguments)] - pub(crate) fn create_widget(&self, w: WidgetRect) -> Response { + pub(crate) fn create_widget(&self, w: WidgetRect, ignore_focus: bool) -> Response { // Remember this widget self.write(|ctx| { let viewport = ctx.viewport(); @@ -1060,12 +1060,12 @@ impl Context { // but also to know when we have reached the widget we are checking for cover. viewport.this_frame.widgets.insert(w.layer_id, w); - if w.sense.focusable { + if w.sense.focusable && !ignore_focus { ctx.memory.interested_in_focus(w.id); } }); - if !w.enabled || !w.sense.focusable || !w.layer_id.allow_interaction() { + if !w.enabled || !w.sense.focusable || !w.layer_id.allow_interaction() && !ignore_focus { // Not interested or allowed input: self.memory_mut(|mem| mem.surrender_focus(w.id)); } @@ -1078,7 +1078,7 @@ impl Context { let res = self.get_response(w); #[cfg(feature = "accesskit")] - if w.sense.focusable { + if w.sense.focusable && !ignore_focus { // Make sure anything that can receive focus has an AccessKit node. // TODO(mwcampbell): For nodes that are filled from widget info, // some information is written to the node twice. diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index d6a9a8a0c7f4..a52d54a709cc 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -857,14 +857,17 @@ impl Response { return self.clone(); } - self.ctx.create_widget(WidgetRect { - layer_id: self.layer_id, - id: self.id, - rect: self.rect, - interact_rect: self.interact_rect, - sense: self.sense | sense, - enabled: self.enabled, - }) + self.ctx.create_widget( + WidgetRect { + layer_id: self.layer_id, + id: self.id, + rect: self.rect, + interact_rect: self.interact_rect, + sense: self.sense | sense, + enabled: self.enabled, + }, + false, + ) } /// Adjust the scroll position until this UI becomes visible. diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index e502f11e148d..7693092829dc 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -109,6 +109,7 @@ impl Ui { invisible, sizing_pass, style, + sense, } = ui_builder; debug_assert!( @@ -146,14 +147,17 @@ impl Ui { // Register in the widget stack early, to ensure we are behind all widgets we contain: let start_rect = Rect::NOTHING; // This will be overwritten when/if `interact_bg` is called - ui.ctx().create_widget(WidgetRect { - id: ui.id, - layer_id: ui.layer_id(), - rect: start_rect, - interact_rect: start_rect, - sense: Sense::hover(), - enabled: ui.enabled, - }); + ui.ctx().create_widget( + WidgetRect { + id: ui.id, + layer_id: ui.layer_id(), + rect: start_rect, + interact_rect: start_rect, + sense: sense.unwrap_or(Sense::hover()), + enabled: ui.enabled, + }, + false, + ); if disabled { ui.disable(); @@ -220,6 +224,7 @@ impl Ui { invisible, sizing_pass, style, + sense, } = ui_builder; let mut painter = self.painter.clone(); @@ -273,14 +278,17 @@ impl Ui { // Register in the widget stack early, to ensure we are behind all widgets we contain: let start_rect = Rect::NOTHING; // This will be overwritten when/if `interact_bg` is called - child_ui.ctx().create_widget(WidgetRect { - id: child_ui.id, - layer_id: child_ui.layer_id(), - rect: start_rect, - interact_rect: start_rect, - sense: Sense::hover(), - enabled: child_ui.enabled, - }); + child_ui.ctx().create_widget( + WidgetRect { + id: child_ui.id, + layer_id: child_ui.layer_id(), + rect: start_rect, + interact_rect: start_rect, + sense: sense.unwrap_or(Sense::hover()), + enabled: child_ui.enabled, + }, + false, + ); child_ui } @@ -948,14 +956,17 @@ impl Ui { impl Ui { /// Check for clicks, drags and/or hover on a specific region of this [`Ui`]. pub fn interact(&self, rect: Rect, id: Id, sense: Sense) -> Response { - self.ctx().create_widget(WidgetRect { - id, - layer_id: self.layer_id(), - rect, - interact_rect: self.clip_rect().intersect(rect), - sense, - enabled: self.enabled, - }) + self.ctx().create_widget( + WidgetRect { + id, + layer_id: self.layer_id(), + rect, + interact_rect: self.clip_rect().intersect(rect), + sense, + enabled: self.enabled, + }, + false, + ) } /// Deprecated: use [`Self::interact`] instead. @@ -975,8 +986,24 @@ impl Ui { /// /// The rectangle of the [`Response`] (and interactive area) will be [`Self::min_rect`]. pub fn interact_bg(&self, sense: Sense) -> Response { + // We remove the id from used_ids to prevent a duplicate id warning from showing + // when the ui was created with `UiBuilder::sense`. + // This is a bit hacky, is there a better way? + self.ctx().frame_state_mut(|fs| { + fs.used_ids.remove(&self.id); + }); // This will update the WidgetRect that was first created in `Ui::new`. - self.interact(self.min_rect(), self.id, sense) + self.ctx().create_widget( + WidgetRect { + id: self.id, + layer_id: self.layer_id(), + rect: self.min_rect(), + interact_rect: self.clip_rect().intersect(self.min_rect()), + sense, + enabled: self.enabled, + }, + true, + ) } /// Is the pointer (mouse/touch) above this rectangle in this [`Ui`]? @@ -2685,6 +2712,33 @@ impl Ui { (InnerResponse { inner, response }, payload) } + + /// Create a child ui scope and pass in its response. + /// You can use this e.g. to create interactive containers or custom buttons. + /// + /// This basically does three things: + /// 1. Read [`Ui`]s response via [`Context::read_response`]. + /// 2. Create a child ui scope and call the content fn + /// 3. Call [`Ui::interact_bg`] to set the right [`WidgetRect`] + pub fn interact_scope( + &mut self, + sense: Sense, + content: impl FnOnce(&mut Self, Option) -> R, + ) -> InnerResponse { + let id_source = "interact_ui"; + let id = self.id.with(Id::new(id_source)); + let response = self.ctx().read_response(id); + + self.scope_dyn( + UiBuilder::new().sense(sense).id_source(id_source), + Box::new(|ui| { + let inner = content(ui, response); + let response = ui.interact_bg(sense); + InnerResponse::new(inner, response) + }), + ) + .inner + } } /// # Menus diff --git a/crates/egui/src/ui_builder.rs b/crates/egui/src/ui_builder.rs index 166210b9ab2a..e00a9ec26de5 100644 --- a/crates/egui/src/ui_builder.rs +++ b/crates/egui/src/ui_builder.rs @@ -1,6 +1,6 @@ use std::{hash::Hash, sync::Arc}; -use crate::{Id, Layout, Rect, Style, UiStackInfo}; +use crate::{Id, Layout, Rect, Sense, Style, UiStackInfo}; #[allow(unused_imports)] // Used for doclinks use crate::Ui; @@ -21,6 +21,7 @@ pub struct UiBuilder { pub invisible: bool, pub sizing_pass: bool, pub style: Option>, + pub sense: Option, } impl UiBuilder { @@ -116,4 +117,10 @@ impl UiBuilder { self.style = Some(style.into()); self } + + /// Sense of the Ui. Should be the same as the one passed to [`Ui::interact_bg`] + pub fn sense(mut self, sense: Sense) -> Self { + self.sense = Some(sense); + self + } }