theme.rs

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