tawasuyu

tawasuyu/brahman

Rust 582TOML 222Markdown 83JSON 198635 Definiciones

⎇ 53dbdf0f1deea10d6fc567ea2b43e1e75d6d60cd crates/modules/ui_engine/libs/theme/src/lib.rs Rust · 334 líneas · 13.2 KB Historia Quién tocó qué crudo

chore: monorepo inicial con arje + minga + yahweh absorbidos 53dbdf0f1 · Sergio ·

1//! `yahweh_theme` — paleta de colores y backgrounds compartidos.
2//!
3//! El `Theme` se instala como `Global` de GPUI. Los widgets lo leen vía
4//! `cx.global::<Theme>()` durante su `render`, y se subscriben con
5//! `cx.observe_global::<Theme>(…)` para auto-redibujarse cuando cambia.
6//!
7//! Filosofía: el theme es **dato puro** (sin lógica de UI). No conoce a
8//! ningún widget concreto. Cada widget pide slots semánticos (panel_bg,
9//! row_hover, accent…) sin acoplarse a colores hex específicos.
10
11use gpui::{Background, Global, Hsla, hsla, linear_color_stop, linear_gradient};
12
13/// Paleta semántica del theme. Cada slot tiene un nombre funcional, no
14/// cromático — así los widgets piden "fondo de panel" sin acoplarse a
15/// "azul oscuro".
16///
17/// Convención de slots:
18/// - `bg_*` se devuelve como `Background` (soporta gradientes); los widgets
19///   lo pasan a `.bg(...)` directamente.
20/// - `fg_*`, `accent`, `border` son `Hsla` (colores planos para texto y
21///   ornamentos).
22/// - `bg_row_*` son `Hsla` porque las filas de una lista virtualizada se
23///   beneficiarían poco de un gradiente individual.
24#[derive(Clone, Debug)]
25pub struct Theme {
26    pub name: &'static str,
27    pub is_dark: bool,
28
29    // Fondos.
30    pub bg_app: Background,
31    pub bg_panel: Background,
32    pub bg_panel_alt: Background,
33    pub bg_row_hover: Hsla,
34    pub bg_row_active: Hsla,
35
36    // Foregrounds.
37    pub fg_text: Hsla,
38    pub fg_muted: Hsla,
39    pub fg_disabled: Hsla,
40
41    // Acentos y ornamentos.
42    pub accent: Hsla,
43    pub accent_strong: Hsla,
44    pub border: Hsla,
45    pub border_strong: Hsla,
46
47    /// Marker colors para indicar "este file está abierto en container N".
48    /// Paleta circular — el N-ésimo container usa `marker_palette[n % len]`.
49    pub marker_palette: Vec<Hsla>,
50}
51
52impl Global for Theme {}
53
54impl Theme {
55    pub fn global(cx: &gpui::App) -> &Self {
56        cx.global::<Self>()
57    }
58
59    /// Default — primer preset de `all()`. La Shell lo carga al boot si no
60    /// hay otro persistido.
61    pub fn install_default(cx: &mut gpui::App) {
62        cx.set_global(Self::nebula());
63    }
64
65    /// Reemplaza el theme global. GPUI notifica a todos los `observe_global`
66    /// suscriptores en el siguiente frame.
67    pub fn set(cx: &mut gpui::App, theme: Self) {
68        cx.set_global(theme);
69    }
70
71    /// Lista todos los presets en orden estable. Usado por el switcher para
72    /// ciclar y por el menú de "Tema" cuando lo agreguemos.
73    pub fn all() -> Vec<Self> {
74        vec![
75            Self::nebula(),
76            Self::aurora(),
77            Self::sunset(),
78            Self::flat_dark(),
79            Self::solarized_light(),
80            Self::high_contrast(),
81        ]
82    }
83
84    /// Devuelve el preset cuyo `name` matchea (case-insensitive). `None` si
85    /// el nombre no existe — útil para validar input de usuario al cargar
86    /// preferencias persistidas.
87    pub fn by_name(name: &str) -> Option<Self> {
88        Self::all()
89            .into_iter()
90            .find(|t| t.name.eq_ignore_ascii_case(name))
91    }
92
93    /// Próximo preset en la rotación de `all()`. Si `current` no está, se
94    /// vuelve al primero. La rotación es circular (último → primero).
95    pub fn next_after(current: &str) -> Self {
96        let all = Self::all();
97        let idx = all.iter().position(|t| t.name == current);
98        match idx {
99            Some(i) => all[(i + 1) % all.len()].clone(),
100            None => all[0].clone(),
101        }
102    }
103
104    // =====================================================================
105    // Presets
106    // =====================================================================
107
108    /// **Nebula** — default. Gradiente vertical violáceo profundo → teal
109    /// medianoche. Pensado para sentirse moderno y descansado de noche.
110    pub fn nebula() -> Self {
111        let bg_app = linear_gradient(
112            165.0,
113            linear_color_stop(hsla(265.0 / 360.0, 0.38, 0.07, 1.0), 0.0),
114            linear_color_stop(hsla(195.0 / 360.0, 0.42, 0.09, 1.0), 1.0),
115        );
116        let bg_panel = linear_gradient(
117            165.0,
118            linear_color_stop(hsla(245.0 / 360.0, 0.28, 0.10, 1.0), 0.0),
119            linear_color_stop(hsla(210.0 / 360.0, 0.30, 0.12, 1.0), 1.0),
120        );
121        let bg_panel_alt = linear_gradient(
122            165.0,
123            linear_color_stop(hsla(255.0 / 360.0, 0.25, 0.13, 1.0), 0.0),
124            linear_color_stop(hsla(220.0 / 360.0, 0.27, 0.14, 1.0), 1.0),
125        );
126
127        Self {
128            name: "Nebula",
129            is_dark: true,
130            bg_app,
131            bg_panel,
132            bg_panel_alt,
133            bg_row_hover: hsla(220.0 / 360.0, 0.30, 0.20, 0.45),
134            bg_row_active: hsla(280.0 / 360.0, 0.55, 0.28, 0.65),
135            fg_text: hsla(210.0 / 360.0, 0.35, 0.88, 1.0),
136            fg_muted: hsla(215.0 / 360.0, 0.22, 0.58, 1.0),
137            fg_disabled: hsla(215.0 / 360.0, 0.10, 0.40, 1.0),
138            accent: hsla(280.0 / 360.0, 0.65, 0.65, 1.0),
139            accent_strong: hsla(285.0 / 360.0, 0.78, 0.74, 1.0),
140            border: hsla(225.0 / 360.0, 0.20, 0.22, 1.0),
141            border_strong: hsla(280.0 / 360.0, 0.40, 0.45, 1.0),
142            marker_palette: vec![
143                hsla(280.0 / 360.0, 0.65, 0.55, 0.45),
144                hsla(195.0 / 360.0, 0.65, 0.50, 0.45),
145                hsla(35.0 / 360.0, 0.75, 0.55, 0.45),
146                hsla(135.0 / 360.0, 0.55, 0.50, 0.45),
147                hsla(0.0, 0.60, 0.55, 0.45),
148            ],
149        }
150    }
151
152    /// **Aurora** — verdes-cian-azul, evoca aurora boreal. Más frío que
153    /// Nebula, contraste alto.
154    pub fn aurora() -> Self {
155        let bg_app = linear_gradient(
156            190.0,
157            linear_color_stop(hsla(170.0 / 360.0, 0.45, 0.06, 1.0), 0.0),
158            linear_color_stop(hsla(220.0 / 360.0, 0.50, 0.09, 1.0), 1.0),
159        );
160        let bg_panel = linear_gradient(
161            190.0,
162            linear_color_stop(hsla(165.0 / 360.0, 0.32, 0.10, 1.0), 0.0),
163            linear_color_stop(hsla(215.0 / 360.0, 0.36, 0.12, 1.0), 1.0),
164        );
165        let bg_panel_alt = linear_gradient(
166            190.0,
167            linear_color_stop(hsla(170.0 / 360.0, 0.30, 0.13, 1.0), 0.0),
168            linear_color_stop(hsla(220.0 / 360.0, 0.32, 0.15, 1.0), 1.0),
169        );
170
171        Self {
172            name: "Aurora",
173            is_dark: true,
174            bg_app,
175            bg_panel,
176            bg_panel_alt,
177            bg_row_hover: hsla(180.0 / 360.0, 0.40, 0.22, 0.50),
178            bg_row_active: hsla(160.0 / 360.0, 0.55, 0.30, 0.65),
179            fg_text: hsla(180.0 / 360.0, 0.20, 0.92, 1.0),
180            fg_muted: hsla(185.0 / 360.0, 0.18, 0.62, 1.0),
181            fg_disabled: hsla(185.0 / 360.0, 0.10, 0.40, 1.0),
182            accent: hsla(150.0 / 360.0, 0.70, 0.55, 1.0),
183            accent_strong: hsla(160.0 / 360.0, 0.85, 0.65, 1.0),
184            border: hsla(195.0 / 360.0, 0.25, 0.20, 1.0),
185            border_strong: hsla(160.0 / 360.0, 0.55, 0.45, 1.0),
186            marker_palette: vec![
187                hsla(150.0 / 360.0, 0.75, 0.50, 0.45),
188                hsla(195.0 / 360.0, 0.70, 0.50, 0.45),
189                hsla(225.0 / 360.0, 0.70, 0.55, 0.45),
190                hsla(85.0 / 360.0, 0.65, 0.50, 0.45),
191                hsla(330.0 / 360.0, 0.65, 0.55, 0.45),
192            ],
193        }
194    }
195
196    /// **Sunset** — naranjas-rosas-violetas profundos. Cálido, alto contraste
197    /// con texto claro.
198    pub fn sunset() -> Self {
199        let bg_app = linear_gradient(
200            170.0,
201            linear_color_stop(hsla(20.0 / 360.0, 0.50, 0.08, 1.0), 0.0),
202            linear_color_stop(hsla(310.0 / 360.0, 0.45, 0.10, 1.0), 1.0),
203        );
204        let bg_panel = linear_gradient(
205            170.0,
206            linear_color_stop(hsla(15.0 / 360.0, 0.32, 0.12, 1.0), 0.0),
207            linear_color_stop(hsla(315.0 / 360.0, 0.30, 0.13, 1.0), 1.0),
208        );
209        let bg_panel_alt = linear_gradient(
210            170.0,
211            linear_color_stop(hsla(20.0 / 360.0, 0.30, 0.15, 1.0), 0.0),
212            linear_color_stop(hsla(320.0 / 360.0, 0.28, 0.16, 1.0), 1.0),
213        );
214
215        Self {
216            name: "Sunset",
217            is_dark: true,
218            bg_app,
219            bg_panel,
220            bg_panel_alt,
221            bg_row_hover: hsla(25.0 / 360.0, 0.40, 0.25, 0.45),
222            bg_row_active: hsla(5.0 / 360.0, 0.55, 0.32, 0.65),
223            fg_text: hsla(30.0 / 360.0, 0.30, 0.92, 1.0),
224            fg_muted: hsla(25.0 / 360.0, 0.20, 0.62, 1.0),
225            fg_disabled: hsla(25.0 / 360.0, 0.10, 0.42, 1.0),
226            accent: hsla(15.0 / 360.0, 0.78, 0.62, 1.0),
227            accent_strong: hsla(355.0 / 360.0, 0.85, 0.68, 1.0),
228            border: hsla(15.0 / 360.0, 0.25, 0.25, 1.0),
229            border_strong: hsla(355.0 / 360.0, 0.55, 0.45, 1.0),
230            marker_palette: vec![
231                hsla(15.0 / 360.0, 0.80, 0.55, 0.45),
232                hsla(310.0 / 360.0, 0.65, 0.55, 0.45),
233                hsla(45.0 / 360.0, 0.80, 0.55, 0.45),
234                hsla(285.0 / 360.0, 0.65, 0.60, 0.45),
235                hsla(355.0 / 360.0, 0.70, 0.55, 0.45),
236            ],
237        }
238    }
239
240    /// **Flat Dark** — sin gradientes, paleta cool gris-azulado. Para quien
241    /// prefiere monocromía. Útil para contrastar visualmente con los temas
242    /// de gradiente.
243    pub fn flat_dark() -> Self {
244        let bg_app: Background = hsla(220.0 / 360.0, 0.15, 0.09, 1.0).into();
245        let bg_panel: Background = hsla(220.0 / 360.0, 0.15, 0.12, 1.0).into();
246        let bg_panel_alt: Background = hsla(220.0 / 360.0, 0.15, 0.14, 1.0).into();
247        Self {
248            name: "Flat Dark",
249            is_dark: true,
250            bg_app,
251            bg_panel,
252            bg_panel_alt,
253            bg_row_hover: hsla(220.0 / 360.0, 0.20, 0.20, 1.0),
254            bg_row_active: hsla(220.0 / 360.0, 0.40, 0.30, 1.0),
255            fg_text: hsla(210.0 / 360.0, 0.20, 0.85, 1.0),
256            fg_muted: hsla(215.0 / 360.0, 0.15, 0.55, 1.0),
257            fg_disabled: hsla(215.0 / 360.0, 0.10, 0.40, 1.0),
258            accent: hsla(210.0 / 360.0, 0.70, 0.55, 1.0),
259            accent_strong: hsla(210.0 / 360.0, 0.85, 0.65, 1.0),
260            border: hsla(220.0 / 360.0, 0.15, 0.20, 1.0),
261            border_strong: hsla(220.0 / 360.0, 0.30, 0.35, 1.0),
262            marker_palette: vec![
263                hsla(210.0 / 360.0, 0.65, 0.55, 0.40),
264                hsla(160.0 / 360.0, 0.55, 0.50, 0.40),
265                hsla(30.0 / 360.0, 0.75, 0.55, 0.40),
266                hsla(0.0, 0.55, 0.55, 0.40),
267            ],
268        }
269    }
270
271    /// **Solarized Light** — preset claro inspirado en la paleta clásica de
272    /// Schoonover. Sin gradientes (en light un gradiente sutil pasa
273    /// desapercibido y solo introduce ruido).
274    pub fn solarized_light() -> Self {
275        let bg_app: Background = hsla(44.0 / 360.0, 0.87, 0.94, 1.0).into();
276        let bg_panel: Background = hsla(46.0 / 360.0, 0.42, 0.88, 1.0).into();
277        let bg_panel_alt: Background = hsla(46.0 / 360.0, 0.42, 0.92, 1.0).into();
278        Self {
279            name: "Solarized Light",
280            is_dark: false,
281            bg_app,
282            bg_panel,
283            bg_panel_alt,
284            bg_row_hover: hsla(46.0 / 360.0, 0.45, 0.80, 0.65),
285            bg_row_active: hsla(45.0 / 360.0, 0.55, 0.72, 0.85),
286            fg_text: hsla(196.0 / 360.0, 0.13, 0.30, 1.0),
287            fg_muted: hsla(196.0 / 360.0, 0.13, 0.45, 1.0),
288            fg_disabled: hsla(196.0 / 360.0, 0.10, 0.62, 1.0),
289            accent: hsla(205.0 / 360.0, 0.69, 0.42, 1.0),
290            accent_strong: hsla(205.0 / 360.0, 0.82, 0.38, 1.0),
291            border: hsla(46.0 / 360.0, 0.30, 0.78, 1.0),
292            border_strong: hsla(205.0 / 360.0, 0.40, 0.55, 1.0),
293            marker_palette: vec![
294                hsla(205.0 / 360.0, 0.69, 0.42, 0.30),
295                hsla(175.0 / 360.0, 0.74, 0.32, 0.30),
296                hsla(45.0 / 360.0, 1.00, 0.36, 0.30),
297                hsla(331.0 / 360.0, 0.74, 0.42, 0.30),
298                hsla(18.0 / 360.0, 0.89, 0.40, 0.30),
299            ],
300        }
301    }
302
303    /// **High Contrast** — accesibilidad. Negro puro con texto blanco y
304    /// ornamentos amarillo/verde fuertes. Suficientemente diferente para
305    /// notar inmediatamente al usar el switcher.
306    pub fn high_contrast() -> Self {
307        let bg_app: Background = hsla(0.0, 0.0, 0.0, 1.0).into();
308        let bg_panel: Background = hsla(0.0, 0.0, 0.05, 1.0).into();
309        let bg_panel_alt: Background = hsla(0.0, 0.0, 0.10, 1.0).into();
310        Self {
311            name: "High Contrast",
312            is_dark: true,
313            bg_app,
314            bg_panel,
315            bg_panel_alt,
316            bg_row_hover: hsla(60.0 / 360.0, 1.00, 0.50, 0.35),
317            bg_row_active: hsla(120.0 / 360.0, 1.00, 0.40, 0.55),
318            fg_text: hsla(0.0, 0.0, 1.0, 1.0),
319            fg_muted: hsla(0.0, 0.0, 0.75, 1.0),
320            fg_disabled: hsla(0.0, 0.0, 0.50, 1.0),
321            accent: hsla(60.0 / 360.0, 1.00, 0.60, 1.0),
322            accent_strong: hsla(60.0 / 360.0, 1.00, 0.75, 1.0),
323            border: hsla(0.0, 0.0, 0.30, 1.0),
324            border_strong: hsla(60.0 / 360.0, 1.00, 0.60, 1.0),
325            marker_palette: vec![
326                hsla(60.0 / 360.0, 1.00, 0.55, 0.50),
327                hsla(120.0 / 360.0, 1.00, 0.50, 0.50),
328                hsla(180.0 / 360.0, 1.00, 0.55, 0.50),
329                hsla(0.0, 1.00, 0.60, 0.50),
330                hsla(300.0 / 360.0, 1.00, 0.65, 0.50),
331            ],
332        }
333    }
334}