label.rs

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