diff --git a/Cargo.lock b/Cargo.lock index 8c02500b53..3fb2915d6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5176,9 +5176,12 @@ dependencies = [ "parking_lot 0.11.2", "playground_macros", "refineable", + "rust-embed", "serde", + "settings", "simplelog", "smallvec", + "theme", "util", ] diff --git a/crates/gpui/playground/Cargo.toml b/crates/gpui/playground/Cargo.toml index d907d3d6b6..1fee470d3d 100644 --- a/crates/gpui/playground/Cargo.toml +++ b/crates/gpui/playground/Cargo.toml @@ -16,9 +16,12 @@ log.workspace = true playground_macros = { path = "../playground_macros" } parking_lot.workspace = true refineable.workspace = true +rust-embed.workspace = true serde.workspace = true +settings = { path = "../../settings" } simplelog = "0.9" smallvec.workspace = true +theme = { path = "../../theme" } util = { path = "../../util" } [dev-dependencies] diff --git a/crates/gpui/playground/src/playground.rs b/crates/gpui/playground/src/playground.rs index 27ea6e22c9..43bd8d7b10 100644 --- a/crates/gpui/playground/src/playground.rs +++ b/crates/gpui/playground/src/playground.rs @@ -3,9 +3,12 @@ use crate::element::Element; use gpui::{ geometry::{rect::RectF, vector::vec2f}, platform::WindowOptions, + serde_json, ViewContext, }; use log::LevelFilter; +use settings::{default_settings, SettingsStore}; use simplelog::SimpleLogger; +use theme::ThemeSettings; use themes::Theme; use view::view; use workspace::workspace; @@ -30,6 +33,13 @@ fn main() { SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger"); gpui::App::new(()).unwrap().run(|cx| { + let mut store = SettingsStore::default(); + store + .set_default_settings(default_settings().as_ref(), cx) + .unwrap(); + cx.set_global(store); + theme::init(Assets, cx); + cx.add_window( WindowOptions { bounds: gpui::platform::WindowBounds::Fixed(RectF::new( @@ -39,12 +49,51 @@ fn main() { center: true, ..Default::default() }, - |_| view(|cx| playground(Theme::default())), + |_| view(|cx| playground(cx)), ); cx.platform().activate(true); }); } -fn playground(theme: Theme) -> impl Element { - workspace().themed(theme) +fn playground(cx: &mut ViewContext) -> impl Element { + workspace().themed(current_theme(cx)) +} + +// Nathan: During the transition, we will include the base theme on the legacy Theme struct. +fn current_theme(cx: &mut ViewContext) -> Theme { + settings::get::(cx) + .theme + .deserialized_base_theme + .lock() + .get_or_insert_with(|| { + let theme: Theme = + serde_json::from_value(settings::get::(cx).theme.base_theme.clone()) + .unwrap(); + Box::new(theme) + }) + .downcast_ref::() + .unwrap() + .clone() +} + +use anyhow::{anyhow, Result}; +use gpui::AssetSource; +use rust_embed::RustEmbed; + +#[derive(RustEmbed)] +#[folder = "../../../assets"] +#[include = "themes/**/*"] +#[exclude = "*.DS_Store"] +pub struct Assets; + +impl AssetSource for Assets { + fn load(&self, path: &str) -> Result> { + Self::get(path) + .map(|f| f.data) + .ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path)) + } + + fn list(&self, path: &str) -> Vec> { + Self::iter().filter(|p| p.starts_with(path)).collect() + } } diff --git a/crates/gpui/playground/src/themes.rs b/crates/gpui/playground/src/themes.rs index 34d430089d..e372e1e31b 100644 --- a/crates/gpui/playground/src/themes.rs +++ b/crates/gpui/playground/src/themes.rs @@ -9,59 +9,59 @@ use std::{collections::HashMap, fmt, marker::PhantomData}; #[derive(Deserialize, Clone, Default, Debug)] pub struct Theme { - name: String, - is_light: bool, - lowest: Layer, - middle: Layer, - highest: Layer, - popover_shadow: Shadow, - modal_shadow: Shadow, + pub name: String, + pub is_light: bool, + pub lowest: Layer, + pub middle: Layer, + pub highest: Layer, + pub popover_shadow: Shadow, + pub modal_shadow: Shadow, #[serde(deserialize_with = "deserialize_player_colors")] - players: Vec, + pub players: Vec, #[serde(deserialize_with = "deserialize_syntax_colors")] - syntax: HashMap, + pub syntax: HashMap, } #[derive(Deserialize, Clone, Default, Debug)] pub struct Layer { - base: StyleSet, - variant: StyleSet, - on: StyleSet, - accent: StyleSet, - positive: StyleSet, - warning: StyleSet, - negative: StyleSet, + pub base: StyleSet, + pub variant: StyleSet, + pub on: StyleSet, + pub accent: StyleSet, + pub positive: StyleSet, + pub warning: StyleSet, + pub negative: StyleSet, } #[derive(Deserialize, Clone, Default, Debug)] pub struct StyleSet { #[serde(rename = "default")] - default: ContainerColors, - hovered: ContainerColors, - pressed: ContainerColors, - active: ContainerColors, - disabled: ContainerColors, - inverted: ContainerColors, + pub default: ContainerColors, + pub hovered: ContainerColors, + pub pressed: ContainerColors, + pub active: ContainerColors, + pub disabled: ContainerColors, + pub inverted: ContainerColors, } #[derive(Deserialize, Clone, Default, Debug)] pub struct ContainerColors { - background: Hsla, - foreground: Hsla, - border: Hsla, + pub background: Hsla, + pub foreground: Hsla, + pub border: Hsla, } #[derive(Deserialize, Clone, Default, Debug)] pub struct PlayerColors { - selection: Hsla, - cursor: Hsla, + pub selection: Hsla, + pub cursor: Hsla, } #[derive(Deserialize, Clone, Default, Debug)] pub struct Shadow { - blur: u8, - color: Hsla, - offset: Vec, + pub blur: u8, + pub color: Hsla, + pub offset: Vec, } pub fn theme<'a>(cx: &'a WindowContext) -> &'a Theme { @@ -107,6 +107,11 @@ fn deserialize_syntax_colors<'de, D>(deserializer: D) -> Result, { + #[derive(Deserialize)] + struct ColorWrapper { + color: Hsla, + } + struct SyntaxVisitor; impl<'de> Visitor<'de> for SyntaxVisitor { @@ -122,8 +127,8 @@ where { let mut result = HashMap::new(); while let Some(key) = map.next_key()? { - let hsla: Hsla = map.next_value()?; // Deserialize values as Hsla - result.insert(key, hsla); + let wrapper: ColorWrapper = map.next_value()?; // Deserialize values as Hsla + result.insert(key, wrapper.color); } Ok(result) } diff --git a/crates/gpui/playground/src/workspace.rs b/crates/gpui/playground/src/workspace.rs index ab8a06a41d..36a3caf1b7 100644 --- a/crates/gpui/playground/src/workspace.rs +++ b/crates/gpui/playground/src/workspace.rs @@ -2,6 +2,7 @@ use crate::{ div::div, element::{Element, IntoElement, ParentElement}, style::StyleHelpers, + themes::theme, }; use gpui::{geometry::pixels, ViewContext}; use playground_macros::Element; @@ -16,20 +17,22 @@ pub fn workspace() -> impl Element { impl WorkspaceElement { fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - // let theme = &cx.theme::().colors; + let theme = theme(cx); div() .full() .flex() .flex_col() - // .fill(theme.base(0.5)) + .fill(theme.middle.base.default.background) .child(self.title_bar(cx)) .child(self.stage(cx)) .child(self.status_bar(cx)) } fn title_bar(&mut self, cx: &mut ViewContext) -> impl IntoElement { - // let colors = &theme(cx).colors; - div().h(pixels(cx.titlebar_height())) //.fill(colors.base(0.)) + let theme = theme(cx); + div() + .h(pixels(cx.titlebar_height())) + .fill(theme.lowest.base.default.background) } fn status_bar(&mut self, cx: &mut ViewContext) -> impl IntoElement { diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index a5faba8eaf..49d9fd25fe 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -10,11 +10,12 @@ use gpui::{ fonts::{HighlightStyle, TextStyle}, platform, AppContext, AssetSource, Border, MouseState, }; +use parking_lot::Mutex; use schemars::JsonSchema; use serde::{de::DeserializeOwned, Deserialize}; use serde_json::Value; use settings::SettingsStore; -use std::{collections::HashMap, ops::Deref, sync::Arc}; +use std::{any::Any, collections::HashMap, ops::Deref, sync::Arc}; use ui::{CheckboxStyle, CopilotCTAButton, IconStyle, ModalStyle}; pub use theme_registry::*; @@ -67,6 +68,14 @@ pub struct Theme { pub welcome: WelcomeStyle, pub titlebar: Titlebar, pub component_test: ComponentTest, + // Nathan: New elements are styled in Rust, directly from the base theme. + // We store it on the legacy theme so we can mix both kinds of elements during the transition. + #[schemars(skip)] + pub base_theme: serde_json::Value, + // A place to cache deserialized base theme. + #[serde(skip_deserializing)] + #[schemars(skip)] + pub deserialized_base_theme: Mutex>>, } #[derive(Deserialize, Default, Clone, JsonSchema)] diff --git a/styles/src/build_themes.ts b/styles/src/build_themes.ts index 17575663a1..6a6c4e2abd 100644 --- a/styles/src/build_themes.ts +++ b/styles/src/build_themes.ts @@ -32,6 +32,9 @@ function write_themes(themes: Theme[], output_directory: string) { setTheme(theme) const style_tree = app() + // Nathan: New elements will read directly from the theme colors. + // Adding this during the transition. Afterwards, we can port all themes to Rust. + style_tree.base_theme = theme const style_tree_json = JSON.stringify(style_tree, null, 2) const temp_path = path.join(temp_directory, `${theme.name}.json`) const out_path = path.join(