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