label.rs

  1use gpui2::{relative, 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 => gpui2::red(),
 25            Self::Modified => gpui2::red(),
 26            Self::Deleted => gpui2::red(),
 27            Self::Disabled => cx.theme().colors().text_disabled,
 28            Self::Hidden => gpui2::red(),
 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_px()
 83                        .my_auto()
 84                        .w_full()
 85                        .h_px()
 86                        .bg(LabelColor::Hidden.hsla(cx)),
 87                )
 88            })
 89            .text_size(ui_size(cx, 1.))
 90            .when(self.line_height_style == LineHeightStyle::UILabel, |this| {
 91                this.line_height(relative(1.))
 92            })
 93            .text_color(self.color.hsla(cx))
 94            .child(self.label.clone())
 95    }
 96}
 97
 98#[derive(Component)]
 99pub struct HighlightedLabel {
100    label: SharedString,
101    color: LabelColor,
102    highlight_indices: Vec<usize>,
103    strikethrough: bool,
104}
105
106impl HighlightedLabel {
107    pub fn new(label: impl Into<SharedString>, highlight_indices: Vec<usize>) -> Self {
108        Self {
109            label: label.into(),
110            color: LabelColor::Default,
111            highlight_indices,
112            strikethrough: false,
113        }
114    }
115
116    pub fn color(mut self, color: LabelColor) -> Self {
117        self.color = color;
118        self
119    }
120
121    pub fn set_strikethrough(mut self, strikethrough: bool) -> Self {
122        self.strikethrough = strikethrough;
123        self
124    }
125
126    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
127        let highlight_color = cx.theme().colors().text_accent;
128
129        let mut highlight_indices = self.highlight_indices.iter().copied().peekable();
130
131        let mut runs: SmallVec<[Run; 8]> = SmallVec::new();
132
133        for (char_ix, char) in self.label.char_indices() {
134            let mut color = self.color.hsla(cx);
135
136            if let Some(highlight_ix) = highlight_indices.peek() {
137                if char_ix == *highlight_ix {
138                    color = highlight_color;
139
140                    highlight_indices.next();
141                }
142            }
143
144            let last_run = runs.last_mut();
145
146            let start_new_run = if let Some(last_run) = last_run {
147                if color == last_run.color {
148                    last_run.text.push(char);
149                    false
150                } else {
151                    true
152                }
153            } else {
154                true
155            };
156
157            if start_new_run {
158                runs.push(Run {
159                    text: char.to_string(),
160                    color,
161                });
162            }
163        }
164
165        div()
166            .flex()
167            .when(self.strikethrough, |this| {
168                this.relative().child(
169                    div()
170                        .absolute()
171                        .top_px()
172                        .my_auto()
173                        .w_full()
174                        .h_px()
175                        .bg(LabelColor::Hidden.hsla(cx)),
176                )
177            })
178            .children(
179                runs.into_iter()
180                    .map(|run| div().text_color(run.color).child(run.text)),
181            )
182    }
183}
184
185/// A run of text that receives the same style.
186struct Run {
187    pub text: String,
188    pub color: Hsla,
189}
190
191#[cfg(feature = "stories")]
192pub use stories::*;
193
194#[cfg(feature = "stories")]
195mod stories {
196    use super::*;
197    use crate::Story;
198    use gpui2::{Div, Render};
199
200    pub struct LabelStory;
201
202    impl Render for LabelStory {
203        type Element = Div<Self>;
204
205        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
206            Story::container(cx)
207                .child(Story::title_for::<_, Label>(cx))
208                .child(Story::label(cx, "Default"))
209                .child(Label::new("Hello, world!"))
210                .child(Story::label(cx, "Highlighted"))
211                .child(HighlightedLabel::new(
212                    "Hello, world!",
213                    vec![0, 1, 2, 7, 8, 12],
214                ))
215        }
216    }
217}