theme: Add `panel.overlay_background` and `panel.overlay_hover` (#34655)

Bret Comnes and Smit Barmase created

In https://github.com/zed-industries/zed/pull/33994 sticky scroll was
added to project_panel.

I love this feature! 

This introduces a new element layering not seen before. On themes that
use transparency, the overlapping elements can make it difficult to read
project panel entries. This PR introduces a new selector:
~~panel.sticky_entry.background~~ `panel.overlay_background` This
selector lets you set the background of entries when they become sticky.

Closes https://github.com/zed-industries/zed/issues/34654

Before:

<img width="373" height="104" alt="Screenshot 2025-07-17 at 10 19 11 AM"
src="https://github.com/user-attachments/assets/d5bab065-53ca-4b27-b5d8-3b3f8d1f7a81"
/>

After:

<img width="292" height="445" alt="Screenshot 2025-07-17 at 11 46 57 AM"
src="https://github.com/user-attachments/assets/4cd2b87b-2989-4489-972f-872d2dc13a33"
/>

<img width="348" height="390" alt="Screenshot 2025-07-17 at 11 39 57 AM"
src="https://github.com/user-attachments/assets/49c0757f-2c50-4e01-92c6-2ae7e4132a53"
/>

<img width="668" height="187" alt="Screenshot 2025-07-17 at 11 39 29 AM"
src="https://github.com/user-attachments/assets/167536c2-5872-4306-90c6-c6b68276b618"
/>

Release Notes:

- Add `panel.sticky_entry.background` theme selector for modifying
project panel entries when they become sticky when scrolling and overlap
with entries below them.

---------

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>

Change summary

crates/project_panel/src/project_panel.rs | 16 ++++++++--
crates/theme/src/default_colors.rs        |  4 ++
crates/theme/src/fallback_themes.rs       |  9 ++++--
crates/theme/src/schema.rs                | 34 +++++++++++++++++++-----
crates/theme/src/styles/colors.rs         | 10 +++++++
5 files changed, 58 insertions(+), 15 deletions(-)

Detailed changes

crates/project_panel/src/project_panel.rs 🔗

@@ -384,12 +384,20 @@ struct ItemColors {
     focused: Hsla,
 }
 
-fn get_item_color(cx: &App) -> ItemColors {
+fn get_item_color(is_sticky: bool, cx: &App) -> ItemColors {
     let colors = cx.theme().colors();
 
     ItemColors {
-        default: colors.panel_background,
-        hover: colors.element_hover,
+        default: if is_sticky {
+            colors.panel_overlay_background
+        } else {
+            colors.panel_background
+        },
+        hover: if is_sticky {
+            colors.panel_overlay_hover
+        } else {
+            colors.element_hover
+        },
         marked: colors.element_selected,
         focused: colors.panel_focused_border,
         drag_over: colors.drop_target_background,
@@ -3903,7 +3911,7 @@ impl ProjectPanel {
 
         let filename_text_color = details.filename_text_color;
         let diagnostic_severity = details.diagnostic_severity;
-        let item_colors = get_item_color(cx);
+        let item_colors = get_item_color(is_sticky, cx);
 
         let canonical_path = details
             .canonical_path

crates/theme/src/default_colors.rs 🔗

@@ -83,6 +83,8 @@ impl ThemeColors {
             panel_indent_guide: neutral().light_alpha().step_5(),
             panel_indent_guide_hover: neutral().light_alpha().step_6(),
             panel_indent_guide_active: neutral().light_alpha().step_6(),
+            panel_overlay_background: neutral().light().step_2(),
+            panel_overlay_hover: neutral().light_alpha().step_4(),
             pane_focused_border: blue().light().step_5(),
             pane_group_border: neutral().light().step_6(),
             scrollbar_thumb_background: neutral().light_alpha().step_3(),
@@ -206,6 +208,8 @@ impl ThemeColors {
             panel_indent_guide: neutral().dark_alpha().step_4(),
             panel_indent_guide_hover: neutral().dark_alpha().step_6(),
             panel_indent_guide_active: neutral().dark_alpha().step_6(),
+            panel_overlay_background: neutral().dark().step_2(),
+            panel_overlay_hover: neutral().dark_alpha().step_4(),
             pane_focused_border: blue().dark().step_5(),
             pane_group_border: neutral().dark().step_6(),
             scrollbar_thumb_background: neutral().dark_alpha().step_3(),

crates/theme/src/fallback_themes.rs 🔗

@@ -59,6 +59,7 @@ 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.);
     let elevated_surface = hsla(225. / 360., 12. / 100., 17. / 100., 1.);
+    let hover = hsla(225.0 / 360., 11.8 / 100., 26.7 / 100., 1.0);
 
     let blue = hsla(207.8 / 360., 81. / 100., 66. / 100., 1.0);
     let gray = hsla(218.8 / 360., 10. / 100., 40. / 100., 1.0);
@@ -108,14 +109,14 @@ pub(crate) fn zed_default_dark() -> Theme {
                 surface_background: bg,
                 background: bg,
                 element_background: hsla(223.0 / 360., 13. / 100., 21. / 100., 1.0),
-                element_hover: hsla(225.0 / 360., 11.8 / 100., 26.7 / 100., 1.0),
+                element_hover: hover,
                 element_active: hsla(220.0 / 360., 11.8 / 100., 20.0 / 100., 1.0),
                 element_selected: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0),
                 element_disabled: SystemColors::default().transparent,
                 element_selection_background: player.local().selection.alpha(0.25),
                 drop_target_background: hsla(220.0 / 360., 8.3 / 100., 21.4 / 100., 1.0),
                 ghost_element_background: SystemColors::default().transparent,
-                ghost_element_hover: hsla(225.0 / 360., 11.8 / 100., 26.7 / 100., 1.0),
+                ghost_element_hover: hover,
                 ghost_element_active: hsla(220.0 / 360., 11.8 / 100., 20.0 / 100., 1.0),
                 ghost_element_selected: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0),
                 ghost_element_disabled: SystemColors::default().transparent,
@@ -202,10 +203,12 @@ pub(crate) fn zed_default_dark() -> Theme {
                 panel_indent_guide: hsla(228. / 360., 8. / 100., 25. / 100., 1.),
                 panel_indent_guide_hover: hsla(225. / 360., 13. / 100., 12. / 100., 1.),
                 panel_indent_guide_active: hsla(225. / 360., 13. / 100., 12. / 100., 1.),
+                panel_overlay_background: bg,
+                panel_overlay_hover: hover,
                 pane_focused_border: blue,
                 pane_group_border: hsla(225. / 360., 13. / 100., 12. / 100., 1.),
                 scrollbar_thumb_background: gpui::transparent_black(),
-                scrollbar_thumb_hover_background: hsla(225.0 / 360., 11.8 / 100., 26.7 / 100., 1.0),
+                scrollbar_thumb_hover_background: hover,
                 scrollbar_thumb_active_background: hsla(
                     225.0 / 360.,
                     11.8 / 100.,

crates/theme/src/schema.rs 🔗

@@ -351,6 +351,12 @@ pub struct ThemeColorsContent {
     #[serde(rename = "panel.indent_guide_active")]
     pub panel_indent_guide_active: Option<String>,
 
+    #[serde(rename = "panel.overlay_background")]
+    pub panel_overlay_background: Option<String>,
+
+    #[serde(rename = "panel.overlay_hover")]
+    pub panel_overlay_hover: Option<String>,
+
     #[serde(rename = "pane.focused_border")]
     pub pane_focused_border: Option<String>,
 
@@ -674,6 +680,14 @@ impl ThemeColorsContent {
             .scrollbar_thumb_border
             .as_ref()
             .and_then(|color| try_parse_color(color).ok());
+        let element_hover = self
+            .element_hover
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok());
+        let panel_background = self
+            .panel_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok());
         ThemeColorsRefinement {
             border,
             border_variant: self
@@ -712,10 +726,7 @@ impl ThemeColorsContent {
                 .element_background
                 .as_ref()
                 .and_then(|color| try_parse_color(color).ok()),
-            element_hover: self
-                .element_hover
-                .as_ref()
-                .and_then(|color| try_parse_color(color).ok()),
+            element_hover,
             element_active: self
                 .element_active
                 .as_ref()
@@ -832,10 +843,7 @@ impl ThemeColorsContent {
                 .search_match_background
                 .as_ref()
                 .and_then(|color| try_parse_color(color).ok()),
-            panel_background: self
-                .panel_background
-                .as_ref()
-                .and_then(|color| try_parse_color(color).ok()),
+            panel_background,
             panel_focused_border: self
                 .panel_focused_border
                 .as_ref()
@@ -852,6 +860,16 @@ impl ThemeColorsContent {
                 .panel_indent_guide_active
                 .as_ref()
                 .and_then(|color| try_parse_color(color).ok()),
+            panel_overlay_background: self
+                .panel_overlay_background
+                .as_ref()
+                .and_then(|color| try_parse_color(color).ok())
+                .or(panel_background),
+            panel_overlay_hover: self
+                .panel_overlay_hover
+                .as_ref()
+                .and_then(|color| try_parse_color(color).ok())
+                .or(element_hover),
             pane_focused_border: self
                 .pane_focused_border
                 .as_ref()

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

@@ -131,6 +131,12 @@ pub struct ThemeColors {
     pub panel_indent_guide: Hsla,
     pub panel_indent_guide_hover: Hsla,
     pub panel_indent_guide_active: Hsla,
+
+    /// The color of the overlay surface on top of panel.
+    pub panel_overlay_background: Hsla,
+    /// The color of the overlay surface on top of panel when hovered over.
+    pub panel_overlay_hover: Hsla,
+
     pub pane_focused_border: Hsla,
     pub pane_group_border: Hsla,
     /// The color of the scrollbar thumb.
@@ -326,6 +332,8 @@ pub enum ThemeColorField {
     PanelIndentGuide,
     PanelIndentGuideHover,
     PanelIndentGuideActive,
+    PanelOverlayBackground,
+    PanelOverlayHover,
     PaneFocusedBorder,
     PaneGroupBorder,
     ScrollbarThumbBackground,
@@ -438,6 +446,8 @@ impl ThemeColors {
             ThemeColorField::PanelIndentGuide => self.panel_indent_guide,
             ThemeColorField::PanelIndentGuideHover => self.panel_indent_guide_hover,
             ThemeColorField::PanelIndentGuideActive => self.panel_indent_guide_active,
+            ThemeColorField::PanelOverlayBackground => self.panel_overlay_background,
+            ThemeColorField::PanelOverlayHover => self.panel_overlay_hover,
             ThemeColorField::PaneFocusedBorder => self.pane_focused_border,
             ThemeColorField::PaneGroupBorder => self.pane_group_border,
             ThemeColorField::ScrollbarThumbBackground => self.scrollbar_thumb_background,