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