label.rs

  1use std::marker::PhantomData;
  2
  3use gpui2::{relative, 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 color = ThemeColor::new(cx);
 26        // TODO: Remove
 27        let theme = theme(cx);
 28
 29        match self {
 30            Self::Default => color.text,
 31            Self::Muted => color.text_muted,
 32            Self::Created => theme.middle.positive.default.foreground,
 33            Self::Modified => theme.middle.warning.default.foreground,
 34            Self::Deleted => theme.middle.negative.default.foreground,
 35            Self::Disabled => color.text_disabled,
 36            Self::Hidden => theme.middle.variant.default.foreground,
 37            Self::Placeholder => color.text_placeholder,
 38            Self::Accent => 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        let color = ThemeColor::new(cx);
 88
 89        div()
 90            .when(self.strikethrough, |this| {
 91                this.relative().child(
 92                    div()
 93                        .absolute()
 94                        .top_px()
 95                        .my_auto()
 96                        .w_full()
 97                        .h_px()
 98                        .bg(LabelColor::Hidden.hsla(cx)),
 99                )
100            })
101            .text_size(ui_size(cx, 1.))
102            .when(self.line_height_style == LineHeightStyle::UILabel, |this| {
103                this.line_height(relative(1.))
104            })
105            .text_color(self.color.hsla(cx))
106            .child(self.label.clone())
107    }
108}
109
110#[derive(Element)]
111pub struct HighlightedLabel<S: 'static + Send + Sync> {
112    state_type: PhantomData<S>,
113    label: SharedString,
114    color: LabelColor,
115    highlight_indices: Vec<usize>,
116    strikethrough: bool,
117}
118
119impl<S: 'static + Send + Sync> HighlightedLabel<S> {
120    pub fn new(label: impl Into<SharedString>, highlight_indices: Vec<usize>) -> Self {
121        Self {
122            state_type: PhantomData,
123            label: label.into(),
124            color: LabelColor::Default,
125            highlight_indices,
126            strikethrough: false,
127        }
128    }
129
130    pub fn color(mut self, color: LabelColor) -> Self {
131        self.color = color;
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 color = ThemeColor::new(cx);
142
143        let highlight_color = color.text_accent;
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(
195                runs.into_iter()
196                    .map(|run| div().text_color(run.color).child(run.text)),
197            )
198    }
199}
200
201/// A run of text that receives the same style.
202struct Run {
203    pub text: String,
204    pub color: Hsla,
205}
206
207#[cfg(feature = "stories")]
208pub use stories::*;
209
210#[cfg(feature = "stories")]
211mod stories {
212    use crate::Story;
213
214    use super::*;
215
216    #[derive(Element)]
217    pub struct LabelStory<S: 'static + Send + Sync> {
218        state_type: PhantomData<S>,
219    }
220
221    impl<S: 'static + Send + Sync> LabelStory<S> {
222        pub fn new() -> Self {
223            Self {
224                state_type: PhantomData,
225            }
226        }
227
228        fn render(
229            &mut self,
230            _view: &mut S,
231            cx: &mut ViewContext<S>,
232        ) -> impl Element<ViewState = S> {
233            Story::container(cx)
234                .child(Story::title_for::<_, Label<S>>(cx))
235                .child(Story::label(cx, "Default"))
236                .child(Label::new("Hello, world!"))
237                .child(Story::label(cx, "Highlighted"))
238                .child(HighlightedLabel::new(
239                    "Hello, world!",
240                    vec![0, 1, 2, 7, 8, 12],
241                ))
242        }
243    }
244}