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