Skip to content

Commit

Permalink
Improve hit-test of thin widgets, and widgets across layers (emilk#5468)
Browse files Browse the repository at this point in the history
When there are multiple layers (e.g. with custom transforms) close to
each other, the hit test code used to only consider widgets in the layer
directly under the mouse. This can make it difficult to hit thin widgets
just on the outside of a transform layer.

This PR fixes that.

It also prioritizes thin widgets, so that if there is both a thin widget
and a thick widget under the mouse cursor, you will always hit the thin
widgets, even if the thin widgets is layered behind the thick one. This
makes it easier to hit thin resize-handles.

In theory this should allow us to make `resize_grab_radius_side` and
`resize_grab_radius_corner` smaller in a future PR, if we want to.
  • Loading branch information
emilk authored Dec 16, 2024
1 parent 3af9079 commit f7efb21
Show file tree
Hide file tree
Showing 5 changed files with 258 additions and 119 deletions.
40 changes: 17 additions & 23 deletions crates/egui/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -498,19 +498,8 @@ impl ContextImpl {
viewport.this_pass.begin_pass(screen_rect);

{
let area_order = self.memory.areas().order_map();

let mut layers: Vec<LayerId> = viewport.prev_pass.widgets.layer_ids().collect();

layers.sort_by(|a, b| {
if a.order == b.order {
// Maybe both are windows, so respect area order:
area_order.get(a).cmp(&area_order.get(b))
} else {
// comparing e.g. background to tooltips
a.order.cmp(&b.order)
}
});
layers.sort_by(|&a, &b| self.memory.areas().compare_order(a, b));

viewport.hits = if let Some(pos) = viewport.input.pointer.interact_pos() {
let interact_radius = self.memory.options.style().interaction.interact_radius;
Expand Down Expand Up @@ -2248,7 +2237,8 @@ impl Context {
for id in contains_pointer {
let mut widget_text = format!("{id:?}");
if let Some(rect) = widget_rects.get(id) {
widget_text += &format!(" {:?} {:?}", rect.rect, rect.sense);
widget_text +=
&format!(" {:?} {:?} {:?}", rect.layer_id, rect.rect, rect.sense);
}
if let Some(info) = widget_rects.info(id) {
widget_text += &format!(" {info:?}");
Expand All @@ -2274,11 +2264,17 @@ impl Context {
if self.style().debug.show_widget_hits {
let hits = self.write(|ctx| ctx.viewport().hits.clone());
let WidgetHits {
close,
contains_pointer,
click,
drag,
} = hits;

if false {
for widget in &close {
paint_widget(widget, "close", Color32::from_gray(70));
}
}
if true {
for widget in &contains_pointer {
paint_widget(widget, "contains_pointer", Color32::BLUE);
Expand Down Expand Up @@ -3161,28 +3157,26 @@ impl Context {
self.memory_mut(|mem| *mem.areas_mut() = Default::default());
}
});
ui.indent("areas", |ui| {
ui.label("Visible areas, ordered back to front.");
ui.label("Hover to highlight");
ui.indent("layers", |ui| {
ui.label("Layers, ordered back to front.");
let layers_ids: Vec<LayerId> = self.memory(|mem| mem.areas().order().to_vec());
for layer_id in layers_ids {
let area = AreaState::load(self, layer_id.id);
if let Some(area) = area {
if let Some(area) = AreaState::load(self, layer_id.id) {
let is_visible = self.memory(|mem| mem.areas().is_visible(&layer_id));
if !is_visible {
continue;
}
let text = format!("{} - {:?}", layer_id.short_debug_format(), area.rect(),);
// TODO(emilk): `Sense::hover_highlight()`
if ui
.add(Label::new(RichText::new(text).monospace()).sense(Sense::click()))
.hovered
&& is_visible
{
let response =
ui.add(Label::new(RichText::new(text).monospace()).sense(Sense::click()));
if response.hovered && is_visible {
ui.ctx()
.debug_painter()
.debug_rect(area.rect(), Color32::RED, "");
}
} else {
ui.monospace(layer_id.short_debug_format());
}
}
});
Expand Down
Loading

0 comments on commit f7efb21

Please sign in to comment.