@@ -3067,21 +3067,29 @@ fn handle_tooltip_mouse_move(
}
Action::ScheduleShow => {
let delayed_show_task = window.spawn(cx, {
- let active_tooltip = active_tooltip.clone();
+ let weak_active_tooltip = Rc::downgrade(active_tooltip);
let build_tooltip = build_tooltip.clone();
let check_is_hovered_during_prepaint = check_is_hovered_during_prepaint.clone();
async move |cx| {
cx.background_executor().timer(TOOLTIP_SHOW_DELAY).await;
+ let Some(active_tooltip) = weak_active_tooltip.upgrade() else {
+ return;
+ };
cx.update(|window, cx| {
let new_tooltip =
build_tooltip(window, cx).map(|(view, tooltip_is_hoverable)| {
- let active_tooltip = active_tooltip.clone();
+ let weak_active_tooltip = Rc::downgrade(&active_tooltip);
ActiveTooltip::Visible {
tooltip: AnyTooltip {
view,
mouse_position: window.mouse_position(),
check_visible_and_update: Rc::new(
move |tooltip_bounds, window, cx| {
+ let Some(active_tooltip) =
+ weak_active_tooltip.upgrade()
+ else {
+ return false;
+ };
handle_tooltip_check_visible_and_update(
&active_tooltip,
tooltip_is_hoverable,
@@ -3160,11 +3168,14 @@ fn handle_tooltip_check_visible_and_update(
Action::Hide => clear_active_tooltip(active_tooltip, window),
Action::ScheduleHide(tooltip) => {
let delayed_hide_task = window.spawn(cx, {
- let active_tooltip = active_tooltip.clone();
+ let weak_active_tooltip = Rc::downgrade(active_tooltip);
async move |cx| {
cx.background_executor()
.timer(HOVERABLE_TOOLTIP_HIDE_DELAY)
.await;
+ let Some(active_tooltip) = weak_active_tooltip.upgrade() else {
+ return;
+ };
if active_tooltip.borrow_mut().take().is_some() {
cx.update(|window, _cx| window.refresh()).ok();
}
@@ -3577,6 +3588,112 @@ impl ScrollHandle {
#[cfg(test)]
mod tests {
use super::*;
+ use crate::{AppContext as _, Context, InputEvent, MouseMoveEvent, TestAppContext};
+ use std::rc::Weak;
+
+ struct TestTooltipView;
+
+ impl Render for TestTooltipView {
+ fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
+ div().w(px(20.)).h(px(20.)).child("tooltip")
+ }
+ }
+
+ type CapturedActiveTooltip = Rc<RefCell<Option<Weak<RefCell<Option<ActiveTooltip>>>>>>;
+
+ struct TooltipCaptureElement {
+ child: AnyElement,
+ captured_active_tooltip: CapturedActiveTooltip,
+ }
+
+ impl IntoElement for TooltipCaptureElement {
+ type Element = Self;
+
+ fn into_element(self) -> Self::Element {
+ self
+ }
+ }
+
+ impl Element for TooltipCaptureElement {
+ type RequestLayoutState = ();
+ type PrepaintState = ();
+
+ fn id(&self) -> Option<ElementId> {
+ None
+ }
+
+ fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
+ None
+ }
+
+ fn request_layout(
+ &mut self,
+ _id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
+ window: &mut Window,
+ cx: &mut App,
+ ) -> (LayoutId, Self::RequestLayoutState) {
+ (self.child.request_layout(window, cx), ())
+ }
+
+ fn prepaint(
+ &mut self,
+ _id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
+ _bounds: Bounds<Pixels>,
+ _request_layout: &mut Self::RequestLayoutState,
+ window: &mut Window,
+ cx: &mut App,
+ ) -> Self::PrepaintState {
+ self.child.prepaint(window, cx);
+ }
+
+ fn paint(
+ &mut self,
+ _id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
+ _bounds: Bounds<Pixels>,
+ _request_layout: &mut Self::RequestLayoutState,
+ _prepaint: &mut Self::PrepaintState,
+ window: &mut Window,
+ cx: &mut App,
+ ) {
+ self.child.paint(window, cx);
+ window.with_global_id("target".into(), |global_id, window| {
+ window.with_element_state::<InteractiveElementState, _>(
+ global_id,
+ |state, _window| {
+ let state = state.unwrap();
+ *self.captured_active_tooltip.borrow_mut() =
+ state.active_tooltip.as_ref().map(Rc::downgrade);
+ ((), state)
+ },
+ )
+ });
+ }
+ }
+
+ struct TooltipOwner {
+ captured_active_tooltip: CapturedActiveTooltip,
+ }
+
+ impl Render for TooltipOwner {
+ fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
+ TooltipCaptureElement {
+ child: div()
+ .size_full()
+ .child(
+ div()
+ .id("target")
+ .w(px(50.))
+ .h(px(50.))
+ .tooltip(|_, cx| cx.new(|_| TestTooltipView).into()),
+ )
+ .into_any_element(),
+ captured_active_tooltip: self.captured_active_tooltip.clone(),
+ }
+ }
+ }
#[test]
fn scroll_handle_aligns_wide_children_to_left_edge() {
@@ -3615,4 +3732,96 @@ mod tests {
assert_eq!(handle.offset().y, px(-25.));
}
+
+ fn setup_tooltip_owner_test() -> (
+ TestAppContext,
+ crate::AnyWindowHandle,
+ CapturedActiveTooltip,
+ ) {
+ let mut test_app = TestAppContext::single();
+ let captured_active_tooltip: CapturedActiveTooltip = Rc::new(RefCell::new(None));
+ let window = test_app.add_window({
+ let captured_active_tooltip = captured_active_tooltip.clone();
+ move |_, _| TooltipOwner {
+ captured_active_tooltip,
+ }
+ });
+ let any_window = window.into();
+
+ test_app
+ .update_window(any_window, |_, window, cx| {
+ window.draw(cx).clear();
+ })
+ .unwrap();
+
+ test_app
+ .update_window(any_window, |_, window, cx| {
+ window.dispatch_event(
+ MouseMoveEvent {
+ position: point(px(10.), px(10.)),
+ modifiers: Default::default(),
+ pressed_button: None,
+ }
+ .to_platform_input(),
+ cx,
+ );
+ })
+ .unwrap();
+
+ test_app
+ .update_window(any_window, |_, window, cx| {
+ window.draw(cx).clear();
+ })
+ .unwrap();
+
+ (test_app, any_window, captured_active_tooltip)
+ }
+
+ #[test]
+ fn tooltip_waiting_for_show_is_released_when_its_owner_disappears() {
+ let (mut test_app, any_window, captured_active_tooltip) = setup_tooltip_owner_test();
+
+ let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
+ let active_tooltip = weak_active_tooltip.upgrade().unwrap();
+ assert!(matches!(
+ active_tooltip.borrow().as_ref(),
+ Some(ActiveTooltip::WaitingForShow { .. })
+ ));
+
+ test_app
+ .update_window(any_window, |_, window, _| {
+ window.remove_window();
+ })
+ .unwrap();
+ test_app.run_until_parked();
+ drop(active_tooltip);
+
+ assert!(weak_active_tooltip.upgrade().is_none());
+ }
+
+ #[test]
+ fn tooltip_is_released_when_its_owner_disappears() {
+ let (mut test_app, any_window, captured_active_tooltip) = setup_tooltip_owner_test();
+
+ let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
+ let active_tooltip = weak_active_tooltip.upgrade().unwrap();
+
+ test_app.dispatcher.advance_clock(TOOLTIP_SHOW_DELAY);
+ test_app.run_until_parked();
+
+ assert!(matches!(
+ active_tooltip.borrow().as_ref(),
+ Some(ActiveTooltip::Visible { .. })
+ ));
+
+ test_app
+ .update_window(any_window, |_, window, _| {
+ window.remove_window();
+ })
+ .unwrap();
+ test_app.run_until_parked();
+ drop(active_tooltip);
+
+ assert!(weak_active_tooltip.upgrade().is_none());
+ }
}