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