text.rs

  1use crate::{
  2    AnyElement, BorrowWindow, Bounds, Component, Element, ElementId, LayoutId, Pixels,
  3    SharedString, Size, TextRun, ViewContext, WrappedLine,
  4};
  5use parking_lot::{Mutex, MutexGuard};
  6use smallvec::SmallVec;
  7use std::{cell::Cell, rc::Rc, sync::Arc};
  8use util::ResultExt;
  9
 10pub struct Text {
 11    text: SharedString,
 12    runs: Option<Vec<TextRun>>,
 13}
 14
 15impl Text {
 16    /// Renders text with runs of different styles.
 17    ///
 18    /// Callers are responsible for setting the correct style for each run.
 19    /// For text with a uniform style, you can usually avoid calling this constructor
 20    /// and just pass text directly.
 21    pub fn styled(text: SharedString, runs: Vec<TextRun>) -> Self {
 22        Text {
 23            text,
 24            runs: Some(runs),
 25        }
 26    }
 27}
 28
 29impl<V: 'static> Component<V> for Text {
 30    fn render(self) -> AnyElement<V> {
 31        AnyElement::new(self)
 32    }
 33}
 34
 35impl<V: 'static> Element<V> for Text {
 36    type ElementState = TextState;
 37
 38    fn element_id(&self) -> Option<crate::ElementId> {
 39        None
 40    }
 41
 42    fn layout(
 43        &mut self,
 44        _view: &mut V,
 45        element_state: Option<Self::ElementState>,
 46        cx: &mut ViewContext<V>,
 47    ) -> (LayoutId, Self::ElementState) {
 48        let element_state = element_state.unwrap_or_default();
 49        let text_system = cx.text_system().clone();
 50        let text_style = cx.text_style();
 51        let font_size = text_style.font_size.to_pixels(cx.rem_size());
 52        let line_height = text_style
 53            .line_height
 54            .to_pixels(font_size.into(), cx.rem_size());
 55        let text = self.text.clone();
 56
 57        let rem_size = cx.rem_size();
 58
 59        let runs = if let Some(runs) = self.runs.take() {
 60            runs
 61        } else {
 62            vec![text_style.to_run(text.len())]
 63        };
 64
 65        let layout_id = cx.request_measured_layout(Default::default(), rem_size, {
 66            let element_state = element_state.clone();
 67            move |known_dimensions, available_space| {
 68                let wrap_width = known_dimensions.width.or(match available_space.width {
 69                    crate::AvailableSpace::Definite(x) => Some(x),
 70                    _ => None,
 71                });
 72
 73                if let Some(text_state) = element_state.0.lock().as_ref() {
 74                    if text_state.size.is_some()
 75                        && (wrap_width.is_none() || wrap_width == text_state.wrap_width)
 76                    {
 77                        return text_state.size.unwrap();
 78                    }
 79                }
 80
 81                let Some(lines) = text_system
 82                    .shape_text(
 83                        &text,
 84                        font_size,
 85                        &runs[..],
 86                        wrap_width, // Wrap if we know the width.
 87                    )
 88                    .log_err()
 89                else {
 90                    element_state.lock().replace(TextStateInner {
 91                        lines: Default::default(),
 92                        line_height,
 93                        wrap_width,
 94                        size: Some(Size::default()),
 95                    });
 96                    return Size::default();
 97                };
 98
 99                let mut size: Size<Pixels> = Size::default();
100                for line in &lines {
101                    let line_size = line.size(line_height);
102                    size.height += line_size.height;
103                    size.width = size.width.max(line_size.width);
104                }
105
106                element_state.lock().replace(TextStateInner {
107                    lines,
108                    line_height,
109                    wrap_width,
110                    size: Some(size),
111                });
112
113                size
114            }
115        });
116
117        (layout_id, element_state)
118    }
119
120    fn paint(
121        &mut self,
122        bounds: Bounds<Pixels>,
123        _: &mut V,
124        element_state: &mut Self::ElementState,
125        cx: &mut ViewContext<V>,
126    ) {
127        let element_state = element_state.lock();
128        let element_state = element_state
129            .as_ref()
130            .ok_or_else(|| anyhow::anyhow!("measurement has not been performed on {}", &self.text))
131            .unwrap();
132
133        let line_height = element_state.line_height;
134        let mut line_origin = bounds.origin;
135        for line in &element_state.lines {
136            line.paint(line_origin, line_height, cx).log_err();
137            line_origin.y += line.size(line_height).height;
138        }
139    }
140}
141
142#[derive(Default, Clone)]
143pub struct TextState(Arc<Mutex<Option<TextStateInner>>>);
144
145impl TextState {
146    fn lock(&self) -> MutexGuard<Option<TextStateInner>> {
147        self.0.lock()
148    }
149}
150
151struct TextStateInner {
152    lines: SmallVec<[WrappedLine; 1]>,
153    line_height: Pixels,
154    wrap_width: Option<Pixels>,
155    size: Option<Size<Pixels>>,
156}
157
158struct InteractiveText {
159    id: ElementId,
160    text: Text,
161}
162
163struct InteractiveTextState {
164    text_state: TextState,
165    clicked_range_ixs: Rc<Cell<SmallVec<[usize; 1]>>>,
166}
167
168impl<V: 'static> Element<V> for InteractiveText {
169    type ElementState = InteractiveTextState;
170
171    fn element_id(&self) -> Option<ElementId> {
172        Some(self.id.clone())
173    }
174
175    fn layout(
176        &mut self,
177        view_state: &mut V,
178        element_state: Option<Self::ElementState>,
179        cx: &mut ViewContext<V>,
180    ) -> (LayoutId, Self::ElementState) {
181        if let Some(InteractiveTextState {
182            text_state,
183            clicked_range_ixs,
184        }) = element_state
185        {
186            let (layout_id, text_state) = self.text.layout(view_state, Some(text_state), cx);
187            let element_state = InteractiveTextState {
188                text_state,
189                clicked_range_ixs,
190            };
191            (layout_id, element_state)
192        } else {
193            let (layout_id, text_state) = self.text.layout(view_state, None, cx);
194            let element_state = InteractiveTextState {
195                text_state,
196                clicked_range_ixs: Rc::default(),
197            };
198            (layout_id, element_state)
199        }
200    }
201
202    fn paint(
203        &mut self,
204        bounds: Bounds<Pixels>,
205        view_state: &mut V,
206        element_state: &mut Self::ElementState,
207        cx: &mut ViewContext<V>,
208    ) {
209        self.text
210            .paint(bounds, view_state, &mut element_state.text_state, cx)
211    }
212}
213
214impl<V: 'static> Component<V> for SharedString {
215    fn render(self) -> AnyElement<V> {
216        Text {
217            text: self,
218            runs: None,
219        }
220        .render()
221    }
222}
223
224impl<V: 'static> Component<V> for &'static str {
225    fn render(self) -> AnyElement<V> {
226        Text {
227            text: self.into(),
228            runs: None,
229        }
230        .render()
231    }
232}
233
234// TODO: Figure out how to pass `String` to `child` without this.
235// This impl doesn't exist in the `gpui2` crate.
236impl<V: 'static> Component<V> for String {
237    fn render(self) -> AnyElement<V> {
238        Text {
239            text: self.into(),
240            runs: None,
241        }
242        .render()
243    }
244}