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