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 fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
213 Story::container(cx)
214 .child(Story::title_for::<_, Label>(cx))
215 .child(Story::label(cx, "Default"))
216 .child(Label::new("Hello, world!"))
217 .child(Story::label(cx, "Highlighted"))
218 .child(HighlightedLabel::new(
219 "Hello, world!",
220 vec![0, 1, 2, 7, 8, 12],
221 ))
222 }
223 }
224}