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