1pub mod color;
21pub mod palette;
22
23use std::sync::RwLock;
24
25use gpui_kit::component::{Theme, ThemeMode};
26use gpui_kit::{App, Pixels, Window, px, rgba};
27
28pub use color::{Colors, colors, contrast, mix, rgb8, rgb_hsla};
29pub use palette::{Accent, Palette, Rgb};
30
31pub const XS: f32 = 4.;
33pub const SM: f32 = 8.;
35pub const MD: f32 = 12.;
37pub const LG: f32 = 16.;
39pub const XL: f32 = 24.;
41pub const XXL: f32 = 32.;
43
44pub const FONT_SIZE: f32 = 15.;
46pub const FONT_RANGE: (f32, f32) = (12., 18.);
48
49pub const RADIUS_SM: f32 = 4.;
51pub const RADIUS: f32 = 6.;
53pub const RADIUS_LG: f32 = 8.;
55
56pub const PANE_GAP: f32 = 4.;
58pub const PANE_PADDING: f32 = 8.;
60
61pub const LABEL_W: f32 = 112.;
63pub const CONTROL_MIN_W: f32 = 140.;
65pub const TARGET_MIN: f32 = 24.;
67
68#[derive(Clone, Copy, Debug, PartialEq)]
70pub struct Look {
71 pub dark: bool,
73 pub palette: Palette,
75 pub accent: Option<Rgb>,
78 pub font_size: f32,
80}
81
82impl Look {
83 pub const fn new(palette: Palette) -> Self {
85 Look { dark: true, palette, accent: None, font_size: FONT_SIZE }
86 }
87
88 pub fn active_palette(&self) -> Palette {
90 match self.accent {
91 Some(rgb) => Palette::custom(rgb),
92 None => self.palette,
93 }
94 }
95
96 pub const fn with_dark(self, dark: bool) -> Self {
98 Look { dark, ..self }
99 }
100
101 pub const fn with_palette(self, palette: Palette) -> Self {
103 Look { palette, ..self }
104 }
105
106 pub const fn with_accent(self, accent: Option<Rgb>) -> Self {
108 Look { accent, ..self }
109 }
110
111 pub fn with_font_size(self, font_size: f32) -> Self {
113 Look { font_size: font_size.round().clamp(FONT_RANGE.0, FONT_RANGE.1), ..self }
114 }
115}
116
117impl Default for Look {
118 fn default() -> Self {
119 Look::new(Palette::LIME)
120 }
121}
122
123static LOOK: RwLock<Look> = RwLock::new(Look::new(Palette::LIME));
124
125pub fn set_look(look: Look) {
127 let look = look.with_font_size(look.font_size);
128 *LOOK.write().unwrap_or_else(|e| e.into_inner()) = look;
129}
130
131pub fn look() -> Look {
133 *LOOK.read().unwrap_or_else(|e| e.into_inner())
134}
135
136pub fn is_dark() -> bool {
138 look().dark
139}
140
141pub fn system_dark(window: &Window) -> bool {
143 use gpui_kit::WindowAppearance as W;
144 matches!(window.appearance(), W::Dark | W::VibrantDark)
145}
146
147pub fn use_look(look: Look, cx: &mut App) {
149 set_look(look);
150 apply(cx);
151}
152
153pub fn apply(cx: &mut App) {
156 let l = look();
157 Theme::change(if l.dark { ThemeMode::Dark } else { ThemeMode::Light }, None, cx);
158 let c = Colors::resolve(&l);
159 let (on_danger, on_success, on_warning) = (palette::best_text_on(c.danger), palette::best_text_on(c.success), palette::best_text_on(c.warning));
160 Theme::update(cx, |t| {
161 t.font_size = px(l.font_size);
162 t.radius = px(RADIUS);
163 t.radius_lg = px(RADIUS_LG);
164 t.shadow = true;
165 t.mono_font_size = px((l.font_size - 2.).max(10.));
167 let mut code = (*t.highlight_theme).clone();
168 code.style.editor_background = Some(c.raised);
169 code.style.editor_gutter_background = Some(c.raised);
170 code.style.editor_foreground = Some(c.text);
171 code.style.editor_line_number = Some(c.text_muted);
172 code.style.editor_active_line_number = Some(c.text);
173 code.style.editor_active_line = Some(c.hover.opacity(0.5));
174 t.highlight_theme = std::sync::Arc::new(code);
175 let k = &mut t.colors;
176 k.background = c.panel;
177 k.foreground = c.text;
178 k.muted = c.raised;
179 k.muted_foreground = c.text_muted;
180 k.border = c.border;
181 k.input = c.hover;
182 k.ring = c.accent_mark;
183 k.caret = c.text;
184 k.selection = c.accent.opacity(0.45);
185 k.popover = c.raised;
186 k.popover_foreground = c.text;
187 k.sidebar = c.chrome;
188 k.sidebar_foreground = c.text;
189 k.sidebar_border = c.border;
190 k.sidebar_accent = c.control;
191 k.sidebar_accent_foreground = c.text;
192 k.sidebar_primary = c.accent;
193 k.sidebar_primary_foreground = c.on_accent;
194 k.primary = c.accent;
195 k.primary_hover = c.accent_hover;
196 k.primary_active = c.accent;
197 k.primary_foreground = c.on_accent;
198 k.secondary = c.control;
199 k.secondary_hover = c.hover;
200 k.secondary_active = c.hover;
201 k.secondary_foreground = c.text;
202 k.accent = c.control;
203 k.accent_foreground = c.text;
204 k.title_bar = c.chrome;
205 k.title_bar_border = c.border;
206 k.tab_bar = c.chrome;
207 k.tab = c.chrome;
208 k.tab_active = c.panel;
209 k.tab_foreground = c.text_muted;
210 k.tab_active_foreground = c.text;
211 k.list = c.panel;
212 k.list_even = c.panel;
213 k.list_hover = c.control;
214 k.list_active = c.accent.opacity(0.25);
215 k.list_active_border = c.accent_mark;
216 k.list_head = c.raised;
217 k.table = c.panel;
218 k.table_even = c.raised;
219 k.table_head = c.raised;
220 k.table_head_foreground = c.text_muted;
221 k.table_active = c.accent.opacity(0.25);
222 k.table_active_border = c.accent_mark;
223 k.danger = c.danger;
224 k.danger_foreground = on_danger;
225 k.success = c.success;
226 k.success_foreground = on_success;
227 k.warning = c.warning;
228 k.warning_foreground = on_warning;
229 k.red = c.danger;
231 k.green = c.success;
232 k.info = c.accent_mark;
233 k.link = c.accent_mark;
234 k.scrollbar = rgba(0x00000000).into();
235 k.scrollbar_thumb = if l.dark { rgba(0xffffff33).into() } else { rgba(0x00000033).into() };
236 k.scrollbar_thumb_hover = if l.dark { rgba(0xffffff55).into() } else { rgba(0x00000055).into() };
237 k.window_border = c.border;
238 k.overlay = rgba(0x00000080).into();
239 k.drag_border = c.accent_mark;
240 k.drop_target = c.accent.opacity(0.2);
241 k.chart_grid = c.border;
242 });
243}
244
245pub fn sp(v: f32) -> Pixels {
247 px(v)
248}