Skip to main content

gpui/text_system/
window_text_system.rs

1use crate::{
2    InlineLayout, InlineLayoutRequest, LineLayout, LineLayoutCache, LineLayoutIndex, Pixels,
3    Result, ShapedLine, ShapedText, SharedString, TextLayoutOptions, TextRun, TextSystem,
4};
5use derive_more::Deref;
6use std::{ops::Range, sync::Arc};
7
8/// The text layout subsystem for one window, with a cache of shaped lines.
9///
10/// Get it with [`Window::text_system`](crate::Window::text_system). It derefs to the shared [`TextSystem`],
11/// so you can call font methods on it directly.
12///
13/// Layout methods such as [`WindowTextSystem::shape_line`] and
14/// [`WindowTextSystem::shape_text`] cache their results. A layout is reused when
15/// the same text, font size, and shaping settings are requested again. The cache
16/// keeps a layout only while the next frame uses it, so a layout that a frame
17/// doesn't request is dropped at the end of that frame. Changing only colors or
18/// decorations reuses the glyphs and repaints them.
19#[derive(Deref)]
20pub struct WindowTextSystem {
21    line_layout_cache: LineLayoutCache,
22    #[deref]
23    text_system: Arc<TextSystem>,
24}
25
26impl WindowTextSystem {
27    /// Creates a window text system with an empty layout cache on top of `text_system`.
28    ///
29    /// GPUI creates one for each window. You don't need to call this yourself.
30    pub fn new(text_system: Arc<TextSystem>) -> Self {
31        Self {
32            line_layout_cache: LineLayoutCache::new(text_system.platform_text_system.clone()),
33            text_system,
34        }
35    }
36
37    /// Changes whenever fonts are added, which can change the layout of any text.
38    pub(crate) fn font_generation(&self) -> u64 {
39        self.text_system.platform_text_system.font_generation()
40    }
41
42    pub(crate) fn layout_index(&self) -> LineLayoutIndex {
43        self.line_layout_cache.layout_index()
44    }
45
46    pub(crate) fn reuse_layouts(&self, index: Range<LineLayoutIndex>) {
47        self.line_layout_cache.reuse_layouts(index)
48    }
49
50    pub(crate) fn truncate_layouts(&self, index: LineLayoutIndex) {
51        self.line_layout_cache.truncate_layouts(index)
52    }
53
54    /// Shapes one line of text, ready to paint.
55    ///
56    /// Use this for a single line of text that you paint with [`ShapedLine::paint`].
57    /// For text that can wrap or has several paragraphs, use [`Self::shape_text`].
58    /// `runs` styles the text, and the run lengths must add up to `text.len()`.
59    /// Each run's [`TextRun::font`] selects the face used for its bytes.
60    ///
61    /// The result is cached for the frame, as described on [`WindowTextSystem`].
62    ///
63    /// # Panics
64    ///
65    /// In debug builds, panics if `text` contains a `\n`. Release builds don't
66    /// check this, and the result for such text is not meaningful. Use
67    /// [`Self::shape_text`] for multiple lines.
68    ///
69    /// # Examples
70    ///
71    /// ```
72    /// # use gpui::{EmptyView, TestApp, TextRun, font, hsla, px};
73    /// # let mut app = TestApp::new();
74    /// # let mut window = app.open_window(|_window, _cx| EmptyView);
75    /// window.update(|_view, window, _cx| {
76    ///     let run = TextRun {
77    ///         len: "Hello".len(),
78    ///         font: font("Lilex"),
79    ///         color: hsla(0., 0., 0., 1.),
80    ///         ..Default::default()
81    ///     };
82    ///     let line = window.text_system().shape_line("Hello".into(), px(16.), &[run]);
83    ///     assert_eq!(line.len(), 5);
84    /// });
85    /// ```
86    pub fn shape_line(
87        &self,
88        text: SharedString,
89        font_size: Pixels,
90        runs: &[TextRun],
91    ) -> ShapedLine {
92        debug_assert!(
93            text.find('\n').is_none(),
94            "text argument should not contain newlines"
95        );
96
97        let layout = self.layout_line(&text, font_size, runs);
98
99        ShapedLine { layout, text }
100    }
101
102    /// Shapes text that may span several lines, ready to paint.
103    ///
104    /// This is the function to use for paragraphs. Each `\n` starts a new line.
105    /// When `wrap_width` is `Some`, lines wrap at word boundaries to fit within
106    /// that many logical pixels. A word wider than that is not split, so it
107    /// overflows. `None` disables soft wrapping, and only `\n` breaks lines.
108    /// With a wrap width, `line_clamp = Some(n)` limits soft wrapping: once the
109    /// budget is exhausted, the rest of each paragraph occupies an unwrapped line.
110    /// Hard paragraph breaks are preserved, so the total can exceed `n`.
111    /// `Some(0)` behaves like `Some(1)` and disables soft wrapping; it does not hide text.
112    ///
113    /// `runs` styles the text. Each run gives the number of UTF-8 bytes it covers,
114    /// and the runs must cover the whole string and end on character boundaries.
115    /// The `Result` is currently always `Ok`.
116    ///
117    /// Shaping is cached as described on [`WindowTextSystem`]. To lay out text
118    /// inside an element, use [`StyledText`](crate::StyledText) instead. See
119    /// [text](crate::_documentation::guides::text) for an overview.
120    ///
121    /// # Examples
122    ///
123    /// This example supplies the platform's real text system. [`TestApp::new`](crate::TestApp::new)
124    /// uses a simplified test shaper, which does not reproduce paragraph layout.
125    ///
126    /// ```standalone_crate
127    /// # use gpui::{EmptyView, TestApp, TextRun, font, hsla, px};
128    /// let text_system = gpui_platform::current_platform(true).text_system();
129    /// let mut app = TestApp::with_text_system(text_system);
130    /// # let mut window = app.open_window(|_window, _cx| EmptyView);
131    /// window.update(|_view, window, _cx| {
132    ///     let text = "first line\nsecond line";
133    ///     let run = TextRun {
134    ///         len: text.len(),
135    ///         font: font(".SystemUIFont"),
136    ///         color: hsla(0., 0., 0., 1.),
137    ///         ..Default::default()
138    ///     };
139    ///     let shaped = window
140    ///         .text_system()
141    ///         .shape_text(text, px(16.), &[run.clone()], Some(px(200.)), None)
142    ///         .unwrap();
143    ///     assert_eq!(shaped.len(), text.len());
144    ///
145    ///     // A clamp cannot remove the hard break between these paragraphs.
146    ///     let zero = window.text_system()
147    ///         .shape_text(text, px(16.), &[run.clone()], Some(px(20.)), Some(0))
148    ///         .unwrap();
149    ///     let one = window.text_system()
150    ///         .shape_text(text, px(16.), &[run], Some(px(20.)), Some(1))
151    ///         .unwrap();
152    ///     assert_eq!(zero.line_count(), 2);
153    ///     assert_eq!(one.line_count(), 2);
154    /// });
155    /// ```
156    pub fn shape_text<S: AsRef<str> + Into<SharedString>>(
157        &self,
158        text: S,
159        font_size: Pixels,
160        runs: &[TextRun],
161        wrap_width: Option<Pixels>,
162        line_clamp: Option<usize>,
163    ) -> Result<ShapedText> {
164        self.shape_text_with_options(
165            text,
166            font_size,
167            runs,
168            TextLayoutOptions {
169                wrap_width,
170                line_clamp,
171                ..Default::default()
172            },
173        )
174    }
175
176    /// Shapes a text document with explicit paragraph direction, alignment, and bidi options.
177    ///
178    /// This is the lower-level form of [`Self::shape_text`]. Use it when text
179    /// must align within a width different from its wrap width, or must follow
180    /// a specific paragraph direction. The `Result` is currently always `Ok`.
181    #[doc(hidden)]
182    pub fn shape_text_with_options<S: AsRef<str> + Into<SharedString>>(
183        &self,
184        text: S,
185        font_size: Pixels,
186        runs: &[TextRun],
187        options: TextLayoutOptions,
188    ) -> Result<ShapedText> {
189        let text = text.into();
190        let layout = self
191            .line_layout_cache
192            .layout_text_with_options(&text, font_size, runs, options);
193
194        Ok(ShapedText { layout, text })
195    }
196
197    /// Lays out text and atomic boxes together, the way an inline element flows in a paragraph.
198    ///
199    /// Each box is an element that sits between characters, such as an icon or
200    /// a badge. Text wraps around the boxes. The result is cached like other
201    /// layouts.
202    pub fn layout_inline(&self, request: InlineLayoutRequest<'_>) -> Arc<InlineLayout> {
203        self.line_layout_cache.layout_inline(request)
204    }
205
206    /// Lays out one line of text and returns the backend's [`LineLayout`].
207    ///
208    /// Most code should call [`Self::shape_line`] instead, which returns a
209    /// [`ShapedLine`] that you can paint. Use this only when you need the raw
210    /// layout. `text` must not contain newlines, and nothing checks this here.
211    pub fn layout_line(&self, text: &str, font_size: Pixels, runs: &[TextRun]) -> Arc<LineLayout> {
212        self.line_layout_cache
213            .layout_line(&SharedString::new(text), font_size, runs)
214    }
215
216    pub(crate) fn finish_frame(&self) {
217        self.line_layout_cache.finish_frame()
218    }
219}