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