diff --git a/crates/gpui/playground/src/color.rs b/crates/gpui/playground/src/color.rs index 14c60248aade8fbd99f8a3105dd87c6e949ac689..11590f967cdba57edebf52a23fd1721cbf1fc899 100644 --- a/crates/gpui/playground/src/color.rs +++ b/crates/gpui/playground/src/color.rs @@ -1,8 +1,9 @@ #![allow(dead_code)] -use std::{num::ParseIntError, ops::Range}; - +use serde::de::{self, Deserialize, Deserializer, Visitor}; use smallvec::SmallVec; +use std::fmt; +use std::{num::ParseIntError, ops::Range}; pub fn rgb>(hex: u32) -> C { let r = ((hex >> 16) & 0xFF) as f32 / 255.0; @@ -19,6 +20,40 @@ pub struct Rgba { pub a: f32, } +struct RgbaVisitor; + +impl<'de> Visitor<'de> for RgbaVisitor { + type Value = Rgba; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a string in the format #rrggbb or #rrggbbaa") + } + + fn visit_str(self, value: &str) -> Result { + if value.len() == 7 || value.len() == 9 { + let r = u8::from_str_radix(&value[1..3], 16).unwrap() as f32 / 255.0; + let g = u8::from_str_radix(&value[3..5], 16).unwrap() as f32 / 255.0; + let b = u8::from_str_radix(&value[5..7], 16).unwrap() as f32 / 255.0; + let a = if value.len() == 9 { + u8::from_str_radix(&value[7..9], 16).unwrap() as f32 / 255.0 + } else { + 1.0 + }; + Ok(Rgba { r, g, b, a }) + } else { + Err(E::custom( + "Bad format for RGBA. Expected #rrggbb or #rrggbbaa.", + )) + } + } +} + +impl<'de> Deserialize<'de> for Rgba { + fn deserialize>(deserializer: D) -> Result { + deserializer.deserialize_str(RgbaVisitor) + } +} + pub trait Lerp { fn lerp(&self, level: f32) -> Hsla; } @@ -219,6 +254,19 @@ impl Into for Hsla { } } +impl<'de> Deserialize<'de> for Hsla { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + // First, deserialize it into Rgba + let rgba = Rgba::deserialize(deserializer)?; + + // Then, use the From for Hsla implementation to convert it + Ok(Hsla::from(rgba)) + } +} + pub struct ColorScale { colors: SmallVec<[Hsla; 2]>, positions: SmallVec<[f32; 2]>, diff --git a/crates/gpui/playground/src/components.rs b/crates/gpui/playground/src/components.rs index 73dca57f359bc2b17bcbec45e0385cc7c103280c..8513a550c1c7a2ba0bfa648652b6bbec5abc94d2 100644 --- a/crates/gpui/playground/src/components.rs +++ b/crates/gpui/playground/src/components.rs @@ -4,7 +4,7 @@ use crate::{ interactive::Interactive, style::StyleHelpers, text::ArcCow, - themes::Theme, + // themes::Theme, }; use gpui::{platform::MouseButton, ViewContext}; use playground_macros::Element; @@ -82,10 +82,10 @@ impl Button { view: &mut V, cx: &mut ViewContext, ) -> impl IntoElement + Interactive { - let colors = &cx.theme::().colors; + // let colors = &cx.theme::().colors; let button = div() - .fill(colors.error(0.5)) + // .fill(colors.error(0.5)) .h_4() .children(self.label.clone()); diff --git a/crates/gpui/playground/src/playground.rs b/crates/gpui/playground/src/playground.rs index 6d05d19a66cc789db6dc85aa9fa0ebe8d9173458..27ea6e22c9ee7e4ba57ce4dbe176dc175c6a840c 100644 --- a/crates/gpui/playground/src/playground.rs +++ b/crates/gpui/playground/src/playground.rs @@ -1,12 +1,12 @@ #![allow(dead_code, unused_variables)] -use element::Element; +use crate::element::Element; use gpui::{ geometry::{rect::RectF, vector::vec2f}, platform::WindowOptions, }; use log::LevelFilter; use simplelog::SimpleLogger; -use themes::{rose_pine, Theme, ThemeColors}; +use themes::Theme; use view::view; use workspace::workspace; @@ -39,13 +39,7 @@ fn main() { center: true, ..Default::default() }, - |_| { - view(|cx| { - playground(Theme { - colors: rose_pine::dawn(), - }) - }) - }, + |_| view(|cx| playground(Theme::default())), ); cx.platform().activate(true); }); diff --git a/crates/gpui/playground/src/themes.rs b/crates/gpui/playground/src/themes.rs index 4057491eed33bcfd6722eba10454f84cf67cc948..34d430089ddb19022e88d8c4890a410f8a8debbf 100644 --- a/crates/gpui/playground/src/themes.rs +++ b/crates/gpui/playground/src/themes.rs @@ -1,107 +1,134 @@ use crate::{ - color::{Hsla, Lerp}, + color::Hsla, element::{Element, PaintContext}, layout_context::LayoutContext, }; -use gpui::{AppContext, WindowContext}; -use std::{marker::PhantomData, ops::Range}; +use gpui::WindowContext; +use serde::{de::Visitor, Deserialize, Deserializer}; +use std::{collections::HashMap, fmt, marker::PhantomData}; -pub mod rose_pine; - -#[derive(Clone, Debug)] +#[derive(Deserialize, Clone, Default, Debug)] pub struct Theme { - pub colors: ThemeColors, + name: String, + is_light: bool, + lowest: Layer, + middle: Layer, + highest: Layer, + popover_shadow: Shadow, + modal_shadow: Shadow, + #[serde(deserialize_with = "deserialize_player_colors")] + players: Vec, + #[serde(deserialize_with = "deserialize_syntax_colors")] + syntax: HashMap, } -pub fn theme<'a>(cx: &'a WindowContext) -> &'a Theme { - cx.theme::() +#[derive(Deserialize, Clone, Default, Debug)] +pub struct Layer { + base: StyleSet, + variant: StyleSet, + on: StyleSet, + accent: StyleSet, + positive: StyleSet, + warning: StyleSet, + negative: StyleSet, } -#[derive(Clone, Debug)] -pub struct ThemeColors { - pub base: Range, - pub surface: Range, - pub overlay: Range, - pub muted: Range, - pub subtle: Range, - pub text: Range, - pub highlight_low: Range, - pub highlight_med: Range, - pub highlight_high: Range, - pub success: Range, - pub warning: Range, - pub error: Range, - pub inserted: Range, - pub deleted: Range, - pub modified: Range, +#[derive(Deserialize, Clone, Default, Debug)] +pub struct StyleSet { + #[serde(rename = "default")] + default: ContainerColors, + hovered: ContainerColors, + pressed: ContainerColors, + active: ContainerColors, + disabled: ContainerColors, + inverted: ContainerColors, } -impl ThemeColors { - fn current(cx: &AppContext) -> &Self { - cx.global::>() - .last() - .expect("must call within a theme provider") - } - - pub fn base(&self, level: f32) -> Hsla { - self.base.lerp(level) - } - - pub fn surface(&self, level: f32) -> Hsla { - self.surface.lerp(level) - } - - pub fn overlay(&self, level: f32) -> Hsla { - self.overlay.lerp(level) - } - - pub fn muted(&self, level: f32) -> Hsla { - self.muted.lerp(level) - } - - pub fn subtle(&self, level: f32) -> Hsla { - self.subtle.lerp(level) - } - - pub fn text(&self, level: f32) -> Hsla { - self.text.lerp(level) - } - - pub fn highlight_low(&self, level: f32) -> Hsla { - self.highlight_low.lerp(level) - } - - pub fn highlight_med(&self, level: f32) -> Hsla { - self.highlight_med.lerp(level) - } - - pub fn highlight_high(&self, level: f32) -> Hsla { - self.highlight_high.lerp(level) - } - - pub fn success(&self, level: f32) -> Hsla { - self.success.lerp(level) - } +#[derive(Deserialize, Clone, Default, Debug)] +pub struct ContainerColors { + background: Hsla, + foreground: Hsla, + border: Hsla, +} - pub fn warning(&self, level: f32) -> Hsla { - self.warning.lerp(level) - } +#[derive(Deserialize, Clone, Default, Debug)] +pub struct PlayerColors { + selection: Hsla, + cursor: Hsla, +} - pub fn error(&self, level: f32) -> Hsla { - self.error.lerp(level) - } +#[derive(Deserialize, Clone, Default, Debug)] +pub struct Shadow { + blur: u8, + color: Hsla, + offset: Vec, +} - pub fn inserted(&self, level: f32) -> Hsla { - self.inserted.lerp(level) - } +pub fn theme<'a>(cx: &'a WindowContext) -> &'a Theme { + cx.theme::() +} - pub fn deleted(&self, level: f32) -> Hsla { - self.deleted.lerp(level) - } +fn deserialize_player_colors<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + struct PlayerArrayVisitor; + + impl<'de> Visitor<'de> for PlayerArrayVisitor { + type Value = Vec; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("an object with integer keys") + } + + fn visit_map>( + self, + mut map: A, + ) -> Result { + let mut players = Vec::with_capacity(8); + while let Some((key, value)) = map.next_entry::()? { + if key < 8 { + players.push(value); + } else { + return Err(serde::de::Error::invalid_value( + serde::de::Unexpected::Unsigned(key as u64), + &"a key in range 0..7", + )); + } + } + Ok(players) + } + } + + deserializer.deserialize_map(PlayerArrayVisitor) +} - pub fn modified(&self, level: f32) -> Hsla { - self.modified.lerp(level) - } +fn deserialize_syntax_colors<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + struct SyntaxVisitor; + + impl<'de> Visitor<'de> for SyntaxVisitor { + type Value = HashMap; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a map with keys and objects with a single color field as values") + } + + fn visit_map(self, mut map: M) -> Result, M::Error> + where + M: serde::de::MapAccess<'de>, + { + 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); + } + Ok(result) + } + } + deserializer.deserialize_map(SyntaxVisitor) } pub struct Themed { diff --git a/crates/gpui/playground/src/workspace.rs b/crates/gpui/playground/src/workspace.rs index bae8f80c24ad17ba465f0a3ae6355334f3e90872..ab8a06a41d8db9705aae6224bd5565ea0497fb5c 100644 --- a/crates/gpui/playground/src/workspace.rs +++ b/crates/gpui/playground/src/workspace.rs @@ -1,10 +1,9 @@ -use crate::div::div; -use crate::element::{IntoElement, ParentElement}; -use crate::style::StyleHelpers; -use crate::themes::theme; -use crate::{element::Element, themes::Theme}; -use gpui::geometry::pixels; -use gpui::ViewContext; +use crate::{ + div::div, + element::{Element, IntoElement, ParentElement}, + style::StyleHelpers, +}; +use gpui::{geometry::pixels, ViewContext}; use playground_macros::Element; use crate as playground; @@ -17,29 +16,27 @@ 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 = &cx.theme::().colors; div() .full() .flex() .flex_col() - .fill(theme.base(0.5)) + // .fill(theme.base(0.5)) .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 colors = &theme(cx).colors; + div().h(pixels(cx.titlebar_height())) //.fill(colors.base(0.)) } fn status_bar(&mut self, cx: &mut ViewContext) -> impl IntoElement { - let colors = &theme(cx).colors; - div().h(pixels(cx.titlebar_height())).fill(colors.base(0.)) + div().h(pixels(cx.titlebar_height())) //.fill(colors.base(0.)) } fn stage(&mut self, cx: &mut ViewContext) -> impl IntoElement { - let colors = &theme(cx).colors; div().flex_grow() } }