text.rs

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