Detailed changes
@@ -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
@@ -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]>>);
@@ -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);
@@ -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);
-
- 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(),
- };
+ // 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(),
+ };
- 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(),
+ );
+ }
}
- }
- // 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;
- }
+ // 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.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)
+ self.window.tooltip_bounds = Some(TooltipBounds {
+ id: tooltip_request.id,
+ bounds: tooltip_bounds,
+ });
+ return Some(element);
+ }
+ None
}
fn prepaint_deferred_draws(&mut self, deferred_draw_indices: &[usize]) {