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