From f97046b86fae67d9cbf8ccf4e7b86781f9d2f7a9 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 3 Nov 2023 14:15:32 -0600 Subject: [PATCH 1/4] MOAR TOOLTIPS --- crates/gpui2/src/interactive.rs | 51 ++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index def7315a7c..4a6185f737 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -17,6 +17,7 @@ use std::{ ops::Deref, path::PathBuf, sync::Arc, + time::{Duration, Instant}, }; const DRAG_THRESHOLD: f64 = 2.; @@ -603,9 +604,6 @@ pub trait ElementInteraction: 'static { let was_hovered = element_state.hover_state.clone(); let has_mouse_down = element_state.pending_mouse_down.lock().is_some(); - let active_tooltip = element_state.active_tooltip.clone(); - let tooltip_builder = stateful.tooltip_builder.clone(); - cx.on_mouse_event(move |view_state, event: &MouseMoveEvent, phase, cx| { if phase != DispatchPhase::Bubble { return; @@ -616,23 +614,39 @@ pub trait ElementInteraction: 'static { if is_hovered != was_hovered.clone() { *was_hovered = is_hovered; drop(was_hovered); - if let Some(tooltip_builder) = &tooltip_builder { - let mut active_tooltip = active_tooltip.lock(); - if is_hovered && active_tooltip.is_none() { - *active_tooltip = Some(tooltip_builder(view_state, cx)); - } else if !is_hovered { - active_tooltip.take(); - } - } hover_listener(view_state, is_hovered, cx); } }); } - if let Some(active_tooltip) = element_state.active_tooltip.lock().as_ref() { - if *element_state.hover_state.lock() { - cx.active_tooltip = Some(active_tooltip.clone()); + // if we're hovered: + // if no timer, start timer + // if timer hits 1s, call tooltip_builder() + // + + if let Some(tooltip_builder) = &stateful.tooltip_builder { + let mut active_tooltip = element_state.active_tooltip.lock(); + let is_hovered = bounds.contains_point(&cx.mouse_position()) + && !element_state.pending_mouse_down.lock().is_some(); + + if is_hovered { + if let Some(active_tooltip) = active_tooltip { + active_tooltip.view = Some(tooltip_builder(cx)) + } else { + *active_tooltip = Some(ActiveTooltip { + hover_start: Instant::now(), + view: None, + }); + } + } else { + active_tooltip.take(); + } + + if let Some(active_tooltip) = element_state.active_tooltip.lock().as_ref() { + if *element_state.hover_state.lock() { + cx.active_tooltip = Some(active_tooltip.clone()); + } } } @@ -823,7 +837,12 @@ pub struct InteractiveElementState { hover_state: Arc>, pending_mouse_down: Arc>>, scroll_offset: Option>>>, - active_tooltip: Arc>>, + active_tooltip: Arc>>, +} + +pub struct ActiveTooltip { + hover_start: Instant, + view: Option, } impl InteractiveElementState { @@ -1175,7 +1194,7 @@ pub(crate) type DragListener = pub(crate) type HoverListener = Box) + 'static>; -pub(crate) type TooltipBuilder = Arc) -> AnyView + 'static>; +pub(crate) type TooltipBuilder = Arc) -> AnyView + 'static>; pub type KeyListener = Box< dyn Fn( From 3834e26f7150215502754eae999f7fc35ca11008 Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 3 Nov 2023 18:02:58 -0400 Subject: [PATCH 2/4] Tooltips in mouse event handler & fix executor timer Co-Authored-By: Conrad Irwin --- crates/gpui2/build.rs | 1 + crates/gpui2/src/interactive.rs | 56 +++++++++------------ crates/gpui2/src/platform/mac/dispatcher.rs | 14 +----- crates/gpui2/src/window.rs | 13 +++-- crates/ui2/src/components/tooltip.rs | 34 +++++++++++-- crates/workspace2/src/pane.rs | 8 +-- 6 files changed, 67 insertions(+), 59 deletions(-) diff --git a/crates/gpui2/build.rs b/crates/gpui2/build.rs index c9abfaa6bb..6e8a0868b9 100644 --- a/crates/gpui2/build.rs +++ b/crates/gpui2/build.rs @@ -20,6 +20,7 @@ fn generate_dispatch_bindings() { .header("src/platform/mac/dispatch.h") .allowlist_var("_dispatch_main_q") .allowlist_var("DISPATCH_QUEUE_PRIORITY_DEFAULT") + .allowlist_var("DISPATCH_TIME_NOW") .allowlist_function("dispatch_get_global_queue") .allowlist_function("dispatch_async_f") .allowlist_function("dispatch_after_f") diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index 4a6185f737..f7a8f033c4 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -17,7 +17,6 @@ use std::{ ops::Deref, path::PathBuf, sync::Arc, - time::{Duration, Instant}, }; const DRAG_THRESHOLD: f64 = 2.; @@ -602,13 +601,14 @@ pub trait ElementInteraction: 'static { if let Some(hover_listener) = stateful.hover_listener.take() { let was_hovered = element_state.hover_state.clone(); - let has_mouse_down = element_state.pending_mouse_down.lock().is_some(); + let has_mouse_down = element_state.pending_mouse_down.clone(); cx.on_mouse_event(move |view_state, event: &MouseMoveEvent, phase, cx| { if phase != DispatchPhase::Bubble { return; } - let is_hovered = bounds.contains_point(&event.position) && !has_mouse_down; + let is_hovered = + bounds.contains_point(&event.position) && has_mouse_down.lock().is_none(); let mut was_hovered = was_hovered.lock(); if is_hovered != was_hovered.clone() { @@ -620,33 +620,30 @@ pub trait ElementInteraction: 'static { }); } - // if we're hovered: - // if no timer, start timer - // if timer hits 1s, call tooltip_builder() - // + if let Some(tooltip_builder) = stateful.tooltip_builder.take() { + let tooltip_view = element_state.tooltip_view.clone(); + let pending_mouse_down = element_state.pending_mouse_down.clone(); - if let Some(tooltip_builder) = &stateful.tooltip_builder { - let mut active_tooltip = element_state.active_tooltip.lock(); - let is_hovered = bounds.contains_point(&cx.mouse_position()) - && !element_state.pending_mouse_down.lock().is_some(); + cx.on_mouse_event(move |view_state, event: &MouseMoveEvent, phase, cx| { + if phase != DispatchPhase::Bubble { + return; + } - if is_hovered { - if let Some(active_tooltip) = active_tooltip { - active_tooltip.view = Some(tooltip_builder(cx)) + let is_hovered = bounds.contains_point(&event.position) + && pending_mouse_down.lock().is_none(); + let mut tooltip_view = tooltip_view.lock(); + + if is_hovered { + if tooltip_view.is_none() { + *tooltip_view = Some(tooltip_builder(view_state, cx)); + } } else { - *active_tooltip = Some(ActiveTooltip { - hover_start: Instant::now(), - view: None, - }); + tooltip_view.take(); } - } else { - active_tooltip.take(); - } + }); - if let Some(active_tooltip) = element_state.active_tooltip.lock().as_ref() { - if *element_state.hover_state.lock() { - cx.active_tooltip = Some(active_tooltip.clone()); - } + if let Some(active_tooltip) = element_state.tooltip_view.lock().as_ref() { + cx.active_tooltip = Some(active_tooltip.clone()); } } @@ -837,12 +834,7 @@ pub struct InteractiveElementState { hover_state: Arc>, pending_mouse_down: Arc>>, scroll_offset: Option>>>, - active_tooltip: Arc>>, -} - -pub struct ActiveTooltip { - hover_start: Instant, - view: Option, + tooltip_view: Arc>>, } impl InteractiveElementState { @@ -1194,7 +1186,7 @@ pub(crate) type DragListener = pub(crate) type HoverListener = Box) + 'static>; -pub(crate) type TooltipBuilder = Arc) -> AnyView + 'static>; +pub(crate) type TooltipBuilder = Arc) -> AnyView + 'static>; pub type KeyListener = Box< dyn Fn( diff --git a/crates/gpui2/src/platform/mac/dispatcher.rs b/crates/gpui2/src/platform/mac/dispatcher.rs index f5334912c6..68c0e3b4f5 100644 --- a/crates/gpui2/src/platform/mac/dispatcher.rs +++ b/crates/gpui2/src/platform/mac/dispatcher.rs @@ -11,11 +11,7 @@ use objc::{ }; use parking::{Parker, Unparker}; use parking_lot::Mutex; -use std::{ - ffi::c_void, - sync::Arc, - time::{Duration, SystemTime}, -}; +use std::{ffi::c_void, sync::Arc, time::Duration}; include!(concat!(env!("OUT_DIR"), "/dispatch_sys.rs")); @@ -62,16 +58,10 @@ impl PlatformDispatcher for MacDispatcher { } fn dispatch_after(&self, duration: Duration, runnable: Runnable) { - let now = SystemTime::now(); - let after_duration = now - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_nanos() as u64 - + duration.as_nanos() as u64; unsafe { let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.try_into().unwrap(), 0); - let when = dispatch_time(0, after_duration as i64); + let when = dispatch_time(DISPATCH_TIME_NOW as u64, duration.as_nanos() as i64); dispatch_after_f( when, queue, diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 46ac30592b..fe6f516e43 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -989,11 +989,14 @@ impl<'a> WindowContext<'a> { }); } else if let Some(active_tooltip) = self.app.active_tooltip.take() { self.stack(1, |cx| { - cx.with_element_offset(Some(cx.mouse_position()), |cx| { - let available_space = - size(AvailableSpace::MinContent, AvailableSpace::MinContent); - active_tooltip.draw(available_space, cx); - }); + cx.with_element_offset( + Some(cx.mouse_position() + Point::new(px(8.0), px(8.0))), + |cx| { + let available_space = + size(AvailableSpace::MinContent, AvailableSpace::MinContent); + active_tooltip.draw(available_space, cx); + }, + ); }); } diff --git a/crates/ui2/src/components/tooltip.rs b/crates/ui2/src/components/tooltip.rs index eb53b506eb..f94518224d 100644 --- a/crates/ui2/src/components/tooltip.rs +++ b/crates/ui2/src/components/tooltip.rs @@ -1,20 +1,44 @@ +use std::time::Duration; + use gpui2::{ - div, px, Div, ParentElement, Render, SharedString, Styled, View, ViewContext, VisualContext, + div, px, Component, Div, ParentElement, Render, SharedString, Styled, View, ViewContext, + VisualContext, WindowContext, }; use theme2::ActiveTheme; +const DELAY: Duration = Duration::from_millis(500); + #[derive(Clone, Debug)] pub struct TextTooltip { title: SharedString, + visible: bool, } impl TextTooltip { pub fn new(str: SharedString) -> Self { - Self { title: str } + Self { + title: str, + visible: false, + } } - pub fn build_view(str: SharedString, cx: &mut C) -> C::Result> { - cx.build_view(|cx| TextTooltip::new(str)) + pub fn build_view(str: SharedString, cx: &mut WindowContext) -> View { + let view = cx.build_view(|cx| TextTooltip::new(str)); + + let handle = view.downgrade(); + cx.spawn(|mut cx| async move { + cx.background_executor().timer(DELAY).await; + + handle + .update(&mut cx, |this, cx| { + this.visible = true; + cx.notify(); + }) + .ok(); + }) + .detach(); + + view } } @@ -24,9 +48,11 @@ impl Render for TextTooltip { fn render(&mut self, cx: &mut ViewContext) -> Self::Element { let theme = cx.theme(); div() + .when(!self.visible, |this| this.invisible()) .bg(theme.colors().background) .rounded(px(8.)) .border() + .font("Zed Sans") .border_color(theme.colors().border) .text_color(theme.colors().text) .pl_2() diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index a1ec168488..64b995b538 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -9,8 +9,8 @@ use crate::{ use anyhow::Result; use collections::{HashMap, HashSet, VecDeque}; use gpui::{ - AnyView, AppContext, AsyncWindowContext, Component, Div, EntityId, EventEmitter, FocusHandle, - Model, PromptLevel, Render, Task, View, ViewContext, VisualContext, WeakView, WindowContext, + AppContext, AsyncWindowContext, Component, Div, EntityId, EventEmitter, FocusHandle, Model, + PromptLevel, Render, Task, View, ViewContext, VisualContext, WeakView, WindowContext, }; use parking_lot::Mutex; use project2::{Project, ProjectEntryId, ProjectPath}; @@ -1398,13 +1398,9 @@ impl Pane { .group("") .id(item.id()) .cursor_pointer() - .on_hover(|_, hovered, _| { - dbg!(hovered); - }) .when_some(item.tab_tooltip_text(cx), |div, text| { div.tooltip(move |_, cx| TextTooltip::build_view(text.clone(), cx)) }) - // .tooltip(|pane, cx| cx.build_view(|cx| div().child(title))) // .on_drag(move |pane, cx| pane.render_tab(ix, item.boxed_clone(), detail, cx)) // .drag_over::(|d| d.bg(cx.theme().colors().element_drop_target)) // .on_drop(|_view, state: View, cx| { From 4725cd2cd6b396c5adf19c0a27d87a1750598cf3 Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 3 Nov 2023 18:37:15 -0400 Subject: [PATCH 3/4] Move more tooltip logic into gpui2 & fix tooltip moving on paint Co-Authored-By: Conrad Irwin --- crates/gpui2/src/app.rs | 7 +++- crates/gpui2/src/geometry.rs | 2 +- crates/gpui2/src/interactive.rs | 58 ++++++++++++++++++++++------ crates/gpui2/src/window.rs | 13 +++---- crates/ui2/src/components/tooltip.rs | 33 +--------------- crates/workspace2/src/pane.rs | 2 +- 6 files changed, 61 insertions(+), 54 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 974395e897..9e454dc652 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -157,7 +157,7 @@ pub struct AppContext { flushing_effects: bool, pending_updates: usize, pub(crate) active_drag: Option, - pub(crate) active_tooltip: Option, + pub(crate) active_tooltip: Option, pub(crate) next_frame_callbacks: HashMap>, pub(crate) frame_consumers: HashMap>, pub(crate) background_executor: BackgroundExecutor, @@ -898,3 +898,8 @@ pub(crate) struct AnyDrag { pub view: AnyView, pub cursor_offset: Point, } + +pub(crate) struct AnyTooltip { + pub view: AnyView, + pub cursor_offset: Point, +} diff --git a/crates/gpui2/src/geometry.rs b/crates/gpui2/src/geometry.rs index 081b11aae0..d6755a5397 100644 --- a/crates/gpui2/src/geometry.rs +++ b/crates/gpui2/src/geometry.rs @@ -21,7 +21,7 @@ pub fn point(x: T, y: T) -> Point { } impl Point { - pub fn new(x: T, y: T) -> Self { + pub const fn new(x: T, y: T) -> Self { Self { x, y } } diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index f7a8f033c4..dd09ad512b 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -1,8 +1,8 @@ use crate::{ - div, point, px, Action, AnyDrag, AnyView, AppContext, BorrowWindow, Bounds, Component, - DispatchContext, DispatchPhase, Div, Element, ElementId, FocusHandle, KeyMatch, Keystroke, - Modifiers, Overflow, Pixels, Point, Render, SharedString, Size, Style, StyleRefinement, View, - ViewContext, + div, point, px, Action, AnyDrag, AnyTooltip, AnyView, AppContext, BorrowWindow, Bounds, + Component, DispatchContext, DispatchPhase, Div, Element, ElementId, FocusHandle, KeyMatch, + Keystroke, Modifiers, Overflow, Pixels, Point, Render, SharedString, Size, Style, + StyleRefinement, View, ViewContext, }; use collections::HashMap; use derive_more::{Deref, DerefMut}; @@ -17,9 +17,12 @@ use std::{ ops::Deref, path::PathBuf, sync::Arc, + time::Duration, }; const DRAG_THRESHOLD: f64 = 2.; +const TOOLTIP_DELAY: Duration = Duration::from_millis(500); +const TOOLTIP_OFFSET: Point = Point::new(px(10.0), px(8.0)); pub trait StatelessInteractive: Element { fn stateless_interaction(&mut self) -> &mut StatelessInteraction; @@ -621,7 +624,7 @@ pub trait ElementInteraction: 'static { } if let Some(tooltip_builder) = stateful.tooltip_builder.take() { - let tooltip_view = element_state.tooltip_view.clone(); + let active_tooltip = element_state.active_tooltip.clone(); let pending_mouse_down = element_state.pending_mouse_down.clone(); cx.on_mouse_event(move |view_state, event: &MouseMoveEvent, phase, cx| { @@ -631,19 +634,44 @@ pub trait ElementInteraction: 'static { let is_hovered = bounds.contains_point(&event.position) && pending_mouse_down.lock().is_none(); - let mut tooltip_view = tooltip_view.lock(); + let mut tooltip_lock = active_tooltip.lock(); if is_hovered { - if tooltip_view.is_none() { - *tooltip_view = Some(tooltip_builder(view_state, cx)); + if tooltip_lock.is_none() { + *tooltip_lock = Some(ActiveTooltip { + view: tooltip_builder(view_state, cx), + visible: false, + coordinates: event.position, + }); + + let active_tooltip = active_tooltip.clone(); + cx.spawn(move |view, mut cx| async move { + cx.background_executor().timer(TOOLTIP_DELAY).await; + + view.update(&mut cx, |_, cx| { + if let Some(active_tooltip) = active_tooltip.lock().as_mut() { + active_tooltip.visible = true; + active_tooltip.coordinates = + cx.mouse_position() + TOOLTIP_OFFSET; + } + cx.notify(); + }) + .ok() + }) + .detach(); } } else { - tooltip_view.take(); + tooltip_lock.take(); } }); - if let Some(active_tooltip) = element_state.tooltip_view.lock().as_ref() { - cx.active_tooltip = Some(active_tooltip.clone()); + if let Some(active_tooltip) = element_state.active_tooltip.lock().as_ref() { + if active_tooltip.visible { + cx.active_tooltip = Some(AnyTooltip { + view: active_tooltip.view.clone(), + cursor_offset: active_tooltip.coordinates, + }); + } } } @@ -834,7 +862,13 @@ pub struct InteractiveElementState { hover_state: Arc>, pending_mouse_down: Arc>>, scroll_offset: Option>>>, - tooltip_view: Arc>>, + active_tooltip: Arc>>, +} + +struct ActiveTooltip { + view: AnyView, + visible: bool, + coordinates: Point, } impl InteractiveElementState { diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index fe6f516e43..9cab40082b 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -989,14 +989,11 @@ impl<'a> WindowContext<'a> { }); } else if let Some(active_tooltip) = self.app.active_tooltip.take() { self.stack(1, |cx| { - cx.with_element_offset( - Some(cx.mouse_position() + Point::new(px(8.0), px(8.0))), - |cx| { - let available_space = - size(AvailableSpace::MinContent, AvailableSpace::MinContent); - active_tooltip.draw(available_space, cx); - }, - ); + cx.with_element_offset(Some(active_tooltip.cursor_offset), |cx| { + let available_space = + size(AvailableSpace::MinContent, AvailableSpace::MinContent); + active_tooltip.view.draw(available_space, cx); + }); }); } diff --git a/crates/ui2/src/components/tooltip.rs b/crates/ui2/src/components/tooltip.rs index f94518224d..0bc00a9be1 100644 --- a/crates/ui2/src/components/tooltip.rs +++ b/crates/ui2/src/components/tooltip.rs @@ -1,44 +1,16 @@ use std::time::Duration; -use gpui2::{ - div, px, Component, Div, ParentElement, Render, SharedString, Styled, View, ViewContext, - VisualContext, WindowContext, -}; +use gpui2::{div, px, Div, ParentElement, Render, SharedString, Styled, ViewContext}; use theme2::ActiveTheme; -const DELAY: Duration = Duration::from_millis(500); - #[derive(Clone, Debug)] pub struct TextTooltip { title: SharedString, - visible: bool, } impl TextTooltip { pub fn new(str: SharedString) -> Self { - Self { - title: str, - visible: false, - } - } - - pub fn build_view(str: SharedString, cx: &mut WindowContext) -> View { - let view = cx.build_view(|cx| TextTooltip::new(str)); - - let handle = view.downgrade(); - cx.spawn(|mut cx| async move { - cx.background_executor().timer(DELAY).await; - - handle - .update(&mut cx, |this, cx| { - this.visible = true; - cx.notify(); - }) - .ok(); - }) - .detach(); - - view + Self { title: str } } } @@ -48,7 +20,6 @@ impl Render for TextTooltip { fn render(&mut self, cx: &mut ViewContext) -> Self::Element { let theme = cx.theme(); div() - .when(!self.visible, |this| this.invisible()) .bg(theme.colors().background) .rounded(px(8.)) .border() diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index 64b995b538..131a3e977b 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -1399,7 +1399,7 @@ impl Pane { .id(item.id()) .cursor_pointer() .when_some(item.tab_tooltip_text(cx), |div, text| { - div.tooltip(move |_, cx| TextTooltip::build_view(text.clone(), cx)) + div.tooltip(move |_, cx| cx.build_view(|cx| TextTooltip::new(text.clone()))) }) // .on_drag(move |pane, cx| pane.render_tab(ix, item.boxed_clone(), detail, cx)) // .drag_over::(|d| d.bg(cx.theme().colors().element_drop_target)) From de5458cfe0f419324a7258647b887c0eb8db5c71 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 3 Nov 2023 21:40:28 -0600 Subject: [PATCH 4/4] Update tooltip code a bit This fixes a tiny UX bug where the tooltip would appear to move if you hovered over an element, then moved your mouse out and back within 500ms. The fix is to retain the task, so we can drop it to cancel it when the mouse leaves. Also changes the time we construct the tooltip to the time it first shows. --- crates/gpui2/src/app.rs | 1 + crates/gpui2/src/interactive.rs | 64 ++++++++++++++-------------- crates/ui2/src/components/tooltip.rs | 2 - 3 files changed, 32 insertions(+), 35 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 9e454dc652..bc9101fa0c 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -899,6 +899,7 @@ pub(crate) struct AnyDrag { pub cursor_offset: Point, } +#[derive(Clone)] pub(crate) struct AnyTooltip { pub view: AnyView, pub cursor_offset: Point, diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index dd09ad512b..da208b3813 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -2,7 +2,7 @@ use crate::{ div, point, px, Action, AnyDrag, AnyTooltip, AnyView, AppContext, BorrowWindow, Bounds, Component, DispatchContext, DispatchPhase, Div, Element, ElementId, FocusHandle, KeyMatch, Keystroke, Modifiers, Overflow, Pixels, Point, Render, SharedString, Size, Style, - StyleRefinement, View, ViewContext, + StyleRefinement, Task, View, ViewContext, }; use collections::HashMap; use derive_more::{Deref, DerefMut}; @@ -627,50 +627,48 @@ pub trait ElementInteraction: 'static { let active_tooltip = element_state.active_tooltip.clone(); let pending_mouse_down = element_state.pending_mouse_down.clone(); - cx.on_mouse_event(move |view_state, event: &MouseMoveEvent, phase, cx| { + cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| { if phase != DispatchPhase::Bubble { return; } let is_hovered = bounds.contains_point(&event.position) && pending_mouse_down.lock().is_none(); - let mut tooltip_lock = active_tooltip.lock(); - - if is_hovered { - if tooltip_lock.is_none() { - *tooltip_lock = Some(ActiveTooltip { - view: tooltip_builder(view_state, cx), - visible: false, - coordinates: event.position, - }); + if !is_hovered { + active_tooltip.lock().take(); + return; + } + if active_tooltip.lock().is_none() { + let task = cx.spawn({ let active_tooltip = active_tooltip.clone(); - cx.spawn(move |view, mut cx| async move { - cx.background_executor().timer(TOOLTIP_DELAY).await; + let tooltip_builder = tooltip_builder.clone(); - view.update(&mut cx, |_, cx| { - if let Some(active_tooltip) = active_tooltip.lock().as_mut() { - active_tooltip.visible = true; - active_tooltip.coordinates = - cx.mouse_position() + TOOLTIP_OFFSET; - } + move |view, mut cx| async move { + cx.background_executor().timer(TOOLTIP_DELAY).await; + view.update(&mut cx, move |view_state, cx| { + active_tooltip.lock().replace(ActiveTooltip { + waiting: None, + tooltip: Some(AnyTooltip { + view: tooltip_builder(view_state, cx), + cursor_offset: cx.mouse_position() + TOOLTIP_OFFSET, + }), + }); cx.notify(); }) - .ok() - }) - .detach(); - } - } else { - tooltip_lock.take(); + .ok(); + } + }); + active_tooltip.lock().replace(ActiveTooltip { + waiting: Some(task), + tooltip: None, + }); } }); if let Some(active_tooltip) = element_state.active_tooltip.lock().as_ref() { - if active_tooltip.visible { - cx.active_tooltip = Some(AnyTooltip { - view: active_tooltip.view.clone(), - cursor_offset: active_tooltip.coordinates, - }); + if active_tooltip.tooltip.is_some() { + cx.active_tooltip = active_tooltip.tooltip.clone() } } } @@ -866,9 +864,9 @@ pub struct InteractiveElementState { } struct ActiveTooltip { - view: AnyView, - visible: bool, - coordinates: Point, + #[allow(unused)] // used to drop the task + waiting: Option>, + tooltip: Option, } impl InteractiveElementState { diff --git a/crates/ui2/src/components/tooltip.rs b/crates/ui2/src/components/tooltip.rs index 0bc00a9be1..c05214eea4 100644 --- a/crates/ui2/src/components/tooltip.rs +++ b/crates/ui2/src/components/tooltip.rs @@ -1,5 +1,3 @@ -use std::time::Duration; - use gpui2::{div, px, Div, ParentElement, Render, SharedString, Styled, ViewContext}; use theme2::ActiveTheme;