text.rs

  1use crate::{
  2    AnyElement, BorrowWindow, Bounds, Component, Element, ElementId, LayoutId, Pixels,
  3    SharedString, Size, TextRun, ViewContext, WindowContext, WrappedLine,
  4};
  5use anyhow::anyhow;
  6use parking_lot::{Mutex, MutexGuard};
  7use smallvec::SmallVec;
  8use std::{cell::Cell, rc::Rc, sync::Arc};
  9use util::ResultExt;
 10
 11impl<V: 'static> Element<V> for &'static str {
 12    type State = TextState;
 13
 14    fn element_id(&self) -> Option<ElementId> {
 15        None
 16    }
 17
 18    fn layout(
 19        &mut self,
 20        _: &mut V,
 21        _: Option<Self::State>,
 22        cx: &mut ViewContext<V>,
 23    ) -> (LayoutId, Self::State) {
 24        let mut state = TextState::default();
 25        let layout_id = state.layout(SharedString::from(*self), None, cx);
 26        (layout_id, state)
 27    }
 28
 29    fn paint(
 30        self,
 31        bounds: Bounds<Pixels>,
 32        _: &mut V,
 33        state: &mut TextState,
 34        cx: &mut ViewContext<V>,
 35    ) {
 36        state.paint(bounds, self, cx)
 37    }
 38}
 39
 40impl<V: 'static> Element<V> for SharedString {
 41    type State = TextState;
 42
 43    fn element_id(&self) -> Option<ElementId> {
 44        Some(self.clone().into())
 45    }
 46
 47    fn layout(
 48        &mut self,
 49        _: &mut V,
 50        _: Option<Self::State>,
 51        cx: &mut ViewContext<V>,
 52    ) -> (LayoutId, Self::State) {
 53        let mut state = TextState::default();
 54        let layout_id = state.layout(self.clone(), None, cx);
 55        (layout_id, state)
 56    }
 57
 58    fn paint(
 59        self,
 60        bounds: Bounds<Pixels>,
 61        _: &mut V,
 62        state: &mut TextState,
 63        cx: &mut ViewContext<V>,
 64    ) {
 65        let text_str: &str = self.as_ref();
 66        state.paint(bounds, text_str, cx)
 67    }
 68}
 69
 70pub struct Text {
 71    text: SharedString,
 72    runs: Option<Vec<TextRun>>,
 73}
 74
 75impl Text {
 76    /// Renders text with runs of different styles.
 77    ///
 78    /// Callers are responsible for setting the correct style for each run.
 79    /// For text with a uniform style, you can usually avoid calling this constructor
 80    /// and just pass text directly.
 81    pub fn styled(text: SharedString, runs: Vec<TextRun>) -> Self {
 82        Text {
 83            text,
 84            runs: Some(runs),
 85        }
 86    }
 87}
 88
 89impl<V: 'static> Component<V> for Text {
 90    fn render(self) -> AnyElement<V> {
 91        AnyElement::new(self)
 92    }
 93}
 94
 95impl<V: 'static> Element<V> for Text {
 96    type State = TextState;
 97
 98    fn element_id(&self) -> Option<crate::ElementId> {
 99        None
100    }
101
102    fn layout(
103        &mut self,
104        _view: &mut V,
105        element_state: Option<Self::State>,
106        cx: &mut ViewContext<V>,
107    ) -> (LayoutId, Self::State) {
108        let element_state = element_state.unwrap_or_default();
109        let text_system = cx.text_system().clone();
110        let text_style = cx.text_style();
111        let font_size = text_style.font_size.to_pixels(cx.rem_size());
112        let line_height = text_style
113            .line_height
114            .to_pixels(font_size.into(), cx.rem_size());
115        let text = self.text.clone();
116
117        let rem_size = cx.rem_size();
118
119        let runs = if let Some(runs) = self.runs.take() {
120            runs
121        } else {
122            vec![text_style.to_run(text.len())]
123        };
124
125        let layout_id = cx.request_measured_layout(Default::default(), rem_size, {
126            let element_state = element_state.clone();
127            move |known_dimensions, _| {
128                let Some(lines) = text_system
129                    .shape_text(
130                        &text,
131                        font_size,
132                        &runs[..],
133                        known_dimensions.width, // Wrap if we know the width.
134                    )
135                    .log_err()
136                else {
137                    element_state.lock().replace(TextStateInner {
138                        lines: Default::default(),
139                        line_height,
140                    });
141                    return Size::default();
142                };
143
144                let mut size: Size<Pixels> = Size::default();
145                for line in &lines {
146                    let line_size = line.size(line_height);
147                    size.height += line_size.height;
148                    size.width = size.width.max(line_size.width);
149                }
150
151                element_state
152                    .lock()
153                    .replace(TextStateInner { lines, line_height });
154
155                size
156            }
157        });
158
159        (layout_id, element_state)
160    }
161
162    fn paint(
163        self,
164        bounds: Bounds<Pixels>,
165        _: &mut V,
166        element_state: &mut Self::State,
167        cx: &mut ViewContext<V>,
168    ) {
169        let element_state = element_state.lock();
170        let element_state = element_state
171            .as_ref()
172            .ok_or_else(|| anyhow!("measurement has not been performed on {}", &self.text))
173            .unwrap();
174
175        let line_height = element_state.line_height;
176        let mut line_origin = bounds.origin;
177        for line in &element_state.lines {
178            line.paint(line_origin, line_height, cx).log_err();
179            line_origin.y += line.size(line_height).height;
180        }
181    }
182}
183
184#[derive(Default, Clone)]
185pub struct TextState(Arc<Mutex<Option<TextStateInner>>>);
186
187impl TextState {
188    fn lock(&self) -> MutexGuard<Option<TextStateInner>> {
189        self.0.lock()
190    }
191
192    fn layout(
193        &mut self,
194        text: SharedString,
195        runs: Option<Vec<TextRun>>,
196        cx: &mut WindowContext,
197    ) -> LayoutId {
198        let text_system = cx.text_system().clone();
199        let text_style = cx.text_style();
200        let font_size = text_style.font_size.to_pixels(cx.rem_size());
201        let line_height = text_style
202            .line_height
203            .to_pixels(font_size.into(), cx.rem_size());
204        let text = SharedString::from(text);
205
206        let rem_size = cx.rem_size();
207
208        let runs = if let Some(runs) = runs {
209            runs
210        } else {
211            vec![text_style.to_run(text.len())]
212        };
213
214        let layout_id = cx.request_measured_layout(Default::default(), rem_size, {
215            let element_state = self.clone();
216            move |known_dimensions, _| {
217                let Some(lines) = text_system
218                    .shape_text(
219                        &text,
220                        font_size,
221                        &runs[..],
222                        known_dimensions.width, // Wrap if we know the width.
223                    )
224                    .log_err()
225                else {
226                    element_state.lock().replace(TextStateInner {
227                        lines: Default::default(),
228                        line_height,
229                    });
230                    return Size::default();
231                };
232
233                let mut size: Size<Pixels> = Size::default();
234                for line in &lines {
235                    let line_size = line.size(line_height);
236                    size.height += line_size.height;
237                    size.width = size.width.max(line_size.width);
238                }
239
240                element_state
241                    .lock()
242                    .replace(TextStateInner { lines, line_height });
243
244                size
245            }
246        });
247
248        layout_id
249    }
250
251    fn paint(&mut self, bounds: Bounds<Pixels>, text: &str, cx: &mut WindowContext) {
252        let element_state = self.lock();
253        let element_state = element_state
254            .as_ref()
255            .ok_or_else(|| anyhow!("measurement has not been performed on {}", text))
256            .unwrap();
257
258        let line_height = element_state.line_height;
259        let mut line_origin = bounds.origin;
260        for line in &element_state.lines {
261            line.paint(line_origin, line_height, cx).log_err();
262            line_origin.y += line.size(line_height).height;
263        }
264    }
265}
266
267struct TextStateInner {
268    lines: SmallVec<[WrappedLine; 1]>,
269    line_height: Pixels,
270}
271
272struct InteractiveText {
273    id: ElementId,
274    text: Text,
275}
276
277struct InteractiveTextState {
278    text_state: TextState,
279    clicked_range_ixs: Rc<Cell<SmallVec<[usize; 1]>>>,
280}
281
282impl<V: 'static> Element<V> for InteractiveText {
283    type State = InteractiveTextState;
284
285    fn element_id(&self) -> Option<ElementId> {
286        Some(self.id.clone())
287    }
288
289    fn layout(
290        &mut self,
291        view_state: &mut V,
292        element_state: Option<Self::State>,
293        cx: &mut ViewContext<V>,
294    ) -> (LayoutId, Self::State) {
295        if let Some(InteractiveTextState {
296            text_state,
297            clicked_range_ixs,
298        }) = element_state
299        {
300            let (layout_id, text_state) = self.text.layout(view_state, Some(text_state), cx);
301            let element_state = InteractiveTextState {
302                text_state,
303                clicked_range_ixs,
304            };
305            (layout_id, element_state)
306        } else {
307            let (layout_id, text_state) = self.text.layout(view_state, None, cx);
308            let element_state = InteractiveTextState {
309                text_state,
310                clicked_range_ixs: Rc::default(),
311            };
312            (layout_id, element_state)
313        }
314    }
315
316    fn paint(
317        self,
318        bounds: Bounds<Pixels>,
319        view_state: &mut V,
320        element_state: &mut Self::State,
321        cx: &mut ViewContext<V>,
322    ) {
323        self.text
324            .paint(bounds, view_state, &mut element_state.text_state, cx)
325    }
326}
327
328impl<V: 'static> Component<V> for SharedString {
329    fn render(self) -> AnyElement<V> {
330        Text {
331            text: self,
332            runs: None,
333        }
334        .render()
335    }
336}
337
338impl<V: 'static> Component<V> for &'static str {
339    fn render(self) -> AnyElement<V> {
340        Text {
341            text: self.into(),
342            runs: None,
343        }
344        .render()
345    }
346}
347
348// TODO: Figure out how to pass `String` to `child` without this.
349// This impl doesn't exist in the `gpui2` crate.
350impl<V: 'static> Component<V> for String {
351    fn render(self) -> AnyElement<V> {
352        Text {
353            text: self.into(),
354            runs: None,
355        }
356        .render()
357    }
358}