1pub use gpui2::{
2 div, Element, ElementId, IntoAnyElement, ParentElement, SharedString, StatefulInteractive,
3 StatelessInteractive, Styled, ViewContext, WindowContext,
4};
5
6use crate::settings::user_settings;
7pub use crate::{theme, ButtonVariant, ElementExt, Theme};
8
9use gpui2::{hsla, rems, rgb, Hsla, Rems};
10use strum::EnumIter;
11
12// TODO Remove uses in favor of ThemeColor
13#[derive(Default)]
14pub struct SystemColor {
15 pub transparent: Hsla,
16 pub mac_os_traffic_light_red: Hsla,
17 pub mac_os_traffic_light_yellow: Hsla,
18 pub mac_os_traffic_light_green: Hsla,
19 pub state_hover_background: Hsla,
20 pub state_active_background: Hsla,
21}
22
23impl SystemColor {
24 pub fn new() -> SystemColor {
25 SystemColor {
26 transparent: hsla(0.0, 0.0, 0.0, 0.0),
27 mac_os_traffic_light_red: rgb::<Hsla>(0xEC695E),
28 mac_os_traffic_light_yellow: rgb::<Hsla>(0xF4BF4F),
29 mac_os_traffic_light_green: rgb::<Hsla>(0x62C554),
30 state_hover_background: hsla(0.0, 0.0, 0.0, 0.08),
31 state_active_background: hsla(0.0, 0.0, 0.0, 0.16),
32 }
33 }
34 pub fn color(&self) -> Hsla {
35 self.transparent
36 }
37}
38
39#[derive(Clone, Copy)]
40pub struct PlayerThemeColors {
41 pub cursor: Hsla,
42 pub selection: Hsla,
43}
44
45impl PlayerThemeColors {
46 pub fn new(cx: &WindowContext, ix: usize) -> Self {
47 let theme = theme(cx);
48
49 if ix < theme.players.len() {
50 Self {
51 cursor: theme.players[ix].cursor,
52 selection: theme.players[ix].selection,
53 }
54 } else {
55 Self {
56 cursor: rgb::<Hsla>(0xff00ff),
57 selection: rgb::<Hsla>(0xff00ff),
58 }
59 }
60 }
61}
62
63#[derive(Clone, Copy)]
64pub struct SyntaxColor {
65 pub comment: Hsla,
66 pub string: Hsla,
67 pub function: Hsla,
68 pub keyword: Hsla,
69}
70
71impl SyntaxColor {
72 pub fn new(cx: &WindowContext) -> Self {
73 let theme = theme(cx);
74
75 Self {
76 comment: theme
77 .syntax
78 .get("comment")
79 .cloned()
80 .unwrap_or_else(|| rgb::<Hsla>(0xff00ff)),
81 string: theme
82 .syntax
83 .get("string")
84 .cloned()
85 .unwrap_or_else(|| rgb::<Hsla>(0xff00ff)),
86 function: theme
87 .syntax
88 .get("function")
89 .cloned()
90 .unwrap_or_else(|| rgb::<Hsla>(0xff00ff)),
91 keyword: theme
92 .syntax
93 .get("keyword")
94 .cloned()
95 .unwrap_or_else(|| rgb::<Hsla>(0xff00ff)),
96 }
97 }
98}
99
100#[derive(Clone, Copy)]
101pub struct ThemeColor {
102 pub transparent: Hsla,
103 pub mac_os_traffic_light_red: Hsla,
104 pub mac_os_traffic_light_yellow: Hsla,
105 pub mac_os_traffic_light_green: Hsla,
106 pub border: Hsla,
107 pub border_variant: Hsla,
108 pub border_focused: Hsla,
109 pub border_transparent: Hsla,
110 /// The background color of an elevated surface, like a modal, tooltip or toast.
111 pub elevated_surface: Hsla,
112 pub surface: Hsla,
113 /// Window background color
114 pub background: Hsla,
115 /// Default background for elements like filled buttons,
116 /// text fields, checkboxes, radio buttons, etc.
117 /// - TODO: Map to step 3.
118 pub filled_element: Hsla,
119 /// The background color of a hovered element, like a button being hovered
120 /// with a mouse, or hovered on a touch screen.
121 /// - TODO: Map to step 4.
122 pub filled_element_hover: Hsla,
123 /// The background color of an active element, like a button being pressed,
124 /// or tapped on a touch screen.
125 /// - TODO: Map to step 5.
126 pub filled_element_active: Hsla,
127 /// The background color of a selected element, like a selected tab,
128 /// a button toggled on, or a checkbox that is checked.
129 pub filled_element_selected: Hsla,
130 pub filled_element_disabled: Hsla,
131 pub ghost_element: Hsla,
132 /// The background color of a hovered element with no default background,
133 /// like a ghost-style button or an interactable list item.
134 /// - TODO: Map to step 3.
135 pub ghost_element_hover: Hsla,
136 /// - TODO: Map to step 4.
137 pub ghost_element_active: Hsla,
138 pub ghost_element_selected: Hsla,
139 pub ghost_element_disabled: Hsla,
140 pub text: Hsla,
141 pub text_muted: Hsla,
142 pub text_placeholder: Hsla,
143 pub text_disabled: Hsla,
144 pub text_accent: Hsla,
145 pub icon_muted: Hsla,
146 pub syntax: SyntaxColor,
147
148 pub status_bar: Hsla,
149 pub title_bar: Hsla,
150 pub toolbar: Hsla,
151 pub tab_bar: Hsla,
152 pub editor: Hsla,
153 pub editor_subheader: Hsla,
154 pub editor_active_line: Hsla,
155 pub terminal: Hsla,
156 pub image_fallback_background: Hsla,
157
158 pub git_created: Hsla,
159 pub git_modified: Hsla,
160 pub git_deleted: Hsla,
161 pub git_conflict: Hsla,
162 pub git_ignored: Hsla,
163 pub git_renamed: Hsla,
164
165 pub player: [PlayerThemeColors; 8],
166}
167
168impl ThemeColor {
169 pub fn new(cx: &WindowContext) -> Self {
170 let theme = theme(cx);
171 let system_color = SystemColor::new();
172
173 let players = [
174 PlayerThemeColors::new(cx, 0),
175 PlayerThemeColors::new(cx, 1),
176 PlayerThemeColors::new(cx, 2),
177 PlayerThemeColors::new(cx, 3),
178 PlayerThemeColors::new(cx, 4),
179 PlayerThemeColors::new(cx, 5),
180 PlayerThemeColors::new(cx, 6),
181 PlayerThemeColors::new(cx, 7),
182 ];
183
184 Self {
185 transparent: hsla(0.0, 0.0, 0.0, 0.0),
186 mac_os_traffic_light_red: rgb::<Hsla>(0xEC695E),
187 mac_os_traffic_light_yellow: rgb::<Hsla>(0xF4BF4F),
188 mac_os_traffic_light_green: rgb::<Hsla>(0x62C554),
189 border: theme.lowest.base.default.border,
190 border_variant: theme.lowest.variant.default.border,
191 border_focused: theme.lowest.accent.default.border,
192 border_transparent: system_color.transparent,
193 elevated_surface: theme.lowest.base.default.background,
194 surface: theme.middle.base.default.background,
195 background: theme.lowest.base.default.background,
196 filled_element: theme.lowest.base.default.background,
197 filled_element_hover: theme.lowest.base.hovered.background,
198 filled_element_active: theme.lowest.base.active.background,
199 filled_element_selected: theme.lowest.accent.default.background,
200 filled_element_disabled: system_color.transparent,
201 ghost_element: system_color.transparent,
202 ghost_element_hover: theme.lowest.base.default.background,
203 ghost_element_active: theme.lowest.base.hovered.background,
204 ghost_element_selected: theme.lowest.accent.default.background,
205 ghost_element_disabled: system_color.transparent,
206 text: theme.lowest.base.default.foreground,
207 text_muted: theme.lowest.variant.default.foreground,
208 /// TODO: map this to a real value
209 text_placeholder: theme.lowest.negative.default.foreground,
210 text_disabled: theme.lowest.base.disabled.foreground,
211 text_accent: theme.lowest.accent.default.foreground,
212 icon_muted: theme.lowest.variant.default.foreground,
213 syntax: SyntaxColor::new(cx),
214
215 status_bar: theme.lowest.base.default.background,
216 title_bar: theme.lowest.base.default.background,
217 toolbar: theme.highest.base.default.background,
218 tab_bar: theme.middle.base.default.background,
219 editor: theme.highest.base.default.background,
220 editor_subheader: theme.middle.base.default.background,
221 terminal: theme.highest.base.default.background,
222 editor_active_line: theme.highest.on.default.background,
223 image_fallback_background: theme.lowest.base.default.background,
224
225 git_created: theme.lowest.positive.default.foreground,
226 git_modified: theme.lowest.accent.default.foreground,
227 git_deleted: theme.lowest.negative.default.foreground,
228 git_conflict: theme.lowest.warning.default.foreground,
229 git_ignored: theme.lowest.base.disabled.foreground,
230 git_renamed: theme.lowest.warning.default.foreground,
231
232 player: players,
233 }
234 }
235}
236
237#[derive(Default, PartialEq, EnumIter, Clone, Copy)]
238pub enum HighlightColor {
239 #[default]
240 Default,
241 Comment,
242 String,
243 Function,
244 Keyword,
245}
246
247impl HighlightColor {
248 pub fn hsla(&self, theme: &Theme) -> Hsla {
249 let system_color = SystemColor::new();
250
251 match self {
252 Self::Default => theme
253 .syntax
254 .get("primary")
255 .cloned()
256 .unwrap_or_else(|| rgb::<Hsla>(0xff00ff)),
257 Self::Comment => theme
258 .syntax
259 .get("comment")
260 .cloned()
261 .unwrap_or_else(|| rgb::<Hsla>(0xff00ff)),
262 Self::String => theme
263 .syntax
264 .get("string")
265 .cloned()
266 .unwrap_or_else(|| rgb::<Hsla>(0xff00ff)),
267 Self::Function => theme
268 .syntax
269 .get("function")
270 .cloned()
271 .unwrap_or_else(|| rgb::<Hsla>(0xff00ff)),
272 Self::Keyword => theme
273 .syntax
274 .get("keyword")
275 .cloned()
276 .unwrap_or_else(|| rgb::<Hsla>(0xff00ff)),
277 }
278 }
279}
280
281pub fn ui_size(cx: &mut WindowContext, size: f32) -> Rems {
282 const UI_SCALE_RATIO: f32 = 0.875;
283
284 let settings = user_settings(cx);
285
286 rems(*settings.ui_scale * UI_SCALE_RATIO * size)
287}
288
289#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
290pub enum FileSystemStatus {
291 #[default]
292 None,
293 Conflict,
294 Deleted,
295}
296
297impl FileSystemStatus {
298 pub fn to_string(&self) -> String {
299 match self {
300 Self::None => "None".to_string(),
301 Self::Conflict => "Conflict".to_string(),
302 Self::Deleted => "Deleted".to_string(),
303 }
304 }
305}
306
307#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
308pub enum GitStatus {
309 #[default]
310 None,
311 Created,
312 Modified,
313 Deleted,
314 Conflict,
315 Renamed,
316}
317
318impl GitStatus {
319 pub fn to_string(&self) -> String {
320 match self {
321 Self::None => "None".to_string(),
322 Self::Created => "Created".to_string(),
323 Self::Modified => "Modified".to_string(),
324 Self::Deleted => "Deleted".to_string(),
325 Self::Conflict => "Conflict".to_string(),
326 Self::Renamed => "Renamed".to_string(),
327 }
328 }
329
330 pub fn hsla(&self, cx: &WindowContext) -> Hsla {
331 let color = ThemeColor::new(cx);
332 let system_color = SystemColor::new();
333
334 match self {
335 Self::None => system_color.transparent,
336 Self::Created => color.git_created,
337 Self::Modified => color.git_modified,
338 Self::Deleted => color.git_deleted,
339 Self::Conflict => color.git_conflict,
340 Self::Renamed => color.git_renamed,
341 }
342 }
343}
344
345#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
346pub enum DiagnosticStatus {
347 #[default]
348 None,
349 Error,
350 Warning,
351 Info,
352}
353
354#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
355pub enum IconSide {
356 #[default]
357 Left,
358 Right,
359}
360
361#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
362pub enum OrderMethod {
363 #[default]
364 Ascending,
365 Descending,
366 MostRecent,
367}
368
369#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
370pub enum Shape {
371 #[default]
372 Circle,
373 RoundedRectangle,
374}
375
376#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
377pub enum DisclosureControlVisibility {
378 #[default]
379 OnHover,
380 Always,
381}
382
383#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
384pub enum DisclosureControlStyle {
385 /// Shows the disclosure control only when hovered where possible.
386 ///
387 /// More compact, but not available everywhere.
388 ChevronOnHover,
389 /// Shows an icon where possible, otherwise shows a chevron.
390 ///
391 /// For example, in a file tree a folder or file icon is shown
392 /// instead of a chevron
393 Icon,
394 /// Always shows a chevron.
395 Chevron,
396 /// Completely hides the disclosure control where possible.
397 None,
398}
399
400#[derive(Debug, PartialEq, Eq, Clone, Copy, EnumIter)]
401pub enum OverflowStyle {
402 Hidden,
403 Wrap,
404}
405
406#[derive(Default, PartialEq, Copy, Clone, EnumIter, strum::Display)]
407pub enum InteractionState {
408 #[default]
409 Enabled,
410 Hovered,
411 Active,
412 Focused,
413 Disabled,
414}
415
416impl InteractionState {
417 pub fn if_enabled(&self, enabled: bool) -> Self {
418 if enabled {
419 *self
420 } else {
421 InteractionState::Disabled
422 }
423 }
424}
425
426#[derive(Default, PartialEq)]
427pub enum SelectedState {
428 #[default]
429 Unselected,
430 PartiallySelected,
431 Selected,
432}
433
434#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
435pub enum Toggleable {
436 Toggleable(ToggleState),
437 #[default]
438 NotToggleable,
439}
440
441impl Toggleable {
442 pub fn is_toggled(&self) -> bool {
443 match self {
444 Self::Toggleable(ToggleState::Toggled) => true,
445 _ => false,
446 }
447 }
448}
449
450impl From<ToggleState> for Toggleable {
451 fn from(state: ToggleState) -> Self {
452 Self::Toggleable(state)
453 }
454}
455
456#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
457pub enum ToggleState {
458 /// The "on" state of a toggleable element.
459 ///
460 /// Example:
461 /// - A collasable list that is currently expanded
462 /// - A toggle button that is currently on.
463 Toggled,
464 /// The "off" state of a toggleable element.
465 ///
466 /// Example:
467 /// - A collasable list that is currently collapsed
468 /// - A toggle button that is currently off.
469 #[default]
470 NotToggled,
471}
472
473impl From<Toggleable> for ToggleState {
474 fn from(toggleable: Toggleable) -> Self {
475 match toggleable {
476 Toggleable::Toggleable(state) => state,
477 Toggleable::NotToggleable => ToggleState::NotToggled,
478 }
479 }
480}
481
482impl From<bool> for ToggleState {
483 fn from(toggled: bool) -> Self {
484 if toggled {
485 ToggleState::Toggled
486 } else {
487 ToggleState::NotToggled
488 }
489 }
490}