1use crate::ast::NodeType;
5use std::collections::HashMap;
6
7#[derive(Debug, Clone)]
8pub struct Theme {
9 pub name: String,
10 pub background: String,
11 pub canvas_padding: f64,
12 pub node_styles: HashMap<NodeType, NodeStyle>,
13 pub group_style: GroupStyle,
14 pub connection_style: ConnectionStyle,
15 pub font: FontConfig,
16}
17
18#[derive(Debug, Clone)]
19pub struct NodeStyle {
20 pub fill: String,
21 pub stroke: String,
22 pub text_color: String,
23 pub type_color: String,
24 pub tag_bg: String,
25 pub tag_text: String,
26}
27
28#[derive(Debug, Clone)]
29pub struct GroupStyle {
30 pub fills: Vec<String>, pub strokes: Vec<String>,
32 pub text_color: String,
33 pub corner_radius: f64,
34 pub padding: f64,
35 pub label_size: f64,
36}
37
38#[derive(Debug, Clone)]
39pub struct ConnectionStyle {
40 pub stroke: String,
41 pub stroke_width: f64,
42 pub dashed_stroke: String,
43 pub blocked_stroke: String,
44 pub text_color: String,
45 pub text_bg: String,
46 pub arrow_size: f64,
47 pub label_size: f64,
48 pub tag_bg: String,
49 pub tag_text: String,
50}
51
52#[derive(Debug, Clone)]
53pub struct FontConfig {
54 pub family: String,
55 pub node_label_size: f64,
56 pub node_type_size: f64,
57 pub tag_size: f64,
58}
59
60impl Theme {
61 pub fn node_style(&self, node_type: &NodeType) -> &NodeStyle {
62 self.node_styles.get(node_type).unwrap_or_else(|| {
63 self.node_styles.get(&NodeType::Service).unwrap()
64 })
65 }
66
67 pub fn group_fill(&self, depth: usize) -> &str {
68 let idx = depth.min(self.group_style.fills.len() - 1);
69 &self.group_style.fills[idx]
70 }
71
72 pub fn group_stroke(&self, depth: usize) -> &str {
73 let idx = depth.min(self.group_style.strokes.len() - 1);
74 &self.group_style.strokes[idx]
75 }
76}
77
78pub fn get_theme(name: &str) -> Theme {
79 match name {
80 "dark" => dark_theme(),
81 "blueprint" => blueprint_theme(),
82 "mono" => mono_theme(),
83 "sketch" => light_theme(), _ => light_theme(),
85 }
86}
87
88fn make_node_styles(entries: Vec<(NodeType, &str, &str, &str, &str, &str, &str)>) -> HashMap<NodeType, NodeStyle> {
89 entries.into_iter().map(|(nt, fill, stroke, text, type_c, tag_bg, tag_text)| {
90 (nt, NodeStyle {
91 fill: fill.into(),
92 stroke: stroke.into(),
93 text_color: text.into(),
94 type_color: type_c.into(),
95 tag_bg: tag_bg.into(),
96 tag_text: tag_text.into(),
97 })
98 }).collect()
99}
100
101fn light_theme() -> Theme {
102 Theme {
103 name: "light".into(),
104 background: "#FAFBFC".into(),
105 canvas_padding: 40.0,
106 node_styles: make_node_styles(vec![
107 (NodeType::Service, "#4A90D9", "#2E6AB0", "#FFFFFF", "#B8D4F0", "#EBF2FA", "#2E6AB0"),
108 (NodeType::Db, "#E8913A", "#C47425", "#FFFFFF", "#F5D5B0", "#FDF0E2", "#C47425"),
109 (NodeType::Cache, "#50B88E", "#3A9171", "#FFFFFF", "#B8E4D0", "#E8F6F0", "#3A9171"),
110 (NodeType::Queue, "#8B6CC1", "#6B4FA0", "#FFFFFF", "#CFC0E5", "#F0EBF7", "#6B4FA0"),
111 (NodeType::Gateway, "#3AAFA9", "#2B8A85", "#FFFFFF", "#B0DCD9", "#E4F4F3", "#2B8A85"),
112 (NodeType::User, "#6B7B8D", "#4F5D6B", "#FFFFFF", "#BCC5CE", "#E8ECF0", "#4F5D6B"),
113 (NodeType::Store, "#D4884A", "#B06E35", "#FFFFFF", "#EDD0B3", "#FAF0E3", "#B06E35"),
114 (NodeType::Fn, "#C75C9B", "#A44580", "#FFFFFF", "#E7B8D3", "#F8EAF2", "#A44580"),
115 (NodeType::Worker, "#5A8F6A", "#437352", "#FFFFFF", "#B5D4BD", "#E6F0E9", "#437352"),
116 (NodeType::External, "#95A5B6", "#6E8091", "#FFFFFF", "#CDD5DC", "#EDF0F3", "#6E8091"),
117 ]),
118 group_style: GroupStyle {
119 fills: vec![
120 "rgba(0,0,0,0.03)".into(),
121 "rgba(0,0,0,0.02)".into(),
122 "rgba(0,0,0,0.01)".into(),
123 ],
124 strokes: vec![
125 "#CBD5E0".into(),
126 "#E2E8F0".into(),
127 "#EDF2F7".into(),
128 ],
129 text_color: "#4A5568".into(),
130 corner_radius: 12.0,
131 padding: 24.0,
132 label_size: 13.0,
133 },
134 connection_style: ConnectionStyle {
135 stroke: "#8896A4".into(),
136 stroke_width: 1.5,
137 dashed_stroke: "#A0AEC0".into(),
138 blocked_stroke: "#E53E3E".into(),
139 text_color: "#4A5568".into(),
140 text_bg: "#FFFFFF".into(),
141 arrow_size: 8.0,
142 label_size: 11.0,
143 tag_bg: "#EDF2F7".into(),
144 tag_text: "#4A5568".into(),
145 },
146 font: FontConfig {
147 family: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif".into(),
148 node_label_size: 14.0,
149 node_type_size: 10.0,
150 tag_size: 9.0,
151 },
152 }
153}
154
155fn dark_theme() -> Theme {
156 Theme {
157 name: "dark".into(),
158 background: "#1A202C".into(),
159 canvas_padding: 40.0,
160 node_styles: make_node_styles(vec![
161 (NodeType::Service, "#2B6CB0", "#3182CE", "#E2E8F0", "#90CDF4", "#1A365D", "#90CDF4"),
162 (NodeType::Db, "#C05621", "#DD6B20", "#E2E8F0", "#FBD38D", "#652B19", "#FBD38D"),
163 (NodeType::Cache, "#276749", "#38A169", "#E2E8F0", "#9AE6B4", "#1C4532", "#9AE6B4"),
164 (NodeType::Queue, "#553C9A", "#805AD5", "#E2E8F0", "#D6BCFA", "#322659", "#D6BCFA"),
165 (NodeType::Gateway, "#234E52", "#319795", "#E2E8F0", "#81E6D9", "#1D4044", "#81E6D9"),
166 (NodeType::User, "#4A5568", "#718096", "#E2E8F0", "#CBD5E0", "#2D3748", "#CBD5E0"),
167 (NodeType::Store, "#9C4221", "#C05621", "#E2E8F0", "#FBD38D", "#652B19", "#FBD38D"),
168 (NodeType::Fn, "#702459", "#B83280", "#E2E8F0", "#FBB6CE", "#521B41", "#FBB6CE"),
169 (NodeType::Worker, "#22543D", "#38A169", "#E2E8F0", "#9AE6B4", "#1C4532", "#9AE6B4"),
170 (NodeType::External, "#2D3748", "#4A5568", "#CBD5E0", "#A0AEC0", "#1A202C", "#A0AEC0"),
171 ]),
172 group_style: GroupStyle {
173 fills: vec![
174 "rgba(255,255,255,0.04)".into(),
175 "rgba(255,255,255,0.03)".into(),
176 "rgba(255,255,255,0.02)".into(),
177 ],
178 strokes: vec![
179 "#4A5568".into(),
180 "#2D3748".into(),
181 "#1A202C".into(),
182 ],
183 text_color: "#A0AEC0".into(),
184 corner_radius: 12.0,
185 padding: 24.0,
186 label_size: 13.0,
187 },
188 connection_style: ConnectionStyle {
189 stroke: "#718096".into(),
190 stroke_width: 1.5,
191 dashed_stroke: "#4A5568".into(),
192 blocked_stroke: "#FC8181".into(),
193 text_color: "#A0AEC0".into(),
194 text_bg: "#2D3748".into(),
195 arrow_size: 8.0,
196 label_size: 11.0,
197 tag_bg: "#2D3748".into(),
198 tag_text: "#A0AEC0".into(),
199 },
200 font: FontConfig {
201 family: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif".into(),
202 node_label_size: 14.0,
203 node_type_size: 10.0,
204 tag_size: 9.0,
205 },
206 }
207}
208
209fn blueprint_theme() -> Theme {
210 Theme {
211 name: "blueprint".into(),
212 background: "#0D2137".into(),
213 canvas_padding: 40.0,
214 node_styles: make_node_styles(vec![
215 (NodeType::Service, "#133D6B", "#2980B9", "#D6EAF8", "#85C1E9", "#0B2545", "#85C1E9"),
216 (NodeType::Db, "#1B4D3E", "#27AE60", "#D5F5E3", "#82E0AA", "#0B3D2E", "#82E0AA"),
217 (NodeType::Cache, "#4A235A", "#8E44AD", "#E8DAEF", "#BB8FCE", "#2C1338", "#BB8FCE"),
218 (NodeType::Queue, "#1B4F72", "#2E86C1", "#D6EAF8", "#85C1E9", "#0B3D5C", "#85C1E9"),
219 (NodeType::Gateway, "#0E4D4D", "#17A589", "#D1F2EB", "#76D7C4", "#0A3D3D", "#76D7C4"),
220 (NodeType::User, "#1C2833", "#5D6D7E", "#D6DBDF", "#AEB6BF", "#0E1A25", "#AEB6BF"),
221 (NodeType::Store, "#1B3A4B", "#2E86C1", "#D6EAF8", "#85C1E9", "#0B2A3B", "#85C1E9"),
222 (NodeType::Fn, "#4A235A", "#AF7AC5", "#E8DAEF", "#D2B4DE", "#2C1338", "#D2B4DE"),
223 (NodeType::Worker, "#1B4D3E", "#2ECC71", "#D5F5E3", "#82E0AA", "#0B3D2E", "#82E0AA"),
224 (NodeType::External, "#1C2833", "#5D6D7E", "#D6DBDF", "#AEB6BF", "#0E1A25", "#AEB6BF"),
225 ]),
226 group_style: GroupStyle {
227 fills: vec![
228 "rgba(41,128,185,0.08)".into(),
229 "rgba(41,128,185,0.05)".into(),
230 "rgba(41,128,185,0.03)".into(),
231 ],
232 strokes: vec![
233 "#2980B9".into(),
234 "#1F6FA3".into(),
235 "#155A8A".into(),
236 ],
237 text_color: "#85C1E9".into(),
238 corner_radius: 4.0,
239 padding: 24.0,
240 label_size: 13.0,
241 },
242 connection_style: ConnectionStyle {
243 stroke: "#5DADE2".into(),
244 stroke_width: 1.0,
245 dashed_stroke: "#3498DB".into(),
246 blocked_stroke: "#E74C3C".into(),
247 text_color: "#85C1E9".into(),
248 text_bg: "#0D2137".into(),
249 arrow_size: 8.0,
250 label_size: 11.0,
251 tag_bg: "#133D6B".into(),
252 tag_text: "#85C1E9".into(),
253 },
254 font: FontConfig {
255 family: "'SF Mono', 'Fira Code', 'Consolas', monospace".into(),
256 node_label_size: 13.0,
257 node_type_size: 9.0,
258 tag_size: 9.0,
259 },
260 }
261}
262
263fn mono_theme() -> Theme {
264 Theme {
265 name: "mono".into(),
266 background: "#FFFFFF".into(),
267 canvas_padding: 40.0,
268 node_styles: make_node_styles(vec![
269 (NodeType::Service, "#F7F7F7", "#333333", "#333333", "#888888", "#EEEEEE", "#555555"),
270 (NodeType::Db, "#F0F0F0", "#333333", "#333333", "#888888", "#EEEEEE", "#555555"),
271 (NodeType::Cache, "#F7F7F7", "#333333", "#333333", "#888888", "#EEEEEE", "#555555"),
272 (NodeType::Queue, "#F0F0F0", "#333333", "#333333", "#888888", "#EEEEEE", "#555555"),
273 (NodeType::Gateway, "#F7F7F7", "#333333", "#333333", "#888888", "#EEEEEE", "#555555"),
274 (NodeType::User, "#F0F0F0", "#333333", "#333333", "#888888", "#EEEEEE", "#555555"),
275 (NodeType::Store, "#F7F7F7", "#333333", "#333333", "#888888", "#EEEEEE", "#555555"),
276 (NodeType::Fn, "#F0F0F0", "#333333", "#333333", "#888888", "#EEEEEE", "#555555"),
277 (NodeType::Worker, "#F7F7F7", "#333333", "#333333", "#888888", "#EEEEEE", "#555555"),
278 (NodeType::External, "#F0F0F0", "#555555", "#333333", "#888888", "#EEEEEE", "#555555"),
279 ]),
280 group_style: GroupStyle {
281 fills: vec![
282 "rgba(0,0,0,0.02)".into(),
283 "rgba(0,0,0,0.01)".into(),
284 "rgba(0,0,0,0.005)".into(),
285 ],
286 strokes: vec![
287 "#CCCCCC".into(),
288 "#DDDDDD".into(),
289 "#EEEEEE".into(),
290 ],
291 text_color: "#666666".into(),
292 corner_radius: 8.0,
293 padding: 24.0,
294 label_size: 13.0,
295 },
296 connection_style: ConnectionStyle {
297 stroke: "#999999".into(),
298 stroke_width: 1.0,
299 dashed_stroke: "#BBBBBB".into(),
300 blocked_stroke: "#CC3333".into(),
301 text_color: "#666666".into(),
302 text_bg: "#FFFFFF".into(),
303 arrow_size: 8.0,
304 label_size: 11.0,
305 tag_bg: "#F0F0F0".into(),
306 tag_text: "#666666".into(),
307 },
308 font: FontConfig {
309 family: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif".into(),
310 node_label_size: 14.0,
311 node_type_size: 10.0,
312 tag_size: 9.0,
313 },
314 }
315}