label.rs

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