1use crate::core::layout::{self, Layout};
28use crate::core::mouse;
29use crate::core::overlay;
30use crate::core::renderer;
31use crate::core::text;
32use crate::core::time::{Duration, Instant};
33use crate::core::widget::{self, Widget};
34use crate::core::window;
35use crate::core::{Element, Event, Length, Pixels, Point, Rectangle, Shell, Size, Vector};
36
37pub struct Tooltip<W, V> {
61 content: W,
62 tooltip: V,
63 position: Position,
64 gap: f32,
65 snap_within_viewport: bool,
66 delay: Duration,
67}
68
69impl<W, V> Tooltip<W, V> {
70 pub fn new(content: W, tooltip: V) -> Self {
78 Tooltip {
79 content,
80 tooltip,
81 position: Position::default(),
82 gap: 0.0,
83 snap_within_viewport: true,
84 delay: Duration::ZERO,
85 }
86 }
87
88 pub fn position(mut self, position: Position) -> Self {
94 self.position = position;
95 self
96 }
97
98 pub fn gap(mut self, gap: impl Into<Pixels>) -> Self {
100 self.gap = gap.into().0;
101 self
102 }
103
104 pub fn delay(mut self, delay: Duration) -> Self {
108 self.delay = delay;
109 self
110 }
111
112 pub fn snap_within_viewport(mut self, snap: bool) -> Self {
114 self.snap_within_viewport = snap;
115 self
116 }
117}
118
119impl<W, V> widget::Meta for Tooltip<W, V> {}
120
121impl<W, V, Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Tooltip<W, V>
122where
123 Renderer: text::Renderer,
124 W: Widget<Message, Theme, Renderer>,
125 V: Widget<Message, Theme, Renderer>,
126{
127 fn diff(&mut self, tree: &mut widget::Tree) {
128 let state = tree.state.downcast_mut::<State>();
129
130 if let State::Open { needs_relayout, .. } = state {
133 *needs_relayout = true;
134 }
135
136 if tree.children.len() != 2 {
137 tree.children = vec![
138 widget::Tree::new(&self.content),
139 widget::Tree::new(&self.tooltip),
140 ];
141 }
142
143 tree.children[0].diff(&mut self.content);
144 tree.children[1].diff(&mut self.tooltip);
145 }
146
147 fn state(&self) -> widget::tree::State {
148 widget::tree::State::new(State::default())
149 }
150
151 fn tag(&self) -> widget::tree::Tag {
152 widget::tree::Tag::of::<State>()
153 }
154
155 fn size(&self) -> Size<Length> {
156 self.content.size()
157 }
158
159 fn layout(&mut self, tree: &mut widget::Tree, renderer: &Renderer, limits: &layout::Limits) {
160 self.content.layout(&mut tree.children[0], renderer, limits);
161
162 tree.size = tree.children[0].size;
163 }
164
165 fn update(
166 &mut self,
167 tree: &mut widget::Tree,
168 event: &Event,
169 layout: Layout,
170 cursor: mouse::Cursor,
171 renderer: &Renderer,
172 shell: &mut Shell<'_, Message>,
173 viewport: &Rectangle,
174 ) {
175 if let Event::Mouse(_) | Event::Window(window::Event::RedrawRequested(_)) = event {
176 let state = tree.state.downcast_mut::<State>();
177 let now = Instant::now();
178 let cursor_position = cursor.position_over(layout.bounds());
179
180 match (&*state, cursor_position) {
181 (State::Idle, Some(cursor_position)) => {
182 if self.delay == Duration::ZERO {
183 *state = State::Open {
184 cursor_position,
185 needs_relayout: true,
186 };
187 shell.invalidate_overlay();
188 } else {
189 *state = State::Hovered { at: now };
190 }
191
192 shell.request_redraw_at(now + self.delay);
193 }
194 (State::Hovered { .. }, None) => {
195 *state = State::Idle;
196 }
197 (State::Hovered { at, .. }, _) if at.elapsed() < self.delay => {
198 shell.request_redraw_at(now + self.delay - at.elapsed());
199 }
200 (State::Hovered { .. }, Some(cursor_position)) => {
201 *state = State::Open {
202 cursor_position,
203 needs_relayout: true,
204 };
205 shell.invalidate_overlay();
206 }
207 (
208 &State::Open {
209 cursor_position: last_position,
210 ..
211 },
212 Some(cursor_position),
213 ) if self.position == Position::FollowCursor
214 && last_position != cursor_position =>
215 {
216 if let State::Open {
217 cursor_position: ref mut position,
218 ..
219 } = *state
220 {
221 *position = cursor_position;
222 }
223
224 shell.request_redraw();
225 }
226 (State::Open { .. }, None) => {
227 *state = State::Idle;
228 shell.invalidate_overlay();
229
230 if !matches!(event, Event::Window(window::Event::RedrawRequested(_)),) {
231 shell.request_redraw();
232 }
233 }
234 (State::Open { .. }, Some(_)) | (&State::Idle, None) => (),
235 }
236 }
237
238 self.content.update(
239 &mut tree.children[0],
240 event,
241 layout,
242 cursor,
243 renderer,
244 shell,
245 viewport,
246 );
247 }
248
249 fn mouse_interaction(
250 &self,
251 tree: &widget::Tree,
252 layout: Layout,
253 cursor: mouse::Cursor,
254 viewport: &Rectangle,
255 renderer: &Renderer,
256 ) -> mouse::Interaction {
257 self.content
258 .mouse_interaction(&tree.children[0], layout, cursor, viewport, renderer)
259 }
260
261 fn draw(
262 &self,
263 tree: &widget::Tree,
264 renderer: &mut Renderer,
265 theme: &Theme,
266 inherited_style: &renderer::Style,
267 layout: Layout,
268 cursor: mouse::Cursor,
269 viewport: &Rectangle,
270 ) {
271 self.content.draw(
272 &tree.children[0],
273 renderer,
274 theme,
275 inherited_style,
276 layout,
277 cursor,
278 viewport,
279 );
280 }
281
282 fn overlay<'b>(
283 &'b mut self,
284 tree: &'b mut widget::Tree,
285 layout: Layout,
286 renderer: &Renderer,
287 viewport: &Rectangle,
288 translation: Vector,
289 window: Size,
290 ) -> Vec<overlay::Element<'b, Message, Theme, Renderer>> {
291 let state = tree.state.downcast_mut::<State>();
292
293 let mut children = tree.children.iter_mut();
294
295 let content = self.content.overlay(
296 children.next().unwrap(),
297 layout,
298 renderer,
299 viewport,
300 translation,
301 window,
302 );
303
304 let tooltip_tree = children.next().unwrap();
305
306 if let State::Open { needs_relayout, .. } = state
309 && *needs_relayout
310 {
311 self.tooltip.layout(
312 tooltip_tree,
313 renderer,
314 &layout::Limits::new(
315 Size::ZERO,
316 if self.snap_within_viewport {
317 window
318 } else {
319 Size::INFINITE
320 },
321 ),
322 );
323
324 *needs_relayout = false;
325 }
326
327 let tooltip = if let State::Open {
328 cursor_position, ..
329 } = &*state
330 {
331 let content_bounds = layout.bounds() + translation;
332 let cursor_position = *cursor_position + translation;
333 let tooltip_size = tooltip_tree.size;
334
335 let viewport = Rectangle::with_size(window);
336
337 let tooltip_bounds = self.position.resolve(
338 content_bounds,
339 tooltip_size,
340 self.gap,
341 cursor_position,
342 viewport,
343 self.snap_within_viewport,
344 );
345
346 Some(overlay::Element::new(Box::new(Overlay {
347 layout: Layout::new(tooltip_size).move_to(tooltip_bounds.position()),
348 tooltip: &mut self.tooltip,
349 tree: tooltip_tree,
350 window,
351 })))
352 } else {
353 None
354 };
355
356 content.into_iter().chain(tooltip).collect()
357 }
358
359 fn operate(
360 &mut self,
361 tree: &mut widget::Tree,
362 layout: Layout,
363 viewport: &Rectangle,
364 renderer: &Renderer,
365 operation: &mut dyn widget::Operation,
366 ) {
367 operation.container(None, layout.bounds(), viewport);
368 operation.traverse(&mut |operation| {
369 self.content
370 .operate(&mut tree.children[0], layout, viewport, renderer, operation);
371 });
372 }
373}
374
375impl<'a, W, V, Message, Theme, Renderer> From<Tooltip<W, V>>
376 for Element<'a, Message, Theme, Renderer>
377where
378 Message: 'a,
379 Theme: 'a,
380 Renderer: text::Renderer + 'a,
381 W: Widget<Message, Theme, Renderer> + 'a,
382 V: Widget<Message, Theme, Renderer> + 'a,
383{
384 fn from(tooltip: Tooltip<W, V>) -> Element<'a, Message, Theme, Renderer> {
385 tooltip._boxed()
386 }
387}
388
389pub use crate::overlay::Position;
391
392#[derive(Debug, Clone, PartialEq, Default)]
393enum State {
394 #[default]
395 Idle,
396 Hovered {
397 at: Instant,
398 },
399 Open {
400 cursor_position: Point,
401 needs_relayout: bool,
402 },
403}
404
405struct Overlay<'b, V> {
406 layout: Layout,
407 tooltip: &'b mut V,
408 tree: &'b mut widget::Tree,
409 window: Size,
410}
411
412impl<V, Message, Theme, Renderer> overlay::Overlay<Message, Theme, Renderer> for Overlay<'_, V>
413where
414 Renderer: text::Renderer,
415 V: Widget<Message, Theme, Renderer>,
416{
417 fn update(
418 &mut self,
419 event: &Event,
420 cursor: mouse::Cursor,
421 renderer: &Renderer,
422 shell: &mut Shell<'_, Message>,
423 ) {
424 self.tooltip.update(
425 self.tree,
426 event,
427 self.layout,
428 cursor,
429 renderer,
430 shell,
431 &Rectangle::with_size(self.window),
432 );
433 }
434
435 fn mouse_interaction(&self, cursor: mouse::Cursor, renderer: &Renderer) -> mouse::Interaction {
436 self.tooltip.mouse_interaction(
437 self.tree,
438 self.layout,
439 cursor,
440 &Rectangle::with_size(self.window),
441 renderer,
442 )
443 }
444
445 fn draw(
446 &self,
447 renderer: &mut Renderer,
448 theme: &Theme,
449 inherited_style: &renderer::Style,
450 cursor_position: mouse::Cursor,
451 ) {
452 let viewport = Rectangle::with_size(self.window);
455
456 renderer.with_layer(viewport, |renderer| {
457 self.tooltip.draw(
458 self.tree,
459 renderer,
460 theme,
461 inherited_style,
462 self.layout,
463 cursor_position,
464 &viewport,
465 );
466 });
467 }
468
469 fn operate(&mut self, renderer: &Renderer, operation: &mut dyn widget::Operation) {
470 let viewport = Rectangle::with_size(self.window);
471
472 operation.container(None, self.layout.bounds(), &viewport);
473
474 operation.traverse(&mut |operation| {
475 self.tooltip
476 .operate(self.tree, self.layout, &viewport, renderer, operation);
477 });
478 }
479
480 fn overlay<'c>(
481 &'c mut self,
482 renderer: &Renderer,
483 ) -> Vec<overlay::Element<'c, Message, Theme, Renderer>> {
484 self.tooltip.overlay(
485 self.tree,
486 self.layout,
487 renderer,
488 &Rectangle::with_size(self.window),
489 Vector::ZERO,
490 self.window,
491 )
492 }
493}