1use gpui::{Hsla, WindowAppearance};
6
7#[derive(Clone, Debug, PartialEq)]
8pub struct GridTheme {
9 pub bg: Hsla,
10 pub header_bg: Hsla,
11 pub filter_bg: Hsla,
12 pub filter_active_bg: Hsla,
13 pub row_header_bg: Hsla,
14 pub selection_bg: Hsla,
15 pub alt_row_bg: Hsla,
16 pub grid_line: Hsla,
17 pub header_fg: Hsla,
18 pub text_fg: Hsla,
19 pub negative_fg: Hsla,
20 pub sort_indicator: Hsla,
21 pub filter_cursor: Hsla,
22 pub menu_bg: Hsla,
24 pub menu_hover_bg: Hsla,
26 pub menu_fg: Hsla,
28 pub muted_text: Hsla,
32 pub null_fg: Hsla,
35 pub null_bg: Hsla,
38 pub pivot_group_bg: Hsla,
40 pub pivot_subtotal_bg: Hsla,
43 pub pivot_grand_total_bg: Hsla,
45 pub pivot_total_fg: Hsla,
47 pub pivot_drop_zone_bg: Hsla,
49 pub pivot_drop_zone_active_bg: Hsla,
51 pub pivot_chip_bg: Hsla,
53 pub pivot_chip_fg: Hsla,
55}
56
57impl Default for GridTheme {
58 fn default() -> Self {
59 Self {
60 bg: hsla(0.0, 0.0, 1.0, 1.0),
61 header_bg: hsla(0.0, 0.0, 0.93, 1.0),
62 filter_bg: hsla(0.0, 0.0, 0.96, 1.0),
63 filter_active_bg: hsla(0.58, 0.30, 0.85, 1.0),
64 row_header_bg: hsla(0.0, 0.0, 0.90, 1.0),
65 selection_bg: hsla(0.58, 0.50, 0.80, 0.50),
66 alt_row_bg: hsla(0.0, 0.0, 0.95, 1.0),
67 grid_line: hsla(0.0, 0.0, 0.85, 1.0),
68 header_fg: hsla(0.0, 0.0, 0.15, 1.0),
69 text_fg: hsla(0.0, 0.0, 0.1, 1.0),
70 negative_fg: hsla(0.0, 0.75, 0.45, 1.0),
71 sort_indicator: hsla(0.58, 0.50, 0.40, 1.0),
72 filter_cursor: hsla(0.0, 0.0, 0.1, 1.0),
73 menu_bg: hsla(0.0, 0.0, 1.0, 1.0),
74 menu_hover_bg: hsla(0.58, 0.45, 0.85, 1.0),
75 menu_fg: hsla(0.0, 0.0, 0.1, 1.0),
76 muted_text: hsla(0.0, 0.0, 0.5, 1.0),
77 null_fg: hsla(0.0, 0.0, 0.45, 1.0),
78 null_bg: hsla(0.13, 0.55, 0.90, 1.0),
79 pivot_group_bg: hsla(0.58, 0.20, 0.92, 1.0),
80 pivot_subtotal_bg: hsla(0.58, 0.15, 0.88, 1.0),
81 pivot_grand_total_bg: hsla(0.58, 0.25, 0.82, 1.0),
82 pivot_total_fg: hsla(0.0, 0.0, 0.08, 1.0),
83 pivot_drop_zone_bg: hsla(0.0, 0.0, 0.96, 1.0),
84 pivot_drop_zone_active_bg: hsla(0.58, 0.40, 0.88, 1.0),
85 pivot_chip_bg: hsla(0.58, 0.30, 0.90, 1.0),
86 pivot_chip_fg: hsla(0.0, 0.0, 0.12, 1.0),
87 }
88 }
89}
90
91fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
92 Hsla { h, s, l, a }
93}
94
95impl GridTheme {
96 #[must_use]
99 pub fn light() -> Self {
100 Self::default()
101 }
102
103 #[must_use]
106 pub fn dark() -> Self {
107 Self {
108 bg: hsla(0.0, 0.0, 0.12, 1.0),
109 header_bg: hsla(0.0, 0.0, 0.18, 1.0),
110 filter_bg: hsla(0.0, 0.0, 0.15, 1.0),
111 filter_active_bg: hsla(0.58, 0.40, 0.30, 1.0),
112 row_header_bg: hsla(0.0, 0.0, 0.16, 1.0),
113 selection_bg: hsla(0.58, 0.50, 0.45, 0.50),
114 alt_row_bg: hsla(0.0, 0.0, 0.15, 1.0),
115 grid_line: hsla(0.0, 0.0, 0.28, 1.0),
116 header_fg: hsla(0.0, 0.0, 0.80, 1.0),
117 text_fg: hsla(0.0, 0.0, 0.90, 1.0),
118 negative_fg: hsla(0.0, 0.70, 0.62, 1.0),
119 sort_indicator: hsla(0.58, 0.60, 0.68, 1.0),
120 filter_cursor: hsla(0.0, 0.0, 0.90, 1.0),
121 menu_bg: hsla(0.0, 0.0, 0.16, 1.0),
122 menu_hover_bg: hsla(0.58, 0.45, 0.38, 1.0),
123 menu_fg: hsla(0.0, 0.0, 0.90, 1.0),
124 muted_text: hsla(0.0, 0.0, 0.55, 1.0),
125 null_fg: hsla(0.0, 0.0, 0.60, 1.0),
126 null_bg: hsla(0.13, 0.35, 0.22, 1.0),
127 pivot_group_bg: hsla(0.58, 0.20, 0.20, 1.0),
128 pivot_subtotal_bg: hsla(0.58, 0.18, 0.24, 1.0),
129 pivot_grand_total_bg: hsla(0.58, 0.28, 0.30, 1.0),
130 pivot_total_fg: hsla(0.0, 0.0, 0.95, 1.0),
131 pivot_drop_zone_bg: hsla(0.0, 0.0, 0.15, 1.0),
132 pivot_drop_zone_active_bg: hsla(0.58, 0.40, 0.32, 1.0),
133 pivot_chip_bg: hsla(0.58, 0.35, 0.28, 1.0),
134 pivot_chip_fg: hsla(0.0, 0.0, 0.92, 1.0),
135 }
136 }
137
138 #[must_use]
142 pub fn for_appearance(appearance: WindowAppearance) -> Self {
143 match appearance {
144 WindowAppearance::Dark | WindowAppearance::VibrantDark => Self::dark(),
145 WindowAppearance::Light | WindowAppearance::VibrantLight => Self::light(),
146 }
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
160 fn default_theme_exposes_distinct_menu_colors() {
161 let t = GridTheme::default();
162 assert_eq!(t.menu_bg.a, 1.0, "menu background must be opaque");
164 assert_ne!(
166 t.menu_hover_bg, t.menu_bg,
167 "menu hover fill must differ from the menu background"
168 );
169 assert_ne!(
171 t.menu_fg, t.menu_bg,
172 "menu label color must contrast with the menu background"
173 );
174 }
175
176 #[test]
180 fn light_matches_default_and_dark_differs() {
181 assert_eq!(
182 GridTheme::light().bg,
183 GridTheme::default().bg,
184 "light() must alias the default palette"
185 );
186 let dark = GridTheme::dark();
187 assert_ne!(dark.bg, GridTheme::light().bg, "dark bg must differ");
188 assert!(
190 dark.bg.l < dark.text_fg.l,
191 "dark theme must be light text on a dark surface"
192 );
193 assert_eq!(dark.menu_bg.a, 1.0, "dark menu background must be opaque");
194 assert_ne!(
195 dark.menu_hover_bg, dark.menu_bg,
196 "dark menu hover fill must differ from the menu background"
197 );
198 }
199
200 #[test]
205 fn pivot_surfaces_are_distinct_in_both_palettes() {
206 for t in [GridTheme::light(), GridTheme::dark()] {
207 assert_ne!(t.pivot_grand_total_bg, t.pivot_group_bg);
208 assert_ne!(t.pivot_grand_total_bg, t.pivot_subtotal_bg);
209 assert_ne!(t.pivot_total_fg, t.pivot_grand_total_bg);
210 assert_ne!(t.pivot_drop_zone_active_bg, t.pivot_drop_zone_bg);
211 assert_ne!(t.pivot_chip_fg, t.pivot_chip_bg);
212 }
213 }
214
215 #[test]
218 fn for_appearance_maps_dark_and_light_variants() {
219 assert_eq!(
220 GridTheme::for_appearance(WindowAppearance::Dark).bg,
221 GridTheme::dark().bg
222 );
223 assert_eq!(
224 GridTheme::for_appearance(WindowAppearance::VibrantDark).bg,
225 GridTheme::dark().bg
226 );
227 assert_eq!(
228 GridTheme::for_appearance(WindowAppearance::Light).bg,
229 GridTheme::light().bg
230 );
231 assert_eq!(
232 GridTheme::for_appearance(WindowAppearance::VibrantLight).bg,
233 GridTheme::light().bg
234 );
235 }
236}