text.rs

  1use crate::{
  2    register_tooltip_mouse_handlers, set_tooltip_on_window, ActiveTooltip, AnyView, App, Bounds,
  3    DispatchPhase, Element, ElementId, GlobalElementId, HighlightStyle, Hitbox, IntoElement,
  4    LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, SharedString, Size,
  5    TextOverflow, TextRun, TextStyle, TooltipId, WhiteSpace, Window, WrappedLine,
  6    WrappedLineLayout,
  7};
  8use anyhow::anyhow;
  9use smallvec::SmallVec;
 10use std::{
 11    cell::{Cell, RefCell},
 12    mem,
 13    ops::Range,
 14    rc::Rc,
 15    sync::Arc,
 16};
 17use util::ResultExt;
 18
 19impl Element for &'static str {
 20    type RequestLayoutState = TextLayout;
 21    type PrepaintState = ();
 22
 23    fn id(&self) -> Option<ElementId> {
 24        None
 25    }
 26
 27    fn request_layout(
 28        &mut self,
 29        _id: Option<&GlobalElementId>,
 30        window: &mut Window,
 31        cx: &mut App,
 32    ) -> (LayoutId, Self::RequestLayoutState) {
 33        let mut state = TextLayout::default();
 34        let layout_id = state.layout(SharedString::from(*self), None, window, cx);
 35        (layout_id, state)
 36    }
 37
 38    fn prepaint(
 39        &mut self,
 40        _id: Option<&GlobalElementId>,
 41        bounds: Bounds<Pixels>,
 42        text_layout: &mut Self::RequestLayoutState,
 43        _window: &mut Window,
 44        _cx: &mut App,
 45    ) {
 46        text_layout.prepaint(bounds, self)
 47    }
 48
 49    fn paint(
 50        &mut self,
 51        _id: Option<&GlobalElementId>,
 52        _bounds: Bounds<Pixels>,
 53        text_layout: &mut TextLayout,
 54        _: &mut (),
 55        window: &mut Window,
 56        cx: &mut App,
 57    ) {
 58        text_layout.paint(self, window, cx)
 59    }
 60}
 61
 62impl IntoElement for &'static str {
 63    type Element = Self;
 64
 65    fn into_element(self) -> Self::Element {
 66        self
 67    }
 68}
 69
 70impl IntoElement for String {
 71    type Element = SharedString;
 72
 73    fn into_element(self) -> Self::Element {
 74        self.into()
 75    }
 76}
 77
 78impl Element for SharedString {
 79    type RequestLayoutState = TextLayout;
 80    type PrepaintState = ();
 81
 82    fn id(&self) -> Option<ElementId> {
 83        None
 84    }
 85
 86    fn request_layout(
 87        &mut self,
 88
 89        _id: Option<&GlobalElementId>,
 90
 91        window: &mut Window,
 92        cx: &mut App,
 93    ) -> (LayoutId, Self::RequestLayoutState) {
 94        let mut state = TextLayout::default();
 95        let layout_id = state.layout(self.clone(), None, window, cx);
 96        (layout_id, state)
 97    }
 98
 99    fn prepaint(
100        &mut self,
101        _id: Option<&GlobalElementId>,
102        bounds: Bounds<Pixels>,
103        text_layout: &mut Self::RequestLayoutState,
104        _window: &mut Window,
105        _cx: &mut App,
106    ) {
107        text_layout.prepaint(bounds, self.as_ref())
108    }
109
110    fn paint(
111        &mut self,
112        _id: Option<&GlobalElementId>,
113        _bounds: Bounds<Pixels>,
114        text_layout: &mut Self::RequestLayoutState,
115        _: &mut Self::PrepaintState,
116        window: &mut Window,
117        cx: &mut App,
118    ) {
119        text_layout.paint(self.as_ref(), window, cx)
120    }
121}
122
123impl IntoElement for SharedString {
124    type Element = Self;
125
126    fn into_element(self) -> Self::Element {
127        self
128    }
129}
130
131/// Renders text with runs of different styles.
132///
133/// Callers are responsible for setting the correct style for each run.
134/// For text with a uniform style, you can usually avoid calling this constructor
135/// and just pass text directly.
136pub struct StyledText {
137    text: SharedString,
138    runs: Option<Vec<TextRun>>,
139    delayed_highlights: Option<Vec<(Range<usize>, HighlightStyle)>>,
140    layout: TextLayout,
141}
142
143impl StyledText {
144    /// Construct a new styled text element from the given string.
145    pub fn new(text: impl Into<SharedString>) -> Self {
146        StyledText {
147            text: text.into(),
148            runs: None,
149            delayed_highlights: None,
150            layout: TextLayout::default(),
151        }
152    }
153
154    /// Get the layout for this element. This can be used to map indices to pixels and vice versa.
155    pub fn layout(&self) -> &TextLayout {
156        &self.layout
157    }
158
159    /// Set the styling attributes for the given text, as well as
160    /// as any ranges of text that have had their style customized.
161    pub fn with_default_highlights(
162        mut self,
163        default_style: &TextStyle,
164        highlights: impl IntoIterator<Item = (Range<usize>, HighlightStyle)>,
165    ) -> Self {
166        debug_assert!(
167            self.delayed_highlights.is_none(),
168            "Can't use `with_default_highlights` and `with_highlights`"
169        );
170        let runs = Self::compute_runs(&self.text, default_style, highlights);
171        self.runs = Some(runs);
172        self
173    }
174
175    /// Set the styling attributes for the given text, as well as
176    /// as any ranges of text that have had their style customized.
177    pub fn with_highlights(
178        mut self,
179        highlights: impl IntoIterator<Item = (Range<usize>, HighlightStyle)>,
180    ) -> Self {
181        debug_assert!(
182            self.runs.is_none(),
183            "Can't use `with_highlights` and `with_default_highlights`"
184        );
185        self.delayed_highlights = Some(highlights.into_iter().collect::<Vec<_>>());
186        self
187    }
188
189    fn compute_runs(
190        text: &str,
191        default_style: &TextStyle,
192        highlights: impl IntoIterator<Item = (Range<usize>, HighlightStyle)>,
193    ) -> Vec<TextRun> {
194        let mut runs = Vec::new();
195        let mut ix = 0;
196        for (range, highlight) in highlights {
197            if ix < range.start {
198                runs.push(default_style.clone().to_run(range.start - ix));
199            }
200            runs.push(
201                default_style
202                    .clone()
203                    .highlight(highlight)
204                    .to_run(range.len()),
205            );
206            ix = range.end;
207        }
208        if ix < text.len() {
209            runs.push(default_style.to_run(text.len() - ix));
210        }
211        runs
212    }
213
214    /// Set the text runs for this piece of text.
215    pub fn with_runs(mut self, runs: Vec<TextRun>) -> Self {
216        self.runs = Some(runs);
217        self
218    }
219}
220
221impl Element for StyledText {
222    type RequestLayoutState = ();
223    type PrepaintState = ();
224
225    fn id(&self) -> Option<ElementId> {
226        None
227    }
228
229    fn request_layout(
230        &mut self,
231        _id: Option<&GlobalElementId>,
232        window: &mut Window,
233        cx: &mut App,
234    ) -> (LayoutId, Self::RequestLayoutState) {
235        let runs = self.runs.take().or_else(|| {
236            self.delayed_highlights.take().map(|delayed_highlights| {
237                Self::compute_runs(&self.text, &window.text_style(), delayed_highlights)
238            })
239        });
240
241        let layout_id = self.layout.layout(self.text.clone(), runs, window, cx);
242        (layout_id, ())
243    }
244
245    fn prepaint(
246        &mut self,
247        _id: Option<&GlobalElementId>,
248        bounds: Bounds<Pixels>,
249        _: &mut Self::RequestLayoutState,
250        _window: &mut Window,
251        _cx: &mut App,
252    ) {
253        self.layout.prepaint(bounds, &self.text)
254    }
255
256    fn paint(
257        &mut self,
258        _id: Option<&GlobalElementId>,
259        _bounds: Bounds<Pixels>,
260        _: &mut Self::RequestLayoutState,
261        _: &mut Self::PrepaintState,
262        window: &mut Window,
263        cx: &mut App,
264    ) {
265        self.layout.paint(&self.text, window, cx)
266    }
267}
268
269impl IntoElement for StyledText {
270    type Element = Self;
271
272    fn into_element(self) -> Self::Element {
273        self
274    }
275}
276
277/// The Layout for TextElement. This can be used to map indices to pixels and vice versa.
278#[derive(Default, Clone)]
279pub struct TextLayout(Rc<RefCell<Option<TextLayoutInner>>>);
280
281struct TextLayoutInner {
282    lines: SmallVec<[WrappedLine; 1]>,
283    line_height: Pixels,
284    wrap_width: Option<Pixels>,
285    size: Option<Size<Pixels>>,
286    bounds: Option<Bounds<Pixels>>,
287}
288
289impl TextLayout {
290    fn layout(
291        &self,
292        text: SharedString,
293        runs: Option<Vec<TextRun>>,
294        window: &mut Window,
295        _: &mut App,
296    ) -> LayoutId {
297        let text_style = window.text_style();
298        let font_size = text_style.font_size.to_pixels(window.rem_size());
299        let line_height = text_style
300            .line_height
301            .to_pixels(font_size.into(), window.rem_size());
302
303        let mut runs = if let Some(runs) = runs {
304            runs
305        } else {
306            vec![text_style.to_run(text.len())]
307        };
308
309        let layout_id = window.request_measured_layout(Default::default(), {
310            let element_state = self.clone();
311
312            move |known_dimensions, available_space, window, cx| {
313                let wrap_width = if text_style.white_space == WhiteSpace::Normal {
314                    known_dimensions.width.or(match available_space.width {
315                        crate::AvailableSpace::Definite(x) => Some(x),
316                        _ => None,
317                    })
318                } else {
319                    None
320                };
321
322                let (truncate_width, ellipsis) =
323                    if let Some(text_overflow) = text_style.text_overflow {
324                        let width = known_dimensions.width.or(match available_space.width {
325                            crate::AvailableSpace::Definite(x) => match text_style.line_clamp {
326                                Some(max_lines) => Some(x * max_lines),
327                                None => Some(x),
328                            },
329                            _ => None,
330                        });
331
332                        match text_overflow {
333                            TextOverflow::Ellipsis(s) => (width, Some(s)),
334                        }
335                    } else {
336                        (None, None)
337                    };
338
339                if let Some(text_layout) = element_state.0.borrow().as_ref() {
340                    if text_layout.size.is_some()
341                        && (wrap_width.is_none() || wrap_width == text_layout.wrap_width)
342                    {
343                        return text_layout.size.unwrap();
344                    }
345                }
346
347                let mut line_wrapper = cx.text_system().line_wrapper(text_style.font(), font_size);
348                let text = if let Some(truncate_width) = truncate_width {
349                    line_wrapper.truncate_line(text.clone(), truncate_width, ellipsis, &mut runs)
350                } else {
351                    text.clone()
352                };
353
354                let Some(lines) = window
355                    .text_system()
356                    .shape_text(
357                        text,
358                        font_size,
359                        &runs,
360                        wrap_width,            // Wrap if we know the width.
361                        text_style.line_clamp, // Limit the number of lines if line_clamp is set.
362                    )
363                    .log_err()
364                else {
365                    element_state.0.borrow_mut().replace(TextLayoutInner {
366                        lines: Default::default(),
367                        line_height,
368                        wrap_width,
369                        size: Some(Size::default()),
370                        bounds: None,
371                    });
372                    return Size::default();
373                };
374
375                let mut size: Size<Pixels> = Size::default();
376                for line in &lines {
377                    let line_size = line.size(line_height);
378                    size.height += line_size.height;
379                    size.width = size.width.max(line_size.width).ceil();
380                }
381
382                element_state.0.borrow_mut().replace(TextLayoutInner {
383                    lines,
384                    line_height,
385                    wrap_width,
386                    size: Some(size),
387                    bounds: None,
388                });
389
390                size
391            }
392        });
393
394        layout_id
395    }
396
397    fn prepaint(&self, bounds: Bounds<Pixels>, text: &str) {
398        let mut element_state = self.0.borrow_mut();
399        let element_state = element_state
400            .as_mut()
401            .ok_or_else(|| anyhow!("measurement has not been performed on {}", text))
402            .unwrap();
403        element_state.bounds = Some(bounds);
404    }
405
406    fn paint(&self, text: &str, window: &mut Window, cx: &mut App) {
407        let element_state = self.0.borrow();
408        let element_state = element_state
409            .as_ref()
410            .ok_or_else(|| anyhow!("measurement has not been performed on {}", text))
411            .unwrap();
412        let bounds = element_state
413            .bounds
414            .ok_or_else(|| anyhow!("prepaint has not been performed on {:?}", text))
415            .unwrap();
416
417        let line_height = element_state.line_height;
418        let mut line_origin = bounds.origin;
419        let text_style = window.text_style();
420        for line in &element_state.lines {
421            line.paint(
422                line_origin,
423                line_height,
424                text_style.text_align,
425                Some(bounds),
426                window,
427                cx,
428            )
429            .log_err();
430            line_origin.y += line.size(line_height).height;
431        }
432    }
433
434    /// Get the byte index into the input of the pixel position.
435    pub fn index_for_position(&self, mut position: Point<Pixels>) -> Result<usize, usize> {
436        let element_state = self.0.borrow();
437        let element_state = element_state
438            .as_ref()
439            .expect("measurement has not been performed");
440        let bounds = element_state
441            .bounds
442            .expect("prepaint has not been performed");
443
444        if position.y < bounds.top() {
445            return Err(0);
446        }
447
448        let line_height = element_state.line_height;
449        let mut line_origin = bounds.origin;
450        let mut line_start_ix = 0;
451        for line in &element_state.lines {
452            let line_bottom = line_origin.y + line.size(line_height).height;
453            if position.y > line_bottom {
454                line_origin.y = line_bottom;
455                line_start_ix += line.len() + 1;
456            } else {
457                let position_within_line = position - line_origin;
458                match line.index_for_position(position_within_line, line_height) {
459                    Ok(index_within_line) => return Ok(line_start_ix + index_within_line),
460                    Err(index_within_line) => return Err(line_start_ix + index_within_line),
461                }
462            }
463        }
464
465        Err(line_start_ix.saturating_sub(1))
466    }
467
468    /// Get the pixel position for the given byte index.
469    pub fn position_for_index(&self, index: usize) -> Option<Point<Pixels>> {
470        let element_state = self.0.borrow();
471        let element_state = element_state
472            .as_ref()
473            .expect("measurement has not been performed");
474        let bounds = element_state
475            .bounds
476            .expect("prepaint has not been performed");
477        let line_height = element_state.line_height;
478
479        let mut line_origin = bounds.origin;
480        let mut line_start_ix = 0;
481
482        for line in &element_state.lines {
483            let line_end_ix = line_start_ix + line.len();
484            if index < line_start_ix {
485                break;
486            } else if index > line_end_ix {
487                line_origin.y += line.size(line_height).height;
488                line_start_ix = line_end_ix + 1;
489                continue;
490            } else {
491                let ix_within_line = index - line_start_ix;
492                return Some(line_origin + line.position_for_index(ix_within_line, line_height)?);
493            }
494        }
495
496        None
497    }
498
499    /// Retrieve the layout for the line containing the given byte index.
500    pub fn line_layout_for_index(&self, index: usize) -> Option<Arc<WrappedLineLayout>> {
501        let element_state = self.0.borrow();
502        let element_state = element_state
503            .as_ref()
504            .expect("measurement has not been performed");
505        let bounds = element_state
506            .bounds
507            .expect("prepaint has not been performed");
508        let line_height = element_state.line_height;
509
510        let mut line_origin = bounds.origin;
511        let mut line_start_ix = 0;
512
513        for line in &element_state.lines {
514            let line_end_ix = line_start_ix + line.len();
515            if index < line_start_ix {
516                break;
517            } else if index > line_end_ix {
518                line_origin.y += line.size(line_height).height;
519                line_start_ix = line_end_ix + 1;
520                continue;
521            } else {
522                return Some(line.layout.clone());
523            }
524        }
525
526        None
527    }
528
529    /// The bounds of this layout.
530    pub fn bounds(&self) -> Bounds<Pixels> {
531        self.0.borrow().as_ref().unwrap().bounds.unwrap()
532    }
533
534    /// The line height for this layout.
535    pub fn line_height(&self) -> Pixels {
536        self.0.borrow().as_ref().unwrap().line_height
537    }
538
539    /// The text for this layout.
540    pub fn text(&self) -> String {
541        self.0
542            .borrow()
543            .as_ref()
544            .unwrap()
545            .lines
546            .iter()
547            .map(|s| s.text.to_string())
548            .collect::<Vec<_>>()
549            .join("\n")
550    }
551}
552
553/// A text element that can be interacted with.
554pub struct InteractiveText {
555    element_id: ElementId,
556    text: StyledText,
557    click_listener:
558        Option<Box<dyn Fn(&[Range<usize>], InteractiveTextClickEvent, &mut Window, &mut App)>>,
559    hover_listener: Option<Box<dyn Fn(Option<usize>, MouseMoveEvent, &mut Window, &mut App)>>,
560    tooltip_builder: Option<Rc<dyn Fn(usize, &mut Window, &mut App) -> Option<AnyView>>>,
561    tooltip_id: Option<TooltipId>,
562    clickable_ranges: Vec<Range<usize>>,
563}
564
565struct InteractiveTextClickEvent {
566    mouse_down_index: usize,
567    mouse_up_index: usize,
568}
569
570#[doc(hidden)]
571#[derive(Default)]
572pub struct InteractiveTextState {
573    mouse_down_index: Rc<Cell<Option<usize>>>,
574    hovered_index: Rc<Cell<Option<usize>>>,
575    active_tooltip: Rc<RefCell<Option<ActiveTooltip>>>,
576}
577
578/// InteractiveTest is a wrapper around StyledText that adds mouse interactions.
579impl InteractiveText {
580    /// Creates a new InteractiveText from the given text.
581    pub fn new(id: impl Into<ElementId>, text: StyledText) -> Self {
582        Self {
583            element_id: id.into(),
584            text,
585            click_listener: None,
586            hover_listener: None,
587            tooltip_builder: None,
588            tooltip_id: None,
589            clickable_ranges: Vec::new(),
590        }
591    }
592
593    /// on_click is called when the user clicks on one of the given ranges, passing the index of
594    /// the clicked range.
595    pub fn on_click(
596        mut self,
597        ranges: Vec<Range<usize>>,
598        listener: impl Fn(usize, &mut Window, &mut App) + 'static,
599    ) -> Self {
600        self.click_listener = Some(Box::new(move |ranges, event, window, cx| {
601            for (range_ix, range) in ranges.iter().enumerate() {
602                if range.contains(&event.mouse_down_index) && range.contains(&event.mouse_up_index)
603                {
604                    listener(range_ix, window, cx);
605                }
606            }
607        }));
608        self.clickable_ranges = ranges;
609        self
610    }
611
612    /// on_hover is called when the mouse moves over a character within the text, passing the
613    /// index of the hovered character, or None if the mouse leaves the text.
614    pub fn on_hover(
615        mut self,
616        listener: impl Fn(Option<usize>, MouseMoveEvent, &mut Window, &mut App) + 'static,
617    ) -> Self {
618        self.hover_listener = Some(Box::new(listener));
619        self
620    }
621
622    /// tooltip lets you specify a tooltip for a given character index in the string.
623    pub fn tooltip(
624        mut self,
625        builder: impl Fn(usize, &mut Window, &mut App) -> Option<AnyView> + 'static,
626    ) -> Self {
627        self.tooltip_builder = Some(Rc::new(builder));
628        self
629    }
630}
631
632impl Element for InteractiveText {
633    type RequestLayoutState = ();
634    type PrepaintState = Hitbox;
635
636    fn id(&self) -> Option<ElementId> {
637        Some(self.element_id.clone())
638    }
639
640    fn request_layout(
641        &mut self,
642        _id: Option<&GlobalElementId>,
643        window: &mut Window,
644        cx: &mut App,
645    ) -> (LayoutId, Self::RequestLayoutState) {
646        self.text.request_layout(None, window, cx)
647    }
648
649    fn prepaint(
650        &mut self,
651        global_id: Option<&GlobalElementId>,
652        bounds: Bounds<Pixels>,
653        state: &mut Self::RequestLayoutState,
654        window: &mut Window,
655        cx: &mut App,
656    ) -> Hitbox {
657        window.with_optional_element_state::<InteractiveTextState, _>(
658            global_id,
659            |interactive_state, window| {
660                let mut interactive_state = interactive_state
661                    .map(|interactive_state| interactive_state.unwrap_or_default());
662
663                if let Some(interactive_state) = interactive_state.as_mut() {
664                    if self.tooltip_builder.is_some() {
665                        self.tooltip_id =
666                            set_tooltip_on_window(&interactive_state.active_tooltip, window);
667                    } else {
668                        // If there is no longer a tooltip builder, remove the active tooltip.
669                        interactive_state.active_tooltip.take();
670                    }
671                }
672
673                self.text.prepaint(None, bounds, state, window, cx);
674                let hitbox = window.insert_hitbox(bounds, false);
675                (hitbox, interactive_state)
676            },
677        )
678    }
679
680    fn paint(
681        &mut self,
682        global_id: Option<&GlobalElementId>,
683        bounds: Bounds<Pixels>,
684        _: &mut Self::RequestLayoutState,
685        hitbox: &mut Hitbox,
686        window: &mut Window,
687        cx: &mut App,
688    ) {
689        let current_view = window.current_view();
690        let text_layout = self.text.layout().clone();
691        window.with_element_state::<InteractiveTextState, _>(
692            global_id.unwrap(),
693            |interactive_state, window| {
694                let mut interactive_state = interactive_state.unwrap_or_default();
695                if let Some(click_listener) = self.click_listener.take() {
696                    let mouse_position = window.mouse_position();
697                    if let Ok(ix) = text_layout.index_for_position(mouse_position) {
698                        if self
699                            .clickable_ranges
700                            .iter()
701                            .any(|range| range.contains(&ix))
702                        {
703                            window.set_cursor_style(crate::CursorStyle::PointingHand, Some(hitbox))
704                        }
705                    }
706
707                    let text_layout = text_layout.clone();
708                    let mouse_down = interactive_state.mouse_down_index.clone();
709                    if let Some(mouse_down_index) = mouse_down.get() {
710                        let hitbox = hitbox.clone();
711                        let clickable_ranges = mem::take(&mut self.clickable_ranges);
712                        window.on_mouse_event(
713                            move |event: &MouseUpEvent, phase, window: &mut Window, cx| {
714                                if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
715                                    if let Ok(mouse_up_index) =
716                                        text_layout.index_for_position(event.position)
717                                    {
718                                        click_listener(
719                                            &clickable_ranges,
720                                            InteractiveTextClickEvent {
721                                                mouse_down_index,
722                                                mouse_up_index,
723                                            },
724                                            window,
725                                            cx,
726                                        )
727                                    }
728
729                                    mouse_down.take();
730                                    window.refresh();
731                                }
732                            },
733                        );
734                    } else {
735                        let hitbox = hitbox.clone();
736                        window.on_mouse_event(move |event: &MouseDownEvent, phase, window, _| {
737                            if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
738                                if let Ok(mouse_down_index) =
739                                    text_layout.index_for_position(event.position)
740                                {
741                                    mouse_down.set(Some(mouse_down_index));
742                                    window.refresh();
743                                }
744                            }
745                        });
746                    }
747                }
748
749                window.on_mouse_event({
750                    let mut hover_listener = self.hover_listener.take();
751                    let hitbox = hitbox.clone();
752                    let text_layout = text_layout.clone();
753                    let hovered_index = interactive_state.hovered_index.clone();
754                    move |event: &MouseMoveEvent, phase, window, cx| {
755                        if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
756                            let current = hovered_index.get();
757                            let updated = text_layout.index_for_position(event.position).ok();
758                            if current != updated {
759                                hovered_index.set(updated);
760                                if let Some(hover_listener) = hover_listener.as_ref() {
761                                    hover_listener(updated, event.clone(), window, cx);
762                                }
763                                cx.notify(current_view);
764                            }
765                        }
766                    }
767                });
768
769                if let Some(tooltip_builder) = self.tooltip_builder.clone() {
770                    let active_tooltip = interactive_state.active_tooltip.clone();
771                    let build_tooltip = Rc::new({
772                        let tooltip_is_hoverable = false;
773                        let text_layout = text_layout.clone();
774                        move |window: &mut Window, cx: &mut App| {
775                            text_layout
776                                .index_for_position(window.mouse_position())
777                                .ok()
778                                .and_then(|position| tooltip_builder(position, window, cx))
779                                .map(|view| (view, tooltip_is_hoverable))
780                        }
781                    });
782
783                    // Use bounds instead of testing hitbox since this is called during prepaint.
784                    let check_is_hovered_during_prepaint = Rc::new({
785                        let source_bounds = hitbox.bounds;
786                        let text_layout = text_layout.clone();
787                        let pending_mouse_down = interactive_state.mouse_down_index.clone();
788                        move |window: &Window| {
789                            text_layout
790                                .index_for_position(window.mouse_position())
791                                .is_ok()
792                                && source_bounds.contains(&window.mouse_position())
793                                && pending_mouse_down.get().is_none()
794                        }
795                    });
796
797                    let check_is_hovered = Rc::new({
798                        let hitbox = hitbox.clone();
799                        let text_layout = text_layout.clone();
800                        let pending_mouse_down = interactive_state.mouse_down_index.clone();
801                        move |window: &Window| {
802                            text_layout
803                                .index_for_position(window.mouse_position())
804                                .is_ok()
805                                && hitbox.is_hovered(window)
806                                && pending_mouse_down.get().is_none()
807                        }
808                    });
809
810                    register_tooltip_mouse_handlers(
811                        &active_tooltip,
812                        self.tooltip_id,
813                        build_tooltip,
814                        check_is_hovered,
815                        check_is_hovered_during_prepaint,
816                        window,
817                    );
818                }
819
820                self.text.paint(None, bounds, &mut (), &mut (), window, cx);
821
822                ((), interactive_state)
823            },
824        );
825    }
826}
827
828impl IntoElement for InteractiveText {
829    type Element = Self;
830
831    fn into_element(self) -> Self::Element {
832        self
833    }
834}