Delay hiding git blame tooltip (#22644)

It's easy to overshoot the bottom of the tooltip when cursoring to a
button, such as opening the commit from a blame tooltip. Before this
change the tooltip would immediately disappear, and now it sticks around
for a bit.

Also:

* Shares the implementation with `elements/text.rs`. This will
particularly be handy when it makes use of hoverable tooltips.

* Improves the fix to #21657.

- Now the element will no longer think it has an active tooltip that it
registers with the window.

- It will instead display the next available tooltip, whereas I believe
before the next available tooltip would be suppressed.

* Fixes bug where `cx.refresh()` wasn't called when text tooltip is
hidden due to a mouse down event.

* Ports over fix in https://github.com/zed-industries/zed/pull/14832 to
`elements/text.rs`

Release Notes:

- The tooltip for inline git blame now waits a bit before disappearing
when the mouse leaves it.
This commit is contained in:
Michael Sloan
2025-01-18 20:52:14 +00:00
committed by GitHub
parent 985544ffb9
commit ac214c52c9
4 changed files with 391 additions and 215 deletions
+4 -5
View File
@@ -1635,11 +1635,10 @@ pub struct AnyTooltip {
/// The absolute position of the mouse when the tooltip was deployed.
pub mouse_position: Point<Pixels>,
/// Whether the tooltitp can be hovered or not.
pub hoverable: bool,
/// Bounds of the element that triggered the tooltip appearance.
pub origin_bounds: Bounds<Pixels>,
/// Given the bounds of the tooltip, checks whether the tooltip should still be visible and
/// updates its state accordingly. This is needed atop the hovered element's mouse move handler
/// to handle the case where the element is not painted (e.g. via use of `visible_on_hover`).
pub check_visible_and_update: Rc<dyn Fn(Bounds<Pixels>, &mut WindowContext) -> bool>,
}
/// A keystroke event, and potentially the associated action
+287 -96
View File
@@ -42,7 +42,8 @@ use taffy::style::Overflow;
use util::ResultExt;
const DRAG_THRESHOLD: f64 = 2.;
pub(crate) const TOOLTIP_DELAY: Duration = Duration::from_millis(500);
const TOOLTIP_SHOW_DELAY: Duration = Duration::from_millis(500);
const HOVERABLE_TOOLTIP_HIDE_DELAY: Duration = Duration::from_millis(500);
/// The styling information for a given group.
pub struct GroupStyle {
@@ -1425,17 +1426,17 @@ impl Interactivity {
element_state.map(|element_state| element_state.unwrap_or_default());
let style = self.compute_style_internal(None, element_state.as_mut(), cx);
if let Some(element_state) = element_state.as_ref() {
if let Some(element_state) = element_state.as_mut() {
if let Some(clicked_state) = element_state.clicked_state.as_ref() {
let clicked_state = clicked_state.borrow();
self.active = Some(clicked_state.element);
}
if let Some(active_tooltip) = element_state.active_tooltip.as_ref() {
if let Some(active_tooltip) = active_tooltip.borrow().as_ref() {
if let Some(tooltip) = active_tooltip.tooltip.clone() {
self.tooltip_id = Some(cx.set_tooltip(tooltip));
}
if self.tooltip_builder.is_some() {
self.tooltip_id = set_tooltip_on_window(active_tooltip, cx);
} else {
// If there is no longer a tooltip builder, remove the active tooltip.
element_state.active_tooltip.take();
}
}
}
@@ -1935,13 +1936,7 @@ impl Interactivity {
});
}
// Ensure to remove active tooltip if tooltip builder is none
if self.tooltip_builder.is_none() {
element_state.active_tooltip.take();
}
if let Some(tooltip_builder) = self.tooltip_builder.take() {
let tooltip_is_hoverable = tooltip_builder.hoverable;
let active_tooltip = element_state
.active_tooltip
.get_or_insert_with(Default::default)
@@ -1951,85 +1946,24 @@ impl Interactivity {
.get_or_insert_with(Default::default)
.clone();
cx.on_mouse_event({
let active_tooltip = active_tooltip.clone();
let hitbox = hitbox.clone();
let source_bounds = hitbox.bounds;
let tooltip_id = self.tooltip_id;
move |_: &MouseMoveEvent, phase, cx| {
let is_hovered =
pending_mouse_down.borrow().is_none() && hitbox.is_hovered(cx);
let tooltip_is_hovered =
tooltip_id.map_or(false, |tooltip_id| tooltip_id.is_hovered(cx));
if !is_hovered && (!tooltip_is_hoverable || !tooltip_is_hovered) {
if active_tooltip.borrow_mut().take().is_some() {
cx.refresh();
}
return;
}
if phase != DispatchPhase::Bubble {
return;
}
if active_tooltip.borrow().is_none() {
let task = cx.spawn({
let active_tooltip = active_tooltip.clone();
let build_tooltip = tooltip_builder.build.clone();
move |mut cx| async move {
cx.background_executor().timer(TOOLTIP_DELAY).await;
cx.update(|cx| {
active_tooltip.borrow_mut().replace(ActiveTooltip {
tooltip: Some(AnyTooltip {
view: build_tooltip(cx),
mouse_position: cx.mouse_position(),
hoverable: tooltip_is_hoverable,
origin_bounds: source_bounds,
}),
_task: None,
});
cx.refresh();
})
.ok();
}
});
active_tooltip.borrow_mut().replace(ActiveTooltip {
tooltip: None,
_task: Some(task),
});
}
}
let tooltip_is_hoverable = tooltip_builder.hoverable;
let build_tooltip = Rc::new(move |cx: &mut WindowContext| {
Some(((tooltip_builder.build)(cx), tooltip_is_hoverable))
});
cx.on_mouse_event({
let active_tooltip = active_tooltip.clone();
let tooltip_id = self.tooltip_id;
move |_: &MouseDownEvent, _, cx| {
let tooltip_is_hovered =
tooltip_id.map_or(false, |tooltip_id| tooltip_id.is_hovered(cx));
if (!tooltip_is_hoverable || !tooltip_is_hovered)
&& active_tooltip.borrow_mut().take().is_some()
{
cx.refresh();
}
}
// Use bounds instead of testing hitbox since check_is_hovered is also called
// during prepaint.
let source_bounds = hitbox.bounds;
let check_is_hovered = Rc::new(move |cx: &WindowContext| {
pending_mouse_down.borrow().is_none()
&& source_bounds.contains(&cx.mouse_position())
});
cx.on_mouse_event({
let active_tooltip = active_tooltip.clone();
let tooltip_id = self.tooltip_id;
move |_: &ScrollWheelEvent, _, cx| {
let tooltip_is_hovered =
tooltip_id.map_or(false, |tooltip_id| tooltip_id.is_hovered(cx));
if (!tooltip_is_hoverable || !tooltip_is_hovered)
&& active_tooltip.borrow_mut().take().is_some()
{
cx.refresh();
}
}
})
register_tooltip_mouse_handlers(
&active_tooltip,
self.tooltip_id,
build_tooltip,
check_is_hovered,
cx,
);
}
let active_state = element_state
@@ -2284,12 +2218,6 @@ pub struct InteractiveElementState {
pub(crate) active_tooltip: Option<Rc<RefCell<Option<ActiveTooltip>>>>,
}
/// The current active tooltip
pub struct ActiveTooltip {
pub(crate) tooltip: Option<AnyTooltip>,
pub(crate) _task: Option<Task<()>>,
}
/// Whether or not the element or a group that contains it is clicked by the mouse.
#[derive(Copy, Clone, Default, Eq, PartialEq)]
pub struct ElementClickedState {
@@ -2306,6 +2234,269 @@ impl ElementClickedState {
}
}
pub(crate) enum ActiveTooltip {
/// Currently delaying before showing the tooltip.
WaitingForShow { _task: Task<()> },
/// Tooltip is visible, element was hovered or for hoverable tooltips, the tooltip was hovered.
Visible {
tooltip: AnyTooltip,
is_hoverable: bool,
},
/// Tooltip is visible and hoverable, but the mouse is no longer hovering. Currently delaying
/// before hiding it.
WaitingForHide {
tooltip: AnyTooltip,
_task: Task<()>,
},
}
pub(crate) fn clear_active_tooltip(
active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
cx: &mut WindowContext,
) {
match active_tooltip.borrow_mut().take() {
None => {}
Some(ActiveTooltip::WaitingForShow { .. }) => {}
Some(ActiveTooltip::Visible { .. }) => cx.refresh(),
Some(ActiveTooltip::WaitingForHide { .. }) => cx.refresh(),
}
}
pub(crate) fn clear_active_tooltip_if_not_hoverable(
active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
cx: &mut WindowContext,
) {
let should_clear = match active_tooltip.borrow().as_ref() {
None => false,
Some(ActiveTooltip::WaitingForShow { .. }) => false,
Some(ActiveTooltip::Visible { is_hoverable, .. }) => !is_hoverable,
Some(ActiveTooltip::WaitingForHide { .. }) => false,
};
if should_clear {
active_tooltip.borrow_mut().take();
cx.refresh();
}
}
pub(crate) fn set_tooltip_on_window(
active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
cx: &mut WindowContext,
) -> Option<TooltipId> {
let tooltip = match active_tooltip.borrow().as_ref() {
None => return None,
Some(ActiveTooltip::WaitingForShow { .. }) => return None,
Some(ActiveTooltip::Visible { tooltip, .. }) => tooltip.clone(),
Some(ActiveTooltip::WaitingForHide { tooltip, .. }) => tooltip.clone(),
};
Some(cx.set_tooltip(tooltip))
}
pub(crate) fn register_tooltip_mouse_handlers(
active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
tooltip_id: Option<TooltipId>,
build_tooltip: Rc<dyn Fn(&mut WindowContext) -> Option<(AnyView, bool)>>,
check_is_hovered: Rc<dyn Fn(&WindowContext) -> bool>,
cx: &mut WindowContext,
) {
cx.on_mouse_event({
let active_tooltip = active_tooltip.clone();
let build_tooltip = build_tooltip.clone();
let check_is_hovered = check_is_hovered.clone();
move |_: &MouseMoveEvent, phase, cx| {
handle_tooltip_mouse_move(
&active_tooltip,
&build_tooltip,
&check_is_hovered,
phase,
cx,
)
}
});
cx.on_mouse_event({
let active_tooltip = active_tooltip.clone();
move |_: &MouseDownEvent, _, cx| {
if !tooltip_id.map_or(false, |tooltip_id| tooltip_id.is_hovered(cx)) {
clear_active_tooltip_if_not_hoverable(&active_tooltip, cx);
}
}
});
cx.on_mouse_event({
let active_tooltip = active_tooltip.clone();
move |_: &ScrollWheelEvent, _, cx| {
if !tooltip_id.map_or(false, |tooltip_id| tooltip_id.is_hovered(cx)) {
clear_active_tooltip_if_not_hoverable(&active_tooltip, cx);
}
}
});
}
fn handle_tooltip_mouse_move(
active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
build_tooltip: &Rc<dyn Fn(&mut WindowContext) -> Option<(AnyView, bool)>>,
check_is_hovered: &Rc<dyn Fn(&WindowContext) -> bool>,
phase: DispatchPhase,
cx: &mut WindowContext,
) {
// Separates logic for what mutation should occur from applying it, to avoid overlapping
// RefCell borrows.
enum Action {
None,
CancelShow,
ScheduleShow,
}
let action = match active_tooltip.borrow().as_ref() {
None => {
let is_hovered = check_is_hovered(cx);
if is_hovered && phase.bubble() {
Action::ScheduleShow
} else {
Action::None
}
}
Some(ActiveTooltip::WaitingForShow { .. }) => {
let is_hovered = check_is_hovered(cx);
if is_hovered {
Action::None
} else {
Action::CancelShow
}
}
// These are handled in check_visible_and_update.
Some(ActiveTooltip::Visible { .. }) | Some(ActiveTooltip::WaitingForHide { .. }) => {
Action::None
}
};
match action {
Action::None => {}
Action::CancelShow => {
// Cancel waiting to show tooltip when it is no longer hovered.
active_tooltip.borrow_mut().take();
}
Action::ScheduleShow => {
let delayed_show_task = cx.spawn({
let active_tooltip = active_tooltip.clone();
let build_tooltip = build_tooltip.clone();
let check_is_hovered = check_is_hovered.clone();
move |mut cx| async move {
cx.background_executor().timer(TOOLTIP_SHOW_DELAY).await;
cx.update(|cx| {
let new_tooltip = build_tooltip(cx).map(|(view, tooltip_is_hoverable)| {
let active_tooltip = active_tooltip.clone();
ActiveTooltip::Visible {
tooltip: AnyTooltip {
view,
mouse_position: cx.mouse_position(),
check_visible_and_update: Rc::new(move |tooltip_bounds, cx| {
handle_tooltip_check_visible_and_update(
&active_tooltip,
tooltip_is_hoverable,
&check_is_hovered,
tooltip_bounds,
cx,
)
}),
},
is_hoverable: tooltip_is_hoverable,
}
});
*active_tooltip.borrow_mut() = new_tooltip;
cx.refresh();
})
.ok();
}
});
active_tooltip
.borrow_mut()
.replace(ActiveTooltip::WaitingForShow {
_task: delayed_show_task,
});
}
}
}
/// Returns a callback which will be called by window prepaint to update tooltip visibility. The
/// purpose of doing this logic here instead of the mouse move handler is that the mouse move
/// handler won't get called when the element is not painted (e.g. via use of `visible_on_hover`).
fn handle_tooltip_check_visible_and_update(
active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
tooltip_is_hoverable: bool,
check_is_hovered: &Rc<dyn Fn(&WindowContext) -> bool>,
tooltip_bounds: Bounds<Pixels>,
cx: &mut WindowContext,
) -> bool {
// Separates logic for what mutation should occur from applying it, to avoid overlapping RefCell
// borrows.
enum Action {
None,
Hide,
ScheduleHide(AnyTooltip),
CancelHide(AnyTooltip),
}
let is_hovered = check_is_hovered(cx)
|| (tooltip_is_hoverable && tooltip_bounds.contains(&cx.mouse_position()));
let action = match active_tooltip.borrow().as_ref() {
Some(ActiveTooltip::Visible { tooltip, .. }) => {
if is_hovered {
Action::None
} else {
if tooltip_is_hoverable {
Action::ScheduleHide(tooltip.clone())
} else {
Action::Hide
}
}
}
Some(ActiveTooltip::WaitingForHide { tooltip, .. }) => {
if is_hovered {
Action::CancelHide(tooltip.clone())
} else {
Action::None
}
}
None | Some(ActiveTooltip::WaitingForShow { .. }) => Action::None,
};
match action {
Action::None => {}
Action::Hide => {
clear_active_tooltip(&active_tooltip, cx);
}
Action::ScheduleHide(tooltip) => {
let delayed_hide_task = cx.spawn({
let active_tooltip = active_tooltip.clone();
move |mut cx| async move {
cx.background_executor()
.timer(HOVERABLE_TOOLTIP_HIDE_DELAY)
.await;
if active_tooltip.borrow_mut().take().is_some() {
cx.update(|cx| cx.refresh()).ok();
}
}
});
active_tooltip
.borrow_mut()
.replace(ActiveTooltip::WaitingForHide {
tooltip,
_task: delayed_hide_task,
});
}
Action::CancelHide(tooltip) => {
// Cancel waiting to hide tooltip when it becomes hovered.
active_tooltip.borrow_mut().replace(ActiveTooltip::Visible {
tooltip,
is_hoverable: true,
});
}
}
active_tooltip.borrow().is_some()
}
#[derive(Default)]
pub(crate) struct GroupHitboxes(HashMap<SharedString, SmallVec<[HitboxId; 1]>>);
+41 -64
View File
@@ -1,8 +1,9 @@
use crate::{
ActiveTooltip, AnyTooltip, AnyView, Bounds, DispatchPhase, Element, ElementId, GlobalElementId,
HighlightStyle, Hitbox, IntoElement, LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
Pixels, Point, SharedString, Size, TextRun, TextStyle, Truncate, WhiteSpace, WindowContext,
WrappedLine, WrappedLineLayout, TOOLTIP_DELAY,
register_tooltip_mouse_handlers, set_tooltip_on_window, ActiveTooltip, AnyView, Bounds,
DispatchPhase, Element, ElementId, GlobalElementId, HighlightStyle, Hitbox, IntoElement,
LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, SharedString, Size,
TextRun, TextStyle, TooltipId, Truncate, WhiteSpace, WindowContext, WrappedLine,
WrappedLineLayout,
};
use anyhow::anyhow;
use parking_lot::{Mutex, MutexGuard};
@@ -505,6 +506,7 @@ pub struct InteractiveText {
Option<Box<dyn Fn(&[Range<usize>], InteractiveTextClickEvent, &mut WindowContext)>>,
hover_listener: Option<Box<dyn Fn(Option<usize>, MouseMoveEvent, &mut WindowContext)>>,
tooltip_builder: Option<Rc<dyn Fn(usize, &mut WindowContext) -> Option<AnyView>>>,
tooltip_id: Option<TooltipId>,
clickable_ranges: Vec<Range<usize>>,
}
@@ -531,6 +533,7 @@ impl InteractiveText {
click_listener: None,
hover_listener: None,
tooltip_builder: None,
tooltip_id: None,
clickable_ranges: Vec::new(),
}
}
@@ -600,15 +603,16 @@ impl Element for InteractiveText {
cx.with_optional_element_state::<InteractiveTextState, _>(
global_id,
|interactive_state, cx| {
let interactive_state = interactive_state
let mut interactive_state = interactive_state
.map(|interactive_state| interactive_state.unwrap_or_default());
if let Some(interactive_state) = interactive_state.as_ref() {
if let Some(active_tooltip) = interactive_state.active_tooltip.borrow().as_ref()
{
if let Some(tooltip) = active_tooltip.tooltip.clone() {
cx.set_tooltip(tooltip);
}
if let Some(interactive_state) = interactive_state.as_mut() {
if self.tooltip_builder.is_some() {
self.tooltip_id =
set_tooltip_on_window(&interactive_state.active_tooltip, cx);
} else {
// If there is no longer a tooltip builder, remove the active tooltip.
interactive_state.active_tooltip.take();
}
}
@@ -704,64 +708,37 @@ impl Element for InteractiveText {
});
if let Some(tooltip_builder) = self.tooltip_builder.clone() {
let hitbox = hitbox.clone();
let source_bounds = hitbox.bounds;
let active_tooltip = interactive_state.active_tooltip.clone();
let pending_mouse_down = interactive_state.mouse_down_index.clone();
let text_layout = text_layout.clone();
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
let position = text_layout.index_for_position(event.position).ok();
let is_hovered = position.is_some()
&& hitbox.is_hovered(cx)
&& pending_mouse_down.get().is_none();
if !is_hovered {
active_tooltip.take();
return;
}
let position = position.unwrap();
if phase != DispatchPhase::Bubble {
return;
}
if active_tooltip.borrow().is_none() {
let task = cx.spawn({
let active_tooltip = active_tooltip.clone();
let tooltip_builder = tooltip_builder.clone();
move |mut cx| async move {
cx.background_executor().timer(TOOLTIP_DELAY).await;
cx.update(|cx| {
let new_tooltip =
tooltip_builder(position, cx).map(|tooltip| {
ActiveTooltip {
tooltip: Some(AnyTooltip {
view: tooltip,
mouse_position: cx.mouse_position(),
hoverable: true,
origin_bounds: source_bounds,
}),
_task: None,
}
});
*active_tooltip.borrow_mut() = new_tooltip;
cx.refresh();
})
.ok();
}
});
*active_tooltip.borrow_mut() = Some(ActiveTooltip {
tooltip: None,
_task: Some(task),
});
let build_tooltip = Rc::new({
let tooltip_is_hoverable = false;
let text_layout = text_layout.clone();
move |cx: &mut WindowContext| {
text_layout
.index_for_position(cx.mouse_position())
.ok()
.and_then(|position| tooltip_builder(position, cx))
.map(|view| (view, tooltip_is_hoverable))
}
});
let active_tooltip = interactive_state.active_tooltip.clone();
cx.on_mouse_event(move |_: &MouseDownEvent, _, _| {
active_tooltip.take();
// Use bounds instead of testing hitbox since check_is_hovered is also
// called during prepaint.
let source_bounds = hitbox.bounds;
let check_is_hovered = Rc::new({
let text_layout = text_layout.clone();
move |cx: &WindowContext| {
text_layout.index_for_position(cx.mouse_position()).is_ok()
&& source_bounds.contains(&cx.mouse_position())
&& pending_mouse_down.get().is_none()
}
});
register_tooltip_mouse_handlers(
&active_tooltip,
self.tooltip_id,
build_tooltip,
check_is_hovered,
cx,
);
}
self.text.paint(None, bounds, &mut (), &mut (), cx);
+59 -50
View File
@@ -1550,62 +1550,71 @@ impl<'a> WindowContext<'a> {
}
fn prepaint_tooltip(&mut self) -> Option<AnyElement> {
let tooltip_request = self.window.next_frame.tooltip_requests.last().cloned()?;
let tooltip_request = tooltip_request.unwrap();
let mut element = tooltip_request.tooltip.view.clone().into_any();
let mouse_position = tooltip_request.tooltip.mouse_position;
let tooltip_size = element.layout_as_root(AvailableSpace::min_size(), self);
// Use indexing instead of iteration to avoid borrowing self for the duration of the loop.
for tooltip_request_index in (0..self.window.next_frame.tooltip_requests.len()).rev() {
let Some(Some(tooltip_request)) = self
.window
.next_frame
.tooltip_requests
.get(tooltip_request_index)
.cloned()
else {
log::error!("Unexpectedly absent TooltipRequest");
continue;
};
let mut element = tooltip_request.tooltip.view.clone().into_any();
let mouse_position = tooltip_request.tooltip.mouse_position;
let tooltip_size = element.layout_as_root(AvailableSpace::min_size(), self);
let mut tooltip_bounds = Bounds::new(mouse_position + point(px(1.), px(1.)), tooltip_size);
let window_bounds = Bounds {
origin: Point::default(),
size: self.viewport_size(),
};
let mut tooltip_bounds =
Bounds::new(mouse_position + point(px(1.), px(1.)), tooltip_size);
let window_bounds = Bounds {
origin: Point::default(),
size: self.viewport_size(),
};
if tooltip_bounds.right() > window_bounds.right() {
let new_x = mouse_position.x - tooltip_bounds.size.width - px(1.);
if new_x >= Pixels::ZERO {
tooltip_bounds.origin.x = new_x;
} else {
tooltip_bounds.origin.x = cmp::max(
Pixels::ZERO,
tooltip_bounds.origin.x - tooltip_bounds.right() - window_bounds.right(),
);
if tooltip_bounds.right() > window_bounds.right() {
let new_x = mouse_position.x - tooltip_bounds.size.width - px(1.);
if new_x >= Pixels::ZERO {
tooltip_bounds.origin.x = new_x;
} else {
tooltip_bounds.origin.x = cmp::max(
Pixels::ZERO,
tooltip_bounds.origin.x - tooltip_bounds.right() - window_bounds.right(),
);
}
}
}
if tooltip_bounds.bottom() > window_bounds.bottom() {
let new_y = mouse_position.y - tooltip_bounds.size.height - px(1.);
if new_y >= Pixels::ZERO {
tooltip_bounds.origin.y = new_y;
} else {
tooltip_bounds.origin.y = cmp::max(
Pixels::ZERO,
tooltip_bounds.origin.y - tooltip_bounds.bottom() - window_bounds.bottom(),
);
if tooltip_bounds.bottom() > window_bounds.bottom() {
let new_y = mouse_position.y - tooltip_bounds.size.height - px(1.);
if new_y >= Pixels::ZERO {
tooltip_bounds.origin.y = new_y;
} else {
tooltip_bounds.origin.y = cmp::max(
Pixels::ZERO,
tooltip_bounds.origin.y - tooltip_bounds.bottom() - window_bounds.bottom(),
);
}
}
// It's possible for an element to have an active tooltip while not being painted (e.g.
// via the `visible_on_hover` method). Since mouse listeners are not active in this
// case, instead update the tooltip's visibility here.
let is_visible =
(tooltip_request.tooltip.check_visible_and_update)(tooltip_bounds, self);
if !is_visible {
continue;
}
self.with_absolute_element_offset(tooltip_bounds.origin, |cx| element.prepaint(cx));
self.window.tooltip_bounds = Some(TooltipBounds {
id: tooltip_request.id,
bounds: tooltip_bounds,
});
return Some(element);
}
// Element's parent can get hidden (e.g. via the `visible_on_hover` method),
// and element's `paint` won't be called (ergo, mouse listeners also won't be active) to detect that the tooltip has to be removed.
// Ensure it's not stuck around in such cases.
let invalidate_tooltip = !tooltip_request
.tooltip
.origin_bounds
.contains(&self.mouse_position())
&& (!tooltip_request.tooltip.hoverable
|| !tooltip_bounds.contains(&self.mouse_position()));
if invalidate_tooltip {
return None;
}
self.with_absolute_element_offset(tooltip_bounds.origin, |cx| element.prepaint(cx));
self.window.tooltip_bounds = Some(TooltipBounds {
id: tooltip_request.id,
bounds: tooltip_bounds,
});
Some(element)
None
}
fn prepaint_deferred_draws(&mut self, deferred_draw_indices: &[usize]) {