@@ -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,
@@ -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| {