label.rs

  1use crate::prelude::*;
  2use crate::styled_ext::StyledExt;
  3use gpui::{relative, Div, Hsla, IntoElement, StyledText, TextRun, WindowContext};
  4
  5#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
  6pub enum LabelSize {
  7    #[default]
  8    Default,
  9    Small,
 10}
 11
 12#[derive(Default, PartialEq, Copy, Clone)]
 13pub enum LineHeightStyle {
 14    #[default]
 15    TextLabel,
 16    /// Sets the line height to 1
 17    UILabel,
 18}
 19
 20#[derive(IntoElement, Clone)]
 21pub struct Label {
 22    label: SharedString,
 23    size: LabelSize,
 24    line_height_style: LineHeightStyle,
 25    color: Color,
 26    strikethrough: bool,
 27}
 28
 29impl RenderOnce for Label {
 30    type Rendered = Div;
 31
 32    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
 33        div()
 34            .when(self.strikethrough, |this| {
 35                this.relative().child(
 36                    div()
 37                        .absolute()
 38                        .top_1_2()
 39                        .w_full()
 40                        .h_px()
 41                        .bg(Color::Hidden.color(cx)),
 42                )
 43            })
 44            .map(|this| match self.size {
 45                LabelSize::Default => this.text_ui(),
 46                LabelSize::Small => this.text_ui_sm(),
 47            })
 48            .when(self.line_height_style == LineHeightStyle::UILabel, |this| {
 49                this.line_height(relative(1.))
 50            })
 51            .text_color(self.color.color(cx))
 52            .child(self.label.clone())
 53    }
 54}
 55
 56impl Label {
 57    pub fn new(label: impl Into<SharedString>) -> Self {
 58        Self {
 59            label: label.into(),
 60            size: LabelSize::Default,
 61            line_height_style: LineHeightStyle::default(),
 62            color: Color::Default,
 63            strikethrough: false,
 64        }
 65    }
 66
 67    pub fn size(mut self, size: LabelSize) -> Self {
 68        self.size = size;
 69        self
 70    }
 71
 72    pub fn color(mut self, color: Color) -> Self {
 73        self.color = color;
 74        self
 75    }
 76
 77    pub fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self {
 78        self.line_height_style = line_height_style;
 79        self
 80    }
 81
 82    pub fn set_strikethrough(mut self, strikethrough: bool) -> Self {
 83        self.strikethrough = strikethrough;
 84        self
 85    }
 86}
 87
 88#[derive(IntoElement)]
 89pub struct HighlightedLabel {
 90    label: SharedString,
 91    size: LabelSize,
 92    color: Color,
 93    highlight_indices: Vec<usize>,
 94    strikethrough: bool,
 95}
 96
 97impl RenderOnce for HighlightedLabel {
 98    type Rendered = Div;
 99
100    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
101        let highlight_color = cx.theme().colors().text_accent;
102        let mut text_style = cx.text_style().clone();
103
104        let mut highlight_indices = self.highlight_indices.iter().copied().peekable();
105
106        let mut runs: Vec<TextRun> = Vec::new();
107
108        for (char_ix, char) in self.label.char_indices() {
109            let mut color = self.color.color(cx);
110
111            if let Some(highlight_ix) = highlight_indices.peek() {
112                if char_ix == *highlight_ix {
113                    color = highlight_color;
114                    highlight_indices.next();
115                }
116            }
117
118            let last_run = runs.last_mut();
119            let start_new_run = if let Some(last_run) = last_run {
120                if color == last_run.color {
121                    last_run.len += char.len_utf8();
122                    false
123                } else {
124                    true
125                }
126            } else {
127                true
128            };
129
130            if start_new_run {
131                text_style.color = color;
132                runs.push(text_style.to_run(char.len_utf8()))
133            }
134        }
135
136        div()
137            .flex()
138            .when(self.strikethrough, |this| {
139                this.relative().child(
140                    div()
141                        .absolute()
142                        .top_px()
143                        .my_auto()
144                        .w_full()
145                        .h_px()
146                        .bg(Color::Hidden.color(cx)),
147                )
148            })
149            .map(|this| match self.size {
150                LabelSize::Default => this.text_ui(),
151                LabelSize::Small => this.text_ui_sm(),
152            })
153            .child(StyledText::new(self.label).with_runs(runs))
154    }
155}
156
157impl HighlightedLabel {
158    /// shows a label with the given characters highlighted.
159    /// characters are identified by utf8 byte position.
160    pub fn new(label: impl Into<SharedString>, highlight_indices: Vec<usize>) -> Self {
161        Self {
162            label: label.into(),
163            size: LabelSize::Default,
164            color: Color::Default,
165            highlight_indices,
166            strikethrough: false,
167        }
168    }
169
170    pub fn size(mut self, size: LabelSize) -> Self {
171        self.size = size;
172        self
173    }
174
175    pub fn color(mut self, color: Color) -> Self {
176        self.color = color;
177        self
178    }
179
180    pub fn set_strikethrough(mut self, strikethrough: bool) -> Self {
181        self.strikethrough = strikethrough;
182        self
183    }
184}
185
186/// A run of text that receives the same style.
187struct Run {
188    pub text: String,
189    pub color: Hsla,
190}