text.rs

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