gpui2: Add on_hover events

Conrad Irwin created

Change summary

crates/gpui2/src/interactive.rs | 60 +++++++++++++++++++++++++++++++++++
crates/workspace2/src/pane.rs   |  4 ++
2 files changed, 64 insertions(+)

Detailed changes

crates/gpui2/src/interactive.rs 🔗

@@ -333,6 +333,37 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
             }));
         self
     }
+
+    fn on_hover(mut self, listener: impl 'static + Fn(&mut V, bool, &mut ViewContext<V>)) -> Self
+    where
+        Self: Sized,
+    {
+        debug_assert!(
+            self.stateful_interaction().hover_listener.is_none(),
+            "calling on_hover more than once on the same element is not supported"
+        );
+        self.stateful_interaction().hover_listener = Some(Box::new(listener));
+        self
+    }
+
+    fn tooltip<W>(
+        mut self,
+        build_tooltip: impl Fn(&mut V, &mut ViewContext<V>) -> View<W> + 'static,
+    ) -> Self
+    where
+        Self: Sized,
+        W: 'static + Render,
+    {
+        debug_assert!(
+            self.stateful_interaction().tooltip_builder.is_none(),
+            "calling tooltip more than once on the same element is not supported"
+        );
+        self.stateful_interaction().tooltip_builder = Some(Box::new(move |view_state, cx| {
+            build_tooltip(view_state, cx).into()
+        }));
+
+        self
+    }
 }
 
 pub trait ElementInteraction<V: 'static>: 'static {
@@ -568,6 +599,24 @@ pub trait ElementInteraction<V: 'static>: '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();
+                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 mut was_hovered = was_hovered.lock();
+
+                    if is_hovered != was_hovered.clone() {
+                        *was_hovered = is_hovered;
+                        drop(was_hovered);
+                        hover_listener(view_state, is_hovered, cx);
+                    }
+                });
+            }
+
             let active_state = element_state.active_state.clone();
             if active_state.lock().is_none() {
                 let active_group_bounds = stateful
@@ -639,6 +688,8 @@ pub struct StatefulInteraction<V> {
     active_style: StyleRefinement,
     group_active_style: Option<GroupStyle>,
     drag_listener: Option<DragListener<V>>,
+    hover_listener: Option<HoverListener<V>>,
+    tooltip_builder: Option<TooltipBuilder<V>>,
 }
 
 impl<V: 'static> ElementInteraction<V> for StatefulInteraction<V> {
@@ -666,6 +717,8 @@ impl<V> From<ElementId> for StatefulInteraction<V> {
             stateless: StatelessInteraction::default(),
             click_listeners: SmallVec::new(),
             drag_listener: None,
+            hover_listener: None,
+            tooltip_builder: None,
             active_style: StyleRefinement::default(),
             group_active_style: None,
         }
@@ -695,6 +748,8 @@ impl<V> StatelessInteraction<V> {
             stateless: self,
             click_listeners: SmallVec::new(),
             drag_listener: None,
+            hover_listener: None,
+            tooltip_builder: None,
             active_style: StyleRefinement::default(),
             group_active_style: None,
         }
@@ -746,6 +801,7 @@ impl ActiveState {
 #[derive(Default)]
 pub struct InteractiveElementState {
     active_state: Arc<Mutex<ActiveState>>,
+    hover_state: Arc<Mutex<bool>>,
     pending_mouse_down: Arc<Mutex<Option<MouseDownEvent>>>,
     scroll_offset: Option<Arc<Mutex<Point<Pixels>>>>,
 }
@@ -1097,6 +1153,10 @@ pub type ClickListener<V> = Box<dyn Fn(&mut V, &ClickEvent, &mut ViewContext<V>)
 pub(crate) type DragListener<V> =
     Box<dyn Fn(&mut V, Point<Pixels>, &mut ViewContext<V>) -> AnyDrag + 'static>;
 
+pub(crate) type HoverListener<V> = Box<dyn Fn(&mut V, bool, &mut ViewContext<V>) + 'static>;
+
+pub(crate) type TooltipBuilder<V> = Box<dyn Fn(&mut V, &mut ViewContext<V>) -> AnyView + 'static>;
+
 pub type KeyListener<V> = Box<
     dyn Fn(
             &mut V,

crates/workspace2/src/pane.rs 🔗

@@ -1399,6 +1399,10 @@ impl Pane {
             .group("")
             .id(item.id())
             .cursor_pointer()
+            .on_hover(|_, hovered, _| {
+                dbg!(hovered);
+            })
+            // .tooltip(|pane, cx| cx.create_view( tooltip.child("Hovering the tab"))
             // .on_drag(move |pane, cx| pane.render_tab(ix, item.boxed_clone(), detail, cx))
             // .drag_over::<DraggedTab>(|d| d.bg(cx.theme().colors().element_drop_target))
             // .on_drop(|_view, state: View<DraggedTab>, cx| {