theme_preview.rs

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