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