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}