1use kdl::{KdlDocument, KdlNode, KdlValue};
2use serde::{Deserialize, Serialize};
3
4use crate::{
5 data::PaletteColor, kdl_children_or_error, kdl_first_entry_as_string, kdl_get_child,
6 kdl_get_child_entry_bool_value, kdl_get_child_entry_string_value,
7};
8
9use super::config::ConfigError;
10
11#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
12pub struct WebClientTheme {
13 pub background: Option<String>,
14 pub foreground: Option<String>,
15 pub black: Option<String>,
16 pub blue: Option<String>,
17 pub bright_black: Option<String>,
18 pub bright_blue: Option<String>,
19 pub bright_cyan: Option<String>,
20 pub bright_green: Option<String>,
21 pub bright_magenta: Option<String>,
22 pub bright_red: Option<String>,
23 pub bright_white: Option<String>,
24 pub bright_yellow: Option<String>,
25 pub cursor: Option<String>,
26 pub cursor_accent: Option<String>,
27 pub cyan: Option<String>,
28 pub green: Option<String>,
29 pub magenta: Option<String>,
30 pub red: Option<String>,
31 pub selection_background: Option<String>,
32 pub selection_foreground: Option<String>,
33 pub selection_inactive_background: Option<String>,
34 pub white: Option<String>,
35 pub yellow: Option<String>,
36}
37
38impl WebClientTheme {
39 pub fn from_kdl(kdl: &KdlNode) -> Result<Self, ConfigError> {
40 let mut theme = WebClientTheme::default();
41 let colors = kdl_children_or_error!(kdl, "empty theme");
42
43 let extract_color = |name: &str| -> Result<Option<String>, ConfigError> {
45 if colors.get(name).is_some() {
46 let color = PaletteColor::try_from((name, colors))?;
47 Ok(Some(color.as_rgb_str()))
48 } else {
49 Ok(None)
50 }
51 };
52
53 theme.background = extract_color("background")?;
54 theme.foreground = extract_color("foreground")?;
55 theme.black = extract_color("black")?;
56 theme.blue = extract_color("blue")?;
57 theme.bright_black = extract_color("bright_black")?;
58 theme.bright_blue = extract_color("bright_blue")?;
59 theme.bright_cyan = extract_color("bright_cyan")?;
60 theme.bright_green = extract_color("bright_green")?;
61 theme.bright_magenta = extract_color("bright_magenta")?;
62 theme.bright_red = extract_color("bright_red")?;
63 theme.bright_white = extract_color("bright_white")?;
64 theme.bright_yellow = extract_color("bright_yellow")?;
65 theme.cursor = extract_color("cursor")?;
66 theme.cursor_accent = extract_color("cursor_accent")?;
67 theme.cyan = extract_color("cyan")?;
68 theme.green = extract_color("green")?;
69 theme.magenta = extract_color("magenta")?;
70 theme.red = extract_color("red")?;
71 theme.selection_background = extract_color("selection_background")?;
72 theme.selection_foreground = extract_color("selection_foreground")?;
73 theme.selection_inactive_background = extract_color("selection_inactive_background")?;
74 theme.white = extract_color("white")?;
75 theme.yellow = extract_color("yellow")?;
76
77 Ok(theme)
78 }
79
80 pub fn to_kdl(&self) -> KdlNode {
81 macro_rules! add_color_nodes {
82 ($theme_children:expr, $self:expr, $($field:ident),+ $(,)?) => {
83 $(
84 if let Some(color) = &$self.$field {
85 let node = PaletteColor::from_rgb_str(color).to_kdl(stringify!($field));
86 $theme_children.nodes_mut().push(node);
87 }
88 )+
89 };
90 }
91 let mut theme_node = KdlNode::new("theme");
92 let mut theme_children = KdlDocument::new();
93
94 add_color_nodes!(
95 theme_children,
96 self,
97 background,
98 foreground,
99 black,
100 blue,
101 bright_black,
102 bright_blue,
103 bright_cyan,
104 bright_green,
105 bright_magenta,
106 bright_red,
107 bright_white,
108 bright_yellow,
109 cursor,
110 cursor_accent,
111 cyan,
112 green,
113 magenta,
114 red,
115 selection_background,
116 selection_foreground,
117 selection_inactive_background,
118 white,
119 yellow,
120 );
121
122 theme_node.set_children(theme_children);
123 theme_node
124 }
125}
126
127#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
128pub enum CursorInactiveStyle {
129 Outline,
130 Block,
131 Bar,
132 Underline,
133 NoStyle,
134}
135
136impl std::fmt::Display for CursorInactiveStyle {
137 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138 match self {
139 CursorInactiveStyle::Block => write!(f, "block"),
140 CursorInactiveStyle::Bar => write!(f, "bar"),
141 CursorInactiveStyle::Underline => write!(f, "underline"),
142 CursorInactiveStyle::Outline => write!(f, "outline"),
143 CursorInactiveStyle::NoStyle => write!(f, "none"),
144 }
145 }
146}
147
148impl CursorInactiveStyle {
149 pub fn from_kdl(kdl: &KdlNode) -> Result<Self, ConfigError> {
150 match kdl_first_entry_as_string!(kdl) {
151 Some("block") => Ok(CursorInactiveStyle::Block),
152 Some("bar") => Ok(CursorInactiveStyle::Bar),
153 Some("underline") => Ok(CursorInactiveStyle::Underline),
154 Some("outline") => Ok(CursorInactiveStyle::Outline),
155 Some("no_style") => Ok(CursorInactiveStyle::NoStyle),
156 _ => Err(ConfigError::new_kdl_error(
157 format!("Must be 'block', 'bar', 'underline', 'outline' or 'no_style'"),
158 kdl.span().offset(),
159 kdl.span().len(),
160 )),
161 }
162 }
163 pub fn to_kdl(&self) -> KdlNode {
164 let mut cursor_inactive_style_node = KdlNode::new("cursor_inactive_style");
165 match self {
166 CursorInactiveStyle::Block => {
167 cursor_inactive_style_node.push(KdlValue::String("block".to_owned()));
168 },
169 CursorInactiveStyle::Bar => {
170 cursor_inactive_style_node.push(KdlValue::String("bar".to_owned()));
171 },
172 CursorInactiveStyle::Underline => {
173 cursor_inactive_style_node.push(KdlValue::String("underline".to_owned()));
174 },
175 CursorInactiveStyle::Outline => {
176 cursor_inactive_style_node.push(KdlValue::String("outline".to_owned()));
177 },
178 CursorInactiveStyle::NoStyle => {
179 cursor_inactive_style_node.push(KdlValue::String("no_style".to_owned()));
180 },
181 }
182 cursor_inactive_style_node
183 }
184}
185
186#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
187pub enum CursorStyle {
188 Block,
189 Bar,
190 Underline,
191}
192
193impl std::fmt::Display for CursorStyle {
194 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195 match self {
196 CursorStyle::Block => write!(f, "block"),
197 CursorStyle::Bar => write!(f, "bar"),
198 CursorStyle::Underline => write!(f, "underline"),
199 }
200 }
201}
202
203impl CursorStyle {
204 pub fn from_kdl(kdl: &KdlNode) -> Result<Self, ConfigError> {
205 match kdl_first_entry_as_string!(kdl) {
206 Some("block") => Ok(CursorStyle::Block),
207 Some("bar") => Ok(CursorStyle::Bar),
208 Some("underline") => Ok(CursorStyle::Underline),
209 _ => Err(ConfigError::new_kdl_error(
210 format!("Must be 'block', 'bar' or 'underline'"),
211 kdl.span().offset(),
212 kdl.span().len(),
213 )),
214 }
215 }
216 pub fn to_kdl(&self) -> KdlNode {
217 let mut cursor_style_node = KdlNode::new("cursor_style");
218 match self {
219 CursorStyle::Block => {
220 cursor_style_node.push(KdlValue::String("block".to_owned()));
221 },
222 CursorStyle::Bar => {
223 cursor_style_node.push(KdlValue::String("bar".to_owned()));
224 },
225 CursorStyle::Underline => {
226 cursor_style_node.push(KdlValue::String("underline".to_owned()));
227 },
228 }
229 cursor_style_node
230 }
231}
232
233#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
234pub struct WebClientConfig {
235 pub font: String,
236 pub theme: Option<WebClientTheme>,
237 pub cursor_blink: bool,
238 pub cursor_inactive_style: Option<CursorInactiveStyle>,
239 pub cursor_style: Option<CursorStyle>,
240 pub mac_option_is_meta: bool,
241 pub base_url: Option<String>,
242}
243
244impl Default for WebClientConfig {
245 fn default() -> Self {
246 WebClientConfig {
247 font: "monospace".to_string(),
248 theme: None,
249 cursor_blink: false,
250 cursor_inactive_style: None,
251 cursor_style: None,
252 mac_option_is_meta: true, base_url: None,
254 }
255 }
256}
257
258impl WebClientConfig {
259 pub fn from_kdl(kdl: &KdlNode) -> Result<Self, ConfigError> {
260 let mut web_client_config = WebClientConfig::default();
261
262 if let Some(font) = kdl_get_child_entry_string_value!(kdl, "font") {
263 web_client_config.font = font.to_owned();
264 }
265
266 if let Some(theme_node) = kdl_get_child!(kdl, "theme") {
267 web_client_config.theme = Some(WebClientTheme::from_kdl(theme_node)?);
268 }
269
270 if let Some(cursor_blink) = kdl_get_child_entry_bool_value!(kdl, "cursor_blink") {
271 web_client_config.cursor_blink = cursor_blink;
272 }
273
274 if let Some(cursor_inactive_style_node) = kdl_get_child!(kdl, "cursor_inactive_style") {
275 web_client_config.cursor_inactive_style =
276 Some(CursorInactiveStyle::from_kdl(cursor_inactive_style_node)?);
277 }
278
279 if let Some(cursor_style_node) = kdl_get_child!(kdl, "cursor_style") {
280 web_client_config.cursor_style = Some(CursorStyle::from_kdl(cursor_style_node)?);
281 }
282
283 if let Some(mac_option_is_meta) = kdl_get_child_entry_bool_value!(kdl, "mac_option_is_meta")
284 {
285 web_client_config.mac_option_is_meta = mac_option_is_meta;
286 }
287
288 if let Some(base_url) = kdl_get_child_entry_string_value!(kdl, "base_url") {
289 web_client_config.base_url = Some(base_url.to_owned());
290 }
291
292 Ok(web_client_config)
293 }
294
295 pub fn to_kdl(&self) -> KdlNode {
296 let mut web_client_node = KdlNode::new("web_client");
297 let mut web_client_children = KdlDocument::new();
298
299 let mut font_node = KdlNode::new("font");
300 font_node.push(KdlValue::String(self.font.clone()));
301 web_client_children.nodes_mut().push(font_node);
302
303 if let Some(theme_node) = self.theme.as_ref().map(|t| t.to_kdl()) {
304 web_client_children.nodes_mut().push(theme_node);
305 }
306
307 if self.cursor_blink {
308 let mut cursor_blink_node = KdlNode::new("cursor_blink");
310 cursor_blink_node.push(KdlValue::Bool(true));
311 web_client_children.nodes_mut().push(cursor_blink_node);
312 }
313
314 if let Some(cursor_inactive_style_node) =
315 self.cursor_inactive_style.as_ref().map(|c| c.to_kdl())
316 {
317 web_client_children
318 .nodes_mut()
319 .push(cursor_inactive_style_node);
320 }
321
322 if let Some(cursor_style_node) = self.cursor_style.as_ref().map(|c| c.to_kdl()) {
323 web_client_children.nodes_mut().push(cursor_style_node);
324 }
325
326 if !self.mac_option_is_meta {
327 let mut mac_option_is_meta_node = KdlNode::new("mac_option_is_meta");
329 mac_option_is_meta_node.push(KdlValue::Bool(false));
330 web_client_children
331 .nodes_mut()
332 .push(mac_option_is_meta_node);
333 }
334
335 if let Some(base_url) = &self.base_url {
336 let mut base_url_node = KdlNode::new("base_url");
337 base_url_node.push(KdlValue::String(base_url.clone()));
338 web_client_children.nodes_mut().push(base_url_node);
339 }
340
341 web_client_node.set_children(web_client_children);
342 web_client_node
343 }
344
345 pub fn merge(&self, other: WebClientConfig) -> Self {
346 let mut merged = self.clone();
347 merged.font = other.font;
348 merged.theme = other.theme;
349 merged.cursor_blink = other.cursor_blink;
350 merged.cursor_inactive_style = other.cursor_inactive_style;
351 merged.cursor_style = other.cursor_style;
352 merged.mac_option_is_meta = other.mac_option_is_meta;
353 merged.base_url = other.base_url;
354 merged
355 }
356}