Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-core/src/elements/hoverable.rs
StratoSDK / crates / strato-ui-core / src / elements / hoverable.rs
1use super::{Point, SelectableElement, Selection, SelectionFragment, ZIndex};
2use crate::platform::Cursor;
3use crate::text::word_boundaries::WordBoundariesPolicy;
4use crate::text::{IsRect, SelectionDirection, SelectionType};
5use crate::TaskId;
6use crate::{
7 event::DispatchedEvent, AfterLayoutContext, AppContext, Element, Event, EventContext,
8 PaintContext,
9};
10use instant::Instant;
11use pathfinder_geometry::rect::RectF;
12use pathfinder_geometry::vector::Vector2F;
13use std::mem;
14use std::sync::{Arc, Mutex, MutexGuard};
15use std::time::Duration;
16 
17/// First arg is is_hovered. True when hovering in, false when hovering out.
18type HoverHandler = Box<dyn FnMut(bool, &mut EventContext, &AppContext, Vector2F)>;
19type ClickHandler = Box<dyn FnMut(&mut EventContext, &AppContext, Vector2F)>;
20 
21pub struct Hoverable {
22 child: Box<dyn Element>,
23 state: MouseStateHandle,
24 origin: Option<Point>,
25 hover_handler: Option<HoverHandler>,
26 // A click is comprised of a mouse down and a mouse up,
27 // both within the hoverable.
28 click_handler: Option<ClickHandler>,
29 mouse_down_handler: Option<ClickHandler>,
30 double_click_handler: Option<ClickHandler>,
31 middle_click_handler: Option<ClickHandler>,
32 right_click_handler: Option<ClickHandler>,
33 forward_click_handler: Option<ClickHandler>,
34 back_click_handler: Option<ClickHandler>,
35 disabled: bool,
36 hover_in_delay: Option<Duration>,
37 hover_out_delay: Option<Duration>,
38 skip_synthetic_hover_out: bool,
39 hover_cursor: Option<Cursor>,
40 reset_cursor_after_click: bool,
41 // This is a short-term solution for properly handling events on stacks. A stack will always
42 // put its children on higher z-indexes than its origin, so a hit test using the standard
43 // `z_index` method would always result in the event being covered (by the children of the
44 // stack). Instead, we track the upper-bound of z-indexes _contained by_ the child element.
45 // Then we use that upper bound to do the hit testing, which means a parent will always get
46 // events from its children, regardless of whether they are stacks or not.
47 child_max_z_index: Option<ZIndex>,
48 //
49 suppress_drag: bool,
50 defer_events_to_children: bool,
51}
52 
53#[derive(Clone, Debug, Default)]
54pub struct MouseState {
55 click_count: Option<u32>,
56 
57 /// Whether the element should be considered hovered.
58 ///
59 /// When there are hover delays, this does not necessarily
60 /// mean that the mouse is actively over the element;
61 /// see [`Self::is_mouse_over_element`] and [`Self::hovered`] for more details.
62 pub(crate) is_hovered: bool,
63 
64 /// Whether the mouse is currently over the element.
65 ///
66 /// This property is _not_ delayed by hover delays.
67 is_mouse_over_element: bool,
68 
69 /// Keep track of whether the last event changing the hover
70 /// state is a synthetic mouse move. If there are two consecutive
71 /// events that both want to alter the hover state, we stop the
72 /// invocation to prevent the potential infinite loop. Note that
73 /// any non-synthetic event should reset this state to false.
74 last_event_is_synthetic_hover: bool,
75 
76 /// A timer that starts when the mouse begins hovering the element.
77 ///
78 /// Only [`Some`] if [`Hoverable::hover_in_delay`] is set.
79 hover_in_timer: Option<HoverTimer>,
80 
81 /// A timer that starts when the mouse is no longer hovering the element.
82 ///
83 /// Only [`Some`] if [`Hoverable::hover_out_delay`] is set.
84 hover_out_timer: Option<HoverTimer>,
85}
86 
87impl MouseState {
88 /// True iff the element is actively being clicked.
89 pub fn is_clicked(&self) -> bool {
90 self.click_count.is_some()
91 }
92 
93 /// [`Some`] iff the element is actively being clicked.
94 /// The number represents how many clicks were registered
95 /// in the corresponding mouse down event.
96 pub fn click_count(&self) -> Option<u32> {
97 self.click_count
98 }
99 
100 /// True iff the element is considered hovered.
101 ///
102 /// This does not necessarily imply that the mouse
103 /// is actively hovering the element because this
104 /// takes into account any delays. For example,
105 /// if there is a hover-in delay, this will be
106 /// true _after_ the delay (if the mouse is still covering the element).
107 /// See [`Self::is_mouse_over_element`] for that.
108 pub fn is_hovered(&self) -> bool {
109 self.is_hovered
110 }
111 
112 /// True iff the mouse is currently over the element.
113 /// This is not affected by any hover delays.
114 pub fn is_mouse_over_element(&self) -> bool {
115 self.is_mouse_over_element
116 }
117 
118 pub fn reset_hover_state(&mut self) {
119 self.is_hovered = false;
120 }
121 
122 /// Fully clear interaction state. Useful when a click triggers navigation or focus changes,
123 /// and the original element will no longer receive follow-up mouse events (e.g. mouseup).
124 /// This prevents immediate re-hover from synthetic mouse events during layout.
125 pub fn reset_interaction_state(&mut self) {
126 // Clear pressed state so clicked styles don't persist
127 self.click_count = None;
128 // Clear hover states so hover styles/tooltips don't persist
129 self.is_hovered = false;
130 self.is_mouse_over_element = false;
131 // Treat the next synthetic hover as a no-op (avoids instant re-hover during layout)
132 self.last_event_is_synthetic_hover = true;
133 // Cancel any pending hover timers
134 self.hover_in_timer = None;
135 self.hover_out_timer = None;
136 }
137 
138 fn set_hover_timer(&mut self, timer_type: HoverTimerType, hover_timer: HoverTimer) {
139 match timer_type {
140 HoverTimerType::HoverIn => self.hover_in_timer = Some(hover_timer),
141 HoverTimerType::HoverOut => self.hover_out_timer = Some(hover_timer),
142 }
143 }
144 
145 fn hover_timer(&self, timer_type: HoverTimerType) -> Option<&HoverTimer> {
146 match timer_type {
147 HoverTimerType::HoverIn => self.hover_in_timer.as_ref(),
148 HoverTimerType::HoverOut => self.hover_out_timer.as_ref(),
149 }
150 }
151 
152 fn take_hover_timer(&mut self, timer_type: HoverTimerType) -> Option<HoverTimer> {
153 match timer_type {
154 HoverTimerType::HoverIn => self.hover_in_timer.take(),
155 HoverTimerType::HoverOut => self.hover_out_timer.take(),
156 }
157 }
158}
159 
160pub type MouseStateHandle = Arc<Mutex<MouseState>>;
161 
162#[derive(Clone, Debug)]
163struct HoverTimer {
164 hover_at: Instant,
165 timer_id: TaskId,
166}
167 
168#[derive(Clone, Copy, Debug)]
169enum HoverTimerType {
170 HoverIn,
171 HoverOut,
172}
173 
174impl HoverTimerType {
175 fn opposite(&self) -> HoverTimerType {
176 match self {
177 Self::HoverIn => Self::HoverOut,
178 Self::HoverOut => Self::HoverIn,
179 }
180 }
181}
182 
183impl Hoverable {
184 pub fn new<F>(state: MouseStateHandle, build_child: F) -> Self
185 where
186 F: FnOnce(&MouseState) -> Box<dyn Element>,
187 {
188 let child = build_child(&state.lock().unwrap());
189 Self {
190 child,
191 state,
192 origin: None,
193 hover_handler: None,
194 click_handler: None,
195 mouse_down_handler: None,
196 double_click_handler: None,
197 middle_click_handler: None,
198 right_click_handler: None,
199 forward_click_handler: None,
200 back_click_handler: None,
201 hover_in_delay: None,
202 hover_out_delay: None,
203 skip_synthetic_hover_out: false,
204 hover_cursor: None,
205 reset_cursor_after_click: false,
206 disabled: false,
207 child_max_z_index: None,
208 suppress_drag: true,
209 defer_events_to_children: false,
210 }
211 }
212 
213 /// Adds additional behavior on hover to any existing hover handler, instead
214 /// of replacing the existing handler.
215 pub fn additional_on_hover<F>(mut self, mut callback: F) -> Self
216 where
217 F: 'static + FnMut(bool, &mut EventContext, &AppContext, Vector2F),
218 {
219 let Some(mut hover_handler) = self.hover_handler else {
220 return self.on_hover(callback);
221 };
222 
223 hover_handler = Box::new(move |is_hovered, ctx, app, pos| {
224 hover_handler(is_hovered, ctx, app, pos);
225 callback(is_hovered, ctx, app, pos);
226 });
227 self.hover_handler = Some(hover_handler);
228 self
229 }
230 
231 /// Fires whenever [`MouseState::hovered`] changes.
232 pub fn on_hover<F>(mut self, callback: F) -> Self
233 where
234 F: 'static + FnMut(bool, &mut EventContext, &AppContext, Vector2F),
235 {
236 self.hover_handler = Some(Box::new(callback));
237 self
238 }
239 
240 /// Fires when the mouse is released within the hoverable after it was pressed within the hoverable.
241 pub fn on_click<F>(mut self, callback: F) -> Self
242 where
243 F: 'static + FnMut(&mut EventContext, &AppContext, Vector2F),
244 {
245 self.click_handler = Some(Box::new(callback));
246 self
247 }
248 
249 /// Fires on `LeftMouseDown` (instead of on mouse up).
250 /// Useful when an action should happen immediately on press (e.g. tab activation).
251 pub fn on_mouse_down<F>(mut self, callback: F) -> Self
252 where
253 F: 'static + FnMut(&mut EventContext, &AppContext, Vector2F),
254 {
255 self.mouse_down_handler = Some(Box::new(callback));
256 self
257 }
258 
259 pub fn on_double_click<F>(mut self, callback: F) -> Self
260 where
261 F: 'static + FnMut(&mut EventContext, &AppContext, Vector2F),
262 {
263 self.double_click_handler = Some(Box::new(callback));
264 self
265 }
266 
267 pub fn on_middle_click<F>(mut self, callback: F) -> Self
268 where
269 F: 'static + FnMut(&mut EventContext, &AppContext, Vector2F),
270 {
271 self.middle_click_handler = Some(Box::new(callback));
272 self
273 }
274 
275 pub fn on_right_click<F>(mut self, callback: F) -> Self
276 where
277 F: 'static + FnMut(&mut EventContext, &AppContext, Vector2F),
278 {
279 self.right_click_handler = Some(Box::new(callback));
280 self
281 }
282 
283 pub fn on_back_click<F>(mut self, callback: F) -> Self
284 where
285 F: 'static + FnMut(&mut EventContext, &AppContext, Vector2F),
286 {
287 self.back_click_handler = Some(Box::new(callback));
288 self
289 }
290 
291 pub fn on_forward_click<F>(mut self, callback: F) -> Self
292 where
293 F: 'static + FnMut(&mut EventContext, &AppContext, Vector2F),
294 {
295 self.forward_click_handler = Some(Box::new(callback));
296 self
297 }
298 
299 /// Sets a delay between the time that the mouse hovers
300 /// over the element and the time that the mouse state is
301 /// considered hovered via [`MouseState::hovered`], including
302 /// when the [`Hoverable::on_hover`] fires.
303 pub fn with_hover_in_delay(mut self, delay: Duration) -> Self {
304 self.hover_in_delay = Some(delay);
305 self
306 }
307 
308 /// Sets a delay between the time that the mouse stops hovering
309 /// over the element and the time that the mouse state is
310 /// considered unhovered via [`MouseState::hovered`], including
311 /// when the [`Hoverable::on_hover`] fires.
312 pub fn with_hover_out_delay(mut self, delay: Duration) -> Self {
313 self.hover_out_delay = Some(delay);
314 self
315 }
316 
317 /// Skip firing [`Hoverable::on_hover`] when an item is hovered on synthetic mouse events.
318 /// Synthetic events are generated by the UI framework when layout changes,
319 /// even though the mouse hasn't actually moved.
320 pub fn with_skip_synthetic_hover_out(mut self) -> Self {
321 self.skip_synthetic_hover_out = true;
322 self
323 }
324 
325 /// Change the mouse cursor when hovered
326 pub fn with_cursor(mut self, cursor: Cursor) -> Self {
327 self.hover_cursor = Some(cursor);
328 self
329 }
330 
331 pub fn with_reset_cursor_after_click(mut self) -> Self {
332 self.reset_cursor_after_click = true;
333 self
334 }
335 
336 pub fn with_propagate_drag(mut self) -> Self {
337 self.suppress_drag = false;
338 self
339 }
340 
341 /// When enabled, skips this Hoverable's click handler if a child element
342 /// already handled the click event.
343 pub fn with_defer_events_to_children(mut self) -> Self {
344 self.defer_events_to_children = true;
345 self
346 }
347 
348 pub fn disable(mut self) -> Self {
349 self.disabled = true;
350 self
351 }
352 
353 fn state(&mut self) -> MutexGuard<'_, MouseState> {
354 self.state.lock().unwrap()
355 }
356 
357 /// Determine if the mouse is currently over the element.
358 ///
359 /// If there is another element above this one at the cursor position, then we treat that as
360 /// outside the element for purposes of [`MouseState`].
361 fn is_mouse_over_element(&self, ctx: &EventContext, position: Vector2F) -> bool {
362 let Some(origin) = self.origin else {
363 log::warn!("self.origin was None in `Hoverable::is_mouse_over_element`");
364 return false;
365 };
366 let Some(size) = self.size() else {
367 log::warn!("self.size() was None in `Hoverable::is_mouse_over_element`");
368 return false;
369 };
370 let Some(z_index) = self.child_max_z_index else {
371 log::warn!("self.child_max_z_index was None in `Hoverable::is_mouse_over_element`");
372 return false;
373 };
374 
375 let is_hovering = ctx
376 .visible_rect(origin, size)
377 .is_some_and(|bound| bound.contains_point(position));
378 
379 let point = Point::from_vec2f(position, z_index);
380 let is_covered = ctx.is_covered(point);
381 
382 is_hovering && !is_covered
383 }
384 
385 fn set_cursor(&mut self, ctx: &mut EventContext) {
386 if let Some((z_index, cursor)) = self.z_index().zip(self.hover_cursor) {
387 ctx.set_cursor(cursor, z_index);
388 }
389 }
390 
391 fn reset_cursor(&mut self, ctx: &mut EventContext) {
392 if self.hover_cursor.is_some() {
393 ctx.reset_cursor();
394 }
395 }
396 
397 fn hover_delay(&self, is_hovered: bool) -> Option<Duration> {
398 if is_hovered {
399 self.hover_in_delay
400 } else {
401 self.hover_out_delay
402 }
403 }
404 
405 /// The main handler for [`Event::MouseMoved`] events.
406 fn handle_mouse_moved(
407 &mut self,
408 position: Vector2F,
409 is_synthetic: bool,
410 ctx: &mut EventContext,
411 app: &AppContext,
412 ) -> bool {
413 let was_mouse_over_element = self.state().is_mouse_over_element;
414 let is_hovered = self.is_mouse_over_element(ctx, position);
415 self.state().is_mouse_over_element = is_hovered;
416 
417 // The type of timer that we might need to set, if there's a corresponding delay.
418 let hover_timer_type = if is_hovered {
419 HoverTimerType::HoverIn
420 } else {
421 HoverTimerType::HoverOut
422 };
423 
424 // If there's a pending hover task for the opposite delay,
425 // cancel it because we're now handling a new hover action.
426 if let Some(timer) = self.state().take_hover_timer(hover_timer_type.opposite()) {
427 ctx.clear_notify_timer(timer.timer_id);
428 }
429 
430 // We set / reset cursors immediately (not taking into account
431 // delays) because we want to reflect the correct cursor as
432 // the user is moving their mouse.
433 if was_mouse_over_element != is_hovered {
434 if is_hovered {
435 self.set_cursor(ctx);
436 } else {
437 self.reset_cursor(ctx);
438 }
439 ctx.notify();
440 }
441 
442 // If there aren't any delays, then we can just handle
443 // the mouse movement immediately.
444 let Some(hover_delay) = self.hover_delay(is_hovered) else {
445 return self.handle_mouse_moved_without_delay(
446 is_hovered,
447 position,
448 is_synthetic,
449 ctx,
450 app,
451 );
452 };
453 
454 // If a timer has already been started, then only handle
455 // the event if the timer is expired. Otherwise, we'll wait
456 // until the timer expires.
457 let timer = self.state().hover_timer(hover_timer_type).cloned();
458 if let Some(timer) = timer {
459 if Instant::now() >= timer.hover_at {
460 return self.handle_mouse_moved_without_delay(
461 is_hovered,
462 position,
463 is_synthetic,
464 ctx,
465 app,
466 );
467 }
468 } else {
469 // If a timer has not been started, start it now.
470 let (timer_id, hover_at) = ctx.notify_after(hover_delay);
471 self.state()
472 .set_hover_timer(hover_timer_type, HoverTimer { hover_at, timer_id });
473 }
474 false
475 }
476 
477 /// Handles [`Event::MouseMoved`] events when the
478 /// element is going transitioning between hovered <-> unhovered
479 /// states (identified by `is_hovered`).
480 ///
481 /// This does _not_ take into account any delays; the handler
482 /// immediately sets the hovered state and fires any related
483 /// callbacks.
484 fn handle_mouse_moved_without_delay(
485 &mut self,
486 is_hovered: bool,
487 position: Vector2F,
488 is_synthetic: bool,
489 ctx: &mut EventContext,
490 app: &AppContext,
491 ) -> bool {
492 // If there's no change in hover-state, then there's
493 // no work to do.
494 //
495 // Note: we intentionally compare the `hovered` property
496 // and not the `is_mouse_over_element` property.
497 let was_hovered = self.state().is_hovered;
498 if was_hovered == is_hovered {
499 return false;
500 }
501 self.state().is_hovered = is_hovered;
502 
503 // We should only handle this event if not both the previous and current instance of the state change
504 // is triggered by a synthetic mouse event. This is to prevent infinite loops when a child element
505 // conditional on the state of the hoverable might in return trigger the state change of the hoverable.
506 //
507 // TODO: we should re-consider this approach. It can lead to missed `on_hover` dispatches.
508 let was_synthetic = mem::replace(
509 &mut self.state().last_event_is_synthetic_hover,
510 is_synthetic,
511 );
512 if was_synthetic && is_synthetic {
513 log::warn!(
514 "Not handling MouseMoved event in Hoverable due to back-to-back synthetic events."
515 );
516 return false;
517 }
518 
519 // Skip synthetic hover-out events if configured to do so.
520 if !is_hovered && is_synthetic && self.skip_synthetic_hover_out {
521 return false;
522 }
523 
524 // If there's a [`Hoverable::on_hover`] callback registered, call it.
525 if let Some(handler) = self.hover_handler.as_mut() {
526 handler(is_hovered, ctx, app, position);
527 };
528 
529 ctx.notify();
530 true
531 }
532}
533 
534impl Element for Hoverable {
535 fn layout(
536 &mut self,
537 constraint: crate::SizeConstraint,
538 ctx: &mut crate::LayoutContext,
539 app: &AppContext,
540 ) -> Vector2F {
541 self.child.layout(constraint, ctx, app)
542 }
543 
544 fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &AppContext) {
545 self.child.after_layout(ctx, app)
546 }
547 
548 fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) {
549 self.origin = Some(Point::from_vec2f(origin, ctx.scene.z_index()));
550 self.child.paint(origin, ctx, app);
551 
552 self.child_max_z_index = Some(ctx.scene.max_active_z_index());
553 }
554 
555 fn size(&self) -> Option<Vector2F> {
556 self.child.size()
557 }
558 
559 fn dispatch_event(
560 &mut self,
561 event: &DispatchedEvent,
562 ctx: &mut EventContext,
563 app: &AppContext,
564 ) -> bool {
565 let handled = self.child.dispatch_event(event, ctx, app);
566 if self.disabled {
567 return handled;
568 }
569 
570 if self.defer_events_to_children && handled {
571 return true;
572 }
573 
574 if self.bounds().is_none() {
575 return handled;
576 }
577 
578 if !matches!(event.raw_event(), Event::MouseMoved { .. }) {
579 self.state().last_event_is_synthetic_hover = false;
580 }
581 
582 // If there's a mouse-down event outside of the element,
583 // there's nothing to do except reset the hover state
584 // (because there might have been a hover delay in-progress).
585 if let Some(position) = event.raw_event().mouse_down_position() {
586 if !self.is_mouse_over_element(ctx, position) {
587 self.state().is_hovered = false;
588 self.state().is_mouse_over_element = false;
589 return handled;
590 }
591 }
592 
593 match event.raw_event() {
594 Event::MiddleMouseDown { position, .. } => {
595 if let Some(handler) = self.middle_click_handler.as_mut() {
596 handler(ctx, app, *position);
597 ctx.notify();
598 return true;
599 }
600 }
601 Event::BackMouseDown { position, .. } => {
602 if let Some(handler) = self.back_click_handler.as_mut() {
603 handler(ctx, app, *position);
604 ctx.notify();
605 return true;
606 }
607 }
608 Event::ForwardMouseDown { position, .. } => {
609 if let Some(handler) = self.forward_click_handler.as_mut() {
610 handler(ctx, app, *position);
611 ctx.notify();
612 return true;
613 }
614 }
615 Event::RightMouseDown { position, .. } => {
616 if let Some(handler) = self.right_click_handler.as_mut() {
617 handler(ctx, app, *position);
618 ctx.notify();
619 return true;
620 }
621 }
622 Event::LeftMouseDown {
623 click_count,
624 position,
625 ..
626 } => {
627 // Mouse-down sets the mouse state handle accordingly.
628 self.state().click_count = Some(*click_count);
629 
630 // Fire the mouse-down handler immediately if one is set.
631 if let Some(handler) = self.mouse_down_handler.as_mut() {
632 handler(ctx, app, *position);
633 ctx.notify();
634 return true;
635 }
636 
637 // We mark this as handled if we have a handler waiting to take action on the mouse-up event.
638 if self.click_handler.is_some()
639 || (*click_count == 2 && self.double_click_handler.is_some())
640 {
641 ctx.notify();
642 return true;
643 }
644 }
645 Event::LeftMouseUp { position, .. } => {
646 // Mouse-up should always reset clicked and double-clicked to false.
647 let click_count = self.state().click_count.take();
648 
649 // If the event occurs outside the element, don't handle it.
650 if !self.is_mouse_over_element(ctx, *position) {
651 return handled;
652 }
653 
654 if self.reset_cursor_after_click {
655 ctx.reset_cursor();
656 }
657 
658 // The double-clicked handler takes precendence. However, we should still fall back to the single-click handler
659 // on a double-click if there's no double-click handler set.
660 if matches!(click_count, Some(2)) && self.double_click_handler.is_some() {
661 let handler = self
662 .double_click_handler
663 .as_mut()
664 .expect("handler should exist");
665 handler(ctx, app, *position);
666 ctx.notify();
667 return true;
668 } else if click_count.is_some() && self.click_handler.is_some() {
669 let handler = self.click_handler.as_mut().expect("handler should exist");
670 handler(ctx, app, *position);
671 ctx.notify();
672 return true;
673 }
674 }
675 Event::MouseMoved {
676 position,
677 is_synthetic,
678 ..
679 } => {
680 if self.handle_mouse_moved(*position, *is_synthetic, ctx, app) {
681 return true;
682 }
683 }
684 Event::LeftMouseDragged { .. } => {
685 if self.suppress_drag && self.state().is_clicked() {
686 return true;
687 }
688 }
689 _ => {}
690 }
691 
692 handled
693 }
694 
695 fn origin(&self) -> Option<Point> {
696 self.origin
697 }
698 
699 fn as_selectable_element(&self) -> Option<&dyn SelectableElement> {
700 Some(self as &dyn SelectableElement)
701 }
702 
703 #[cfg(any(test, feature = "test-util"))]
704 fn debug_text_content(&self) -> Option<String> {
705 self.child.debug_text_content()
706 }
707}
708 
709impl SelectableElement for Hoverable {
710 fn get_selection(
711 &self,
712 selection_start: Vector2F,
713 selection_end: Vector2F,
714 is_rect: IsRect,
715 ) -> Option<Vec<SelectionFragment>> {
716 self.child
717 .as_selectable_element()
718 .and_then(|selectable_child| {
719 selectable_child.get_selection(selection_start, selection_end, is_rect)
720 })
721 }
722 
723 fn expand_selection(
724 &self,
725 point: Vector2F,
726 direction: SelectionDirection,
727 unit: SelectionType,
728 word_boundaries_policy: &WordBoundariesPolicy,
729 ) -> Option<Vector2F> {
730 self.child
731 .as_selectable_element()
732 .and_then(|selectable_child| {
733 selectable_child.expand_selection(point, direction, unit, word_boundaries_policy)
734 })
735 }
736 
737 fn is_point_semantically_before(
738 &self,
739 absolute_point: Vector2F,
740 absolute_point_other: Vector2F,
741 ) -> Option<bool> {
742 self.child
743 .as_selectable_element()
744 .and_then(|selectable_child| {
745 selectable_child.is_point_semantically_before(absolute_point, absolute_point_other)
746 })
747 }
748 
749 fn smart_select(
750 &self,
751 absolute_point: Vector2F,
752 smart_select_fn: crate::elements::SmartSelectFn,
753 ) -> Option<(Vector2F, Vector2F)> {
754 self.child
755 .as_selectable_element()
756 .and_then(|selectable_child| {
757 selectable_child.smart_select(absolute_point, smart_select_fn)
758 })
759 }
760 
761 fn calculate_clickable_bounds(&self, current_selection: Option<Selection>) -> Vec<RectF> {
762 self.child
763 .as_selectable_element()
764 .map(|selectable_child| selectable_child.calculate_clickable_bounds(current_selection))
765 .unwrap_or_default()
766 }
767}
768 
769#[cfg(test)]
770#[path = "hoverable_test.rs"]
771mod tests;
772