#![allow(unused, dead_code)] use gpui::{Hsla, Length}; use std::sync::Arc; use theme::{Theme, ThemeRegistry}; use ui::{ IntoElement, RenderOnce, component_prelude::Documented, prelude::*, utils::inner_corner_radius, }; /// Shows a preview of a theme as an abstract illustration /// of a thumbnail-sized editor. #[derive(IntoElement, RegisterComponent, Documented)] pub struct ThemePreviewTile { theme: Arc, seed: f32, } impl ThemePreviewTile { pub const CORNER_RADIUS: Pixels = px(8.0); pub fn new(theme: Arc, seed: f32) -> Self { Self { theme, seed } } } impl RenderOnce for ThemePreviewTile { fn render(self, _window: &mut ui::Window, _cx: &mut ui::App) -> impl IntoElement { let color = self.theme.colors(); let root_radius = Self::CORNER_RADIUS; let root_border = px(2.0); let root_padding = px(2.0); let child_border = px(1.0); let inner_radius = inner_corner_radius(root_radius, root_border, root_padding, child_border); let item_skeleton = |w: Length, h: Pixels, bg: Hsla| div().w(w).h(h).rounded_full().bg(bg); let skeleton_height = px(2.); let sidebar_seeded_width = |seed: f32, index: usize| { let value = (seed * 1000.0 + index as f32 * 10.0).sin() * 0.5 + 0.5; 0.5 + value * 0.45 }; let sidebar_skeleton_items = 8; let sidebar_skeleton = (0..sidebar_skeleton_items) .map(|i| { let width = sidebar_seeded_width(self.seed, i); item_skeleton( relative(width).into(), skeleton_height, color.text.alpha(0.45), ) }) .collect::>(); let sidebar = div() .h_full() .w(relative(0.25)) .border_r(px(1.)) .border_color(color.border_transparent) .bg(color.panel_background) .child( v_flex() .p_2() .size_full() .gap_1() .children(sidebar_skeleton), ); let pseudo_code_skeleton = |theme: Arc, seed: f32| -> AnyElement { let colors = theme.colors(); let syntax = theme.syntax(); let keyword_color = syntax.get("keyword").color; let function_color = syntax.get("function").color; let string_color = syntax.get("string").color; let comment_color = syntax.get("comment").color; let variable_color = syntax.get("variable").color; let type_color = syntax.get("type").color; let punctuation_color = syntax.get("punctuation").color; let syntax_colors = [ keyword_color, function_color, string_color, variable_color, type_color, punctuation_color, comment_color, ]; let line_width = |line_idx: usize, block_idx: usize| -> f32 { let val = (seed * 100.0 + line_idx as f32 * 20.0 + block_idx as f32 * 5.0).sin() * 0.5 + 0.5; 0.05 + val * 0.2 }; let indentation = |line_idx: usize| -> f32 { let step = line_idx % 6; if step < 3 { step as f32 * 0.1 } else { (5 - step) as f32 * 0.1 } }; let pick_color = |line_idx: usize, block_idx: usize| -> Hsla { let idx = ((seed * 10.0 + line_idx as f32 * 7.0 + block_idx as f32 * 3.0).sin() * 3.5) .abs() as usize % syntax_colors.len(); syntax_colors[idx].unwrap_or(colors.text) }; let line_count = 13; let lines = (0..line_count) .map(|line_idx| { let block_count = (((seed * 30.0 + line_idx as f32 * 12.0).sin() * 0.5 + 0.5) * 3.0) .round() as usize + 2; let indent = indentation(line_idx); let blocks = (0..block_count) .map(|block_idx| { let width = line_width(line_idx, block_idx); let color = pick_color(line_idx, block_idx); item_skeleton(relative(width).into(), skeleton_height, color) }) .collect::>(); h_flex().gap(px(2.)).ml(relative(indent)).children(blocks) }) .collect::>(); v_flex() .size_full() .p_1() .gap_1p5() .children(lines) .into_any_element() }; let pane = v_flex().h_full().flex_grow().child( div() .size_full() .overflow_hidden() .bg(color.editor_background) .p_2() .child(pseudo_code_skeleton(self.theme.clone(), self.seed)), ); let content = div().size_full().flex().child(sidebar).child(pane); div() .size_full() .rounded(root_radius) .p(root_padding) .child( div() .size_full() .rounded(inner_radius) .border(child_border) .border_color(color.border) .bg(color.background) .child(content), ) } } impl Component for ThemePreviewTile { fn description() -> Option<&'static str> { Some(Self::DOCS) } fn preview(_window: &mut Window, cx: &mut App) -> Option { let theme_registry = ThemeRegistry::global(cx); let one_dark = theme_registry.get("One Dark"); let one_light = theme_registry.get("One Light"); let gruvbox_dark = theme_registry.get("Gruvbox Dark"); let gruvbox_light = theme_registry.get("Gruvbox Light"); let themes_to_preview = vec![ one_dark.clone().ok(), one_light.clone().ok(), gruvbox_dark.clone().ok(), gruvbox_light.clone().ok(), ] .into_iter() .flatten() .collect::>(); Some( v_flex() .gap_6() .p_4() .children({ if let Some(one_dark) = one_dark.ok() { vec![example_group(vec![single_example( "Default", div() .w(px(240.)) .h(px(180.)) .child(ThemePreviewTile::new(one_dark.clone(), 0.42)) .into_any_element(), )])] } else { vec![] } }) .child( example_group(vec![single_example( "Default Themes", h_flex() .gap_4() .children( themes_to_preview .iter() .enumerate() .map(|(_, theme)| { div() .w(px(200.)) .h(px(140.)) .child(ThemePreviewTile::new(theme.clone(), 0.42)) }) .collect::>(), ) .into_any_element(), )]) .grow(), ) .into_any_element(), ) } }