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    /// The text for this layout (with soft-wraps as newlines)
561    pub fn wrapped_text(&self) -> String {
562        let mut lines = Vec::new();
563        for wrapped in self.0.borrow().as_ref().unwrap().lines.iter() {
564            let mut seen = 0;
565            for boundary in wrapped.layout.wrap_boundaries.iter() {
566                let index = wrapped.layout.unwrapped_layout.runs[boundary.run_ix].glyphs
567                    [boundary.glyph_ix]
568                    .index;
569
570                lines.push(wrapped.text[seen..index].to_string());
571                seen = index;
572            }
573            lines.push(wrapped.text[seen..].to_string());
574        }
575
576        lines.join("\n")
577    }
578}
579
580/// A text element that can be interacted with.
581pub struct InteractiveText {
582    element_id: ElementId,
583    text: StyledText,
584    click_listener:
585        Option<Box<dyn Fn(&[Range<usize>], InteractiveTextClickEvent, &mut Window, &mut App)>>,
586    hover_listener: Option<Box<dyn Fn(Option<usize>, MouseMoveEvent, &mut Window, &mut App)>>,
587    tooltip_builder: Option<Rc<dyn Fn(usize, &mut Window, &mut App) -> Option<AnyView>>>,
588    tooltip_id: Option<TooltipId>,
589    clickable_ranges: Vec<Range<usize>>,
590}
591
592struct InteractiveTextClickEvent {
593    mouse_down_index: usize,
594    mouse_up_index: usize,
595}
596
597#[doc(hidden)]
598#[derive(Default)]
599pub struct InteractiveTextState {
600    mouse_down_index: Rc<Cell<Option<usize>>>,
601    hovered_index: Rc<Cell<Option<usize>>>,
602    active_tooltip: Rc<RefCell<Option<ActiveTooltip>>>,
603}
604
605/// InteractiveTest is a wrapper around StyledText that adds mouse interactions.
606impl InteractiveText {
607    /// Creates a new InteractiveText from the given text.
608    pub fn new(id: impl Into<ElementId>, text: StyledText) -> Self {
609        Self {
610            element_id: id.into(),
611            text,
612            click_listener: None,
613            hover_listener: None,
614            tooltip_builder: None,
615            tooltip_id: None,
616            clickable_ranges: Vec::new(),
617        }
618    }
619
620    /// on_click is called when the user clicks on one of the given ranges, passing the index of
621    /// the clicked range.
622    pub fn on_click(
623        mut self,
624        ranges: Vec<Range<usize>>,
625        listener: impl Fn(usize, &mut Window, &mut App) + 'static,
626    ) -> Self {
627        self.click_listener = Some(Box::new(move |ranges, event, window, cx| {
628            for (range_ix, range) in ranges.iter().enumerate() {
629                if range.contains(&event.mouse_down_index) && range.contains(&event.mouse_up_index)
630                {
631                    listener(range_ix, window, cx);
632                }
633            }
634        }));
635        self.clickable_ranges = ranges;
636        self
637    }
638
639    /// on_hover is called when the mouse moves over a character within the text, passing the
640    /// index of the hovered character, or None if the mouse leaves the text.
641    pub fn on_hover(
642        mut self,
643        listener: impl Fn(Option<usize>, MouseMoveEvent, &mut Window, &mut App) + 'static,
644    ) -> Self {
645        self.hover_listener = Some(Box::new(listener));
646        self
647    }
648
649    /// tooltip lets you specify a tooltip for a given character index in the string.
650    pub fn tooltip(
651        mut self,
652        builder: impl Fn(usize, &mut Window, &mut App) -> Option<AnyView> + 'static,
653    ) -> Self {
654        self.tooltip_builder = Some(Rc::new(builder));
655        self
656    }
657}
658
659impl Element for InteractiveText {
660    type RequestLayoutState = ();
661    type PrepaintState = Hitbox;
662
663    fn id(&self) -> Option<ElementId> {
664        Some(self.element_id.clone())
665    }
666
667    fn request_layout(
668        &mut self,
669        _id: Option<&GlobalElementId>,
670        window: &mut Window,
671        cx: &mut App,
672    ) -> (LayoutId, Self::RequestLayoutState) {
673        self.text.request_layout(None, window, cx)
674    }
675
676    fn prepaint(
677        &mut self,
678        global_id: Option<&GlobalElementId>,
679        bounds: Bounds<Pixels>,
680        state: &mut Self::RequestLayoutState,
681        window: &mut Window,
682        cx: &mut App,
683    ) -> Hitbox {
684        window.with_optional_element_state::<InteractiveTextState, _>(
685            global_id,
686            |interactive_state, window| {
687                let mut interactive_state = interactive_state
688                    .map(|interactive_state| interactive_state.unwrap_or_default());
689
690                if let Some(interactive_state) = interactive_state.as_mut() {
691                    if self.tooltip_builder.is_some() {
692                        self.tooltip_id =
693                            set_tooltip_on_window(&interactive_state.active_tooltip, window);
694                    } else {
695                        // If there is no longer a tooltip builder, remove the active tooltip.
696                        interactive_state.active_tooltip.take();
697                    }
698                }
699
700                self.text.prepaint(None, bounds, state, window, cx);
701                let hitbox = window.insert_hitbox(bounds, false);
702                (hitbox, interactive_state)
703            },
704        )
705    }
706
707    fn paint(
708        &mut self,
709        global_id: Option<&GlobalElementId>,
710        bounds: Bounds<Pixels>,
711        _: &mut Self::RequestLayoutState,
712        hitbox: &mut Hitbox,
713        window: &mut Window,
714        cx: &mut App,
715    ) {
716        let current_view = window.current_view();
717        let text_layout = self.text.layout().clone();
718        window.with_element_state::<InteractiveTextState, _>(
719            global_id.unwrap(),
720            |interactive_state, window| {
721                let mut interactive_state = interactive_state.unwrap_or_default();
722                if let Some(click_listener) = self.click_listener.take() {
723                    let mouse_position = window.mouse_position();
724                    if let Ok(ix) = text_layout.index_for_position(mouse_position) {
725                        if self
726                            .clickable_ranges
727                            .iter()
728                            .any(|range| range.contains(&ix))
729                        {
730                            window.set_cursor_style(crate::CursorStyle::PointingHand, Some(hitbox))
731                        }
732                    }
733
734                    let text_layout = text_layout.clone();
735                    let mouse_down = interactive_state.mouse_down_index.clone();
736                    if let Some(mouse_down_index) = mouse_down.get() {
737                        let hitbox = hitbox.clone();
738                        let clickable_ranges = mem::take(&mut self.clickable_ranges);
739                        window.on_mouse_event(
740                            move |event: &MouseUpEvent, phase, window: &mut Window, cx| {
741                                if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
742                                    if let Ok(mouse_up_index) =
743                                        text_layout.index_for_position(event.position)
744                                    {
745                                        click_listener(
746                                            &clickable_ranges,
747                                            InteractiveTextClickEvent {
748                                                mouse_down_index,
749                                                mouse_up_index,
750                                            },
751                                            window,
752                                            cx,
753                                        )
754                                    }
755
756                                    mouse_down.take();
757                                    window.refresh();
758                                }
759                            },
760                        );
761                    } else {
762                        let hitbox = hitbox.clone();
763                        window.on_mouse_event(move |event: &MouseDownEvent, phase, window, _| {
764                            if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
765                                if let Ok(mouse_down_index) =
766                                    text_layout.index_for_position(event.position)
767                                {
768                                    mouse_down.set(Some(mouse_down_index));
769                                    window.refresh();
770                                }
771                            }
772                        });
773                    }
774                }
775
776                window.on_mouse_event({
777                    let mut hover_listener = self.hover_listener.take();
778                    let hitbox = hitbox.clone();
779                    let text_layout = text_layout.clone();
780                    let hovered_index = interactive_state.hovered_index.clone();
781                    move |event: &MouseMoveEvent, phase, window, cx| {
782                        if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
783                            let current = hovered_index.get();
784                            let updated = text_layout.index_for_position(event.position).ok();
785                            if current != updated {
786                                hovered_index.set(updated);
787                                if let Some(hover_listener) = hover_listener.as_ref() {
788                                    hover_listener(updated, event.clone(), window, cx);
789                                }
790                                cx.notify(current_view);
791                            }
792                        }
793                    }
794                });
795
796                if let Some(tooltip_builder) = self.tooltip_builder.clone() {
797                    let active_tooltip = interactive_state.active_tooltip.clone();
798                    let build_tooltip = Rc::new({
799                        let tooltip_is_hoverable = false;
800                        let text_layout = text_layout.clone();
801                        move |window: &mut Window, cx: &mut App| {
802                            text_layout
803                                .index_for_position(window.mouse_position())
804                                .ok()
805                                .and_then(|position| tooltip_builder(position, window, cx))
806                                .map(|view| (view, tooltip_is_hoverable))
807                        }
808                    });
809
810                    // Use bounds instead of testing hitbox since this is called during prepaint.
811                    let check_is_hovered_during_prepaint = Rc::new({
812                        let source_bounds = hitbox.bounds;
813                        let text_layout = text_layout.clone();
814                        let pending_mouse_down = interactive_state.mouse_down_index.clone();
815                        move |window: &Window| {
816                            text_layout
817                                .index_for_position(window.mouse_position())
818                                .is_ok()
819                                && source_bounds.contains(&window.mouse_position())
820                                && pending_mouse_down.get().is_none()
821                        }
822                    });
823
824                    let check_is_hovered = Rc::new({
825                        let hitbox = hitbox.clone();
826                        let text_layout = text_layout.clone();
827                        let pending_mouse_down = interactive_state.mouse_down_index.clone();
828                        move |window: &Window| {
829                            text_layout
830                                .index_for_position(window.mouse_position())
831                                .is_ok()
832                                && hitbox.is_hovered(window)
833                                && pending_mouse_down.get().is_none()
834                        }
835                    });
836
837                    register_tooltip_mouse_handlers(
838                        &active_tooltip,
839                        self.tooltip_id,
840                        build_tooltip,
841                        check_is_hovered,
842                        check_is_hovered_during_prepaint,
843                        window,
844                    );
845                }
846
847                self.text.paint(None, bounds, &mut (), &mut (), window, cx);
848
849                ((), interactive_state)
850            },
851        );
852    }
853}
854
855impl IntoElement for InteractiveText {
856    type Element = Self;
857
858    fn into_element(self) -> Self::Element {
859        self
860    }
861}