Skip to content

Commit

Permalink
Bug fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
emilk committed Feb 11, 2024
1 parent 5cb984e commit 0ef59af
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 47 deletions.
72 changes: 46 additions & 26 deletions crates/egui/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,17 +254,6 @@ impl WidgetRects {

let layer_widgets = self.by_layer.entry(layer_id).or_default();

if let Some(last) = layer_widgets.last_mut() {
if last.id == widget_rect.id {
// e.g. calling `response.interact(…)` right after interacting.
last.sense |= widget_rect.sense;
last.interact_rect = last.interact_rect.union(widget_rect.interact_rect);
return;
}
}

layer_widgets.push(widget_rect);

match self.by_id.entry(widget_rect.id) {
std::collections::hash_map::Entry::Vacant(entry) => {
entry.insert(widget_rect);
Expand All @@ -276,6 +265,17 @@ impl WidgetRects {
existing.interact_rect = existing.interact_rect.union(widget_rect.interact_rect);
}
}

if let Some(last) = layer_widgets.last_mut() {
if last.id == widget_rect.id {
// e.g. calling `response.interact(…)` right after interacting.
last.sense |= widget_rect.sense;
last.interact_rect = last.interact_rect.union(widget_rect.interact_rect);
return;
}
}

layer_widgets.push(widget_rect);
}
}

Expand Down Expand Up @@ -1105,9 +1105,14 @@ impl Context {
layer_id: LayerId,
id: Id,
rect: Rect,
sense: Sense,
mut sense: Sense,
enabled: bool,
) -> Response {
if !enabled {
sense.click = false;
sense.drag = false;
}

// Respect clip rectangle when interacting:
let interact_rect = clip_rect.intersect(rect);

Expand All @@ -1126,7 +1131,7 @@ impl Context {
);
}

