theme.rs

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