1use gpui::prelude::*;
6use gpui::*;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub enum TextSize {
11 Xs,
13 Sm,
15 #[default]
17 Md,
18 Lg,
20 Xl,
22 Xxl,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
28pub enum TextWeight {
29 Light,
31 #[default]
33 Normal,
34 Medium,
36 Semibold,
38 Bold,
40}
41
42impl TextWeight {
43 fn to_font_weight(&self) -> FontWeight {
44 match self {
45 TextWeight::Light => FontWeight::LIGHT,
46 TextWeight::Normal => FontWeight::NORMAL,
47 TextWeight::Medium => FontWeight::MEDIUM,
48 TextWeight::Semibold => FontWeight::SEMIBOLD,
49 TextWeight::Bold => FontWeight::BOLD,
50 }
51 }
52}
53
54pub struct Text {
56 content: SharedString,
57 size: TextSize,
58 weight: TextWeight,
59 color: Option<Rgba>,
60 muted: bool,
61 truncate: bool,
62}
63
64impl Text {
65 pub fn new(content: impl Into<SharedString>) -> Self {
67 Self {
68 content: content.into(),
69 size: TextSize::default(),
70 weight: TextWeight::default(),
71 color: None,
72 muted: false,
73 truncate: false,
74 }
75 }
76
77 pub fn size(mut self, size: TextSize) -> Self {
79 self.size = size;
80 self
81 }
82
83 pub fn weight(mut self, weight: TextWeight) -> Self {
85 self.weight = weight;
86 self
87 }
88
89 pub fn color(mut self, color: Rgba) -> Self {
91 self.color = Some(color);
92 self
93 }
94
95 pub fn muted(mut self, muted: bool) -> Self {
97 self.muted = muted;
98 self
99 }
100
101 pub fn truncate(mut self, truncate: bool) -> Self {
103 self.truncate = truncate;
104 self
105 }
106
107 pub fn build(self) -> Div {
109 let text_color = if let Some(color) = self.color {
110 color
111 } else if self.muted {
112 rgb(0x888888)
113 } else {
114 rgb(0xffffff)
115 };
116
117 let mut text = div()
118 .text_color(text_color)
119 .font_weight(self.weight.to_font_weight());
120
121 text = match self.size {
123 TextSize::Xs => text.text_xs(),
124 TextSize::Sm => text.text_sm(),
125 TextSize::Md => text.text_sm(),
126 TextSize::Lg => text.text_lg(),
127 TextSize::Xl => text.text_xl(),
128 TextSize::Xxl => text.text_2xl(),
129 };
130
131 if self.truncate {
132 text = text.overflow_hidden().whitespace_nowrap();
133 }
134
135 text.child(self.content)
136 }
137}
138
139impl IntoElement for Text {
140 type Element = Div;
141
142 fn into_element(self) -> Self::Element {
143 self.build()
144 }
145}
146
147pub struct Heading {
149 content: SharedString,
150 level: u8,
151}
152
153impl Heading {
154 pub fn new(content: impl Into<SharedString>) -> Self {
156 Self {
157 content: content.into(),
158 level: 1,
159 }
160 }
161
162 pub fn level(mut self, level: u8) -> Self {
164 self.level = level.clamp(1, 6);
165 self
166 }
167
168 pub fn h1(content: impl Into<SharedString>) -> Self {
170 Self::new(content).level(1)
171 }
172
173 pub fn h2(content: impl Into<SharedString>) -> Self {
175 Self::new(content).level(2)
176 }
177
178 pub fn h3(content: impl Into<SharedString>) -> Self {
180 Self::new(content).level(3)
181 }
182
183 pub fn h4(content: impl Into<SharedString>) -> Self {
185 Self::new(content).level(4)
186 }
187
188 pub fn build(self) -> Div {
190 let mut heading = div()
191 .font_weight(FontWeight::BOLD)
192 .text_color(rgb(0xffffff));
193
194 heading = match self.level {
195 1 => heading.text_2xl(),
196 2 => heading.text_xl(),
197 3 => heading.text_lg(),
198 4 => heading,
199 5 => heading.text_sm(),
200 _ => heading.text_xs(),
201 };
202
203 heading.child(self.content)
204 }
205}
206
207impl IntoElement for Heading {
208 type Element = Div;
209
210 fn into_element(self) -> Self::Element {
211 self.build()
212 }
213}
214
215pub struct Code {
217 content: SharedString,
218 inline: bool,
219}
220
221impl Code {
222 pub fn new(content: impl Into<SharedString>) -> Self {
224 Self {
225 content: content.into(),
226 inline: true,
227 }
228 }
229
230 pub fn block(content: impl Into<SharedString>) -> Self {
232 Self {
233 content: content.into(),
234 inline: false,
235 }
236 }
237
238 pub fn build(self) -> Div {
240 if self.inline {
241 div()
242 .px_1()
243 .py(px(1.0))
244 .bg(rgb(0x2a2a2a))
245 .rounded(px(3.0))
246 .text_xs()
247 .text_color(rgb(0xe06c75))
248 .child(self.content)
249 } else {
250 div()
251 .p_3()
252 .bg(rgb(0x1a1a1a))
253 .rounded_md()
254 .text_sm()
255 .text_color(rgb(0xcccccc))
256 .overflow_hidden()
257 .child(self.content)
258 }
259 }
260}
261
262impl IntoElement for Code {
263 type Element = Div;
264
265 fn into_element(self) -> Self::Element {
266 self.build()
267 }
268}
269
270pub struct Link {
272 id: ElementId,
273 content: SharedString,
274 href: Option<SharedString>,
275 external: bool,
276 on_click: Option<Box<dyn Fn(&mut Window, &mut App) + 'static>>,
277}
278
279impl Link {
280 pub fn new(id: impl Into<ElementId>, content: impl Into<SharedString>) -> Self {
282 Self {
283 id: id.into(),
284 content: content.into(),
285 href: None,
286 external: false,
287 on_click: None,
288 }
289 }
290
291 pub fn href(mut self, href: impl Into<SharedString>) -> Self {
293 self.href = Some(href.into());
294 self
295 }
296
297 pub fn external(mut self, external: bool) -> Self {
299 self.external = external;
300 self
301 }
302
303 pub fn on_click(mut self, handler: impl Fn(&mut Window, &mut App) + 'static) -> Self {
305 self.on_click = Some(Box::new(handler));
306 self
307 }
308
309 pub fn build(self) -> Stateful<Div> {
311 let mut link = div()
312 .id(self.id)
313 .text_color(rgb(0x007acc))
314 .cursor_pointer()
315 .hover(|s| s.text_color(rgb(0x0098ff)));
316
317 if let Some(handler) = self.on_click {
318 link = link.on_mouse_up(MouseButton::Left, move |_event, window, cx| {
319 handler(window, cx);
320 });
321 }
322
323 link = link.child(self.content);
324
325 if self.external {
326 link = link.child(div().text_xs().ml_1().child("↗"));
327 }
328
329 link
330 }
331}
332
333impl IntoElement for Link {
334 type Element = Stateful<Div>;
335
336 fn into_element(self) -> Self::Element {
337 self.build()
338 }
339}