1use crate::prelude::*;
2use gpui::{FontWeight, StyleRefinement, UnderlineStyle};
3use settings::Settings;
4use smallvec::SmallVec;
5use theme::ThemeSettings;
6
7/// Sets the size of a label
8#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
9pub enum LabelSize {
10 /// The default size of a label.
11 #[default]
12 Default,
13 /// The large size of a label.
14 Large,
15 /// The small size of a label.
16 Small,
17 /// The extra small size of a label.
18 XSmall,
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 self.base = self
193 .base
194 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone());
195 self
196 }
197
198 fn inline_code(mut self, cx: &App) -> Self {
199 self.base = self
200 .base
201 .font(theme::ThemeSettings::get_global(cx).buffer_font.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 })
229 .when(self.line_height_style == LineHeightStyle::UiLabel, |this| {
230 this.line_height(relative(1.))
231 })
232 .when(self.italic, |this| this.italic())
233 .when(self.underline, |mut this| {
234 this.text_style().underline = Some(UnderlineStyle {
235 thickness: px(1.),
236 color: Some(cx.theme().colors().text_muted.opacity(0.4)),
237 wavy: false,
238 });
239 this
240 })
241 .when(self.strikethrough, |this| this.line_through())
242 .when(self.single_line, |this| this.whitespace_nowrap())
243 .when(self.truncate, |this| {
244 this.min_w_0()
245 .overflow_x_hidden()
246 .whitespace_nowrap()
247 .text_ellipsis()
248 })
249 .when(self.truncate_start, |this| {
250 this.min_w_0()
251 .overflow_x_hidden()
252 .whitespace_nowrap()
253 .text_ellipsis_start()
254 })
255 .text_color(color)
256 .font_weight(
257 self.weight
258 .unwrap_or(ThemeSettings::get_global(cx).ui_font.weight),
259 )
260 .children(self.children)
261 }
262}
263
264impl Component for LabelLike {
265 fn scope() -> ComponentScope {
266 ComponentScope::Typography
267 }
268
269 fn name() -> &'static str {
270 "LabelLike"
271 }
272
273 fn description() -> Option<&'static str> {
274 Some(
275 "A flexible, customizable label-like component that serves as a base for other label types.",
276 )
277 }
278
279 fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
280 Some(
281 v_flex()
282 .gap_6()
283 .children(vec![
284 example_group_with_title(
285 "Sizes",
286 vec![
287 single_example("Default", LabelLike::new().child("Default size").into_any_element()),
288 single_example("Large", LabelLike::new().size(LabelSize::Large).child("Large size").into_any_element()),
289 single_example("Small", LabelLike::new().size(LabelSize::Small).child("Small size").into_any_element()),
290 single_example("XSmall", LabelLike::new().size(LabelSize::XSmall).child("Extra small size").into_any_element()),
291 ],
292 ),
293 example_group_with_title(
294 "Styles",
295 vec![
296 single_example("Bold", LabelLike::new().weight(FontWeight::BOLD).child("Bold text").into_any_element()),
297 single_example("Italic", LabelLike::new().italic().child("Italic text").into_any_element()),
298 single_example("Underline", LabelLike::new().underline().child("Underlined text").into_any_element()),
299 single_example("Strikethrough", LabelLike::new().strikethrough().child("Strikethrough text").into_any_element()),
300 single_example("Inline Code", LabelLike::new().inline_code(cx).child("const value = 42;").into_any_element()),
301 ],
302 ),
303 example_group_with_title(
304 "Colors",
305 vec![
306 single_example("Default", LabelLike::new().child("Default color").into_any_element()),
307 single_example("Accent", LabelLike::new().color(Color::Accent).child("Accent color").into_any_element()),
308 single_example("Error", LabelLike::new().color(Color::Error).child("Error color").into_any_element()),
309 single_example("Alpha", LabelLike::new().alpha(0.5).child("50% opacity").into_any_element()),
310 ],
311 ),
312 example_group_with_title(
313 "Line Height",
314 vec![
315 single_example("Default", LabelLike::new().child("Default line height\nMulti-line text").into_any_element()),
316 single_example("UI Label", LabelLike::new().line_height_style(LineHeightStyle::UiLabel).child("UI label line height\nMulti-line text").into_any_element()),
317 ],
318 ),
319 example_group_with_title(
320 "Special Cases",
321 vec![
322 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()),
323 single_example("Truncate", LabelLike::new().truncate().child("This is a very long text that should be truncated with an ellipsis").into_any_element()),
324 ],
325 ),
326 ])
327 .into_any_element()
328 )
329 }
330}