Skip to main content

toolkit_ry/
theme.rs

1// crates/toolkit-ry/src/theme.rs
2// Themes predefinidos para UI de juegos Ry-Dit
3
4/// Colores RGBA
5#[derive(Debug, Clone, Copy)]
6pub struct ColorRGBA {
7    pub r: u8,
8    pub g: u8,
9    pub b: u8,
10    pub a: u8,
11}
12
13impl ColorRGBA {
14    pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
15        Self { r, g, b, a }
16    }
17
18    pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
19        Self { r, g, b, a: 255 }
20    }
21
22    pub const fn transparent() -> Self {
23        Self { r: 0, g: 0, b: 0, a: 0 }
24    }
25}
26
27/// Tema visual para toolkit-ry
28#[derive(Debug, Clone)]
29pub struct Theme {
30    pub name: &'static str,
31    // Colores base
32    pub bg_color: ColorRGBA,
33    pub panel_bg: ColorRGBA,
34    pub panel_border: ColorRGBA,
35    // Texto
36    pub text_color: ColorRGBA,
37    pub text_dim: ColorRGBA,
38    pub text_highlight: ColorRGBA,
39    // Widgets
40    pub button_bg: ColorRGBA,
41    pub button_hover: ColorRGBA,
42    pub button_pressed: ColorRGBA,
43    pub button_border: ColorRGBA,
44    // HUD
45    pub health_bar_bg: ColorRGBA,
46    pub health_bar_fill: ColorRGBA,
47    pub health_bar_border: ColorRGBA,
48    pub mana_bar_bg: ColorRGBA,
49    pub mana_bar_fill: ColorRGBA,
50    pub xp_bar_bg: ColorRGBA,
51    pub xp_bar_fill: ColorRGBA,
52    // Inventário
53    pub slot_bg: ColorRGBA,
54    pub slot_hover: ColorRGBA,
55    pub slot_border: ColorRGBA,
56    // Diálogos
57    pub dialog_bg: ColorRGBA,
58    pub dialog_border: ColorRGBA,
59    // Menú
60    pub menu_bg: ColorRGBA,
61    pub menu_item_bg: ColorRGBA,
62    pub menu_item_hover: ColorRGBA,
63    pub menu_item_selected: ColorRGBA,
64    // Tipografía
65    pub font_size: u32,
66    pub font_size_small: u32,
67    pub font_size_title: u32,
68    // Espaciado
69    pub padding: f32,
70    pub spacing: f32,
71    pub border_width: f32,
72}
73
74impl Theme {
75    /// Tema oscuro (default) — estilo moderno
76    pub fn dark() -> Self {
77        Self {
78            name: "Dark",
79            bg_color: ColorRGBA::rgb(18, 18, 24),
80            panel_bg: ColorRGBA::rgb(28, 28, 36),
81            panel_border: ColorRGBA::rgb(60, 60, 80),
82            text_color: ColorRGBA::rgb(230, 230, 240),
83            text_dim: ColorRGBA::rgb(140, 140, 160),
84            text_highlight: ColorRGBA::rgb(255, 255, 100),
85            button_bg: ColorRGBA::rgb(50, 50, 70),
86            button_hover: ColorRGBA::rgb(70, 70, 100),
87            button_pressed: ColorRGBA::rgb(90, 90, 130),
88            button_border: ColorRGBA::rgb(100, 100, 130),
89            health_bar_bg: ColorRGBA::rgb(40, 20, 20),
90            health_bar_fill: ColorRGBA::rgb(220, 50, 50),
91            health_bar_border: ColorRGBA::rgb(180, 40, 40),
92            mana_bar_bg: ColorRGBA::rgb(20, 20, 50),
93            mana_bar_fill: ColorRGBA::rgb(50, 100, 220),
94            xp_bar_bg: ColorRGBA::rgb(40, 40, 20),
95            xp_bar_fill: ColorRGBA::rgb(220, 200, 50),
96            slot_bg: ColorRGBA::rgb(35, 35, 45),
97            slot_hover: ColorRGBA::rgb(50, 50, 65),
98            slot_border: ColorRGBA::rgb(70, 70, 90),
99            dialog_bg: ColorRGBA::rgb(25, 25, 35),
100            dialog_border: ColorRGBA::rgb(80, 80, 100),
101            menu_bg: ColorRGBA::rgb(22, 22, 30),
102            menu_item_bg: ColorRGBA::rgb(30, 30, 42),
103            menu_item_hover: ColorRGBA::rgb(50, 50, 70),
104            menu_item_selected: ColorRGBA::rgb(70, 70, 100),
105            font_size: 16,
106            font_size_small: 12,
107            font_size_title: 20,
108            padding: 8.0,
109            spacing: 4.0,
110            border_width: 2.0,
111        }
112    }
113
114    /// Tema claro — estilo limpio
115    pub fn light() -> Self {
116        Self {
117            name: "Light",
118            bg_color: ColorRGBA::rgb(240, 240, 245),
119            panel_bg: ColorRGBA::rgb(255, 255, 255),
120            panel_border: ColorRGBA::rgb(200, 200, 210),
121            text_color: ColorRGBA::rgb(30, 30, 40),
122            text_dim: ColorRGBA::rgb(120, 120, 140),
123            text_highlight: ColorRGBA::rgb(200, 160, 0),
124            button_bg: ColorRGBA::rgb(220, 220, 230),
125            button_hover: ColorRGBA::rgb(200, 200, 215),
126            button_pressed: ColorRGBA::rgb(180, 180, 200),
127            button_border: ColorRGBA::rgb(170, 170, 185),
128            health_bar_bg: ColorRGBA::rgb(230, 210, 210),
129            health_bar_fill: ColorRGBA::rgb(220, 60, 60),
130            health_bar_border: ColorRGBA::rgb(190, 50, 50),
131            mana_bar_bg: ColorRGBA::rgb(210, 210, 240),
132            mana_bar_fill: ColorRGBA::rgb(60, 110, 220),
133            xp_bar_bg: ColorRGBA::rgb(240, 240, 210),
134            xp_bar_fill: ColorRGBA::rgb(220, 200, 60),
135            slot_bg: ColorRGBA::rgb(235, 235, 240),
136            slot_hover: ColorRGBA::rgb(220, 220, 230),
137            slot_border: ColorRGBA::rgb(200, 200, 210),
138            dialog_bg: ColorRGBA::rgb(250, 250, 255),
139            dialog_border: ColorRGBA::rgb(190, 190, 200),
140            menu_bg: ColorRGBA::rgb(245, 245, 250),
141            menu_item_bg: ColorRGBA::rgb(240, 240, 248),
142            menu_item_hover: ColorRGBA::rgb(220, 220, 235),
143            menu_item_selected: ColorRGBA::rgb(200, 200, 220),
144            font_size: 16,
145            font_size_small: 12,
146            font_size_title: 20,
147            padding: 8.0,
148            spacing: 4.0,
149            border_width: 1.0,
150        }
151    }
152
153    /// Tema retro — estilo pixel art 8-bit
154    pub fn retro() -> Self {
155        Self {
156            name: "Retro",
157            bg_color: ColorRGBA::rgb(0, 0, 0),
158            panel_bg: ColorRGBA::rgb(32, 32, 32),
159            panel_border: ColorRGBA::rgb(255, 255, 255),
160            text_color: ColorRGBA::rgb(0, 255, 0),
161            text_dim: ColorRGBA::rgb(0, 180, 0),
162            text_highlight: ColorRGBA::rgb(255, 255, 0),
163            button_bg: ColorRGBA::rgb(64, 64, 64),
164            button_hover: ColorRGBA::rgb(96, 96, 96),
165            button_pressed: ColorRGBA::rgb(128, 128, 128),
166            button_border: ColorRGBA::rgb(255, 255, 255),
167            health_bar_bg: ColorRGBA::rgb(32, 0, 0),
168            health_bar_fill: ColorRGBA::rgb(255, 0, 0),
169            health_bar_border: ColorRGBA::rgb(255, 255, 255),
170            mana_bar_bg: ColorRGBA::rgb(0, 0, 32),
171            mana_bar_fill: ColorRGBA::rgb(0, 100, 255),
172            xp_bar_bg: ColorRGBA::rgb(32, 32, 0),
173            xp_bar_fill: ColorRGBA::rgb(255, 255, 0),
174            slot_bg: ColorRGBA::rgb(48, 48, 48),
175            slot_hover: ColorRGBA::rgb(80, 80, 80),
176            slot_border: ColorRGBA::rgb(255, 255, 255),
177            dialog_bg: ColorRGBA::rgb(0, 0, 64),
178            dialog_border: ColorRGBA::rgb(255, 255, 255),
179            menu_bg: ColorRGBA::rgb(0, 0, 0),
180            menu_item_bg: ColorRGBA::rgb(0, 0, 0),
181            menu_item_hover: ColorRGBA::rgb(0, 64, 0),
182            menu_item_selected: ColorRGBA::rgb(0, 128, 0),
183            font_size: 14,
184            font_size_small: 10,
185            font_size_title: 18,
186            padding: 6.0,
187            spacing: 3.0,
188            border_width: 2.0,
189        }
190    }
191
192    /// Tema neón — estilo cyberpunk con glow
193    pub fn neon() -> Self {
194        Self {
195            name: "Neon",
196            bg_color: ColorRGBA::rgb(5, 5, 15),
197            panel_bg: ColorRGBA::rgb(10, 10, 30),
198            panel_border: ColorRGBA::rgb(0, 255, 255),
199            text_color: ColorRGBA::rgb(255, 255, 255),
200            text_dim: ColorRGBA::rgb(100, 200, 255),
201            text_highlight: ColorRGBA::rgb(255, 0, 255),
202            button_bg: ColorRGBA::rgb(20, 20, 50),
203            button_hover: ColorRGBA::rgb(40, 40, 100),
204            button_pressed: ColorRGBA::rgb(60, 60, 150),
205            button_border: ColorRGBA::rgb(0, 255, 255),
206            health_bar_bg: ColorRGBA::rgb(30, 5, 5),
207            health_bar_fill: ColorRGBA::rgb(255, 50, 50),
208            health_bar_border: ColorRGBA::rgb(255, 100, 100),
209            mana_bar_bg: ColorRGBA::rgb(5, 5, 30),
210            mana_bar_fill: ColorRGBA::rgb(100, 150, 255),
211            xp_bar_bg: ColorRGBA::rgb(30, 30, 5),
212            xp_bar_fill: ColorRGBA::rgb(255, 255, 100),
213            slot_bg: ColorRGBA::rgb(15, 15, 35),
214            slot_hover: ColorRGBA::rgb(30, 30, 70),
215            slot_border: ColorRGBA::rgb(0, 255, 255),
216            dialog_bg: ColorRGBA::rgb(8, 8, 25),
217            dialog_border: ColorRGBA::rgb(255, 0, 255),
218            menu_bg: ColorRGBA::rgb(5, 5, 20),
219            menu_item_bg: ColorRGBA::rgb(10, 10, 30),
220            menu_item_hover: ColorRGBA::rgb(30, 30, 70),
221            menu_item_selected: ColorRGBA::rgb(50, 50, 120),
222            font_size: 16,
223            font_size_small: 12,
224            font_size_title: 22,
225            padding: 10.0,
226            spacing: 6.0,
227            border_width: 1.0,
228        }
229    }
230
231    /// Tema minimalista — ultra limpio
232    pub fn minimal() -> Self {
233        Self {
234            name: "Minimal",
235            bg_color: ColorRGBA::transparent(),
236            panel_bg: ColorRGBA::rgb(20, 20, 25),
237            panel_border: ColorRGBA::transparent(),
238            text_color: ColorRGBA::rgb(240, 240, 245),
239            text_dim: ColorRGBA::rgb(120, 120, 130),
240            text_highlight: ColorRGBA::rgb(255, 255, 255),
241            button_bg: ColorRGBA::transparent(),
242            button_hover: ColorRGBA::rgb(40, 40, 50),
243            button_pressed: ColorRGBA::rgb(60, 60, 70),
244            button_border: ColorRGBA::transparent(),
245            health_bar_bg: ColorRGBA::rgb(40, 20, 20),
246            health_bar_fill: ColorRGBA::rgb(230, 60, 60),
247            health_bar_border: ColorRGBA::transparent(),
248            mana_bar_bg: ColorRGBA::rgb(20, 20, 50),
249            mana_bar_fill: ColorRGBA::rgb(60, 120, 230),
250            xp_bar_bg: ColorRGBA::rgb(40, 40, 20),
251            xp_bar_fill: ColorRGBA::rgb(230, 210, 60),
252            slot_bg: ColorRGBA::rgb(30, 30, 38),
253            slot_hover: ColorRGBA::rgb(45, 45, 55),
254            slot_border: ColorRGBA::transparent(),
255            dialog_bg: ColorRGBA::rgb(15, 15, 22),
256            dialog_border: ColorRGBA::transparent(),
257            menu_bg: ColorRGBA::transparent(),
258            menu_item_bg: ColorRGBA::transparent(),
259            menu_item_hover: ColorRGBA::rgb(30, 30, 40),
260            menu_item_selected: ColorRGBA::rgb(50, 50, 60),
261            font_size: 15,
262            font_size_small: 11,
263            font_size_title: 19,
264            padding: 6.0,
265            spacing: 3.0,
266            border_width: 0.0,
267        }
268    }
269
270    /// Obtener todos los temas disponibles
271    pub fn all() -> &'static [fn() -> Self] {
272        &[Self::dark, Self::light, Self::retro, Self::neon, Self::minimal]
273    }
274
275    /// Obtener tema por nombre
276    pub fn by_name(name: &str) -> Self {
277        match name.to_lowercase().as_str() {
278            "dark" | "oscuro" => Self::dark(),
279            "light" | "claro" => Self::light(),
280            "retro" | "pixel" | "8bit" => Self::retro(),
281            "neon" | "cyber" | "cyberpunk" => Self::neon(),
282            "minimal" | "minimalist" | "min" => Self::minimal(),
283            _ => Self::dark(),
284        }
285    }
286}
287
288#[cfg(test)]
289mod tests {
290    use super::*;
291
292    #[test]
293    fn test_theme_dark() {
294        let theme = Theme::dark();
295        assert_eq!(theme.name, "Dark");
296        assert_eq!(theme.bg_color.r, 18);
297    }
298
299    #[test]
300    fn test_theme_light() {
301        let theme = Theme::light();
302        assert_eq!(theme.name, "Light");
303        assert_eq!(theme.bg_color.r, 240);
304    }
305
306    #[test]
307    fn test_theme_retro() {
308        let theme = Theme::retro();
309        assert_eq!(theme.name, "Retro");
310        assert_eq!(theme.text_color.g, 255);
311    }
312
313    #[test]
314    fn test_theme_neon() {
315        let theme = Theme::neon();
316        assert_eq!(theme.name, "Neon");
317        assert_eq!(theme.panel_border.g, 255);
318    }
319
320    #[test]
321    fn test_theme_minimal() {
322        let theme = Theme::minimal();
323        assert_eq!(theme.name, "Minimal");
324        assert_eq!(theme.border_width, 0.0);
325    }
326
327    #[test]
328    fn test_theme_by_name() {
329        let dark = Theme::by_name("dark");
330        assert_eq!(dark.name, "Dark");
331
332        let retro = Theme::by_name("retro");
333        assert_eq!(retro.name, "Retro");
334
335        let neon = Theme::by_name("cyber");
336        assert_eq!(neon.name, "Neon");
337
338        let default = Theme::by_name("unknown");
339        assert_eq!(default.name, "Dark");
340    }
341
342    #[test]
343    fn test_theme_all() {
344        let themes = Theme::all();
345        assert_eq!(themes.len(), 5);
346    }
347}