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