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