self.interact_with_hovered(
self.interact_with_existing(
layer_id,
id,
rect,
Expand All @@ -1139,16 +1144,21 @@ impl Context {

/// You specify if a thing is hovered, and the function gives a [`Response`].
#[allow(clippy::too_many_arguments)]
pub(crate) fn interact_with_hovered(
pub(crate) fn interact_with_existing(
&self,
layer_id: LayerId,
id: Id,
rect: Rect,
interact_rect: Rect,
sense: Sense,
mut sense: Sense,
enabled: bool,
contains_pointer: bool,
) -> Response {
if !enabled {
sense.click = false;
sense.drag = false;
}

// This is the start - we'll fill in the fields below:
let mut res = Response {
ctx: self.clone(),
Expand All @@ -1159,7 +1169,7 @@ impl Context {
sense,
enabled,
contains_pointer,
hovered: contains_pointer && enabled,
hovered: false,
highlighted: self.frame_state(|fs| fs.highlight_this_frame.contains(&id)),
clicked: Default::default(),
double_clicked: Default::default(),
Expand Down Expand Up @@ -1235,11 +1245,14 @@ impl Context {
res.is_pointer_button_down_on =
interaction.click_id == Some(id) || interaction.drag_id == Some(id);

res.dragged = Some(id) == viewport.interact_widgets.dragged.map(|w| w.id);
res.drag_started = Some(id) == viewport.interact_widgets.drag_started.map(|w| w.id);
res.drag_released = Some(id) == viewport.interact_widgets.drag_ended.map(|w| w.id);
if enabled {
res.hovered = viewport.interact_widgets.hovered.contains_key(&id);
res.dragged = Some(id) == viewport.interact_widgets.dragged.map(|w| w.id);
res.drag_started = Some(id) == viewport.interact_widgets.drag_started.map(|w| w.id);
res.drag_released = Some(id) == viewport.interact_widgets.drag_ended.map(|w| w.id);
}

let clicked = viewport.interact_widgets.clicked.iter().any(|w| w.id == id);
let clicked = Some(id) == viewport.interact_widgets.clicked.map(|w| w.id);

if sense.click && clicked {
// We were clicked - what kind of click?
Expand Down Expand Up @@ -1922,10 +1935,13 @@ impl Context {
top,
click,
drag,
closest_interactive: _,
} = hits;

for widget in &contains_pointer {
paint(widget, "contains_pointer", Color32::BLUE);
if false {
for widget in &contains_pointer {
paint(widget, "contains_pointer", Color32::BLUE);
}
}
for widget in &top {
paint(widget, "top", Color32::WHITE);
Expand All @@ -1949,11 +1965,15 @@ impl Context {
hovered,
} = interact_widgets;

for widget in contains_pointer.values() {
paint(widget, "contains_pointer", Color32::BLUE);
if false {
for widget in contains_pointer.values() {
paint(widget, "contains_pointer", Color32::BLUE);
}
}
for widget in hovered.values() {
paint(widget, "hovered", Color32::WHITE);
if true {
for widget in hovered.values() {
paint(widget, "hovered", Color32::WHITE);
}
}
for widget in &clicked {
paint(widget, "clicked", Color32::RED);
Expand Down
55 changes: 43 additions & 12 deletions crates/egui/src/hit_test.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
use crate::*;

/// Result of a hit-test agains [`WidgetRects`].
/// Result of a hit-test against [`WidgetRects`].
///
/// Answers the question "what is under the mouse pointer?".
///
/// Note that this doesn't care if the mouse button is pressed or not,
/// or if we're currently already dragging something.
///
/// For that you need the `InteractionState`.
/// For that you need the [`crate::InteractionState`].
#[derive(Clone, Debug, Default)]
pub struct WidgetHits {
/// All widgets that contains the pointer, back-to-front.
///
/// i.e. both a Window and the button in it can ontain the pointer.
/// i.e. both a Window and the button in it can contain the pointer.
///
/// Some of these may be widgets in a layer below the top-most layer.
pub contains_pointer: Vec<WidgetRect>,
Expand All @@ -31,6 +31,11 @@ pub struct WidgetHits {
///
/// This is the top one under the pointer, or closest one of the top-most.
pub drag: Option<WidgetRect>,

/// The closest interactive widget under the pointer.
///
/// This is either the same as [`Self::click`] or [`Self::drag`], or both.
pub closest_interactive: Option<WidgetRect>,
}

/// Find the top or closest widgets to the given position,
Expand All @@ -43,16 +48,14 @@ pub fn hit_test(
) -> WidgetHits {
crate::profile_function!();

let hit_rect = Rect::from_center_size(pos, Vec2::splat(2.0 * search_radius));

let search_radius_sq = search_radius * search_radius;

// First pass: find the few widgets close to the given position, sorted back-to-front.
let mut close: Vec<WidgetRect> = layer_order
.iter()
.filter(|layer| layer.order.allow_interaction())
.filter_map(|layer_id| widgets.by_layer.get(layer_id))
.flatten()
.filter(|widget| widget.interact_rect.intersects(hit_rect))
.filter(|w| w.interact_rect.distance_sq_to_pos(pos) <= search_radius_sq)
.copied()
.collect();
Expand All @@ -68,7 +71,7 @@ pub fn hit_test(
let top_layer = top_hit.map(|w| w.layer_id);

if let Some(top_layer) = top_layer {
// Ignore everything in a layer below the top-most layer:
// Ignore all layers not in the same layer as the top hit.
close.retain(|w| w.layer_id == top_layer);
hits.retain(|w| w.layer_id == top_layer);
}
Expand All @@ -81,26 +84,54 @@ pub fn hit_test(
let closest_drag = find_closest(close.iter().copied().filter(|w| w.sense.drag), pos);

let top = top_hit.or(closest);
let click = hit_click.or(closest_click);
let drag = hit_drag.or(closest_drag);
let mut click = hit_click.or(closest_click);
let mut drag = hit_drag.or(closest_drag);

if let (Some(click), Some(drag)) = (&mut click, &mut drag) {
// If one of the widgets is interested in both click and drags, let it win.
// Otherwise we end up in weird situations where both widgets respond to hover,
// but one of the widgets only responds to _one_ of the events.

if click.sense.click && click.sense.drag {
*drag = *click;
} else if drag.sense.click && drag.sense.drag {
*click = *drag;
}
}

let closest_interactive = match (click, drag) {
(Some(click), Some(drag)) => {
if click.interact_rect.distance_sq_to_pos(pos)
< drag.interact_rect.distance_sq_to_pos(pos)
{
Some(click)
} else {
Some(drag)
}
}
(Some(click), None) => Some(click),
(None, Some(drag)) => Some(drag),
(None, None) => None,
};

WidgetHits {
contains_pointer: hits,
top,
click,
drag,
closest_interactive,
}
}

fn find_closest(widgets: impl Iterator<Item = WidgetRect>, pos: Pos2) -> Option<WidgetRect> {
let mut closest = None;
let mut cloest_dist_sq = f32::INFINITY;
let mut closest_dist_sq = f32::INFINITY;
for widget in widgets {
let dist_sq = widget.interact_rect.distance_sq_to_pos(pos);

// In case of a tie, take the last one = the one on top.
if dist_sq <= cloest_dist_sq {
cloest_dist_sq = dist_sq;
if dist_sq <= closest_dist_sq {
closest_dist_sq = dist_sq;
closest = Some(widget);
}
}
Expand Down
6 changes: 4 additions & 2 deletions crates/egui/src/interaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ pub(crate) fn interact(
input: &InputState,
interaction: &mut InteractionState,
) -> InteractionSnapshot {
crate::profile_function!();

if let Some(id) = interaction.click_id {
if !widgets.by_id.contains_key(&id) {
// The widget we were interested in clicking is gone.
Expand Down Expand Up @@ -120,10 +122,10 @@ pub(crate) fn interact(
.collect();

let hovered = if clicked.is_some() || dragged.is_some() {
// If currently clicking or dragging, nother else is hovered.
// If currently clicking or dragging, nothing else is hovered.
clicked.iter().chain(&dragged).map(|w| (w.id, *w)).collect()
} else if hits.click.is_some() || hits.drag.is_some() {
// We are hovering over an interactive widget or two. Just highlight these two.
// We are hovering over an interactive widget or two.
hits.click
.iter()
.chain(&hits.drag)
Expand Down
2 changes: 1 addition & 1 deletion crates/egui/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@ impl Response {
/// ```
#[must_use]
pub fn interact(&self, sense: Sense) -> Self {
self.ctx.interact_with_hovered(
self.ctx.interact_with_existing(
self.layer_id,
self.id,
self.rect,
Expand Down
14 changes: 9 additions & 5 deletions crates/egui/src/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -715,10 +715,10 @@ pub struct Interaction {
/// which is important for e.g. touch screens.
pub interact_radius: f32,

/// Mouse must be this close to the side of a window to resize
/// Radius of the interactive area of the side of a window during drag-to-resize.
pub resize_grab_radius_side: f32,

/// Mouse must be this close to the corner of a window to resize
/// Radius of the interactive area of the corner of a window during drag-to-resize.
pub resize_grab_radius_corner: f32,

/// If `false`, tooltips will show up anytime you hover anything, even is mouse is still moving
Expand Down Expand Up @@ -1135,9 +1135,9 @@ impl Default for Spacing {
impl Default for Interaction {
fn default() -> Self {
Self {
interact_radius: 3.0,
resize_grab_radius_side: 5.0,
resize_grab_radius_corner: 10.0,
interact_radius: 8.0,
show_tooltips_only_when_still: true,
tooltip_delay: 0.3,
selectable_labels: true,
Expand Down Expand Up @@ -1612,7 +1612,7 @@ impl Interaction {
multi_widget_text_select,
} = self;
ui.add(Slider::new(interact_radius, 0.0..=20.0).text("interact_radius"))
.on_hover_text("Interact witgh ghe closest widget within this radius.");
.on_hover_text("Interact with the closest widget within this radius.");
ui.add(Slider::new(resize_grab_radius_side, 0.0..=20.0).text("resize_grab_radius_side"));
ui.add(
Slider::new(resize_grab_radius_corner, 0.0..=20.0).text("resize_grab_radius_corner"),
Expand All @@ -1621,7 +1621,11 @@ impl Interaction {
show_tooltips_only_when_still,
"Only show tooltips if mouse is still",
);
ui.add(Slider::new(tooltip_delay, 0.0..=1.0).text("tooltip_delay"));
ui.add(
Slider::new(tooltip_delay, 0.0..=1.0)
.suffix(" s")
.text("tooltip_delay"),
);

ui.horizontal(|ui| {
ui.checkbox(selectable_labels, "Selectable text in labels");
Expand Down
2 changes: 1 addition & 1 deletion crates/egui/src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -650,7 +650,7 @@ impl Ui {
sense: Sense,
) -> Response {
let interact_rect = rect.intersect(self.clip_rect());
self.ctx().interact_with_hovered(
self.ctx().interact_with_existing(
self.layer_id(),
id,
rect,
Expand Down

0 comments on commit 0ef59af

Please sign in to comment.