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(Element, Clone)]
 42pub struct Label<S: 'static + Send + Sync + Clone> {
 43    state_type: PhantomData<S>,
 44    label: SharedString,
 45    color: LabelColor,
 46    strikethrough: bool,
 47}
 48
 49impl<S: 'static + Send + Sync + Clone> Label<S> {
 50    pub fn new(label: impl Into<SharedString>) -> Self {
 51        Self {
 52            state_type: PhantomData,
 53            label: label.into(),
 54            color: LabelColor::Default,
 55            strikethrough: false,
 56        }
 57    }
 58
 59    pub fn color(mut self, color: LabelColor) -> Self {
 60        self.color = color;
 61        self
 62    }
 63
 64    pub fn set_strikethrough(mut self, strikethrough: bool) -> Self {
 65        self.strikethrough = strikethrough;
 66        self
 67    }
 68
 69    fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
 70        let theme = theme(cx);
 71
 72        div()
 73            .when(self.strikethrough, |this| {
 74                this.relative().child(
 75                    div()
 76                        .absolute()
 77                        .top_px()
 78                        .my_auto()
 79                        .w_full()
 80                        .h_px()
 81                        .bg(LabelColor::Hidden.hsla(cx)),
 82                )
 83            })
 84            .text_size(ui_size(1.))
 85            .text_color(self.color.hsla(cx))
 86            .child(self.label.clone())
 87    }
 88}
 89
 90#[derive(Element, Clone)]
 91pub struct HighlightedLabel<S: 'static + Send + Sync + Clone> {
 92    state_type: PhantomData<S>,
 93    label: SharedString,
 94    color: LabelColor,
 95    highlight_indices: Vec<usize>,
 96    strikethrough: bool,
 97}
 98
 99impl<S: 'static + Send + Sync + Clone> HighlightedLabel<S> {
100    pub fn new(label: impl Into<SharedString>, highlight_indices: Vec<usize>) -> Self {
101        Self {
102            state_type: PhantomData,
103            label: label.into(),
104            color: LabelColor::Default,
105            highlight_indices,
106            strikethrough: false,
107        }
108    }
109
110    pub fn color(mut self, color: LabelColor) -> Self {
111        self.color = color;
112        self
113    }
114
115    pub fn set_strikethrough(mut self, strikethrough: bool) -> Self {
116        self.strikethrough = strikethrough;
117        self
118    }
119
120    fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
121        let theme = theme(cx);
122
123        let highlight_color = theme.lowest.accent.default.foreground;
124
125        let mut highlight_indices = self.highlight_indices.iter().copied().peekable();
126
127        let mut runs: SmallVec<[Run; 8]> = SmallVec::new();
128
129        for (char_ix, char) in self.label.char_indices() {
130            let mut color = self.color.hsla(cx);
131
132            if let Some(highlight_ix) = highlight_indices.peek() {
133                if char_ix == *highlight_ix {
134                    color = highlight_color;
135
136                    highlight_indices.next();
137                }
138            }
139
140            let last_run = runs.last_mut();
141
142            let start_new_run = if let Some(last_run) = last_run {
143                if color == last_run.color {
144                    last_run.text.push(char);
145                    false
146                } else {
147                    true
148                }
149            } else {
150                true
151            };
152
153            if start_new_run {
154                runs.push(Run {
155                    text: char.to_string(),
156                    color,
157                });
158            }
159        }
160
161        div()
162            .flex()
163            .when(self.strikethrough, |this| {
164                this.relative().child(
165                    div()
166                        .absolute()
167                        .top_px()
168                        .my_auto()
169                        .w_full()
170                        .h_px()
171                        .bg(LabelColor::Hidden.hsla(cx)),
172                )
173            })
174            .children(
175                runs.into_iter()
176                    .map(|run| div().text_color(run.color).child(run.text)),
177            )
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 crate::Story;
193
194    use super::*;
195
196    #[derive(Element)]
197    pub struct LabelStory<S: 'static + Send + Sync + Clone> {
198        state_type: PhantomData<S>,
199    }
200
201    impl<S: 'static + Send + Sync + Clone> LabelStory<S> {
202        pub fn new() -> Self {
203            Self {
204                state_type: PhantomData,
205            }
206        }
207
208        fn render(
209            &mut self,
210            _view: &mut S,
211            cx: &mut ViewContext<S>,
212        ) -> impl Element<ViewState = S> {
213            Story::container(cx)
214                .child(Story::title_for::<_, Label<S>>(cx))
215                .child(Story::label(cx, "Default"))
216                .child(Label::new("Hello, world!"))
217                .child(Story::label(cx, "Highlighted"))
218                .child(HighlightedLabel::new(
219                    "Hello, world!",
220                    vec![0, 1, 2, 7, 8, 12],
221                ))
222        }
223    }
224}