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}