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