themes: Make background colors partly transparent by default (#24151)

Max Brunsfeld created

Certain themes define the `created` and `deleted` status colors, but not
`created_background` and `deleted_background`. Previously, Zed would use
`created` and `deleted` colors, and apply a hard-coded opacity change,
but *not* use `created_background` and `deleted_background`, but that
behavior was inadvertently changed in
https://github.com/zed-industries/zed/pull/22994.

This PR restores the old behavior as a fallback. If a theme defines a
status color, but not the corresponding background color, we'll use a
75% transparent version of the foreground color as a fallback.

Release Notes:

- Fixed an issue in certain themes where diffs would render with the
wrong red and green colors for deletions and insertions.

Change summary

crates/theme/src/fallback_themes.rs | 25 +++++++++++++++++++++++--
crates/theme/src/styles/status.rs   |  6 +++---
crates/theme/src/theme.rs           |  5 ++++-
3 files changed, 30 insertions(+), 6 deletions(-)

Detailed changes

crates/theme/src/fallback_themes.rs 🔗

@@ -3,8 +3,9 @@ use std::sync::Arc;
 use gpui::{hsla, FontStyle, FontWeight, HighlightStyle, Hsla, WindowBackgroundAppearance};
 
 use crate::{
-    default_color_scales, AccentColors, Appearance, PlayerColors, StatusColors, SyntaxTheme,
-    SystemColors, Theme, ThemeColors, ThemeFamily, ThemeStyles,
+    default_color_scales, AccentColors, Appearance, PlayerColors, StatusColors,
+    StatusColorsRefinement, SyntaxTheme, SystemColors, Theme, ThemeColors, ThemeFamily,
+    ThemeStyles,
 };
 
 /// The default theme family for Zed.
@@ -21,6 +22,26 @@ pub fn zed_default_themes() -> ThemeFamily {
     }
 }
 
+// If a theme customizes a foreground version of a status color, but does not
+// customize the background color, then use a partly-transparent version of the
+// foreground color for the background color.
+pub(crate) fn apply_status_color_defaults(status: &mut StatusColorsRefinement) {
+    for (fg_color, bg_color) in [
+        (&status.deleted, &mut status.deleted_background),
+        (&status.created, &mut status.created_background),
+        (&status.modified, &mut status.modified_background),
+        (&status.conflict, &mut status.conflict_background),
+        (&status.error, &mut status.error_background),
+        (&status.hidden, &mut status.hidden_background),
+    ] {
+        if bg_color.is_none() {
+            if let Some(fg_color) = fg_color {
+                *bg_color = Some(fg_color.opacity(0.25));
+            }
+        }
+    }
+}
+
 pub(crate) fn zed_default_dark() -> Theme {
     let bg = hsla(215. / 360., 12. / 100., 15. / 100., 1.);
     let editor = hsla(220. / 360., 12. / 100., 18. / 100., 1.);

crates/theme/src/styles/status.rs 🔗

@@ -102,10 +102,10 @@ impl StatusColors {
             conflict_background: red().dark().step_9(),
             conflict_border: red().dark().step_9(),
             created: grass().dark().step_9(),
-            created_background: grass().dark().step_9(),
+            created_background: grass().dark().step_9().opacity(0.25),
             created_border: grass().dark().step_9(),
             deleted: red().dark().step_9(),
-            deleted_background: red().dark().step_9(),
+            deleted_background: red().dark().step_9().opacity(0.25),
             deleted_border: red().dark().step_9(),
             error: red().dark().step_9(),
             error_background: red().dark().step_9(),
@@ -123,7 +123,7 @@ impl StatusColors {
             info_background: blue().dark().step_9(),
             info_border: blue().dark().step_9(),
             modified: yellow().dark().step_9(),
-            modified_background: yellow().dark().step_9(),
+            modified_background: yellow().dark().step_9().opacity(0.25),
             modified_border: yellow().dark().step_9(),
             predictive: neutral().dark_alpha().step_9(),
             predictive_background: neutral().dark_alpha().step_9(),

crates/theme/src/theme.rs 🔗

@@ -24,6 +24,7 @@ use std::sync::Arc;
 
 use ::settings::Settings;
 use anyhow::Result;
+use fallback_themes::apply_status_color_defaults;
 use fs::Fs;
 use gpui::{
     px, App, AssetSource, HighlightStyle, Hsla, Pixels, Refineable, SharedString, WindowAppearance,
@@ -155,7 +156,9 @@ impl ThemeFamily {
             AppearanceContent::Light => StatusColors::light(),
             AppearanceContent::Dark => StatusColors::dark(),
         };
-        refined_status_colors.refine(&theme.style.status_colors_refinement());
+        let mut status_colors_refinement = theme.style.status_colors_refinement();
+        apply_status_color_defaults(&mut status_colors_refinement);
+        refined_status_colors.refine(&status_colors_refinement);
 
         let mut refined_player_colors = match theme.appearance {
             AppearanceContent::Light => PlayerColors::light(),