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 (`…`) 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}
92
93impl Default for LabelLike {
94 fn default() -> Self {
95 Self::new()
96 }
97}
98
99impl LabelLike {
100 /// Creates a new, fully custom label.
101 /// Prefer using [`Label`] or [`HighlightedLabel`] where possible.
102 pub fn new() -> Self {
103 Self {
104 base: div(),
105 size: LabelSize::Default,
106 weight: None,
107 line_height_style: LineHeightStyle::default(),
108 color: Color::Default,
109 strikethrough: false,
110 italic: false,
111 children: SmallVec::new(),
112 alpha: None,
113 underline: false,
114 single_line: false,
115 truncate: false,
116 }
117 }
118}
119
120// Style methods.
121impl LabelLike {
122 fn style(&mut self) -> &mut StyleRefinement {
123 self.base.style()
124 }
125
126 gpui::margin_style_methods!({
127 visibility: pub
128 });
129}
130
131impl LabelCommon for LabelLike {
132 fn size(mut self, size: LabelSize) -> Self {
133 self.size = size;
134 self
135 }
136
137 fn weight(mut self, weight: FontWeight) -> Self {
138 self.weight = Some(weight);
139 self
140 }
141
142 fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self {
143 self.line_height_style = line_height_style;
144 self
145 }
146
147 fn color(mut self, color: Color) -> Self {
148 self.color = color;
149 self
150 }
151
152 fn strikethrough(mut self) -> Self {
153 self.strikethrough = true;
154 self
155 }
156
157 fn italic(mut self) -> Self {
158 self.italic = true;
159 self
160 }
161
162 fn underline(mut self) -> Self {
163 self.underline = true;
164 self
165 }
166
167 fn alpha(mut self, alpha: f32) -> Self {
168 self.alpha = Some(alpha);
169 self
170 }
171
172 /// Truncates overflowing text with an ellipsis (`…`) if needed.
173 fn truncate(mut self) -> Self {
174 self.truncate = true;
175 self
176 }
177
178 fn single_line(mut self) -> Self {
179 self.single_line = true;
180 self
181 }
182
183 fn buffer_font(mut self, cx: &App) -> Self {
184 self.base = self
185 .base
186 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone());
187 self
188 }
189
190 fn inline_code(mut self, cx: &App) -> Self {
191 self.base = self
192 .base
193 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
194 .bg(cx.theme().colors().element_background)
195 .rounded_sm()
196 .px_0p5();
197 self
198 }
199}
200
201impl ParentElement for LabelLike {
202 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
203 self.children.extend(elements)
204 }
205}
206
207impl RenderOnce for LabelLike {
208 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
209 let mut color = self.color.color(cx);
210 if let Some(alpha) = self.alpha {
211 color.fade_out(1.0 - alpha);
212 }
213
214 self.base
215 .map(|this| match self.size {
216 LabelSize::Large => this.text_ui_lg(cx),
217 LabelSize::Default => this.text_ui(cx),
218 LabelSize::Small => this.text_ui_sm(cx),
219 LabelSize::XSmall => this.text_ui_xs(cx),
220 })
221 .when(self.line_height_style == LineHeightStyle::UiLabel, |this| {
222 this.line_height(relative(1.))
223 })
224 .when(self.italic, |this| this.italic())
225 .when(self.underline, |mut this| {
226 this.text_style()
227 .get_or_insert_with(Default::default)
228 .underline = Some(UnderlineStyle {
229 thickness: px(1.),
230 color: None,
231 wavy: false,
232 });
233 this
234 })
235 .when(self.strikethrough, |this| this.line_through())
236 .when(self.single_line, |this| this.whitespace_nowrap())
237 .when(self.truncate, |this| {
238 this.overflow_x_hidden().text_ellipsis()
239 })
240 .text_color(color)
241 .font_weight(
242 self.weight
243 .unwrap_or(ThemeSettings::get_global(cx).ui_font.weight),
244 )
245 .children(self.children)
246 }
247}
248
249impl Component for LabelLike {
250 fn scope() -> ComponentScope {
251 ComponentScope::Typography
252 }
253
254 fn name() -> &'static str {
255 "LabelLike"
256 }
257
258 fn description() -> Option<&'static str> {
259 Some(
260 "A flexible, customizable label-like component that serves as a base for other label types.",
261 )
262 }
263
264 fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
265 Some(
266 v_flex()
267 .gap_6()
268 .children(vec![
269 example_group_with_title(
270 "Sizes",
271 vec![
272 single_example("Default", LabelLike::new().child("Default size").into_any_element()),
273 single_example("Large", LabelLike::new().size(LabelSize::Large).child("Large size").into_any_element()),
274 single_example("Small", LabelLike::new().size(LabelSize::Small).child("Small size").into_any_element()),
275 single_example("XSmall", LabelLike::new().size(LabelSize::XSmall).child("Extra small size").into_any_element()),
276 ],
277 ),
278 example_group_with_title(
279 "Styles",
280 vec![
281 single_example("Bold", LabelLike::new().weight(FontWeight::BOLD).child("Bold text").into_any_element()),
282 single_example("Italic", LabelLike::new().italic().child("Italic text").into_any_element()),
283 single_example("Underline", LabelLike::new().underline().child("Underlined text").into_any_element()),
284 single_example("Strikethrough", LabelLike::new().strikethrough().child("Strikethrough text").into_any_element()),
285 single_example("Inline Code", LabelLike::new().inline_code(cx).child("const value = 42;").into_any_element()),
286 ],
287 ),
288 example_group_with_title(
289 "Colors",
290 vec![
291 single_example("Default", LabelLike::new().child("Default color").into_any_element()),
292 single_example("Accent", LabelLike::new().color(Color::Accent).child("Accent color").into_any_element()),
293 single_example("Error", LabelLike::new().color(Color::Error).child("Error color").into_any_element()),
294 single_example("Alpha", LabelLike::new().alpha(0.5).child("50% opacity").into_any_element()),
295 ],
296 ),
297 example_group_with_title(
298 "Line Height",
299 vec![
300 single_example("Default", LabelLike::new().child("Default line height\nMulti-line text").into_any_element()),
301 single_example("UI Label", LabelLike::new().line_height_style(LineHeightStyle::UiLabel).child("UI label line height\nMulti-line text").into_any_element()),
302 ],
303 ),
304 example_group_with_title(
305 "Special Cases",
306 vec![
307 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()),
308 single_example("Truncate", LabelLike::new().truncate().child("This is a very long text that should be truncated with an ellipsis").into_any_element()),
309 ],
310 ),
311 ])
312 .into_any_element()
313 )
314 }
315}