1use gpui::{Div, SharedString, Svg, div, prelude::*, px};
8use icons::Icon;
9use theme::{TextStyle, Theme, ThemeExt, Typeset};
10
11const DECK_DEPTH: usize = 2;
13const DECK_PEEK: f32 = 4.0;
16const DECK_GAP: f32 = 1.5;
17const DECK_HEIGHT: [f32; DECK_DEPTH] = [0.8, 0.6];
19const DECK_FRONT_INK: f32 = 0.12;
21const DECK_INK: [f32; DECK_DEPTH] = [0.18, 0.09];
22const DECK_PAD: f32 = 3.0;
24const DECK_RADIUS: f32 = 4.0;
26
27pub trait Content: ThemeExt {
28 fn badge(&self, label: impl Into<SharedString>) -> Div {
30 let theme = self.theme();
31 div()
32 .flex_none()
33 .px(px(8.0))
34 .py(px(2.0))
35 .rounded_full()
36 .border_1()
37 .border_color(theme.border)
38 .text_style(TextStyle::Caption)
39 .text_color(theme.text_muted)
40 .child(label.into())
41 }
42
43 fn badge_active(&self, label: impl Into<SharedString>) -> Div {
46 let theme = self.theme();
47 let emerald = theme.success;
48 let emerald_text = theme.success_muted; div()
50 .flex_none()
51 .px(px(8.0))
52 .py(px(2.0))
53 .rounded_full()
54 .bg(emerald.opacity(0.12))
55 .text_style(TextStyle::Caption)
56 .text_color(emerald_text.opacity(0.9))
57 .child(label.into())
58 }
59
60 fn avatar(&self, initials: impl Into<SharedString>) -> Div {
64 let theme = self.theme();
65 div()
66 .flex_none()
67 .size(px(28.0))
68 .rounded_full()
69 .bg(theme.ink(0.12))
70 .flex()
71 .items_center()
72 .justify_center()
73 .text_style(TextStyle::Subheadline)
74 .font_weight(gpui::FontWeight::MEDIUM)
75 .text_color(theme.text_muted)
76 .child(initials.into())
77 }
78
79 fn icon_deck(&self, front: impl Into<Icon>, behind: usize) -> Div {
88 let theme = self.theme();
89 let glyph = TextStyle::Caption.painted();
90 let edge = glyph + DECK_PAD * 2.;
91 div()
92 .flex_none()
93 .relative()
94 .w(px(edge + DECK_PEEK * DECK_DEPTH as f32))
95 .h(px(edge))
96 .children((1..=behind.min(DECK_DEPTH)).map(|at| {
99 let height = edge * DECK_HEIGHT[at - 1];
100 let shown = DECK_PEEK - DECK_GAP;
101 div()
102 .absolute()
103 .top_0()
104 .left(px(edge + DECK_PEEK * (at - 1) as f32 + DECK_GAP))
105 .w(px(shown))
106 .h(px(edge))
107 .overflow_hidden()
108 .child(
109 div()
110 .absolute()
111 .left(px(shown - edge))
112 .top(px((edge - height) / 2.))
113 .w(px(edge))
114 .h(px(height))
115 .rounded(px(DECK_RADIUS))
116 .bg(theme.ink(DECK_INK[at - 1])),
117 )
118 }))
119 .child(
120 div()
121 .absolute()
122 .top_0()
123 .left_0()
124 .size(px(edge))
125 .flex()
126 .items_center()
127 .justify_center()
128 .rounded(px(DECK_RADIUS))
129 .bg(theme.ink(DECK_FRONT_INK))
130 .child(
131 crate::icons::icon(front)
132 .size(px(glyph))
133 .text_color(theme.text),
134 ),
135 )
136 }
137
138 fn tag(&self, label: impl Into<SharedString>) -> Div {
141 let theme = self.theme();
142 div()
143 .self_start()
144 .flex()
145 .flex_row()
146 .items_center()
147 .gap(px(5.0))
148 .pl(px(8.0))
149 .pr(px(5.0))
150 .py(px(3.0))
151 .rounded(px(Theme::control_radius()))
152 .bg(theme.ink(0.07))
153 .border_1()
154 .border_color(theme.border)
155 .text_style(TextStyle::Callout)
156 .text_color(theme.text)
157 .child(label.into())
158 .child(
159 crate::icons::icon(crate::icons::glyph::X)
160 .size(px(10.0))
161 .text_color(theme.text_faint),
162 )
163 }
164
165 fn chip(&self, label: impl Into<SharedString>, tint: gpui::Hsla) -> Div {
170 let theme = self.theme();
171 div()
172 .flex_none()
173 .flex()
174 .flex_row()
175 .items_center()
176 .gap(px(2.0))
177 .px(px(6.0))
178 .rounded(px(Theme::control_radius()))
179 .bg(tint.opacity(0.22))
180 .text_style(TextStyle::Callout)
181 .text_color(theme.text)
182 .child(div().min_w_0().truncate().child(label.into()))
183 }
184
185 fn breadcrumb(&self) -> Div {
188 div()
189 .self_start()
190 .flex()
191 .flex_row()
192 .items_center()
193 .gap(px(4.0))
194 .min_w_0()
195 }
196
197 fn breadcrumb_item(&self, label: impl Into<SharedString>, current: bool) -> Div {
199 let theme = self.theme();
200 let mut crumb = div()
201 .min_w_0()
202 .truncate()
203 .text_style(TextStyle::Callout)
204 .child(label.into());
205 crumb = if current {
206 crumb.text_color(theme.text)
207 } else {
208 crumb.text_color(theme.text_muted).cursor_pointer()
209 };
210 crumb
211 }
212
213 fn breadcrumb_separator(&self) -> Svg {
215 let theme = self.theme();
216 crate::icons::icon(crate::icons::glyph::ChevronRight)
217 .size(px(12.0))
218 .text_color(theme.text_faint)
219 }
220
221 fn empty_state(
223 &self,
224 icon: impl Into<Icon>,
225 title: impl Into<SharedString>,
226 hint: impl Into<SharedString>,
227 ) -> Div {
228 let theme = self.theme();
229 div()
230 .w_full()
231 .flex()
232 .flex_col()
233 .items_center()
234 .justify_center()
235 .gap(px(6.0))
236 .py(px(40.0))
237 .child(
238 crate::icons::icon(icon)
239 .size(px(24.0))
240 .text_color(theme.text_faint),
241 )
242 .child(
243 div()
244 .text_style(TextStyle::Headline)
245 .text_color(theme.text)
246 .child(title.into()),
247 )
248 .child(
249 div()
250 .text_style(TextStyle::Callout)
251 .text_color(theme.text_muted)
252 .child(hint.into()),
253 )
254 }
255}
256
257impl Content for Theme {}