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