From 93d745668805029476e6d7fbb19652116fed51de Mon Sep 17 00:00:00 2001 From: Shuvro Roy Date: Sat, 10 Oct 2026 22:13:37 +0000 Subject: [PATCH] git_ui: Fix commit editor button hover colors (#65448) ## Summary Closes #65376. Replaces #65411 with the hover behavior handled inside the button component, as requested in review. My original description of the problem: > The main problem is when I hover in the blank area the icon color got changed Each commit editor button now uses 0.6 opacity normally and 1.0 only while that button is hovered. The opacity options are handled by `IconButton` and `ButtonLike`, and the component preview includes an example. ### Before fix https://github.com/user-attachments/assets/b7ad6980-e382-4bef-904a-74d877da0291 ### After fix https://github.com/user-attachments/assets/3089e783-c54a-4a94-9e47-a169483aface ## Testing - Built and ran the development app on macOS. Manually checked blank-area hover, each icon independently, expand/collapse actions, and light and dark themes. - `cargo test --locked -p ui --lib`: all 89 existing tests passed. This visual change is covered by manual testing; no dedicated automated regression test was added. - `cargo fmt --all -- --check` and `git diff --check` passed. - `cargo build --locked -p zed --bin zed` passed with the macOS development environment configured. To check, open a Git repository and its Git panel. Move the pointer between the blank space below the commit editor buttons and each button. Only the hovered button should brighten. Also expand and collapse the commit editor. ## Self-review - [x] I've reviewed my diff for quality, security, reliability, and performance. - [x] UI changes follow the [checklist](https://zed.dev/docs/development/ui-checklist). - [x] Tests cover the new or changed behavior through the manual checks described above. Release Notes: - Fixed Git panel commit editor buttons changing opacity when hovering empty space. --------- Co-authored-by: MrSubidubi --- crates/git_ui/src/git_panel.rs | 6 +- .../ui/src/components/button/icon_button.rs | 26 ++++++++- crates/ui/src/components/icon.rs | 56 ++++++++++++------- 3 files changed, 64 insertions(+), 24 deletions(-) diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index 6f43798ee2a..28df7ea3650 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -6803,11 +6803,11 @@ impl GitPanel { .h_full() .gap_px() .p_1p5() - .opacity(0.6) - .hover(|s| s.opacity(1.0)) .child( IconButton::new("expand-commit-editor", IconName::MaximizeAlt) .icon_size(IconSize::Small) + .icon_color(Color::Hidden) + .hover_color(Color::Muted) .tooltip({ move |_window, cx| { Tooltip::for_action_in( @@ -6834,6 +6834,8 @@ impl GitPanel { IconButton::new("fill-commit-editor", icon) .icon_size(IconSize::Small) + .icon_color(Color::Hidden) + .hover_color(Color::Muted) .tooltip({ move |_window, cx| { Tooltip::for_action_in( diff --git a/crates/ui/src/components/button/icon_button.rs b/crates/ui/src/components/button/icon_button.rs index d14aa46a32d..330605f9472 100644 --- a/crates/ui/src/components/button/icon_button.rs +++ b/crates/ui/src/components/button/icon_button.rs @@ -21,6 +21,7 @@ pub struct IconButton { icon: IconName, icon_size: IconSize, icon_color: Color, + hover_color: Option, selected_icon: Option, selected_icon_color: Option, selected_style: Option, @@ -37,6 +38,7 @@ impl IconButton { icon, icon_size: IconSize::default(), icon_color: Color::Default, + hover_color: None, selected_icon: None, selected_icon_color: None, selected_style: None, @@ -105,6 +107,12 @@ impl IconButton { self } + /// Sets the icon color used while the button is hovered. + pub fn hover_color(mut self, color: Color) -> Self { + self.hover_color = Some(color); + self + } + pub fn selected_icon(mut self, icon: impl Into>) -> Self { self.selected_icon = icon.into(); self @@ -263,7 +271,15 @@ impl RenderOnce for IconButton { Color::Custom(base_color.opacity(self.alpha.unwrap_or(1.0))) }; - let icon_element = Icon::new(icon).size(self.icon_size).color(icon_color); + let hover_color = self + .hover_color + .filter(|_| !is_disabled && !is_selected) + .map(|color| Color::Custom(color.color(cx).opacity(self.alpha.unwrap_or(1.0)))); + + let icon_element = Icon::new(icon) + .size(self.icon_size) + .color(icon_color) + .group_hover_color(hover_color); self.base .map(|this| match self.shape { @@ -453,6 +469,14 @@ impl Component for IconButton { .layer(ElevationIndex::Background) .into_any_element(), ), + single_example( + "With Hover Color", + IconButton::new("hover_color", IconName::Check) + .icon_color(Color::Muted) + .hover_color(Color::Default) + .layer(ElevationIndex::Background) + .into_any_element(), + ), ], ), ]) diff --git a/crates/ui/src/components/icon.rs b/crates/ui/src/components/icon.rs index 8935e181afd..bc4c134b117 100644 --- a/crates/ui/src/components/icon.rs +++ b/crates/ui/src/components/icon.rs @@ -148,6 +148,7 @@ enum IconSource { pub struct Icon { source: IconSource, color: Color, + group_hover_color: Option, size: Rems, transformation: Transformation, } @@ -157,6 +158,7 @@ impl Icon { Self { source: IconSource::Embedded(icon.path().into()), color: Color::default(), + group_hover_color: None, size: IconSize::default().rems(), transformation: Transformation::default(), } @@ -175,6 +177,7 @@ impl Icon { Self { source, color: Color::default(), + group_hover_color: None, size: IconSize::default().rems(), transformation: Transformation::default(), } @@ -184,6 +187,7 @@ impl Icon { Self { source: IconSource::ExternalSvg(svg), color: Color::default(), + group_hover_color: None, size: IconSize::default().rems(), transformation: Transformation::default(), } @@ -205,6 +209,15 @@ impl Icon { self } + /// Sets the color used while the enclosing unnamed (`""`) group is hovered, + /// such as the one set up by [`crate::ButtonLike`]. + /// + /// Not to be exposed outside of the `ui` crate. + pub(crate) fn group_hover_color(mut self, color: impl Into>) -> Self { + self.group_hover_color = color.into(); + self + } + /// Sets a custom size for the icon, in [`Rems`]. /// /// Not to be exposed outside of the `ui` crate. @@ -223,27 +236,28 @@ impl Transformable for Icon { impl RenderOnce for Icon { fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { - match self.source { - IconSource::Embedded(path) => svg() - .with_transformation(self.transformation) - .size(self.size) - .flex_none() - .path(path) - .text_color(self.color.color(cx)) - .into_any_element(), - IconSource::ExternalSvg(path) => svg() - .external_path(path) - .with_transformation(self.transformation) - .size(self.size) - .flex_none() - .text_color(self.color.color(cx)) - .into_any_element(), - IconSource::External(path) => img(path) - .size(self.size) - .flex_none() - .text_color(self.color.color(cx)) - .into_any_element(), - } + let color = self.color.color(cx); + let svg = match self.source { + IconSource::Embedded(path) => svg().path(path), + IconSource::ExternalSvg(path) => svg().external_path(path), + IconSource::External(path) => { + return img(path) + .size(self.size) + .flex_none() + .text_color(color) + .into_any_element(); + } + }; + let group_hover_color = self.group_hover_color.map(|color| color.color(cx)); + + svg.with_transformation(self.transformation) + .size(self.size) + .flex_none() + .text_color(color) + .when_some(group_hover_color, |this, hover_color| { + this.group_hover("", |style| style.text_color(hover_color)) + }) + .into_any_element() } }