label_like.rs

  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}