Skip to main content

gpui/text_system/
paragraph.rs

1//! Optional paragraph interop. Layout providers retain their own interaction data.
2use crate::{Bounds, FontRun, Hsla, Pixels, Point, Result, ShapedRun, Size, Window};
3use std::sync::Arc;
4
5/// Side of a logical boundary at a wrap or bidirectional transition.
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub enum TextAffinity {
8    /// Position associated with the preceding logical cluster.
9    Upstream,
10    /// Position associated with the following logical cluster.
11    Downstream,
12}
13
14/// Paragraph-relative UTF-8 position; providers validate and snap cluster edges.
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub struct ParagraphPosition {
17    /// Logical byte offset in the original source.
18    pub byte_index: usize,
19    /// Visual side of this logical boundary.
20    pub affinity: TextAffinity,
21}
22
23/// Physical horizontal movement, independent of logical text direction.
24#[derive(Clone, Copy, Debug)]
25pub enum VisualDirection {
26    /// Move left in visual order.
27    Left,
28    /// Move right in visual order.
29    Right,
30}
31
32/// Immutable shaping result. Reflow must not mutate previously published layouts.
33pub trait PlatformParagraphShape: Send + Sync {
34    /// Break at a positive finite logical width, with automatic direction/start alignment.
35    fn layout(&self, width: Pixels) -> Result<Arc<dyn PlatformParagraphLayout>>;
36}
37
38/// Immutable paragraph paint and interaction data, all in paragraph-local pixels.
39///
40/// Glyph positions include their line baseline, measured from the paragraph top.
41/// Glyph indices remain logical UTF-8 offsets even when paint order is visual.
42pub trait PlatformParagraphLayout: Send + Sync {
43    /// Original unmodified source.
44    fn source(&self) -> &str;
45    /// Logical font size shared by every run in this initial interface.
46    fn font_size(&self) -> Pixels;
47    /// Paragraph layout size, including its allocated wrapping width.
48    fn size(&self) -> Size<Pixels>;
49    /// Resolved faces/glyphs in visual paint order; never requested descriptor IDs.
50    fn runs(&self) -> &[ShapedRun];
51    /// Find a logical boundary at a local point.
52    fn hit_test(&self, point: Point<Pixels>) -> Result<ParagraphPosition>;
53    /// A one-pixel caret; invalid positions return an error.
54    fn caret_bounds(&self, position: ParagraphPosition) -> Result<Bounds<Pixels>>;
55    /// Move to a neighboring visual cluster boundary.
56    fn move_visual(
57        &self,
58        position: ParagraphPosition,
59        direction: VisualDirection,
60    ) -> Result<ParagraphPosition>;
61    /// Possibly discontiguous selection rectangles.
62    fn selection_bounds(
63        &self,
64        anchor: ParagraphPosition,
65        focus: ParagraphPosition,
66    ) -> Result<Vec<Bounds<Pixels>>>;
67}
68
69/// Paint visual glyph runs through the normal bounds cache and sprite atlas.
70///
71/// Call during element paint. `color_for_index` resolves color by logical source
72/// index (not a forward-only style iterator). Backgrounds, selection and decoration
73/// geometry are caller-owned. No font resolution or text shaping occurs here.
74pub fn paint_paragraph(
75    layout: &dyn PlatformParagraphLayout,
76    origin: Point<Pixels>,
77    color_for_index: impl Fn(usize) -> Hsla,
78    window: &mut Window,
79) -> Result<()> {
80    for run in layout.runs() {
81        for glyph in &run.glyphs {
82            let position = origin + glyph.position;
83            if glyph.is_emoji {
84                window.paint_emoji(position, run.font_id, glyph.id, layout.font_size())?;
85            } else {
86                window.paint_glyph(
87                    position,
88                    run.font_id,
89                    glyph.id,
90                    layout.font_size(),
91                    color_for_index(glyph.index),
92                )?;
93            }
94        }
95    }
96    Ok(())
97}
98
99// FontRun is intentionally the same descriptor identity boundary as flat lines.
100/// Borrowed shaping request for one logical paragraph. Direction is inferred and
101/// alignment is start; future shaping options must also be added to cache keys.
102pub struct ParagraphRequest<'a> {
103    /// Original paragraph UTF-8 source.
104    pub text: &'a str,
105    /// Shared logical font size.
106    pub font_size: Pixels,
107    /// Ordered runs covering source bytes, or empty for the backend default.
108    pub runs: &'a [FontRun],
109}