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