theme.rs

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