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