1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-29 04:40:03 -04:00

Make non-interactive tooltips not interactable (#8362)

Fixes two bugs around tooltips:
- During sizing pass tooltips would render at a different size, at which
point they might overlap the interacted widget causing the hover state
to change and the tooltip to never be shown
- Fix: During sizing pass make the area `interactable: false` so events
pass through
- When hovering close to the boarder of a widget (in the area where
`interact_radius` takes effect), there could be a feedback loop where
the tooltip is shown one frame and hidden the next.
- Fix: Always make tooltips `interactable: false` when they don't have
interactive contents

This PR changes the behavior of `Area::interactable` to it's original
behavior: Now interactions will pass through the area background _and_
it's containing widgets. It worked this way initially but got changed in
#4026

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lucas Meurer
2026-07-31 14:30:46 +02:00
committed by GitHub
parent c69834e65a
commit 36341c21fe
8 changed files with 140 additions and 14 deletions

View File

@@ -1,15 +1,15 @@
use std::sync::Arc;
use egui::ScrollArea;
use egui::accesskit::Role;
#[cfg(debug_assertions)]
use egui::epaint::Shape;
use egui::style::ScrollAnimation;
use egui::text::{LayoutJob, TextWrapping};
use egui::{
Align, Button, Color32, FontFamily, FontId, Image, Label, Layout, RichText, Sense, TextBuffer,
TextFormat, TextWrapMode, Ui, include_image, vec2,
Align, Button, Color32, FontFamily, FontId, Image, Label, Layout, Rect, RichText, Sense,
TextBuffer, TextFormat, TextWrapMode, Ui, Vec2, include_image, vec2,
};
use egui::{Pos2, ScrollArea};
use egui_kittest::Harness;
use egui_kittest::kittest::{NodeT as _, Queryable as _};
@@ -481,3 +481,81 @@ fn animated_scroll_beats_sticky_bottom() {
"animated explicit scroll should leave the sticky bottom"
);
}
/// Tests that tooltips are shown correctly for buttons that are only shown on hover.
///
/// Basically, this tests that a tooltip overlapping the mouse cursor does not interfere with a
/// buttons hover state.
#[test]
fn tooltip_should_work_for_hover_button() {
let button_rect = Rect::from_min_size(Pos2::new(4.0, 4.0), Vec2::new(80.0, 20.0));
let mut harness = Harness::builder().with_size((320.0, 80.0)).build_ui(|ui| {
if ui.rect_contains_pointer(button_rect) {
ui.button("A tooltip should be shown")
.on_hover_text("My tooltip");
}
});
harness.hover_at(button_rect.center());
harness.run();
harness.snapshot("test_tooltip_hover_regression");
}
/// Ensure that hovering close to a widget doesn't cause a tooltip feedback loop (due to a
/// difference between `hovered` and `contains_pointer` caused by the interact radius).
#[test]
fn tooltip_covering_button_should_not_cause_feedback_loop() {
let mut harness = Harness::builder().with_size((200.0, 30.0)).build_ui(|ui| {
ui.button("A tooltip should be shown")
.on_hover_text("This tooltip is larger than the button");
});
harness.hover_at(
harness
.get_by_label("A tooltip should be shown")
.rect()
.left_center()
- Vec2::X,
);
harness.run();
harness.snapshot("tooltip_covering_button_should_not_cause_feedback_loop");
}
/// Tests that a tooltip closes when the pointer moves onto a neighboring widget,
/// so that the neighbor can show its own tooltip.
///
/// The two buttons are only `item_spacing.y` (3 pt) apart, which is less than the
/// hit-test `interact_radius` (5 pt), so the first button is still close enough to
/// interact with when the pointer is on the second one.
#[test]
fn tooltip_should_hand_over_to_neighboring_widget() {
let mut harness = Harness::builder().with_size((300.0, 200.0)).build_ui(|ui| {
ui.button("Button A").on_hover_text("Tooltip A");
ui.button("Button B").on_hover_text("Tooltip B");
});
let a_rect = harness.get_by_label("Button A").rect();
let b_rect = harness.get_by_label("Button B").rect();
harness.hover_at(a_rect.center_bottom() - Vec2::Y);
harness.run();
assert!(
harness.query_by_label("Tooltip A").is_some(),
"Tooltip A should be shown when hovering Button A"
);
harness.hover_at(b_rect.center_top() + Vec2::Y);
harness.run();
assert!(
harness.query_by_label("Tooltip B").is_some(),
"Tooltip B should be shown when hovering Button B"
);
assert!(
harness.query_by_label("Tooltip A").is_none(),
"Tooltip A should be hidden when hovering Button B"
);
}

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:59bef0e3593896985988c03171c710d8ed31ed8bb89c7a3f63c060c573e4fb74
size 6510

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ed2f80921ab864b0df3303834a8ab943a3aa5e10456896991c2dca81be0c4968
size 3968