text.rs

  1use crate::{
  2    Bounds, Element, ElementId, LayoutId, Pixels, RenderOnce, SharedString, Size, TextRun,
  3    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 = known_dimensions.width.or(match available_space.width {
163                    crate::AvailableSpace::Definite(x) => Some(x),
164                    _ => None,
165                });
166
167                if let Some(text_state) = element_state.0.lock().as_ref() {
168                    if text_state.size.is_some()
169                        && (wrap_width.is_none() || wrap_width == text_state.wrap_width)
170                    {
171                        return text_state.size.unwrap();
172                    }
173                }
174
175                let Some(lines) = text_system
176                    .shape_text(
177                        &text, font_size, &runs, wrap_width, // Wrap if we know the width.
178                    )
179                    .log_err()
180                else {
181                    element_state.lock().replace(TextStateInner {
182                        lines: Default::default(),
183                        line_height,
184                        wrap_width,
185                        size: Some(Size::default()),
186                    });
187                    return Size::default();
188                };
189
190                let mut size: Size<Pixels> = Size::default();
191                for line in &lines {
192                    let line_size = line.size(line_height);
193                    size.height += line_size.height;
194                    size.width = size.width.max(line_size.width).ceil();
195                }
196
197                element_state.lock().replace(TextStateInner {
198                    lines,
199                    line_height,
200                    wrap_width,
201                    size: Some(size),
202                });
203
204                size
205            }
206        });
207
208        layout_id
209    }
210
211    fn paint(&mut self, bounds: Bounds<Pixels>, text: &str, cx: &mut WindowContext) {
212        let element_state = self.lock();
213        let element_state = element_state
214            .as_ref()
215            .ok_or_else(|| anyhow!("measurement has not been performed on {}", text))
216            .unwrap();
217
218        let line_height = element_state.line_height;
219        let mut line_origin = bounds.origin;
220        for line in &element_state.lines {
221            line.paint(line_origin, line_height, cx).log_err();
222            line_origin.y += line.size(line_height).height;
223        }
224    }
225}
226
227struct InteractiveText {
228    element_id: ElementId,
229    text: StyledText,
230}
231
232struct InteractiveTextState {
233    text_state: TextState,
234    clicked_range_ixs: Rc<Cell<SmallVec<[usize; 1]>>>,
235}
236
237impl Element for InteractiveText {
238    type State = InteractiveTextState;
239
240    fn layout(
241        &mut self,
242        state: Option<Self::State>,
243        cx: &mut WindowContext,
244    ) -> (LayoutId, Self::State) {
245        if let Some(InteractiveTextState {
246            text_state,
247            clicked_range_ixs,
248        }) = state
249        {
250            let (layout_id, text_state) = self.text.layout(Some(text_state), cx);
251            let element_state = InteractiveTextState {
252                text_state,
253                clicked_range_ixs,
254            };
255            (layout_id, element_state)
256        } else {
257            let (layout_id, text_state) = self.text.layout(None, cx);
258            let element_state = InteractiveTextState {
259                text_state,
260                clicked_range_ixs: Rc::default(),
261            };
262            (layout_id, element_state)
263        }
264    }
265
266    fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
267        self.text.paint(bounds, &mut state.text_state, cx)
268    }
269}
270
271impl RenderOnce for InteractiveText {
272    type Element = Self;
273
274    fn element_id(&self) -> Option<ElementId> {
275        Some(self.element_id.clone())
276    }
277
278    fn render_once(self) -> Self::Element {
279        self
280    }
281}