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