1use crate::{
2 AnyElement, BorrowWindow, Bounds, Component, Element, ElementId, LayoutId, Pixels,
3 SharedString, Size, TextRun, ViewContext, 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<V: 'static> Component<V> for Text {
30 fn render(self) -> AnyElement<V> {
31 AnyElement::new(self)
32 }
33}
34
35impl<V: 'static> Element<V> 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 _view: &mut V,
45 element_state: Option<Self::ElementState>,
46 cx: &mut ViewContext<V>,
47 ) -> (LayoutId, Self::ElementState) {
48 let element_state = element_state.unwrap_or_default();
49 let text_system = cx.text_system().clone();
50 let text_style = cx.text_style();
51 let font_size = text_style.font_size.to_pixels(cx.rem_size());
52 let line_height = text_style
53 .line_height
54 .to_pixels(font_size.into(), cx.rem_size());
55 let text = self.text.clone();
56
57 let rem_size = cx.rem_size();
58
59 let runs = if let Some(runs) = self.runs.take() {
60 runs
61 } else {
62 vec![text_style.to_run(text.len())]
63 };
64
65 let layout_id = cx.request_measured_layout(Default::default(), rem_size, {
66 let element_state = element_state.clone();
67 move |known_dimensions, _| {
68 let Some(lines) = text_system
69 .shape_text(
70 &text,
71 font_size,
72 &runs[..],
73 known_dimensions.width, // Wrap if we know the width.
74 )
75 .log_err()
76 else {
77 element_state.lock().replace(TextStateInner {
78 lines: Default::default(),
79 line_height,
80 });
81 return Size::default();
82 };
83
84 let mut size: Size<Pixels> = Size::default();
85 for line in &lines {
86 let line_size = line.size(line_height);
87 size.height += line_size.height;
88 size.width = size.width.max(line_size.width);
89 }
90
91 element_state
92 .lock()
93 .replace(TextStateInner { lines, line_height });
94
95 size
96 }
97 });
98
99 (layout_id, element_state)
100 }
101
102 fn paint(
103 &mut self,
104 bounds: Bounds<Pixels>,
105 _: &mut V,
106 element_state: &mut Self::ElementState,
107 cx: &mut ViewContext<V>,
108 ) {
109 let element_state = element_state.lock();
110 let element_state = element_state
111 .as_ref()
112 .ok_or_else(|| anyhow::anyhow!("measurement has not been performed on {}", &self.text))
113 .unwrap();
114
115 let line_height = element_state.line_height;
116 let mut line_origin = bounds.origin;
117 for line in &element_state.lines {
118 line.paint(line_origin, line_height, cx).log_err();
119 line_origin.y += line.size(line_height).height;
120 }
121 }
122}
123
124#[derive(Default, Clone)]
125pub struct TextState(Arc<Mutex<Option<TextStateInner>>>);
126
127impl TextState {
128 fn lock(&self) -> MutexGuard<Option<TextStateInner>> {
129 self.0.lock()
130 }
131}
132
133struct TextStateInner {
134 lines: SmallVec<[WrappedLine; 1]>,
135 line_height: Pixels,
136}
137
138struct InteractiveText {
139 id: ElementId,
140 text: Text,
141}
142
143struct InteractiveTextState {
144 text_state: TextState,
145 clicked_range_ixs: Rc<Cell<SmallVec<[usize; 1]>>>,
146}
147
148impl<V: 'static> Element<V> for InteractiveText {
149 type ElementState = InteractiveTextState;
150
151 fn element_id(&self) -> Option<ElementId> {
152 Some(self.id.clone())
153 }
154
155 fn layout(
156 &mut self,
157 view_state: &mut V,
158 element_state: Option<Self::ElementState>,
159 cx: &mut ViewContext<V>,
160 ) -> (LayoutId, Self::ElementState) {
161 if let Some(InteractiveTextState {
162 text_state,
163 clicked_range_ixs,
164 }) = element_state
165 {
166 let (layout_id, text_state) = self.text.layout(view_state, Some(text_state), cx);
167 let element_state = InteractiveTextState {
168 text_state,
169 clicked_range_ixs,
170 };
171 (layout_id, element_state)
172 } else {
173 let (layout_id, text_state) = self.text.layout(view_state, None, cx);
174 let element_state = InteractiveTextState {
175 text_state,
176 clicked_range_ixs: Rc::default(),
177 };
178 (layout_id, element_state)
179 }
180 }
181
182 fn paint(
183 &mut self,
184 bounds: Bounds<Pixels>,
185 view_state: &mut V,
186 element_state: &mut Self::ElementState,
187 cx: &mut ViewContext<V>,
188 ) {
189 self.text
190 .paint(bounds, view_state, &mut element_state.text_state, cx)
191 }
192}
193
194impl<V: 'static> Component<V> for SharedString {
195 fn render(self) -> AnyElement<V> {
196 Text {
197 text: self,
198 runs: None,
199 }
200 .render()
201 }
202}
203
204impl<V: 'static> Component<V> for &'static str {
205 fn render(self) -> AnyElement<V> {
206 Text {
207 text: self.into(),
208 runs: None,
209 }
210 .render()
211 }
212}
213
214// TODO: Figure out how to pass `String` to `child` without this.
215// This impl doesn't exist in the `gpui2` crate.
216impl<V: 'static> Component<V> for String {
217 fn render(self) -> AnyElement<V> {
218 Text {
219 text: self.into(),
220 runs: None,
221 }
222 .render()
223 }
224}