text.rs

   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}