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}