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