label.rs

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