Skip to content

Commit

Permalink
Rewrite Sense as bitset
Browse files Browse the repository at this point in the history
  • Loading branch information
polwel committed Jan 1, 2025
1 parent d1fc9ca commit 7f0b9a7
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 117 deletions.
17 changes: 4 additions & 13 deletions crates/egui/src/containers/modal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,7 @@ impl Modal {
response,
} = area.show(ctx, |ui| {
let bg_rect = ui.ctx().screen_rect();
let bg_sense = Sense {
click: true,
drag: true,
focusable: false,
};
let bg_sense = Sense::CLICK | Sense::DRAG;
let mut backdrop = ui.new_child(UiBuilder::new().sense(bg_sense).max_rect(bg_rect));
backdrop.set_min_size(bg_rect.size());
ui.painter().rect_filled(bg_rect, 0.0, backdrop_color);
Expand All @@ -101,14 +97,9 @@ impl Modal {
// We need the extra scope with the sense since frame can't have a sense and since we
// need to prevent the clicks from passing through to the backdrop.
let inner = ui
.scope_builder(
UiBuilder::new().sense(Sense {
click: true,
drag: true,
focusable: false,
}),
|ui| frame.show(ui, content).inner,
)
.scope_builder(UiBuilder::new().sense(Sense::CLICK | Sense::DRAG), |ui| {
frame.show(ui, content).inner
})
.inner;

(inner, backdrop_response)
Expand Down
25 changes: 14 additions & 11 deletions crates/egui/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1151,8 +1151,9 @@ impl Context {
/// 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, allow_focus: bool) -> Response {
let interested_in_focus =
w.enabled && w.sense.focusable && self.memory(|mem| mem.allows_interaction(w.layer_id));
let interested_in_focus = w.enabled
&& w.sense.is_focusable()
&& self.memory(|mem| mem.allows_interaction(w.layer_id));

// Remember this widget
self.write(|ctx| {
Expand All @@ -1173,15 +1174,15 @@ impl Context {
self.memory_mut(|mem| mem.surrender_focus(w.id));
}

if w.sense.interactive() || w.sense.focusable {
if w.sense.interactive() || w.sense.is_focusable() {
self.check_for_id_clash(w.id, w.rect, "widget");
}

#[allow(clippy::let_and_return)]
let res = self.get_response(w);

#[cfg(feature = "accesskit")]
if allow_focus && w.sense.focusable {
if allow_focus && w.sense.is_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 @@ -1257,7 +1258,7 @@ impl Context {
let memory = &mut ctx.memory;

if enabled
&& sense.click
&& sense.senses_click()
&& memory.has_focus(id)
&& (input.key_pressed(Key::Space) || input.key_pressed(Key::Enter))
{
Expand All @@ -1267,13 +1268,14 @@ impl Context {

#[cfg(feature = "accesskit")]
if enabled
&& sense.click
&& sense.senses_click()
&& input.has_accesskit_action_request(id, accesskit::Action::Click)
{
res.flags.set(response::Flags::FAKE_PRIMARY_CLICKED, true);
}

if enabled && sense.click && Some(id) == viewport.interact_widgets.long_touched {
if enabled && sense.senses_click() && Some(id) == viewport.interact_widgets.long_touched
{
res.flags.set(response::Flags::LONG_TOUCHED, true);
}

Expand Down Expand Up @@ -1314,7 +1316,7 @@ impl Context {
any_press = true;
}
PointerEvent::Released { click, .. } => {
if enabled && sense.click && clicked && click.is_some() {
if enabled && sense.senses_click() && clicked && click.is_some() {
res.flags.set(response::Flags::CLICKED, true);
}

Expand Down Expand Up @@ -2165,11 +2167,12 @@ impl Context {
let painter = Painter::new(self.clone(), *layer_id, Rect::EVERYTHING);
for rect in rects {
if rect.sense.interactive() {
let (color, text) = if rect.sense.click && rect.sense.drag {
let (color, text) = if rect.sense.senses_click() && rect.sense.senses_drag()
{
(Color32::from_rgb(0x88, 0, 0x88), "click+drag")
} else if rect.sense.click {
} else if rect.sense.senses_click() {
(Color32::from_rgb(0x88, 0, 0), "click")
} else if rect.sense.drag {
} else if rect.sense.senses_drag() {
(Color32::from_rgb(0, 0, 0x88), "drag")
} else {
// unreachable since we only show interactive
Expand Down
51 changes: 31 additions & 20 deletions crates/egui/src/hit_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use ahash::HashMap;

use emath::TSTransform;

use crate::{ahash, emath, LayerId, Pos2, Rect, WidgetRect, WidgetRects};
use crate::{ahash, emath, LayerId, Pos2, Rect, Sense, WidgetRect, WidgetRects};

/// Result of a hit-test against [`WidgetRects`].
///
Expand Down Expand Up @@ -128,8 +128,8 @@ pub fn hit_test(
// the `enabled` flag everywhere:
for w in &mut close {
if !w.enabled {
w.sense.click = false;
w.sense.drag = false;
w.sense -= Sense::CLICK;
w.sense -= Sense::DRAG;
}
}

Expand Down Expand Up @@ -158,11 +158,11 @@ pub fn hit_test(
restore_widget_rect(wr);
}
if let Some(wr) = &mut hits.drag {
debug_assert!(wr.sense.drag);
debug_assert!(wr.sense.senses_drag());
restore_widget_rect(wr);
}
if let Some(wr) = &mut hits.click {
debug_assert!(wr.sense.click);
debug_assert!(wr.sense.senses_click());
restore_widget_rect(wr);
}
}
Expand All @@ -179,8 +179,16 @@ fn hit_test_on_close(close: &[WidgetRect], pos: Pos2) -> WidgetHits {
#![allow(clippy::collapsible_else_if)]

// First find the best direct hits:
let hit_click = find_closest_within(close.iter().copied().filter(|w| w.sense.click), pos, 0.0);
let hit_drag = find_closest_within(close.iter().copied().filter(|w| w.sense.drag), pos, 0.0);
let hit_click = find_closest_within(
close.iter().copied().filter(|w| w.sense.senses_click()),
pos,
0.0,
);
let hit_drag = find_closest_within(
close.iter().copied().filter(|w| w.sense.senses_drag()),
pos,
0.0,
);

match (hit_click, hit_drag) {
(None, None) => {
Expand All @@ -190,14 +198,14 @@ fn hit_test_on_close(close: &[WidgetRect], pos: Pos2) -> WidgetHits {
close
.iter()
.copied()
.filter(|w| w.sense.click || w.sense.drag),
.filter(|w| w.sense.senses_click() || w.sense.senses_drag()),
pos,
);

if let Some(closest) = closest {
WidgetHits {
click: closest.sense.click.then_some(closest),
drag: closest.sense.drag.then_some(closest),
click: closest.sense.senses_click().then_some(closest),
drag: closest.sense.senses_drag().then_some(closest),
..Default::default()
}
} else {
Expand All @@ -218,9 +226,12 @@ fn hit_test_on_close(close: &[WidgetRect], pos: Pos2) -> WidgetHits {
// or a moveable window.
// It could also be something small, like a slider, or panel resize handle.

let closest_click = find_closest(close.iter().copied().filter(|w| w.sense.click), pos);
let closest_click = find_closest(
close.iter().copied().filter(|w| w.sense.senses_click()),
pos,
);
if let Some(closest_click) = closest_click {
if closest_click.sense.drag {
if closest_click.sense.senses_drag() {
// We have something close that sense both clicks and drag.
// Should we use it over the direct drag-hit?
if hit_drag
Expand All @@ -244,7 +255,7 @@ fn hit_test_on_close(close: &[WidgetRect], pos: Pos2) -> WidgetHits {
}
}
} else {
// These is a close pure-click widget.
// This is a close pure-click widget.
// However, we should be careful to only return two different widgets
// when it is absolutely not going to confuse the user.
if hit_drag
Expand Down Expand Up @@ -277,7 +288,7 @@ fn hit_test_on_close(close: &[WidgetRect], pos: Pos2) -> WidgetHits {
close
.iter()
.copied()
.filter(|w| w.sense.drag && w.id != hit_drag.id),
.filter(|w| w.sense.senses_drag() && w.id != hit_drag.id),
pos,
);

Expand Down Expand Up @@ -331,7 +342,7 @@ fn hit_test_on_close(close: &[WidgetRect], pos: Pos2) -> WidgetHits {

let click_is_on_top_of_drag = drag_idx < click_idx;
if click_is_on_top_of_drag {
if hit_click.sense.drag {
if hit_click.sense.senses_drag() {
// The top thing senses both clicks and drags.
WidgetHits {
click: Some(hit_click),
Expand All @@ -349,7 +360,7 @@ fn hit_test_on_close(close: &[WidgetRect], pos: Pos2) -> WidgetHits {
}
}
} else {
if hit_drag.sense.click {
if hit_drag.sense.senses_click() {
// The top thing senses both clicks and drags.
WidgetHits {
click: Some(hit_drag),
Expand Down Expand Up @@ -393,7 +404,7 @@ fn find_closest_within(
if dist_sq == closest_dist_sq {
// It's a tie! Pick the thin candidate over the thick one.
// This makes it easier to hit a thin resize-handle, for instance:
if should_prioritizie_hits_on_back(closest.interact_rect, widget.interact_rect) {
if should_prioritize_hits_on_back(closest.interact_rect, widget.interact_rect) {
continue;
}
}
Expand All @@ -409,12 +420,12 @@ fn find_closest_within(
closest
}

/// Should we prioritizie hits on `back` over those on `front`?
/// Should we prioritize hits on `back` over those on `front`?
///
/// `back` should be behind the `front` widget.
///
/// Returns true if `back` is a small hit-target and `front` is not.
fn should_prioritizie_hits_on_back(back: Rect, front: Rect) -> bool {
fn should_prioritize_hits_on_back(back: Rect, front: Rect) -> bool {
if front.contains_rect(back) {
return false; // back widget is fully occluded; no way to hit it
}
Expand Down Expand Up @@ -484,7 +495,7 @@ mod tests {
assert_eq!(hits.click.unwrap().id, Id::new("click-and-drag"));
assert_eq!(hits.drag.unwrap().id, Id::new("click-and-drag"));

// Close hit - should still ignore the drag-background so as not to confuse the userr:
// Close hit - should still ignore the drag-background so as not to confuse the user:
let hits = hit_test_on_close(&widgets, pos2(105.0, 5.0));
assert_eq!(hits.click.unwrap().id, Id::new("click-and-drag"));
assert_eq!(hits.drag.unwrap().id, Id::new("click-and-drag"));
Expand Down
6 changes: 3 additions & 3 deletions crates/egui/src/interaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,14 +192,14 @@ pub(crate) fn interact(
// Check if we started dragging something new:
if let Some(widget) = interaction.potential_drag_id.and_then(|id| widgets.get(id)) {
if widget.enabled {
let is_dragged = if widget.sense.click && widget.sense.drag {
let is_dragged = if widget.sense.senses_click() && widget.sense.senses_drag() {
// This widget is sensitive to both clicks and drags.
// When the mouse first is pressed, it could be either,
// so we postpone the decision until we know.
input.pointer.is_decidedly_dragging()
} else {
// This widget is just sensitive to drags, so we can mark it as dragged right away:
widget.sense.drag
widget.sense.senses_drag()
};

if is_dragged {
Expand Down Expand Up @@ -271,7 +271,7 @@ pub(crate) fn interact(
let mut hovered: IdSet = hits.click.iter().chain(&hits.drag).map(|w| w.id).collect();

for w in &hits.contains_pointer {
let is_interactive = w.sense.click || w.sense.drag;
let is_interactive = w.sense.senses_click() || w.sense.senses_drag();
if is_interactive {
// The only interactive widgets we mark as hovered are the ones
// in `hits.click` and `hits.drag`!
Expand Down
6 changes: 3 additions & 3 deletions crates/egui/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ impl Response {
crate::DragAndDrop::set_payload(&self.ctx, payload);
}

if self.hovered() && !self.sense.click {
if self.hovered() && !self.sense.senses_click() {
// Things that can be drag-dropped should use the Grab cursor icon,
// but if the thing is _also_ clickable, that can be annoying.
self.ctx.set_cursor_icon(CursorIcon::Grab);
Expand Down Expand Up @@ -996,10 +996,10 @@ impl Response {
x1: self.rect.max.x.into(),
y1: self.rect.max.y.into(),
});
if self.sense.focusable {
if self.sense.is_focusable() {
builder.add_action(accesskit::Action::Focus);
}
if self.sense.click {
if self.sense.senses_click() {
builder.add_action(accesskit::Action::Click);
}
}
Expand Down
Loading

0 comments on commit 7f0b9a7

Please sign in to comment.