1use gpui::{relative, Hsla, Text, TextRun, WindowContext};
2
3use crate::prelude::*;
4use crate::styled_ext::StyledExt;
5
6#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
7pub enum LabelSize {
8 #[default]
9 Default,
10 Small,
11}
12
13#[derive(Default, PartialEq, Copy, Clone)]
14pub enum TextColor {
15 #[default]
16 Default,
17 Accent,
18 Created,
19 Deleted,
20 Disabled,
21 Error,
22 Hidden,
23 Info,
24 Modified,
25 Muted,
26 Placeholder,
27 Player(u32),
28 Selected,
29 Success,
30 Warning,
31}
32
33impl TextColor {
34 pub fn color(&self, cx: &WindowContext) -> Hsla {
35 match self {
36 TextColor::Default => cx.theme().colors().text,
37 TextColor::Muted => cx.theme().colors().text_muted,
38 TextColor::Created => cx.theme().status().created,
39 TextColor::Modified => cx.theme().status().modified,
40 TextColor::Deleted => cx.theme().status().deleted,
41 TextColor::Disabled => cx.theme().colors().text_disabled,
42 TextColor::Hidden => cx.theme().status().hidden,
43 TextColor::Info => cx.theme().status().info,
44 TextColor::Placeholder => cx.theme().colors().text_placeholder,
45 TextColor::Accent => cx.theme().colors().text_accent,
46 TextColor::Player(i) => cx.theme().styles.player.0[i.clone() as usize].cursor,
47 TextColor::Error => cx.theme().status().error,
48 TextColor::Selected => cx.theme().colors().text_accent,
49 TextColor::Success => cx.theme().status().success,
50 TextColor::Warning => cx.theme().status().warning,
51 }
52 }
53}
54
55#[derive(Default, PartialEq, Copy, Clone)]
56pub enum LineHeightStyle {
57 #[default]
58 TextLabel,
59 /// Sets the line height to 1
60 UILabel,
61}
62
63#[derive(Component)]
64pub struct Label {
65 label: SharedString,
66 size: LabelSize,
67 line_height_style: LineHeightStyle,
68 color: TextColor,
69 strikethrough: bool,
70}
71
72impl Label {
73 pub fn new(label: impl Into<SharedString>) -> Self {
74 Self {
75 label: label.into(),
76 size: LabelSize::Default,
77 line_height_style: LineHeightStyle::default(),
78 color: TextColor::Default,
79 strikethrough: false,
80 }
81 }
82
83 pub fn size(mut self, size: LabelSize) -> Self {
84 self.size = size;
85 self
86 }
87
88 pub fn color(mut self, color: TextColor) -> Self {
89 self.color = color;
90 self
91 }
92
93 pub fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self {
94 self.line_height_style = line_height_style;
95 self
96 }
97
98 pub fn set_strikethrough(mut self, strikethrough: bool) -> Self {
99 self.strikethrough = strikethrough;
100 self
101 }
102
103 fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
104 div()
105 .when(self.strikethrough, |this| {
106 this.relative().child(
107 div()
108 .absolute()
109 .top_1_2()
110 .w_full()
111 .h_px()
112 .bg(TextColor::Hidden.color(cx)),
113 )
114 })
115 .map(|this| match self.size {
116 LabelSize::Default => this.text_ui(),
117 LabelSize::Small => this.text_ui_sm(),
118 })
119 .when(self.line_height_style == LineHeightStyle::UILabel, |this| {
120 this.line_height(relative(1.))
121 })
122 .text_color(self.color.color(cx))
123 .child(self.label.clone())
124 }
125}
126
127#[derive(Component)]
128pub struct HighlightedLabel {
129 label: SharedString,
130 size: LabelSize,
131 color: TextColor,
132 highlight_indices: Vec<usize>,
133 strikethrough: bool,
134}
135
136impl HighlightedLabel {
137 /// shows a label with the given characters highlighted.
138 /// characters are identified by utf8 byte position.
139 pub fn new(label: impl Into<SharedString>, highlight_indices: Vec<usize>) -> Self {
140 Self {
141 label: label.into(),
142 size: LabelSize::Default,
143 color: TextColor::Default,
144 highlight_indices,
145 strikethrough: false,
146 }
147 }
148
149 pub fn size(mut self, size: LabelSize) -> Self {
150 self.size = size;
151 self
152 }
153
154 pub fn color(mut self, color: TextColor) -> Self {
155 self.color = color;
156 self
157 }
158
159 pub fn set_strikethrough(mut self, strikethrough: bool) -> Self {
160 self.strikethrough = strikethrough;
161 self
162 }
163
164 fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
165 let highlight_color = cx.theme().colors().text_accent;
166 let mut text_style = cx.text_style().clone();
167
168 let mut highlight_indices = self.highlight_indices.iter().copied().peekable();
169
170 let mut runs: Vec<TextRun> = Vec::new();
171
172 for (char_ix, char) in self.label.char_indices() {
173 let mut color = self.color.color(cx);
174
175 if let Some(highlight_ix) = highlight_indices.peek() {
176 if char_ix == *highlight_ix {
177 color = highlight_color;
178 highlight_indices.next();
179 }
180 }
181
182 let last_run = runs.last_mut();
183 let start_new_run = if let Some(last_run) = last_run {
184 if color == last_run.color {
185 last_run.len += char.len_utf8();
186 false
187 } else {
188 true
189 }
190 } else {
191 true
192 };
193
194 if start_new_run {
195 text_style.color = color;
196 runs.push(text_style.to_run(char.len_utf8()))
197 }
198 }
199
200 div()
201 .flex()
202 .when(self.strikethrough, |this| {
203 this.relative().child(
204 div()
205 .absolute()
206 .top_px()
207 .my_auto()
208 .w_full()
209 .h_px()
210 .bg(TextColor::Hidden.color(cx)),
211 )
212 })
213 .map(|this| match self.size {
214 LabelSize::Default => this.text_ui(),
215 LabelSize::Small => this.text_ui_sm(),
216 })
217 .child(Text::styled(self.label, runs))
218 }
219}
220
221/// A run of text that receives the same style.
222struct Run {
223 pub text: String,
224 pub color: Hsla,
225}
226
227#[cfg(feature = "stories")]
228pub use stories::*;
229
230#[cfg(feature = "stories")]
231mod stories {
232 use super::*;
233 use crate::Story;
234 use gpui::{Div, Render};
235
236 pub struct LabelStory;
237
238 impl Render for LabelStory {
239 type Element = Div<Self>;
240
241 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
242 Story::container(cx)
243 .child(Story::title_for::<_, Label>(cx))
244 .child(Story::label(cx, "Default"))
245 .child(Label::new("Hello, world!"))
246 .child(Story::label(cx, "Highlighted"))
247 .child(HighlightedLabel::new(
248 "Hello, world!",
249 vec![0, 1, 2, 7, 8, 12],
250 ))
251 .child(HighlightedLabel::new(
252 "Héllo, world!",
253 vec![0, 1, 3, 8, 9, 13],
254 ))
255 }
256 }
257}