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