theme_preview.rs

  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}