label.rs

  1use std::marker::PhantomData;
  2
  3use gpui3::{Hsla, WindowContext};
  4use smallvec::SmallVec;
  5
  6use crate::prelude::*;
  7use crate::theme::theme;
  8
  9#[derive(Default, PartialEq, Copy, Clone)]
 10pub enum LabelColor {
 11    #[default]
 12    Default,
 13    Muted,
 14    Created,
 15    Modified,
 16    Deleted,
 17    Disabled,
 18    Hidden,
 19    Placeholder,
 20    Accent,
 21}
 22
 23impl LabelColor {
 24    pub fn hsla(&self, cx: &WindowContext) -> Hsla {
 25        let theme = theme(cx);
 26
 27        match self {
 28            Self::Default => theme.middle.base.default.foreground,
 29            Self::Muted => theme.middle.variant.default.foreground,
 30            Self::Created => theme.middle.positive.default.foreground,
 31            Self::Modified => theme.middle.warning.default.foreground,
 32            Self::Deleted => theme.middle.negative.default.foreground,
 33            Self::Disabled => theme.middle.base.disabled.foreground,
 34            Self::Hidden => theme.middle.variant.default.foreground,
 35            Self::Placeholder => theme.middle.base.disabled.foreground,
 36            Self::Accent => theme.middle.accent.default.foreground,
 37        }
 38    }
 39}
 40
 41#[derive(Default, PartialEq, Copy, Clone)]
 42pub enum LabelSize {
 43    #[default]
 44    Default,
 45    Small,
 46}
 47
 48#[derive(Element, Clone)]
 49pub struct Label<S: 'static + Send + Sync + Clone> {
 50    state_type: PhantomData<S>,
 51    label: SharedString,
 52    color: LabelColor,
 53    size: LabelSize,
 54    strikethrough: bool,
 55}
 56
 57impl<S: 'static + Send + Sync + Clone> Label<S> {
 58    pub fn new(label: impl Into<SharedString>) -> Self {
 59        Self {
 60            state_type: PhantomData,
 61            label: label.into(),
 62            color: LabelColor::Default,
 63            size: LabelSize::Default,
 64            strikethrough: false,
 65        }
 66    }
 67
 68    pub fn color(mut self, color: LabelColor) -> Self {
 69        self.color = color;
 70        self
 71    }
 72
 73    pub fn size(mut self, size: LabelSize) -> Self {
 74        self.size = size;
 75        self
 76    }
 77
 78    pub fn set_strikethrough(mut self, strikethrough: bool) -> Self {
 79        self.strikethrough = strikethrough;
 80        self
 81    }
 82
 83    fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
 84        let theme = theme(cx);
 85
 86        div()
 87            .when(self.strikethrough, |this| {
 88                this.relative().child(
 89                    div()
 90                        .absolute()
 91                        .top_px()
 92                        .my_auto()
 93                        .w_full()
 94                        .h_px()
 95                        .bg(LabelColor::Hidden.hsla(cx)),
 96                )
 97            })
 98            .text_color(self.color.hsla(cx))
 99            .child(self.label.clone())
100    }
101}
102
103#[derive(Element, Clone)]
104pub struct HighlightedLabel<S: 'static + Send + Sync + Clone> {
105    state_type: PhantomData<S>,
106    label: SharedString,
107    color: LabelColor,
108    size: LabelSize,
109    highlight_indices: Vec<usize>,
110    strikethrough: bool,
111}
112
113impl<S: 'static + Send + Sync + Clone> HighlightedLabel<S> {
114    pub fn new(label: impl Into<SharedString>, highlight_indices: Vec<usize>) -> Self {
115        Self {
116            state_type: PhantomData,
117            label: label.into(),
118            color: LabelColor::Default,
119            size: LabelSize::Default,
120            highlight_indices,
121            strikethrough: false,
122        }
123    }
124
125    pub fn color(mut self, color: LabelColor) -> Self {
126        self.color = color;
127        self
128    }
129
130    pub fn size(mut self, size: LabelSize) -> Self {
131        self.size = size;
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(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
141        let theme = theme(cx);
142
143        let highlight_color = theme.lowest.accent.default.foreground;
144
145        let mut highlight_indices = self.highlight_indices.iter().copied().peekable();
146
147        let mut runs: SmallVec<[Run; 8]> = SmallVec::new();
148
149        for (char_ix, char) in self.label.char_indices() {
150            let mut color = self.color.hsla(cx);
151
152            if let Some(highlight_ix) = highlight_indices.peek() {
153                if char_ix == *highlight_ix {
154                    color = highlight_color;
155
156                    highlight_indices.next();
157                }
158            }
159
160            let last_run = runs.last_mut();
161
162            let start_new_run = if let Some(last_run) = last_run {
163                if color == last_run.color {
164                    last_run.text.push(char);
165                    false
166                } else {
167                    true
168                }
169            } else {
170                true
171            };
172
173            if start_new_run {
174                runs.push(Run {
175                    text: char.to_string(),
176                    color,
177                });
178            }
179        }
180
181        div()
182            .flex()
183            .when(self.strikethrough, |this| {
184                this.relative().child(
185                    div()
186                        .absolute()
187                        .top_px()
188                        .my_auto()
189                        .w_full()
190                        .h_px()
191                        .bg(LabelColor::Hidden.hsla(cx)),
192                )
193            })
194            .children(runs.into_iter().map(|run| {
195                let mut div = div();
196
197                if self.size == LabelSize::Small {
198                    div = div.text_xs();
199                } else {
200                    div = div.text_sm();
201                }
202
203                div.text_color(run.color).child(run.text)
204            }))
205    }
206}
207
208/// A run of text that receives the same style.
209struct Run {
210    pub text: String,
211    pub color: Hsla,
212}
213
214#[cfg(feature = "stories")]
215pub use stories::*;
216
217#[cfg(feature = "stories")]
218mod stories {
219    use crate::Story;
220
221    use super::*;
222
223    #[derive(Element)]
224    pub struct LabelStory<S: 'static + Send + Sync + Clone> {
225        state_type: PhantomData<S>,
226    }
227
228    impl<S: 'static + Send + Sync + Clone> LabelStory<S> {
229        pub fn new() -> Self {
230            Self {
231                state_type: PhantomData,
232            }
233        }
234
235        fn render(
236            &mut self,
237            _view: &mut S,
238            cx: &mut ViewContext<S>,
239        ) -> impl Element<ViewState = S> {
240            Story::container(cx)
241                .child(Story::title_for::<_, Label<S>>(cx))
242                .child(Story::label(cx, "Default"))
243                .child(Label::new("Hello, world!"))
244                .child(Story::label(cx, "Highlighted"))
245                .child(HighlightedLabel::new(
246                    "Hello, world!",
247                    vec![0, 1, 2, 7, 8, 12],
248                ))
249        }
250    }
251}