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}