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