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(Default, PartialEq, Copy, Clone)]
42pub enum LabelSize {
43 #[default]
44 Default,
45 Small,
46}
47
48#[derive(Element, Clone)]
49pub struct Label<S: 'static + Send + Sync + Clone> {
50 state_type: PhantomData<S>,
51 label: String,
52 color: LabelColor,
53 size: LabelSize,
54 highlight_indices: Vec<usize>,
55 strikethrough: bool,
56}
57
58impl<S: 'static + Send + Sync + Clone> Label<S> {
59 pub fn new<L>(label: L) -> Self
60 where
61 L: Into<String>,
62 {
63 Self {
64 state_type: PhantomData,
65 label: label.into(),
66 color: LabelColor::Default,
67 size: LabelSize::Default,
68 highlight_indices: Vec::new(),
69 strikethrough: false,
70 }
71 }
72
73 pub fn color(mut self, color: LabelColor) -> Self {
74 self.color = color;
75 self
76 }
77
78 pub fn size(mut self, size: LabelSize) -> Self {
79 self.size = size;
80 self
81 }
82
83 pub fn with_highlights(mut self, indices: Vec<usize>) -> Self {
84 self.highlight_indices = indices;
85 self
86 }
87
88 pub fn set_strikethrough(mut self, strikethrough: bool) -> Self {
89 self.strikethrough = strikethrough;
90 self
91 }
92
93 fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
94 let theme = theme(cx);
95
96 let highlight_color = theme.lowest.accent.default.foreground;
97
98 let mut highlight_indices = self.highlight_indices.iter().copied().peekable();
99
100 let mut runs: SmallVec<[Run; 8]> = SmallVec::new();
101
102 for (char_ix, char) in self.label.char_indices() {
103 let mut color = self.color.hsla(cx);
104
105 if let Some(highlight_ix) = highlight_indices.peek() {
106 if char_ix == *highlight_ix {
107 color = highlight_color;
108
109 highlight_indices.next();
110 }
111 }
112
113 let last_run = runs.last_mut();
114
115 let start_new_run = if let Some(last_run) = last_run {
116 if color == last_run.color {
117 last_run.text.push(char);
118 false
119 } else {
120 true
121 }
122 } else {
123 true
124 };
125
126 if start_new_run {
127 runs.push(Run {
128 text: char.to_string(),
129 color,
130 });
131 }
132 }
133
134 div()
135 .flex()
136 .when(self.strikethrough, |this| {
137 this.relative().child(
138 div()
139 .absolute()
140 .top_px()
141 .my_auto()
142 .w_full()
143 .h_px()
144 .fill(LabelColor::Hidden.hsla(cx)),
145 )
146 })
147 .children(runs.into_iter().map(|run| {
148 let mut div = div();
149
150 if self.size == LabelSize::Small {
151 div = div.text_xs();
152 } else {
153 div = div.text_sm();
154 }
155
156 div.text_color(run.color).child(run.text)
157 }))
158 }
159}
160
161/// A run of text that receives the same style.
162struct Run {
163 pub text: String,
164 pub color: Hsla,
165}
166
167#[cfg(feature = "stories")]
168pub use stories::*;
169
170#[cfg(feature = "stories")]
171mod stories {
172 use crate::Story;
173
174 use super::*;
175
176 #[derive(Element)]
177 pub struct LabelStory<S: 'static + Send + Sync + Clone> {
178 state_type: PhantomData<S>,
179 }
180
181 impl<S: 'static + Send + Sync + Clone> LabelStory<S> {
182 pub fn new() -> Self {
183 Self {
184 state_type: PhantomData,
185 }
186 }
187
188 fn render(
189 &mut self,
190 _view: &mut S,
191 cx: &mut ViewContext<S>,
192 ) -> impl Element<ViewState = S> {
193 Story::container(cx)
194 .child(Story::title_for::<_, Label<S>>(cx))
195 .child(Story::label(cx, "Default"))
196 .child(Label::new("Hello, world!"))
197 .child(Story::label(cx, "Highlighted"))
198 .child(Label::new("Hello, world!").with_highlights(vec![0, 1, 2, 7, 8, 12]))
199 }
200 }
201}