text.rs

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