1#![deny(missing_docs)]
2use peniko::kurbo::Point;
3use std::cell::RefCell;
4use std::rc::Rc;
5use ui_events::pointer::PointerEvent;
6
7use crate::{
8 action::{TimerToken, add_overlay, exec_after, remove_overlay},
9 context::{EventCx, UpdateCx},
10 event::{Event, EventPropagation, Phase},
11 platform::Duration,
12 prop, prop_extractor, style_class,
13 view::{IntoView, View, ViewId},
14 views::Decorators,
15};
16
17style_class!(
18 pub TooltipClass
20);
21style_class!(
22 pub TooltipContainerClass
24);
25
26prop!(pub Delay: Duration {} = Duration::from_millis(600));
27
28prop_extractor! {
29 TooltipStyle {
30 delay: Delay,
31 }
32}
33
34pub struct Tooltip {
36 id: ViewId,
37 hover_point: Option<(Point, TimerToken)>,
40 overlay: Rc<RefCell<Option<ViewId>>>,
42 tip: Rc<dyn Fn() -> Box<dyn View>>,
44 style: TooltipStyle,
46}
47
48pub fn tooltip<V: IntoView + 'static, T: IntoView + 'static>(
50 child: V,
51 tip: impl Fn() -> T + 'static,
52) -> Tooltip {
53 let id = ViewId::new();
54 let child = child.into_view();
55 id.set_children([child]);
56 let overlay = Rc::new(RefCell::new(None));
57 Tooltip {
58 id,
59 tip: Rc::new(move || tip().into_any()),
60 hover_point: None,
61 overlay: overlay.clone(),
62 style: Default::default(),
63 }
64 .class(TooltipContainerClass)
65 .on_cleanup(move || {
66 if let Some(overlay_id) = overlay.borrow_mut().take() {
67 remove_overlay(overlay_id);
68 }
69 })
70}
71
72impl View for Tooltip {
73 fn id(&self) -> ViewId {
74 self.id
75 }
76
77 fn update(&mut self, _cx: &mut UpdateCx, state: Box<dyn std::any::Any>) {
78 if let Ok(token) = state.downcast::<TimerToken>()
79 && self.hover_point.map(|(_, t)| t) == Some(*token)
80 {
81 let point =
82 self.id.get_visual_origin() + self.hover_point.unwrap().0.to_vec2() + (0., 10.);
83 let overlay_id = add_overlay(
84 (self.tip)()
85 .class(TooltipClass)
86 .style(move |s| s.inset_left(point.x).inset_top(point.y)),
87 );
88 overlay_id.set_style_parent(self.id);
89 *self.overlay.borrow_mut() = Some(overlay_id);
90 }
91 }
92
93 fn style_pass(&mut self, cx: &mut crate::context::StyleCx<'_>) {
94 self.style.read(cx);
95 if self.overlay.borrow().is_some() && self.id.is_hidden() {
96 let id = self.overlay.take().unwrap();
97 self.hover_point = None;
98 remove_overlay(id);
99 }
100 }
101
102 fn event_capture(&mut self, cx: &mut EventCx) -> EventPropagation {
103 self.handle_event(cx)
104 }
105
106 fn event(&mut self, cx: &mut EventCx) -> EventPropagation {
107 if cx.phase != Phase::Target {
108 return EventPropagation::Continue;
109 }
110 self.handle_event(cx)
111 }
112}
113
114impl Tooltip {
115 fn handle_event(&mut self, cx: &mut EventCx) -> EventPropagation {
116 match &cx.event {
117 Event::Pointer(PointerEvent::Move(pu)) => {
118 if self.overlay.borrow().is_none() {
119 let id = self.id();
120 let token = exec_after(self.style.delay(), move |token| {
121 id.update_state(token);
122 });
123 self.hover_point = Some((pu.current.logical_point(), token));
124 }
125 }
126 Event::Pointer(_) | Event::Key(_) => {
127 self.hover_point = None;
128 if let Some(id) = self.overlay.borrow_mut().take() {
129 remove_overlay(id);
130 }
131 }
132 _ => {}
133 }
134 EventPropagation::Continue
135 }
136}
137
138pub trait TooltipExt {
140 fn tooltip<V: IntoView + 'static>(self, tip: impl Fn() -> V + 'static) -> Tooltip;
161}
162
163impl<T: IntoView + 'static> TooltipExt for T {
164 fn tooltip<V: IntoView + 'static>(self, tip: impl Fn() -> V + 'static) -> Tooltip {
165 tooltip(self, tip)
166 }
167}