1use crate::{
2 AnyElement, BorrowWindow, Bounds, Component, Element, LayoutId, Line, Pixels, SharedString,
3 Size, TextRun, ViewContext,
4};
5use parking_lot::Mutex;
6use smallvec::SmallVec;
7use std::{marker::PhantomData, sync::Arc};
8use util::ResultExt;
9
10impl<V: 'static> Component<V> for SharedString {
11 fn render(self) -> AnyElement<V> {
12 Text {
13 text: self,
14 runs: None,
15 state_type: PhantomData,
16 }
17 .render()
18 }
19}
20
21impl<V: 'static> Component<V> for &'static str {
22 fn render(self) -> AnyElement<V> {
23 Text {
24 text: self.into(),
25 runs: None,
26 state_type: PhantomData,
27 }
28 .render()
29 }
30}
31
32// TODO: Figure out how to pass `String` to `child` without this.
33// This impl doesn't exist in the `gpui2` crate.
34impl<V: 'static> Component<V> for String {
35 fn render(self) -> AnyElement<V> {
36 Text {
37 text: self.into(),
38 runs: None,
39 state_type: PhantomData,
40 }
41 .render()
42 }
43}
44
45pub struct Text<V> {
46 text: SharedString,
47 runs: Option<Vec<TextRun>>,
48 state_type: PhantomData<V>,
49}
50
51impl<V: 'static> Text<V> {
52 /// styled renders text that has different runs of different styles.
53 /// callers are responsible for setting the correct style for each run.
54 ////
55 /// For uniform text you can usually just pass a string as a child, and
56 /// cx.text_style() will be used automatically.
57 pub fn styled(text: SharedString, runs: Vec<TextRun>) -> Self {
58 Text {
59 text,
60 runs: Some(runs),
61 state_type: Default::default(),
62 }
63 }
64}
65
66impl<V: 'static> Component<V> for Text<V> {
67 fn render(self) -> AnyElement<V> {
68 AnyElement::new(self)
69 }
70}
71
72impl<V: 'static> Element<V> for Text<V> {
73 type ElementState = Arc<Mutex<Option<TextElementState>>>;
74
75 fn element_id(&self) -> Option<crate::ElementId> {
76 None
77 }
78
79 fn layout(
80 &mut self,
81 _view: &mut V,
82 element_state: Option<Self::ElementState>,
83 cx: &mut ViewContext<V>,
84 ) -> (LayoutId, Self::ElementState) {
85 let element_state = element_state.unwrap_or_default();
86 let text_system = cx.text_system().clone();
87 let text_style = cx.text_style();
88 let font_size = text_style.font_size.to_pixels(cx.rem_size());
89 let line_height = text_style
90 .line_height
91 .to_pixels(font_size.into(), cx.rem_size());
92 let text = self.text.clone();
93
94 let rem_size = cx.rem_size();
95
96 let runs = if let Some(runs) = self.runs.take() {
97 runs
98 } else {
99 vec![text_style.to_run(text.len())]
100 };
101
102 let layout_id = cx.request_measured_layout(Default::default(), rem_size, {
103 let element_state = element_state.clone();
104 move |known_dimensions, _| {
105 let Some(lines) = text_system
106 .layout_text(
107 &text,
108 font_size,
109 &runs[..],
110 known_dimensions.width, // Wrap if we know the width.
111 )
112 .log_err()
113 else {
114 element_state.lock().replace(TextElementState {
115 lines: Default::default(),
116 line_height,
117 });
118 return Size::default();
119 };
120
121 let line_count = lines
122 .iter()
123 .map(|line| line.wrap_count() + 1)
124 .sum::<usize>();
125 let size = Size {
126 width: lines
127 .iter()
128 .map(|line| line.layout.width)
129 .max()
130 .unwrap()
131 .ceil(),
132 height: line_height * line_count,
133 };
134
135 element_state
136 .lock()
137 .replace(TextElementState { lines, line_height });
138
139 size
140 }
141 });
142
143 (layout_id, element_state)
144 }
145
146 fn paint(
147 &mut self,
148 bounds: Bounds<Pixels>,
149 _: &mut V,
150 element_state: &mut Self::ElementState,
151 cx: &mut ViewContext<V>,
152 ) {
153 let element_state = element_state.lock();
154 let element_state = element_state
155 .as_ref()
156 .ok_or_else(|| anyhow::anyhow!("measurement has not been performed on {}", &self.text))
157 .unwrap();
158
159 let line_height = element_state.line_height;
160 let mut line_origin = bounds.origin;
161 for line in &element_state.lines {
162 line.paint(line_origin, line_height, cx).log_err();
163 line_origin.y += line.size(line_height).height;
164 }
165 }
166}
167
168pub struct TextElementState {
169 lines: SmallVec<[Line; 1]>,
170 line_height: Pixels,
171}