1#![allow(unused, dead_code)]
2use gpui::{Hsla, Length};
3use std::{
4 cell::LazyCell,
5 sync::{Arc, OnceLock},
6};
7use theme::{Theme, ThemeColors, ThemeRegistry};
8use ui::{
9 IntoElement, RenderOnce, component_prelude::Documented, prelude::*, utils::inner_corner_radius,
10};
11
12#[derive(Clone, PartialEq)]
13pub enum ThemePreviewStyle {
14 Bordered,
15 Borderless,
16 SideBySide(Arc<Theme>),
17}
18
19/// Shows a preview of a theme as an abstract illustration
20/// of a thumbnail-sized editor.
21#[derive(IntoElement, RegisterComponent, Documented)]
22pub struct ThemePreviewTile {
23 theme: Arc<Theme>,
24 seed: f32,
25 style: ThemePreviewStyle,
26}
27
28fn child_radius() -> Pixels {
29 static CHILD_RADIUS: OnceLock<Pixels> = OnceLock::new();
30 *CHILD_RADIUS.get_or_init(|| {
31 inner_corner_radius(
32 ThemePreviewTile::ROOT_RADIUS,
33 ThemePreviewTile::ROOT_BORDER,
34 ThemePreviewTile::ROOT_PADDING,
35 ThemePreviewTile::CHILD_BORDER,
36 )
37 })
38}
39
40impl ThemePreviewTile {
41 pub const SKELETON_HEIGHT_DEFAULT: Pixels = px(2.);
42 pub const SIDEBAR_SKELETON_ITEM_COUNT: usize = 8;
43 pub const SIDEBAR_WIDTH_DEFAULT: DefiniteLength = relative(0.25);
44 pub const ROOT_RADIUS: Pixels = px(8.0);
45 pub const ROOT_BORDER: Pixels = px(2.0);
46 pub const ROOT_PADDING: Pixels = px(2.0);
47 pub const CHILD_BORDER: Pixels = px(1.0);
48
49 pub fn new(theme: Arc<Theme>, seed: f32) -> Self {
50 Self {
51 theme,
52 seed,
53 style: ThemePreviewStyle::Bordered,
54 }
55 }
56
57 pub fn style(mut self, style: ThemePreviewStyle) -> Self {
58 self.style = style;
59 self
60 }
61
62 pub fn item_skeleton(w: Length, h: Length, bg: Hsla) -> impl IntoElement {
63 div().w(w).h(h).rounded_full().bg(bg)
64 }
65
66 pub fn render_sidebar_skeleton_items(
67 seed: f32,
68 colors: &ThemeColors,
69 skeleton_height: impl Into<Length> + Clone,
70 ) -> [impl IntoElement; Self::SIDEBAR_SKELETON_ITEM_COUNT] {
71 let skeleton_height = skeleton_height.into();
72 std::array::from_fn(|index| {
73 let width = {
74 let value = (seed * 1000.0 + index as f32 * 10.0).sin() * 0.5 + 0.5;
75 0.5 + value * 0.45
76 };
77 Self::item_skeleton(
78 relative(width).into(),
79 skeleton_height,
80 colors.text.alpha(0.45),
81 )
82 })
83 }
84
85 pub fn render_pseudo_code_skeleton(
86 seed: f32,
87 theme: Arc<Theme>,
88 skeleton_height: impl Into<Length>,
89 ) -> impl IntoElement {
90 let colors = theme.colors();
91 let syntax = theme.syntax();
92
93 let keyword_color = syntax.get("keyword").color;
94 let function_color = syntax.get("function").color;
95 let string_color = syntax.get("string").color;
96 let comment_color = syntax.get("comment").color;
97 let variable_color = syntax.get("variable").color;
98 let type_color = syntax.get("type").color;
99 let punctuation_color = syntax.get("punctuation").color;
100
101 let syntax_colors = [
102 keyword_color,
103 function_color,
104 string_color,
105 variable_color,
106 type_color,
107 punctuation_color,
108 comment_color,
109 ];
110
111 let skeleton_height = skeleton_height.into();
112
113 let line_width = |line_idx: usize, block_idx: usize| -> f32 {
114 let val =
115 (seed * 100.0 + line_idx as f32 * 20.0 + block_idx as f32 * 5.0).sin() * 0.5 + 0.5;
116 0.05 + val * 0.2
117 };
118
119 let indentation = |line_idx: usize| -> f32 {
120 let step = line_idx % 6;
121 if step < 3 {
122 step as f32 * 0.1
123 } else {
124 (5 - step) as f32 * 0.1
125 }
126 };
127
128 let pick_color = |line_idx: usize, block_idx: usize| -> Hsla {
129 let idx = ((seed * 10.0 + line_idx as f32 * 7.0 + block_idx as f32 * 3.0).sin() * 3.5)
130 .abs() as usize
131 % syntax_colors.len();
132 syntax_colors[idx].unwrap_or(colors.text)
133 };
134
135 let line_count = 13;
136
137 let lines = (0..line_count)
138 .map(|line_idx| {
139 let block_count = (((seed * 30.0 + line_idx as f32 * 12.0).sin() * 0.5 + 0.5) * 3.0)
140 .round() as usize
141 + 2;
142
143 let indent = indentation(line_idx);
144
145 let blocks = (0..block_count)
146 .map(|block_idx| {
147 let width = line_width(line_idx, block_idx);
148 let color = pick_color(line_idx, block_idx);
149 Self::item_skeleton(relative(width).into(), skeleton_height, color)
150 })
151 .collect::<Vec<_>>();
152
153 h_flex().gap(px(2.)).ml(relative(indent)).children(blocks)
154 })
155 .collect::<Vec<_>>();
156
157 v_flex().size_full().p_1().gap_1p5().children(lines)
158 }
159
160 pub fn render_sidebar(
161 seed: f32,
162 colors: &ThemeColors,
163 width: impl Into<Length> + Clone,
164 skeleton_height: impl Into<Length>,
165 ) -> impl IntoElement {
166 div()
167 .h_full()
168 .w(width)
169 .border_r(px(1.))
170 .border_color(colors.border_transparent)
171 .bg(colors.panel_background)
172 .child(v_flex().p_2().size_full().gap_1().children(
173 Self::render_sidebar_skeleton_items(seed, colors, skeleton_height.into()),
174 ))
175 }
176
177 pub fn render_pane(
178 seed: f32,
179 theme: Arc<Theme>,
180 skeleton_height: impl Into<Length>,
181 ) -> impl IntoElement {
182 v_flex().h_full().flex_grow().child(
183 div()
184 .size_full()
185 .overflow_hidden()
186 .bg(theme.colors().editor_background)
187 .p_2()
188 .child(Self::render_pseudo_code_skeleton(
189 seed,
190 theme,
191 skeleton_height.into(),
192 )),
193 )
194 }
195
196 pub fn render_editor(
197 seed: f32,
198 theme: Arc<Theme>,
199 sidebar_width: impl Into<Length> + Clone,
200 skeleton_height: impl Into<Length> + Clone,
201 ) -> impl IntoElement {
202 div()
203 .size_full()
204 .flex()
205 .bg(theme.colors().background.alpha(1.00))
206 .child(Self::render_sidebar(
207 seed,
208 theme.colors(),
209 sidebar_width,
210 skeleton_height.clone(),
211 ))
212 .child(Self::render_pane(seed, theme, skeleton_height.clone()))
213 }
214
215 fn render_borderless(seed: f32, theme: Arc<Theme>) -> impl IntoElement {
216 return Self::render_editor(
217 seed,
218 theme,
219 Self::SIDEBAR_WIDTH_DEFAULT,
220 Self::SKELETON_HEIGHT_DEFAULT,
221 );
222 }
223
224 fn render_border(seed: f32, theme: Arc<Theme>) -> impl IntoElement {
225 div()
226 .size_full()
227 .p(Self::ROOT_PADDING)
228 .rounded(Self::ROOT_RADIUS)
229 .child(
230 div()
231 .size_full()
232 .rounded(child_radius())
233 .border(Self::CHILD_BORDER)
234 .border_color(theme.colors().border)
235 .child(Self::render_editor(
236 seed,
237 theme.clone(),
238 Self::SIDEBAR_WIDTH_DEFAULT,
239 Self::SKELETON_HEIGHT_DEFAULT,
240 )),
241 )
242 }
243
244 fn render_side_by_side(
245 seed: f32,
246 theme: Arc<Theme>,
247 other_theme: Arc<Theme>,
248 border_color: Hsla,
249 ) -> impl IntoElement {
250 let sidebar_width = relative(0.20);
251
252 return div()
253 .size_full()
254 .p(Self::ROOT_PADDING)
255 .rounded(Self::ROOT_RADIUS)
256 .child(
257 h_flex()
258 .size_full()
259 .relative()
260 .rounded(child_radius())
261 .border(Self::CHILD_BORDER)
262 .border_color(border_color)
263 .overflow_hidden()
264 .child(div().size_full().child(Self::render_editor(
265 seed,
266 theme.clone(),
267 sidebar_width,
268 Self::SKELETON_HEIGHT_DEFAULT,
269 )))
270 .child(
271 div()
272 .size_full()
273 .absolute()
274 .left_1_2()
275 .bg(other_theme.colors().editor_background)
276 .child(Self::render_editor(
277 seed,
278 other_theme,
279 sidebar_width,
280 Self::SKELETON_HEIGHT_DEFAULT,
281 )),
282 ),
283 )
284 .into_any_element();
285 }
286}
287
288impl RenderOnce for ThemePreviewTile {
289 fn render(self, _window: &mut ui::Window, _cx: &mut ui::App) -> impl IntoElement {
290 match self.style {
291 ThemePreviewStyle::Bordered => {
292 Self::render_border(self.seed, self.theme).into_any_element()
293 }
294 ThemePreviewStyle::Borderless => {
295 Self::render_borderless(self.seed, self.theme).into_any_element()
296 }
297 ThemePreviewStyle::SideBySide(other_theme) => Self::render_side_by_side(
298 self.seed,
299 self.theme,
300 other_theme,
301 _cx.theme().colors().border,
302 )
303 .into_any_element(),
304 }
305 }
306}
307
308impl Component for ThemePreviewTile {
309 fn scope() -> ComponentScope {
310 ComponentScope::Onboarding
311 }
312
313 fn name() -> &'static str {
314 "Theme Preview Tile"
315 }
316
317 fn sort_name() -> &'static str {
318 "Theme Preview Tile"
319 }
320
321 fn description() -> Option<&'static str> {
322 Some(Self::DOCS)
323 }
324
325 fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
326 let theme_registry = ThemeRegistry::global(cx);
327
328 let one_dark = theme_registry.get("One Dark");
329 let one_light = theme_registry.get("One Light");
330 let gruvbox_dark = theme_registry.get("Gruvbox Dark");
331 let gruvbox_light = theme_registry.get("Gruvbox Light");
332
333 let themes_to_preview = vec![
334 one_dark.clone().ok(),
335 one_light.clone().ok(),
336 gruvbox_dark.clone().ok(),
337 gruvbox_light.clone().ok(),
338 ]
339 .into_iter()
340 .flatten()
341 .collect::<Vec<_>>();
342
343 Some(
344 v_flex()
345 .gap_6()
346 .p_4()
347 .children({
348 if let Some(one_dark) = one_dark.ok() {
349 vec![example_group(vec![single_example(
350 "Default",
351 div()
352 .w(px(240.))
353 .h(px(180.))
354 .child(ThemePreviewTile::new(one_dark.clone(), 0.42))
355 .into_any_element(),
356 )])]
357 } else {
358 vec![]
359 }
360 })
361 .child(
362 example_group(vec![single_example(
363 "Default Themes",
364 h_flex()
365 .gap_4()
366 .children(
367 themes_to_preview
368 .iter()
369 .enumerate()
370 .map(|(_, theme)| {
371 div()
372 .w(px(200.))
373 .h(px(140.))
374 .child(ThemePreviewTile::new(theme.clone(), 0.42))
375 })
376 .collect::<Vec<_>>(),
377 )
378 .into_any_element(),
379 )])
380 .grow(),
381 )
382 .into_any_element(),
383 )
384 }
385}