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}