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    highlight_indices: Vec<usize>,
 55    strikethrough: bool,
 56}
 57
 58impl<S: 'static + Send + Sync + Clone> Label<S> {
 59    pub fn new(label: impl Into<SharedString>) -> Self {
 60        Self {
 61            state_type: PhantomData,
 62            label: label.into(),
 63            color: LabelColor::Default,
 64            size: LabelSize::Default,
 65            highlight_indices: Vec::new(),
 66            strikethrough: false,
 67        }
 68    }
 69
 70    pub fn color(mut self, color: LabelColor) -> Self {
 71        self.color = color;
 72        self
 73    }
 74
 75    pub fn size(mut self, size: LabelSize) -> Self {
 76        self.size = size;
 77        self
 78    }
 79
 80    pub fn with_highlights(mut self, indices: Vec<usize>) -> Self {
 81        self.highlight_indices = indices;
 82        self
 83    }
 84
 85    pub fn set_strikethrough(mut self, strikethrough: bool) -> Self {
 86        self.strikethrough = strikethrough;
 87        self
 88    }
 89
 90    fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
 91        let theme = theme(cx);
 92
 93        let highlight_color = theme.lowest.accent.default.foreground;
 94
 95        let mut highlight_indices = self.highlight_indices.iter().copied().peekable();
 96
 97        let mut runs: SmallVec<[Run; 8]> = SmallVec::new();
 98
 99        for (char_ix, char) in self.label.char_indices() {
100            let mut color = self.color.hsla(cx);
101
102            if let Some(highlight_ix) = highlight_indices.peek() {
103                if char_ix == *highlight_ix {
104                    color = highlight_color;
105
106                    highlight_indices.next();
107                }
108            }
109
110            let last_run = runs.last_mut();
111
112            let start_new_run = if let Some(last_run) = last_run {
113                if color == last_run.color {
114                    last_run.text.push(char);
115                    false
116                } else {
117                    true
118                }
119            } else {
120                true
121            };
122
123            if start_new_run {
124                runs.push(Run {
125                    text: char.to_string(),
126                    color,
127                });
128            }
129        }
130
131        div()
132            .flex()
133            .when(self.strikethrough, |this| {
134                this.relative().child(
135                    div()
136                        .absolute()
137                        .top_px()
138                        .my_auto()
139                        .w_full()
140                        .h_px()
141                        .bg(LabelColor::Hidden.hsla(cx)),
142                )
143            })
144            .children(runs.into_iter().map(|run| {
145                let mut div = div();
146
147                if self.size == LabelSize::Small {
148                    div = div.text_xs();
149                } else {
150                    div = div.text_sm();
151                }
152
153                div.text_color(run.color).child(run.text)
154            }))
155    }
156}
157
158/// A run of text that receives the same style.
159struct Run {
160    pub text: String,
161    pub color: Hsla,
162}
163
164#[cfg(feature = "stories")]
165pub use stories::*;
166
167#[cfg(feature = "stories")]
168mod stories {
169    use crate::Story;
170
171    use super::*;
172
173    #[derive(Element)]
174    pub struct LabelStory<S: 'static + Send + Sync + Clone> {
175        state_type: PhantomData<S>,
176    }
177
178    impl<S: 'static + Send + Sync + Clone> LabelStory<S> {
179        pub fn new() -> Self {
180            Self {
181                state_type: PhantomData,
182            }
183        }
184
185        fn render(
186            &mut self,
187            _view: &mut S,
188            cx: &mut ViewContext<S>,
189        ) -> impl Element<ViewState = S> {
190            Story::container(cx)
191                .child(Story::title_for::<_, Label<S>>(cx))
192                .child(Story::label(cx, "Default"))
193                .child(Label::new("Hello, world!"))
194                .child(Story::label(cx, "Highlighted"))
195                .child(Label::new("Hello, world!").with_highlights(vec![0, 1, 2, 7, 8, 12]))
196        }
197    }
198}