label_like.rs

  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}