label.rs

  1use gpui::{relative, rems, Hsla, WindowContext};
  2use smallvec::SmallVec;
  3
  4use crate::prelude::*;
  5#[derive(Default, PartialEq, Copy, Clone)]
  6pub enum LabelColor {
  7    #[default]
  8    Default,
  9    Muted,
 10    Created,
 11    Modified,
 12    Deleted,
 13    Disabled,
 14    Hidden,
 15    Placeholder,
 16    Accent,
 17}
 18
 19impl LabelColor {
 20    pub fn hsla(&self, cx: &WindowContext) -> Hsla {
 21        match self {
 22            Self::Default => cx.theme().colors().text,
 23            Self::Muted => cx.theme().colors().text_muted,
 24            Self::Created => cx.theme().status().created,
 25            Self::Modified => cx.theme().status().modified,
 26            Self::Deleted => cx.theme().status().deleted,
 27            Self::Disabled => cx.theme().colors().text_disabled,
 28            Self::Hidden => cx.theme().status().hidden,
 29            Self::Placeholder => cx.theme().colors().text_placeholder,
 30            Self::Accent => cx.theme().colors().text_accent,
 31        }
 32    }
 33}
 34
 35#[derive(Default, PartialEq, Copy, Clone)]
 36pub enum LineHeightStyle {
 37    #[default]
 38    TextLabel,
 39    /// Sets the line height to 1
 40    UILabel,
 41}
 42
 43#[derive(Component)]
 44pub struct Label {
 45    label: SharedString,
 46    line_height_style: LineHeightStyle,
 47    color: LabelColor,
 48    strikethrough: bool,
 49}
 50
 51impl Label {
 52    pub fn new(label: impl Into<SharedString>) -> Self {
 53        Self {
 54            label: label.into(),
 55            line_height_style: LineHeightStyle::default(),
 56            color: LabelColor::Default,
 57            strikethrough: false,
 58        }
 59    }
 60
 61    pub fn color(mut self, color: LabelColor) -> Self {
 62        self.color = color;
 63        self
 64    }
 65
 66    pub fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self {
 67        self.line_height_style = line_height_style;
 68        self
 69    }
 70
 71    pub fn set_strikethrough(mut self, strikethrough: bool) -> Self {
 72        self.strikethrough = strikethrough;
 73        self
 74    }
 75
 76    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
 77        div()
 78            .when(self.strikethrough, |this| {
 79                this.relative().child(
 80                    div()
 81                        .absolute()
 82                        .top_1_2()
 83                        .w_full()
 84                        .h_px()
 85                        .bg(LabelColor::Hidden.hsla(cx)),
 86                )
 87            })
 88            .text_size(rems(1.))
 89            .when(self.line_height_style == LineHeightStyle::UILabel, |this| {
 90                this.line_height(relative(1.))
 91            })
 92            .text_color(self.color.hsla(cx))
 93            .child(self.label.clone())
 94    }
 95}
 96
 97#[derive(Component)]
 98pub struct HighlightedLabel {
 99    label: SharedString,
100    color: LabelColor,
101    highlight_indices: Vec<usize>,
102    strikethrough: bool,
103}
104
105impl HighlightedLabel {
106    pub fn new(label: impl Into<SharedString>, highlight_indices: Vec<usize>) -> Self {
107        Self {
108            label: label.into(),
109            color: LabelColor::Default,
110            highlight_indices,
111            strikethrough: false,
112        }
113    }
114
115    pub fn color(mut self, color: LabelColor) -> Self {
116        self.color = color;
117        self
118    }
119
120    pub fn set_strikethrough(mut self, strikethrough: bool) -> Self {
121        self.strikethrough = strikethrough;
122        self
123    }
124
125    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
126        let highlight_color = cx.theme().colors().text_accent;
127
128        let mut highlight_indices = self.highlight_indices.iter().copied().peekable();
129
130        let mut runs: SmallVec<[Run; 8]> = SmallVec::new();
131
132        for (char_ix, char) in self.label.char_indices() {
133            let mut color = self.color.hsla(cx);
134
135            if let Some(highlight_ix) = highlight_indices.peek() {
136                if char_ix == *highlight_ix {
137                    color = highlight_color;
138
139                    highlight_indices.next();
140                }
141            }
142
143            let last_run = runs.last_mut();
144
145            let start_new_run = if let Some(last_run) = last_run {
146                if color == last_run.color {
147                    last_run.text.push(char);
148                    false
149                } else {
150                    true
151                }
152            } else {
153                true
154            };
155
156            if start_new_run {
157                runs.push(Run {
158                    text: char.to_string(),
159                    color,
160                });
161            }
162        }
163
164        div()
165            .flex()
166            .when(self.strikethrough, |this| {
167                this.relative().child(
168                    div()
169                        .absolute()
170                        .top_px()
171                        .my_auto()
172                        .w_full()
173                        .h_px()
174                        .bg(LabelColor::Hidden.hsla(cx)),
175                )
176            })
177            .children(
178                runs.into_iter()
179                    .map(|run| div().text_color(run.color).child(run.text)),
180            )
181    }
182}
183
184/// A run of text that receives the same style.
185struct Run {
186    pub text: String,
187    pub color: Hsla,
188}
189
190#[cfg(feature = "stories")]
191pub use stories::*;
192
193#[cfg(feature = "stories")]
194mod stories {
195    use super::*;
196    use crate::Story;
197    use gpui::{Div, Render};
198
199    pub struct LabelStory;
200
201    impl Render for LabelStory {
202        type Element = Div<Self>;
203
204        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
205            Story::container(cx)
206                .child(Story::title_for::<_, Label>(cx))
207                .child(Story::label(cx, "Default"))
208                .child(Label::new("Hello, world!"))
209                .child(Story::label(cx, "Highlighted"))
210                .child(HighlightedLabel::new(
211                    "Hello, world!",
212                    vec![0, 1, 2, 7, 8, 12],
213                ))
214        }
215    }
216}