Skip to content

Commit

Permalink
Add Ui::interact_scope
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasmerlin committed Sep 1, 2024
1 parent edea5a4 commit 8e3cc88
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 54 deletions.
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,
},
false,
);

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,
},
false,
);
SideResponse {
hover: response.hovered(),
drag: response.dragged(),
Expand Down
8 changes: 4 additions & 4 deletions crates/egui/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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));
}
Expand All @@ -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.
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,
},
false,
)
}

/// Adjust the scroll position until this UI becomes visible.
Expand Down
104 changes: 79 additions & 25 deletions crates/egui/src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ impl Ui {
invisible,
sizing_pass,
style,
sense,
} = ui_builder;

debug_assert!(
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -220,6 +224,7 @@ impl Ui {
invisible,
sizing_pass,
style,
sense,
} = ui_builder;

let mut painter = self.painter.clone();
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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.
Expand All @@ -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`]?
Expand Down Expand Up @@ -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<R>(
&mut self,
sense: Sense,
content: impl FnOnce(&mut Self, Option<Response>) -> R,
) -> InnerResponse<R> {
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
Expand Down
9 changes: 8 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,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
}
}

0 comments on commit 8e3cc88

Please sign in to comment.