1use iced::{
2 Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle, Size, Widget, layout,
3};
4use std::{
5 hash::Hash,
6 sync::Mutex,
7 time::{Duration, Instant},
8};
9use vek::*;
10
11#[derive(Copy, Clone, Debug)]
12struct Hover {
13 start: Instant,
14 aabr: Aabr<i32>,
15}
16
17impl Hover {
18 fn start(aabr: Aabr<i32>) -> Self {
19 Self {
20 start: Instant::now(),
21 aabr,
22 }
23 }
24}
25
26#[derive(Copy, Clone, Debug)]
27struct Show {
28 hover_pos: Vec2<i32>,
29 aabr: Aabr<i32>,
30}
31
32#[derive(Copy, Clone, Debug)]
33enum State {
34 Idle,
35 Start(Hover),
36 Showing(Show),
37 Fading(Instant, Show, Option<Hover>),
38}
39
40#[derive(Copy, Clone, Debug)]
42struct Update((Aabr<i32>, Vec2<i32>));
43
44#[derive(Debug)]
45pub struct TooltipManager {
47 state: State,
48 update: Mutex<Option<Update>>,
49 hover_pos: Vec2<i32>,
50 hover_dur: Duration,
52 fade_dur: Duration,
54}
55
56impl TooltipManager {
57 pub fn new(hover_dur: Duration, fade_dur: Duration) -> Self {
58 Self {
59 state: State::Idle,
60 update: Mutex::new(None),
61 hover_pos: Default::default(),
62 hover_dur,
63 fade_dur,
64 }
65 }
66
67 pub fn maintain(&mut self) {
71 let update = self.update.get_mut().unwrap().take();
72 self.state = if let Some(Update((aabr, hover_pos))) = update {
74 self.hover_pos = hover_pos;
75 match self.state {
76 State::Idle => State::Start(Hover::start(aabr)),
77 State::Start(hover) if hover.aabr != aabr => State::Start(Hover::start(aabr)),
78 State::Start(hover) => State::Start(hover),
79 State::Showing(show) if show.aabr != aabr => {
80 State::Fading(Instant::now(), show, Some(Hover::start(aabr)))
81 },
82 State::Showing(show) => State::Showing(show),
83 State::Fading(start, show, Some(hover)) if hover.aabr == aabr => {
84 State::Fading(start, show, Some(hover))
85 },
86 State::Fading(start, show, _) => {
87 State::Fading(start, show, Some(Hover::start(aabr)))
88 },
89 }
90 } else {
91 match self.state {
92 State::Idle | State::Start(_) => State::Idle,
93 State::Showing(show) => State::Fading(Instant::now(), show, None),
94 State::Fading(start, show, _) => State::Fading(start, show, None),
95 }
96 };
97
98 self.state = match self.state {
100 State::Start(Hover { start, aabr })
101 | State::Fading(_, _, Some(Hover { start, aabr }))
102 if start.elapsed() >= self.hover_dur =>
103 {
104 State::Showing(Show {
105 aabr,
106 hover_pos: self.hover_pos,
107 })
108 },
109 State::Fading(start, _, hover) if start.elapsed() >= self.fade_dur => match hover {
110 Some(hover) => State::Start(hover),
111 None => State::Idle,
112 },
113 state @ State::Idle
114 | state @ State::Start(_)
115 | state @ State::Showing(_)
116 | state @ State::Fading(_, _, _) => state,
117 };
118 }
119
120 fn update(&self, update: Update) { *self.update.lock().unwrap() = Some(update); }
121
122 fn showing(&self, aabr: Aabr<i32>) -> Option<(Point, f32)> {
125 match self.state {
126 State::Idle | State::Start(_) => None,
127 State::Showing(show) => (show.aabr == aabr).then_some({
128 (
129 Point {
130 x: show.hover_pos.x as f32,
131 y: show.hover_pos.y as f32,
132 },
133 1.0,
134 )
135 }),
136 State::Fading(start, show, _) => (show.aabr == aabr)
137 .then(|| {
138 (
139 Point {
140 x: show.hover_pos.x as f32,
141 y: show.hover_pos.y as f32,
142 },
143 1.0 - start.elapsed().as_secs_f32() / self.fade_dur.as_secs_f32(),
144 )
145 })
146 .filter(|(_, fade)| *fade > 0.0),
147 }
148 }
149}
150
151pub struct Tooltip<'a, M, R: Renderer> {
153 content: Element<'a, M, R>,
154 hover_content: Box<dyn 'a + FnMut() -> Element<'a, M, R>>,
155 manager: &'a TooltipManager,
156}
157
158impl<'a, M, R> Tooltip<'a, M, R>
159where
160 R: Renderer,
161{
162 pub fn new<C, H>(content: C, hover_content: H, manager: &'a TooltipManager) -> Self
163 where
164 C: Into<Element<'a, M, R>>,
165 H: 'a + FnMut() -> Element<'a, M, R>,
166 {
167 Self {
168 content: content.into(),
169 hover_content: Box::new(hover_content),
170 manager,
171 }
172 }
173}
174
175impl<M, R> Widget<M, R> for Tooltip<'_, M, R>
176where
177 R: Renderer,
178{
179 fn width(&self) -> Length { self.content.width() }
180
181 fn height(&self) -> Length { self.content.height() }
182
183 fn layout(&self, renderer: &R, limits: &layout::Limits) -> layout::Node {
184 self.content.layout(renderer, limits)
185 }
186
187 fn draw(
188 &self,
189 renderer: &mut R,
190 defaults: &R::Defaults,
191 layout: Layout<'_>,
192 cursor_position: Point,
193 viewport: &Rectangle,
194 ) -> R::Output {
195 let bounds = layout.bounds();
196 if bounds.contains(cursor_position) {
197 let aabr = aabr_from_bounds(bounds);
201 let m_pos = Vec2::new(
202 cursor_position.x.trunc() as i32,
203 cursor_position.y.trunc() as i32,
204 );
205 self.manager.update(Update((aabr, m_pos)));
206 }
207
208 self.content
209 .draw(renderer, defaults, layout, cursor_position, viewport)
210 }
211
212 fn hash_layout(&self, state: &mut Hasher) {
213 struct Marker;
214 std::any::TypeId::of::<Marker>().hash(state);
215 self.content.hash_layout(state);
216 }
217
218 fn on_event(
219 &mut self,
220 event: Event,
221 layout: Layout<'_>,
222 cursor_position: Point,
223 renderer: &R,
224 clipboard: &mut dyn Clipboard,
225 messages: &mut Vec<M>,
226 ) -> iced::event::Status {
227 self.content.on_event(
228 event,
229 layout,
230 cursor_position,
231 renderer,
232 clipboard,
233 messages,
234 )
235 }
236
237 fn overlay(&mut self, layout: Layout<'_>) -> Option<iced::overlay::Element<'_, M, R>> {
238 let bounds = layout.bounds();
239 let aabr = aabr_from_bounds(bounds);
240
241 self.manager.showing(aabr).map(|(cursor_pos, alpha)| {
242 iced::overlay::Element::new(
243 Point::ORIGIN,
244 Box::new(Overlay::new(
245 (self.hover_content)(),
246 cursor_pos,
247 bounds,
248 alpha,
249 )),
250 )
251 })
252 }
253}
254
255impl<'a, M, R> From<Tooltip<'a, M, R>> for Element<'a, M, R>
256where
257 R: 'a + Renderer,
258 M: 'a,
259{
260 fn from(tooltip: Tooltip<'a, M, R>) -> Element<'a, M, R> { Element::new(tooltip) }
261}
262
263fn aabr_from_bounds(bounds: Rectangle) -> Aabr<i32> {
264 let min = Vec2::new(bounds.x.trunc() as i32, bounds.y.trunc() as i32);
265 let max = min + Vec2::new(bounds.width.trunc() as i32, bounds.height.trunc() as i32);
266 Aabr { min, max }
267}
268
269struct Overlay<'a, M, R: Renderer> {
270 content: Element<'a, M, R>,
271 cursor_position: Point,
273 avoid: Rectangle,
275 alpha: f32,
277}
278
279impl<'a, M, R: Renderer> Overlay<'a, M, R> {
280 pub fn new(
281 content: Element<'a, M, R>,
282 cursor_position: Point,
283 avoid: Rectangle,
284 alpha: f32,
285 ) -> Self {
286 Self {
287 content,
288 cursor_position,
289 avoid,
290 alpha,
291 }
292 }
293}
294
295impl<M, R> iced::Overlay<M, R> for Overlay<'_, M, R>
296where
297 R: Renderer,
298{
299 fn layout(&self, renderer: &R, bounds: Size, position: Point) -> layout::Node {
300 let avoid = Rectangle {
301 x: self.avoid.x + position.x,
302 y: self.avoid.y + position.y,
303 ..self.avoid
304 };
305 let cursor_position = Point {
306 x: self.cursor_position.x + position.x,
307 y: self.cursor_position.y + position.y,
308 };
309
310 const PAD: f32 = 8.0; let space_above = (avoid.y - PAD).max(0.0);
312 let space_below = (bounds.height - avoid.y - avoid.height - PAD).max(0.0);
313
314 let limits = layout::Limits::new(
315 Size::ZERO,
316 Size::new(bounds.width, space_above.max(space_below)),
317 );
318
319 let mut node = self.content.layout(renderer, &limits);
320
321 let size = node.size();
322
323 node.move_to(Point {
324 x: (bounds.width - size.width).min(cursor_position.x),
325 y: if space_above >= space_below {
326 avoid.y - size.height - PAD
327 } else {
328 avoid.y + avoid.height + PAD
329 },
330 });
331
332 node
333 }
334
335 fn draw(
336 &self,
337 renderer: &mut R,
338 defaults: &R::Defaults,
339 layout: Layout<'_>,
340 cursor_position: Point,
341 ) -> R::Output {
342 renderer.draw(
343 self.alpha,
344 defaults,
345 cursor_position,
346 &layout.bounds(),
347 &self.content,
348 layout,
349 )
350 }
351
352 fn hash_layout(&self, state: &mut Hasher, position: Point) {
353 struct Marker;
354 std::any::TypeId::of::<Marker>().hash(state);
355
356 (position.x as u32).hash(state);
357 (position.y as u32).hash(state);
358 (self.cursor_position.x as u32).hash(state);
359 (self.avoid.x as u32).hash(state);
360 (self.avoid.y as u32).hash(state);
361 (self.avoid.height as u32).hash(state);
362 (self.avoid.width as u32).hash(state);
363 self.content.hash_layout(state);
364 }
365}
366
367pub trait Renderer: iced::Renderer {
368 fn draw<M>(
369 &mut self,
370 alpha: f32,
371 defaults: &Self::Defaults,
372 cursor_position: Point,
373 viewport: &Rectangle,
374 content: &Element<'_, M, Self>,
375 content_layout: Layout<'_>,
376 ) -> Self::Output;
377}