Skip to content
19 changes: 11 additions & 8 deletions crates/egui/src/containers/area.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
true,
);

if movable && move_response.dragged() {
if let Some(pivot_pos) = &mut state.pivot_pos {
Expand Down
19 changes: 11 additions & 8 deletions crates/egui/src/containers/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
true,
);
SideResponse {
hover: response.hovered(),
drag: response.dragged(),
Expand Down
13 changes: 8 additions & 5 deletions crates/egui/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1049,8 +1049,11 @@ impl Context {
/// You should use [`Ui::interact`] instead.
///
/// If the widget already exists, its state (sense, Rect, etc) will be updated.
///
/// `allow_focus` should usually be true, unless you call this function multiple times with the
/// same widget, then `allow_focus` should only be true once (like in [`Ui::new`] (true) and [`Ui::remember_min_rect`] (false)).
#[allow(clippy::too_many_arguments)]
pub(crate) fn create_widget(&self, w: WidgetRect) -> Response {
pub(crate) fn create_widget(&self, w: WidgetRect, allow_focus: bool) -> Response {
// Remember this widget
self.write(|ctx| {
let viewport = ctx.viewport();
Expand All @@ -1060,12 +1063,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 allow_focus && w.sense.focusable {
ctx.memory.interested_in_focus(w.id);
}
});

if !w.enabled || !w.sense.focusable || !w.layer_id.allow_interaction() {
if allow_focus && (!w.enabled || !w.sense.focusable || !w.layer_id.allow_interaction()) {
// Not interested or allowed input:
self.memory_mut(|mem| mem.surrender_focus(w.id));
}
Expand All @@ -1078,7 +1081,7 @@ impl Context {
let res = self.get_response(w);

#[cfg(feature = "accesskit")]
if w.sense.focusable {
if allow_focus && w.sense.focusable {
// 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.
Expand Down Expand Up @@ -1114,7 +1117,7 @@ impl Context {
}

/// Do all interaction for an existing widget, without (re-)registering it.
fn get_response(&self, widget_rect: WidgetRect) -> Response {
pub(crate) fn get_response(&self, widget_rect: WidgetRect) -> Response {
let WidgetRect {
id,
layer_id,
Expand Down
2 changes: 1 addition & 1 deletion crates/egui/src/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -706,7 +706,7 @@ impl MenuState {

self.open_submenu(sub_id, pos);
} else if open
&& ui.interact_bg(Sense::hover()).contains_pointer()
&& ui.response().contains_pointer()
&& !button.hovered()
&& !self.hovering_current_submenu(&pointer)
{
Expand Down
19 changes: 11 additions & 8 deletions crates/egui/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
true,
)
}

/// Adjust the scroll position until this UI becomes visible.
Expand Down
136 changes: 109 additions & 27 deletions crates/egui/src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ pub struct Ui {

/// The [`UiStack`] for this [`Ui`].
stack: Arc<UiStack>,

/// The sense for the ui background.
sense: Sense,

/// Whether [`Ui::remember_min_rect`] should be called when the [`Ui`] is dropped.
/// This is an optimization, so we don't call [`Ui::remember_min_rect`] multiple times at the
/// end of a [`Ui::scope`].
min_rect_already_remembered: bool,
}

impl Ui {
Expand All @@ -109,6 +117,7 @@ impl Ui {
invisible,
sizing_pass,
style,
sense,
} = ui_builder;

debug_assert!(
Expand All @@ -122,6 +131,7 @@ impl Ui {
let invisible = invisible || sizing_pass;
let disabled = disabled || invisible || sizing_pass;
let style = style.unwrap_or_else(|| ctx.style());
let sense = sense.unwrap_or(Sense::hover());

let placer = Placer::new(max_rect, layout);
let ui_stack = UiStack {
Expand All @@ -142,18 +152,23 @@ impl Ui {
sizing_pass: false,
menu_state: None,
stack: Arc::new(ui_stack),
sense,
min_rect_already_remembered: false,
};

// 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,
enabled: ui.enabled,
},
true,
);

if disabled {
ui.disable();
Expand Down Expand Up @@ -220,6 +235,7 @@ impl Ui {
invisible,
sizing_pass,
style,
sense,
} = ui_builder;

let mut painter = self.painter.clone();
Expand All @@ -234,6 +250,7 @@ impl Ui {
}
let sizing_pass = self.sizing_pass || sizing_pass;
let style = style.unwrap_or_else(|| self.style.clone());
let sense = sense.unwrap_or(Sense::hover());

if self.sizing_pass {
// During the sizing pass we want widgets to use up as little space as possible,
Expand Down Expand Up @@ -269,18 +286,23 @@ impl Ui {
sizing_pass,
menu_state: self.menu_state.clone(),
stack: Arc::new(ui_stack),
sense,
min_rect_already_remembered: false,
};

// 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,
enabled: child_ui.enabled,
},
true,
);

child_ui
}
Expand Down Expand Up @@ -948,14 +970,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,
},
true,
)
}

/// Deprecated: use [`Self::interact`] instead.
Expand All @@ -970,10 +995,62 @@ impl Ui {
self.interact(rect, id, sense)
}

/// Read the [`Ui`]s background [`Response`].
/// It's [`Sense`] will be based on the [`UiBuilder::sense`] used to create this [`Ui`].
///
/// The rectangle of the [`Response`] (and interactive area) will be [`Self::min_rect`]
/// of the last frame.
///
/// On the first frame, when the [`Ui`] is created, this will return a [`Response`] with a
/// [`Rect`] of [`Rect::NOTHING`].
pub fn response(&self) -> Response {
// This is the inverse of Context::read_response. We prefer a response
// based on last frame's widget rect since the one from this frame is Rect::NOTHING until
// Ui::interact_bg is called or the Ui is dropped.
self.ctx()
.viewport(|viewport| {
viewport
.prev_frame
.widgets
.get(self.id)
.or_else(|| viewport.this_frame.widgets.get(self.id))
.copied()
})
.map(|widget_rect| self.ctx().get_response(widget_rect))
.expect(
"Since we always call Context::create_widget in Ui::new, this should never be None",
)
}

/// Update the [`WidgetRect`] created in [`Ui::new`] or [`Ui::new_child`] with the current
/// [`Ui::min_rect`].
fn remember_min_rect(&mut self) -> Response {
self.min_rect_already_remembered = true;
// 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.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: self.sense,
enabled: self.enabled,
},
false,
)
}

/// Interact with the background of this [`Ui`],
/// i.e. behind all the widgets.
///
/// The rectangle of the [`Response`] (and interactive area) will be [`Self::min_rect`].
#[deprecated = "Use UiBuilder::sense with Ui::response instead"]
pub fn interact_bg(&self, sense: Sense) -> Response {
// This will update the WidgetRect that was first created in `Ui::new`.
self.interact(self.min_rect(), self.id, sense)
Expand All @@ -996,7 +1073,7 @@ impl Ui {
///
/// Note that this tests against the _current_ [`Ui::min_rect`].
/// If you want to test against the final `min_rect`,
/// use [`Self::interact_bg`] instead.
/// use [`Self::response`] instead.
pub fn ui_contains_pointer(&self) -> bool {
self.rect_contains_pointer(self.min_rect())
}
Expand Down Expand Up @@ -2142,7 +2219,8 @@ impl Ui {
let mut child_ui = self.new_child(ui_builder);
self.next_auto_id_salt = next_auto_id_salt; // HACK: we want `scope` to only increment this once, so that `ui.scope` is equivalent to `ui.allocate_space`.
let ret = add_contents(&mut child_ui);
let response = self.allocate_rect(child_ui.min_rect(), Sense::hover());
let response = child_ui.remember_min_rect();
self.allocate_rect(child_ui.min_rect(), Sense::hover());
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also wonder if we should replace this with Ui::advance_cursor_after_rect since we don't need this response?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, makes sense

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in #5130

InnerResponse::new(ret, response)
}

Expand Down Expand Up @@ -2808,9 +2886,13 @@ impl Ui {
}
}

#[cfg(debug_assertions)]
impl Drop for Ui {
fn drop(&mut self) {
if !self.min_rect_already_remembered {
// Register our final `min_rect`
self.remember_min_rect();
}
#[cfg(debug_assertions)]
register_rect(self, self.min_rect());
}
}
Expand Down
15 changes: 14 additions & 1 deletion crates/egui/src/ui_builder.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -21,6 +21,7 @@ pub struct UiBuilder {
pub invisible: bool,
pub sizing_pass: bool,
pub style: Option<Arc<Style>>,
pub sense: Option<Sense>,
}

impl UiBuilder {
Expand Down Expand Up @@ -116,4 +117,16 @@ impl UiBuilder {
self.style = Some(style.into());
self
}

/// Set if you want sense clicks and/or drags. Default is [`Sense::hover`].
/// The sense will be registered below the Senses of any widgets contained in this [`Ui`], so
/// if the user clicks a button contained within this [`Ui`], that button will receive the click
/// instead.
///
/// The response can be read early with [`Ui::response`].
#[inline]
pub fn sense(mut self, sense: Sense) -> Self {
self.sense = Some(sense);
self
}
}
Loading
Loading