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}