theme_preview.rs

  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}