label.rs

  1use gpui::{relative, Hsla, Text, TextRun, WindowContext};
  2
  3use crate::prelude::*;
  4use crate::styled_ext::StyledExt;
  5
  6#[derive(Default, PartialEq, Copy, Clone)]
  7pub enum LabelColor {
  8    #[default]
  9    Default,
 10    Muted,
 11    Created,
 12    Modified,
 13    Deleted,
 14    Disabled,
 15    Hidden,
 16    Placeholder,
 17    Accent,
 18}
 19
 20impl LabelColor {
 21    pub fn hsla(&self, cx: &WindowContext) -> Hsla {
 22        match self {
 23            Self::Default => cx.theme().colors().text,
 24            Self::Muted => cx.theme().colors().text_muted,
 25            Self::Created => cx.theme().status().created,
 26            Self::Modified => cx.theme().status().modified,
 27            Self::Deleted => cx.theme().status().deleted,
 28            Self::Disabled => cx.theme().colors().text_disabled,
 29            Self::Hidden => cx.theme().status().hidden,
 30            Self::Placeholder => cx.theme().colors().text_placeholder,
 31            Self::Accent => cx.theme().colors().text_accent,
 32        }
 33    }
 34}
 35
 36#[derive(Default, PartialEq, Copy, Clone)]
 37pub enum LineHeightStyle {
 38    #[default]
 39    TextLabel,
 40    /// Sets the line height to 1
 41    UILabel,
 42}
 43
 44#[derive(Component)]
 45pub struct Label {
 46    label: SharedString,
 47    line_height_style: LineHeightStyle,
 48    color: LabelColor,
 49    strikethrough: bool,
 50}
 51
 52impl Label {
 53    pub fn new(label: impl Into<SharedString>) -> Self {
 54        Self {
 55            label: label.into(),
 56            line_height_style: LineHeightStyle::default(),
 57            color: LabelColor::Default,
 58            strikethrough: false,
 59        }
 60    }
 61
 62    pub fn color(mut self, color: LabelColor) -> Self {
 63        self.color = color;
 64        self
 65    }
 66
 67    pub fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self {
 68        self.line_height_style = line_height_style;
 69        self
 70    }
 71
 72    pub fn set_strikethrough(mut self, strikethrough: bool) -> Self {
 73        self.strikethrough = strikethrough;
 74        self
 75    }
 76
 77    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
 78        div()
 79            .when(self.strikethrough, |this| {
 80                this.relative().child(
 81                    div()
 82                        .absolute()
 83                        .top_1_2()
 84                        .w_full()
 85                        .h_px()
 86                        .bg(LabelColor::Hidden.hsla(cx)),
 87                )
 88            })
 89            .text_ui()
 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    /// shows a label with the given characters highlighted.
108    /// characters are identified by utf8 byte position.
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 highlight_color = cx.theme().colors().text_accent;
130        let mut text_style = cx.text_style().clone();
131
132        let mut highlight_indices = self.highlight_indices.iter().copied().peekable();
133
134        let mut runs: Vec<TextRun> = Vec::new();
135
136        for (char_ix, char) in self.label.char_indices() {
137            let mut color = self.color.hsla(cx);
138
139            if let Some(highlight_ix) = highlight_indices.peek() {
140                if char_ix == *highlight_ix {
141                    color = highlight_color;
142                    highlight_indices.next();
143                }
144            }
145
146            let last_run = runs.last_mut();
147            let start_new_run = if let Some(last_run) = last_run {
148                if color == last_run.color {
149                    last_run.len += char.len_utf8();
150                    false
151                } else {
152                    true
153                }
154            } else {
155                true
156            };
157
158            if start_new_run {
159                text_style.color = color;
160                runs.push(text_style.to_run(char.len_utf8()))
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            .child(Text::styled(self.label, runs))
178    }
179}
180
181/// A run of text that receives the same style.
182struct Run {
183    pub text: String,
184    pub color: Hsla,
185}
186
187#[cfg(feature = "stories")]
188pub use stories::*;
189
190#[cfg(feature = "stories")]
191mod stories {
192    use super::*;
193    use crate::Story;
194    use gpui::{Div, Render};
195
196    pub struct LabelStory;
197
198    impl Render for LabelStory {
199        type Element = Div<Self>;
200
201        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
202            Story::container(cx)
203                .child(Story::title_for::<_, Label>(cx))
204                .child(Story::label(cx, "Default"))
205                .child(Label::new("Hello, world!"))
206                .child(Story::label(cx, "Highlighted"))
207                .child(HighlightedLabel::new(
208                    "Hello, world!",
209                    vec![0, 1, 2, 7, 8, 12],
210                ))
211                .child(HighlightedLabel::new(
212                    "Héllo, world!",
213                    vec![0, 1, 3, 8, 9, 13],
214                ))
215        }
216    }
217}