theme.rs

  1use std::collections::HashMap;
  2use std::fmt;
  3use std::marker::PhantomData;
  4use std::sync::Arc;
  5
  6use gpui2::color::Hsla;
  7use gpui2::element::Element;
  8use gpui2::{serde_json, AppContext, IntoElement, Vector2F, ViewContext, WindowContext};
  9use serde::de::Visitor;
 10use serde::{Deserialize, Deserializer};
 11use theme::ThemeSettings;
 12
 13#[derive(Deserialize, Clone, Default, Debug)]
 14pub struct Theme {
 15    pub name: String,
 16    pub is_light: bool,
 17    pub lowest: Layer,
 18    pub middle: Layer,
 19    pub highest: Layer,
 20    pub popover_shadow: Shadow,
 21    pub modal_shadow: Shadow,
 22    #[serde(deserialize_with = "deserialize_player_colors")]
 23    pub players: Vec<PlayerColors>,
 24    #[serde(deserialize_with = "deserialize_syntax_colors")]
 25    pub syntax: HashMap<String, Hsla>,
 26}
 27
 28#[derive(Deserialize, Clone, Default, Debug)]
 29pub struct Layer {
 30    pub base: StyleSet,
 31    pub variant: StyleSet,
 32    pub on: StyleSet,
 33    pub accent: StyleSet,
 34    pub positive: StyleSet,
 35    pub warning: StyleSet,
 36    pub negative: StyleSet,
 37}
 38
 39#[derive(Deserialize, Clone, Default, Debug)]
 40pub struct StyleSet {
 41    #[serde(rename = "default")]
 42    pub default: ContainerColors,
 43    pub hovered: ContainerColors,
 44    pub pressed: ContainerColors,
 45    pub active: ContainerColors,
 46    pub disabled: ContainerColors,
 47    pub inverted: ContainerColors,
 48}
 49
 50#[derive(Deserialize, Clone, Default, Debug)]
 51pub struct ContainerColors {
 52    pub background: Hsla,
 53    pub foreground: Hsla,
 54    pub border: Hsla,
 55}
 56
 57#[derive(Deserialize, Clone, Default, Debug)]
 58pub struct PlayerColors {
 59    pub selection: Hsla,
 60    pub cursor: Hsla,
 61}
 62
 63#[derive(Deserialize, Clone, Default, Debug)]
 64pub struct Shadow {
 65    pub blur: u8,
 66    pub color: Hsla,
 67    pub offset: Vec<u8>,
 68}
 69
 70fn deserialize_player_colors<'de, D>(deserializer: D) -> Result<Vec<PlayerColors>, D::Error>
 71where
 72    D: Deserializer<'de>,
 73{
 74    struct PlayerArrayVisitor;
 75
 76    impl<'de> Visitor<'de> for PlayerArrayVisitor {
 77        type Value = Vec<PlayerColors>;
 78
 79        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
 80            formatter.write_str("an object with integer keys")
 81        }
 82
 83        fn visit_map<A: serde::de::MapAccess<'de>>(
 84            self,
 85            mut map: A,
 86        ) -> Result<Self::Value, A::Error> {
 87            let mut players = Vec::with_capacity(8);
 88            while let Some((key, value)) = map.next_entry::<usize, PlayerColors>()? {
 89                if key < 8 {
 90                    players.push(value);
 91                } else {
 92                    return Err(serde::de::Error::invalid_value(
 93                        serde::de::Unexpected::Unsigned(key as u64),
 94                        &"a key in range 0..7",
 95                    ));
 96                }
 97            }
 98            Ok(players)
 99        }
100    }
101
102    deserializer.deserialize_map(PlayerArrayVisitor)
103}
104
105fn deserialize_syntax_colors<'de, D>(deserializer: D) -> Result<HashMap<String, Hsla>, D::Error>
106where
107    D: serde::Deserializer<'de>,
108{
109    #[derive(Deserialize)]
110    struct ColorWrapper {
111        color: Hsla,
112    }
113
114    struct SyntaxVisitor;
115
116    impl<'de> Visitor<'de> for SyntaxVisitor {
117        type Value = HashMap<String, Hsla>;
118
119        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
120            formatter.write_str("a map with keys and objects with a single color field as values")
121        }
122
123        fn visit_map<M>(self, mut map: M) -> Result<HashMap<String, Hsla>, M::Error>
124        where
125            M: serde::de::MapAccess<'de>,
126        {
127            let mut result = HashMap::new();
128            while let Some(key) = map.next_key()? {
129                let wrapper: ColorWrapper = map.next_value()?; // Deserialize values as Hsla
130                result.insert(key, wrapper.color);
131            }
132            Ok(result)
133        }
134    }
135    deserializer.deserialize_map(SyntaxVisitor)
136}
137
138#[derive(IntoElement)]
139pub struct Themed<V: 'static, E: Element<V>> {
140    pub(crate) theme: Theme,
141    pub(crate) child: E,
142    pub(crate) view_type: PhantomData<V>,
143}
144
145impl<V: 'static, E: Element<V>> Element<V> for Themed<V, E> {
146    type PaintState = E::PaintState;
147
148    fn layout(
149        &mut self,
150        view: &mut V,
151        cx: &mut ViewContext<V>,
152    ) -> anyhow::Result<(gpui2::LayoutId, Self::PaintState)>
153    where
154        Self: Sized,
155    {
156        cx.push_theme(self.theme.clone());
157        let result = self.child.layout(view, cx);
158        cx.pop_theme();
159        result
160    }
161
162    fn paint(
163        &mut self,
164        view: &mut V,
165        parent_origin: Vector2F,
166        layout: &gpui2::Layout,
167        state: &mut Self::PaintState,
168        cx: &mut ViewContext<V>,
169    ) where
170        Self: Sized,
171    {
172        cx.push_theme(self.theme.clone());
173        self.child.paint(view, parent_origin, layout, state, cx);
174        cx.pop_theme();
175    }
176}
177
178fn preferred_theme<V: 'static>(cx: &AppContext) -> Theme {
179    settings::get::<ThemeSettings>(cx)
180        .theme
181        .deserialized_base_theme
182        .lock()
183        .get_or_insert_with(|| {
184            let theme: Theme =
185                serde_json::from_value(settings::get::<ThemeSettings>(cx).theme.base_theme.clone())
186                    .unwrap();
187            Box::new(theme)
188        })
189        .downcast_ref::<Theme>()
190        .unwrap()
191        .clone()
192}
193
194pub fn theme(cx: &WindowContext) -> Arc<Theme> {
195    cx.theme::<Theme>()
196}