1use crate::prelude::*;
2use gpui::{FontWeight, Rems, StyleRefinement, UnderlineStyle};
3use smallvec::SmallVec;
4
5/// Sets the size of a label
6#[derive(Debug, PartialEq, Clone, Copy, Default)]
7pub enum LabelSize {
8 /// The default size of a label.
9 #[default]
10 Default,
11 /// The large size of a label.
12 Large,
13 /// The small size of a label.
14 Small,
15 /// The extra small size of a label.
16 XSmall,
17 /// An arbitrary custom size specified in rems.
18 Custom(Rems),
19}
20
21/// Sets the line height of a label
22#[derive(Default, PartialEq, Copy, Clone)]
23pub enum LineHeightStyle {
24 /// The default line height style of a label,
25 /// set by either the UI's default line height,
26 /// or the developer's default buffer line height.
27 #[default]
28 TextLabel,
29 /// Sets the line height to 1.
30 UiLabel,
31}
32
33/// A common set of traits all labels must implement.
34pub trait LabelCommon {
35 /// Sets the size of the label using a [`LabelSize`].
36 fn size(self, size: LabelSize) -> Self;
37
38 /// Sets the font weight of the label.
39 fn weight(self, weight: FontWeight) -> Self;
40
41 /// Sets the line height style of the label using a [`LineHeightStyle`].
42 fn line_height_style(self, line_height_style: LineHeightStyle) -> Self;
43
44 /// Sets the color of the label using a [`Color`].
45 fn color(self, color: Color) -> Self;
46
47 /// Sets the strikethrough property of the label.
48 fn strikethrough(self) -> Self;
49
50 /// Sets the italic property of the label.
51 fn italic(self) -> Self;
52
53 /// Sets the underline property of the label
54 fn underline(self) -> Self;
55
56 /// Sets the alpha property of the label, overwriting the alpha value of the color.
57 fn alpha(self, alpha: f32) -> Self;
58
59 /// Truncates overflowing text with an ellipsis (`…`) at the end if needed.
60 fn truncate(self) -> Self;
61
62 /// Sets the label to render as a single line.
63 fn single_line(self) -> Self;
64
65 /// Sets the font to the buffer's
66 fn buffer_font(self, cx: &App) -> Self;
67
68 /// Styles the label to look like inline code.
69 fn inline_code(self, cx: &App) -> Self;
70}
71
72/// A label-like element that can be used to create a custom label when
73/// prebuilt labels are not sufficient. Use this sparingly, as it is
74/// unconstrained and may make the UI feel less consistent.
75///
76/// This is also used to build the prebuilt labels.
77#[derive(IntoElement)]
78pub struct LabelLike {
79 pub(super) base: Div,
80 size: LabelSize,
81 weight: Option<FontWeight>,
82 line_height_style: LineHeightStyle,
83 pub(crate) color: Color,
84 strikethrough: bool,
85 italic: bool,
86 children: SmallVec<[AnyElement; 2]>,
87 alpha: Option<f32>,
88 underline: bool,
89 single_line: bool,
90 truncate: bool,
91 truncate_start: bool,
92}
93
94impl Default for LabelLike {
95 fn default() -> Self {
96 Self::new()
97 }
98}
99
100impl LabelLike {
101 /// Creates a new, fully custom label.
102 /// Prefer using [`Label`] or [`HighlightedLabel`] where possible.
103 pub fn new() -> Self {
104 Self {
105 base: div(),
106 size: LabelSize::Default,
107 weight: None,
108 line_height_style: LineHeightStyle::default(),
109 color: Color::Default,
110 strikethrough: false,
111 italic: false,
112 children: SmallVec::new(),
113 alpha: None,
114 underline: false,
115 single_line: false,
116 truncate: false,
117 truncate_start: false,
118 }
119 }
120}
121
122// Style methods.
123impl LabelLike {
124 fn style(&mut self) -> &mut StyleRefinement {
125 self.base.style()
126 }
127
128 gpui::margin_style_methods!({
129 visibility: pub
130 });
131
132 /// Truncates overflowing text with an ellipsis (`…`) at the start if needed.
133 pub fn truncate_start(mut self) -> Self {
134 self.truncate_start = true;
135 self
136 }
137}
138
139impl LabelCommon for LabelLike {
140 fn size(mut self, size: LabelSize) -> Self {
141 self.size = size;
142 self
143 }
144
145 fn weight(mut self, weight: FontWeight) -> Self {
146 self.weight = Some(weight);
147 self
148 }
149
150 fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self {
151 self.line_height_style = line_height_style;
152 self
153 }
154
155 fn color(mut self, color: Color) -> Self {
156 self.color = color;
157 self
158 }
159
160 fn strikethrough(mut self) -> Self {
161 self.strikethrough = true;
162 self
163 }
164
165 fn italic(mut self) -> Self {
166 self.italic = true;
167 self
168 }
169
170 fn underline(mut self) -> Self {
171 self.underline = true;
172 self
173 }
174
175 fn alpha(mut self, alpha: f32) -> Self {
176 self.alpha = Some(alpha);
177 self
178 }
179
180 /// Truncates overflowing text with an ellipsis (`…`) at the end if needed.
181 fn truncate(mut self) -> Self {
182 self.truncate = true;
183 self
184 }
185
186 fn single_line(mut self) -> Self {
187 self.single_line = true;
188 self
189 }
190
191 fn buffer_font(mut self, cx: &App) -> Self {
192 let font = theme::theme_settings(cx).buffer_font(cx).clone();
193 self.weight = Some(font.weight);
194 self.base = self.base.font(font);
195 self
196 }
197
198 fn inline_code(mut self, cx: &App) -> Self {
199 self.base = self
200 .base
201 .font(theme::theme_settings(cx).buffer_font(cx).clone())
202 .bg(cx.theme().colors().element_background)
203 .rounded_sm()
204 .px_0p5();
205 self
206 }
207}
208
209impl ParentElement for LabelLike {
210 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
211 self.children.extend(elements)
212 }
213}
214
215impl RenderOnce for LabelLike {
216 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
217 let mut color = self.color.color(cx);
218 if let Some(alpha) = self.alpha {
219 color.fade_out(1.0 - alpha);
220 }
221
222 self.base
223 .map(|this| match self.size {
224 LabelSize::Large => this.text_ui_lg(cx),
225 LabelSize::Default => this.text_ui(cx),
226 LabelSize::Small => this.text_ui_sm(cx),
227 LabelSize::XSmall => this.text_ui_xs(cx),
228 LabelSize::Custom(size) => this.text_size(size),
229 })
230 .when(self.line_height_style == LineHeightStyle::UiLabel, |this| {
231 this.line_height(relative(1.))
232 })
233 .when(self.italic, |this| this.italic())
234 .when(self.underline, |mut this| {
235 this.text_style().underline = Some(UnderlineStyle {
236 thickness: px(1.),
237 color: Some(cx.theme().colors().text_muted.opacity(0.4)),
238 wavy: false,
239 });
240 this
241 })
242 .when(self.strikethrough, |this| this.line_through())
243 .when(self.single_line, |this| this.whitespace_nowrap())
244 .when(self.truncate, |this| {
245 this.min_w_0()
246 .overflow_x_hidden()
247 .whitespace_nowrap()
248 .text_ellipsis()
249 })
250 .when(self.truncate_start, |this| {
251 this.min_w_0()
252 .overflow_x_hidden()
253 .whitespace_nowrap()
254 .text_ellipsis_start()
255 })
256 .text_color(color)
257 .font_weight(
258 self.weight
259 .unwrap_or(theme::theme_settings(cx).ui_font(cx).weight),
260 )
261 .children(self.children)
262 }
263}
264
265impl Component for LabelLike {
266 fn scope() -> ComponentScope {
267 ComponentScope::Typography
268 }
269
270 fn name() -> &'static str {
271 "LabelLike"
272 }
273
274 fn description() -> Option<&'static str> {
275 Some(
276 "A flexible, customizable label-like component that serves as a base for other label types.",
277 )
278 }
279
280 fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
281 Some(
282 v_flex()
283 .gap_6()
284 .children(vec![
285 example_group_with_title(
286 "Sizes",
287 vec![
288 single_example("Default", LabelLike::new().child("Default size").into_any_element()),
289 single_example("Large", LabelLike::new().size(LabelSize::Large).child("Large size").into_any_element()),
290 single_example("Small", LabelLike::new().size(LabelSize::Small).child("Small size").into_any_element()),
291 single_example("XSmall", LabelLike::new().size(LabelSize::XSmall).child("Extra small size").into_any_element()),
292 ],
293 ),
294 example_group_with_title(
295 "Styles",
296 vec![
297 single_example("Bold", LabelLike::new().weight(FontWeight::BOLD).child("Bold text").into_any_element()),
298 single_example("Italic", LabelLike::new().italic().child("Italic text").into_any_element()),
299 single_example("Underline", LabelLike::new().underline().child("Underlined text").into_any_element()),
300 single_example("Strikethrough", LabelLike::new().strikethrough().child("Strikethrough text").into_any_element()),
301 single_example("Inline Code", LabelLike::new().inline_code(cx).child("const value = 42;").into_any_element()),
302 ],
303 ),
304 example_group_with_title(
305 "Colors",
306 vec![
307 single_example("Default", LabelLike::new().child("Default color").into_any_element()),
308 single_example("Accent", LabelLike::new().color(Color::Accent).child("Accent color").into_any_element()),
309 single_example("Error", LabelLike::new().color(Color::Error).child("Error color").into_any_element()),
310 single_example("Alpha", LabelLike::new().alpha(0.5).child("50% opacity").into_any_element()),
311 ],
312 ),
313 example_group_with_title(
314 "Line Height",
315 vec![
316 single_example("Default", LabelLike::new().child("Default line height\nMulti-line text").into_any_element()),
317 single_example("UI Label", LabelLike::new().line_height_style(LineHeightStyle::UiLabel).child("UI label line height\nMulti-line text").into_any_element()),
318 ],
319 ),
320 example_group_with_title(
321 "Special Cases",
322 vec![
323 single_example("Single Line", LabelLike::new().single_line().child("This is a very long text that should be displayed in a single line").into_any_element()),
324 single_example("Truncate", LabelLike::new().truncate().child("This is a very long text that should be truncated with an ellipsis").into_any_element()),
325 ],
326 ),
327 ])
328 .into_any_element()
329 )
330 }
331}