1#![allow(clippy::all)]
2#![allow(noop_method_call)]
3use super::traits::Renderable;
4
5#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
6pub struct ColorPalette {
7 pub bg_primary: String,
8 pub bg_secondary: String,
9 pub bg_tertiary: String,
10 pub bg_elevated: String,
11 pub text_primary: String,
12 pub text_secondary: String,
13 pub text_muted: String,
14 pub accent_primary: String,
15 pub accent_secondary: String,
16 pub accent_success: String,
17 pub accent_warning: String,
18 pub accent_error: String,
19 pub border_subtle: String,
20 pub border_default: String,
21 pub axis_x: String,
22 pub axis_y: String,
23 pub axis_z: String,
24}
25
26impl ColorPalette {
27 #[inline]
28 pub fn dark() -> ColorPalette {
29 ColorPalette {
30 bg_primary: "#0a0a1a".to_string(),
31 bg_secondary: "#16213e".to_string(),
32 bg_tertiary: "#1a2744".to_string(),
33 bg_elevated: "#1e2d4d".to_string(),
34 text_primary: "#e0e0e0".to_string(),
35 text_secondary: "#a0a0a0".to_string(),
36 text_muted: "#666666".to_string(),
37 accent_primary: "#e94560".to_string(),
38 accent_secondary: "#0f3460".to_string(),
39 accent_success: "#4ade80".to_string(),
40 accent_warning: "#facc15".to_string(),
41 accent_error: "#ef4444".to_string(),
42 border_subtle: "rgba(255,255,255,0.05)".to_string(),
43 border_default: "rgba(255,255,255,0.1)".to_string(),
44 axis_x: "#e94560".to_string(),
45 axis_y: "#4ade80".to_string(),
46 axis_z: "#60a5fa".to_string(),
47 }
48 }
49 #[inline]
50 pub fn light() -> ColorPalette {
51 ColorPalette {
52 bg_primary: "#f5f5f5".to_string(),
53 bg_secondary: "#ffffff".to_string(),
54 bg_tertiary: "#fafafa".to_string(),
55 bg_elevated: "#ffffff".to_string(),
56 text_primary: "#1a1a2e".to_string(),
57 text_secondary: "#666666".to_string(),
58 text_muted: "#999999".to_string(),
59 accent_primary: "#e94560".to_string(),
60 accent_secondary: "#3b82f6".to_string(),
61 accent_success: "#22c55e".to_string(),
62 accent_warning: "#eab308".to_string(),
63 accent_error: "#dc2626".to_string(),
64 border_subtle: "rgba(0,0,0,0.05)".to_string(),
65 border_default: "rgba(0,0,0,0.1)".to_string(),
66 axis_x: "#dc2626".to_string(),
67 axis_y: "#16a34a".to_string(),
68 axis_z: "#2563eb".to_string(),
69 }
70 }
71 #[inline]
72 pub fn high_contrast() -> ColorPalette {
73 ColorPalette {
74 bg_primary: "#000000".to_string(),
75 bg_secondary: "#1a1a1a".to_string(),
76 bg_tertiary: "#2a2a2a".to_string(),
77 bg_elevated: "#333333".to_string(),
78 text_primary: "#ffffff".to_string(),
79 text_secondary: "#cccccc".to_string(),
80 text_muted: "#888888".to_string(),
81 accent_primary: "#ffcc00".to_string(),
82 accent_secondary: "#00ffcc".to_string(),
83 accent_success: "#00ff00".to_string(),
84 accent_warning: "#ffff00".to_string(),
85 accent_error: "#ff0000".to_string(),
86 border_subtle: "rgba(255,255,255,0.2)".to_string(),
87 border_default: "rgba(255,255,255,0.4)".to_string(),
88 axis_x: "#ff6666".to_string(),
89 axis_y: "#66ff66".to_string(),
90 axis_z: "#6666ff".to_string(),
91 }
92 }
93 #[inline]
94 pub fn monokai() -> ColorPalette {
95 ColorPalette {
96 bg_primary: "#272822".to_string(),
97 bg_secondary: "#3e3d32".to_string(),
98 bg_tertiary: "#49483e".to_string(),
99 bg_elevated: "#75715e".to_string(),
100 text_primary: "#f8f8f2".to_string(),
101 text_secondary: "#a6a997".to_string(),
102 text_muted: "#75715e".to_string(),
103 accent_primary: "#f92672".to_string(),
104 accent_secondary: "#66d9ef".to_string(),
105 accent_success: "#a6e22e".to_string(),
106 accent_warning: "#fd971f".to_string(),
107 accent_error: "#f92672".to_string(),
108 border_subtle: "rgba(255,255,255,0.05)".to_string(),
109 border_default: "rgba(255,255,255,0.1)".to_string(),
110 axis_x: "#f92672".to_string(),
111 axis_y: "#a6e22e".to_string(),
112 axis_z: "#66d9ef".to_string(),
113 }
114 }
115 #[inline]
116 pub fn nord() -> ColorPalette {
117 ColorPalette {
118 bg_primary: "#2e3440".to_string(),
119 bg_secondary: "#3b4252".to_string(),
120 bg_tertiary: "#434c5e".to_string(),
121 bg_elevated: "#4c566a".to_string(),
122 text_primary: "#eceff4".to_string(),
123 text_secondary: "#d8dee9".to_string(),
124 text_muted: "#81a1c1".to_string(),
125 accent_primary: "#88c0d0".to_string(),
126 accent_secondary: "#81a1c1".to_string(),
127 accent_success: "#a3be8c".to_string(),
128 accent_warning: "#ebcb8b".to_string(),
129 accent_error: "#bf616a".to_string(),
130 border_subtle: "rgba(255,255,255,0.05)".to_string(),
131 border_default: "rgba(255,255,255,0.1)".to_string(),
132 axis_x: "#bf616a".to_string(),
133 axis_y: "#a3be8c".to_string(),
134 axis_z: "#5e81ac".to_string(),
135 }
136 }
137}
138
139#[derive(Debug, Clone)]
140pub struct EditorTheme {
141 pub name: String,
142 pub palette: ColorPalette,
143 pub font_family: String,
144 pub font_size_base: i32,
145 pub border_radius: i32,
146 pub spacing_unit: i32,
147 pub animation_duration: i32,
148}
149
150impl EditorTheme {
151 #[inline]
152 pub fn default() -> EditorTheme {
153 EditorTheme {
154 name: "Windjammer Dark".to_string(),
155 palette: ColorPalette::dark(),
156 font_family: "'Inter', 'SF Pro', -apple-system, sans-serif".to_string(),
157 font_size_base: 13,
158 border_radius: 6,
159 spacing_unit: 8,
160 animation_duration: 150,
161 }
162 }
163 #[inline]
164 pub fn light() -> EditorTheme {
165 EditorTheme {
166 name: "Windjammer Light".to_string(),
167 palette: ColorPalette::light(),
168 font_family: "'Inter', 'SF Pro', -apple-system, sans-serif".to_string(),
169 font_size_base: 13,
170 border_radius: 6,
171 spacing_unit: 8,
172 animation_duration: 150,
173 }
174 }
175 #[inline]
176 pub fn monokai() -> EditorTheme {
177 EditorTheme {
178 name: "Monokai Pro".to_string(),
179 palette: ColorPalette::monokai(),
180 font_family: "'Fira Code', 'JetBrains Mono', monospace".to_string(),
181 font_size_base: 13,
182 border_radius: 4,
183 spacing_unit: 8,
184 animation_duration: 100,
185 }
186 }
187 #[inline]
188 pub fn nord() -> EditorTheme {
189 EditorTheme {
190 name: "Nord".to_string(),
191 palette: ColorPalette::nord(),
192 font_family: "'Source Sans Pro', 'Nunito', sans-serif".to_string(),
193 font_size_base: 14,
194 border_radius: 8,
195 spacing_unit: 10,
196 animation_duration: 200,
197 }
198 }
199}
200
201impl Renderable for EditorTheme {
202 #[inline]
203 fn render(self) -> String {
204 let css = format!(
205 "{}{}{}{}{}{}{}{}{}{}{}",
206 ":root {
207 --bg-primary: ",
208 self.palette.bg_primary.as_str(),
209 ";
210 --bg-secondary: ",
211 self.palette.bg_secondary.as_str(),
212 ";
213 --accent-primary: ",
214 self.palette.accent_primary.as_str(),
215 ";
216 --text-primary: ",
217 self.palette.text_primary.as_str(),
218 ";
219 }
220 body {
221 font-family: ",
222 self.font_family.as_str(),
223 ";
224 color: var(--text-primary);
225 background: var(--bg-primary);
226 }"
227 );
228 css
229 }
230}
231
232#[derive(Debug, Clone, PartialEq, Eq, Default)]
233pub struct ThemeSwitcher {
234 pub current_theme: String,
235 pub themes: Vec<String>,
236 pub on_change: String,
237}
238
239impl ThemeSwitcher {
240 #[inline]
241 pub fn new() -> ThemeSwitcher {
242 let mut themes = Vec::new();
243 themes.push("dark".to_string());
244 themes.push("light".to_string());
245 themes.push("monokai".to_string());
246 themes.push("nord".to_string());
247 themes.push("high-contrast".to_string());
248 ThemeSwitcher {
249 current_theme: "dark".to_string(),
250 themes,
251 on_change: "".to_string(),
252 }
253 }
254 #[inline]
255 pub fn on_change(mut self, handler: String) -> ThemeSwitcher {
256 self.on_change = handler;
257 self
258 }
259}
260
261impl Renderable for ThemeSwitcher {
262 #[inline]
263 fn render(self) -> String {
264 let mut options = "".to_string();
265 for t in &self.themes {
266 let selected = {
267 if t.as_str() == self.current_theme.as_str() {
268 "selected".to_string()
269 } else {
270 "".to_string()
271 }
272 };
273 options += format!("<option value='{}' {}>{}</option>", t, selected, t).as_str();
274 }
275 format!(
276 "
277 <div class='theme-switcher'>
278 <label>🎨 Theme</label>
279 <select onchange='{}(this.value)'>
280 {}
281 </select>
282 </div>
283 ",
284 self.on_change, options
285 )
286 }
287}