workspace: Fix inactive pane dimming (#29473)

Closes #27173

Problem:

Active panes nested within axes were incorrectly receiving opacity
overlays, while inactive panes in nested structures would get multiple
overlays applied, making them appear darker than intended.

Solution:

I fixed this by distinguishing between leaf panes and axes in the
rendering pipeline, applying overlays only to elements that are both
leaf panes and not active, ensuring consistent visual treatment
regardless of their position in the hierarchy.

Release Notes:

- Fixed an issue where `inactive_opacity` settings would be applied to
panes multiple times and even to the active pane when nested within
another pane.
This commit is contained in:
Ron Harel 2025-05-07 09:02:34 +03:00 committed by GitHub
parent 60d51d56cd
commit 1fcd2647ed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -30,6 +30,11 @@ pub struct PaneGroup {
pub root: Member,
}
pub struct PaneRenderResult {
pub element: gpui::AnyElement,
pub contains_active_pane: bool,
}
impl PaneGroup {
pub fn with_root(root: Member) -> Self {
Self { root }
@ -128,7 +133,7 @@ impl PaneGroup {
window: &mut Window,
cx: &mut App,
) -> impl IntoElement {
self.root.render(0, zoomed, render_cx, window, cx)
self.root.render(0, zoomed, render_cx, window, cx).element
}
pub fn panes(&self) -> Vec<&Entity<Pane>> {
@ -398,40 +403,45 @@ impl Member {
render_cx: &dyn PaneLeaderDecorator,
window: &mut Window,
cx: &mut App,
) -> impl IntoElement {
) -> PaneRenderResult {
match self {
Member::Pane(pane) => {
if zoomed == Some(&pane.downgrade().into()) {
return div().into_any();
return PaneRenderResult {
element: div().into_any(),
contains_active_pane: false,
};
}
let decoration = render_cx.decorate(pane, cx);
let is_active = pane == render_cx.active_pane();
div()
.relative()
.flex_1()
.size_full()
.child(
AnyView::from(pane.clone())
.cached(StyleRefinement::default().v_flex().size_full()),
)
.when_some(decoration.border, |this, color| {
this.child(
div()
.absolute()
.size_full()
.left_0()
.top_0()
.border_2()
.border_color(color),
PaneRenderResult {
element: div()
.relative()
.flex_1()
.size_full()
.child(
AnyView::from(pane.clone())
.cached(StyleRefinement::default().v_flex().size_full()),
)
})
.children(decoration.status_box)
.into_any()
.when_some(decoration.border, |this, color| {
this.child(
div()
.absolute()
.size_full()
.left_0()
.top_0()
.border_2()
.border_color(color),
)
})
.children(decoration.status_box)
.into_any(),
contains_active_pane: is_active,
}
}
Member::Axis(axis) => axis
.render(basis + 1, zoomed, render_cx, window, cx)
.into_any(),
Member::Axis(axis) => axis.render(basis + 1, zoomed, render_cx, window, cx),
}
}
@ -752,27 +762,54 @@ impl PaneAxis {
render_cx: &dyn PaneLeaderDecorator,
window: &mut Window,
cx: &mut App,
) -> gpui::AnyElement {
) -> PaneRenderResult {
debug_assert!(self.members.len() == self.flexes.lock().len());
let mut active_pane_ix = None;
let mut contains_active_pane = false;
let mut is_leaf_pane = vec![false; self.members.len()];
pane_axis(
let rendered_children = self
.members
.iter()
.enumerate()
.map(|(ix, member)| {
match member {
Member::Pane(pane) => {
is_leaf_pane[ix] = true;
if pane == render_cx.active_pane() {
active_pane_ix = Some(ix);
contains_active_pane = true;
}
}
Member::Axis(_) => {
is_leaf_pane[ix] = false;
}
}
let result = member.render((basis + ix) * 10, zoomed, render_cx, window, cx);
if result.contains_active_pane {
contains_active_pane = true;
}
result.element.into_any_element()
})
.collect::<Vec<_>>();
let element = pane_axis(
self.axis,
basis,
self.flexes.clone(),
self.bounding_boxes.clone(),
render_cx.workspace().clone(),
)
.children(self.members.iter().enumerate().map(|(ix, member)| {
if matches!(member, Member::Pane(pane) if pane == render_cx.active_pane()) {
active_pane_ix = Some(ix);
}
member
.render((basis + ix) * 10, zoomed, render_cx, window, cx)
.into_any_element()
}))
.with_is_leaf_pane_mask(is_leaf_pane)
.children(rendered_children)
.with_active_pane(active_pane_ix)
.into_any_element()
.into_any_element();
PaneRenderResult {
element,
contains_active_pane,
}
}
}
@ -899,6 +936,7 @@ mod element {
children: SmallVec::new(),
active_pane_ix: None,
workspace,
is_leaf_pane_mask: Vec::new(),
}
}
@ -910,6 +948,8 @@ mod element {
children: SmallVec<[AnyElement; 2]>,
active_pane_ix: Option<usize>,
workspace: WeakEntity<Workspace>,
// Track which children are leaf panes (Member::Pane) vs axes (Member::Axis)
is_leaf_pane_mask: Vec<bool>,
}
pub struct PaneAxisLayout {
@ -921,6 +961,7 @@ mod element {
bounds: Bounds<Pixels>,
element: AnyElement,
handle: Option<PaneAxisHandleLayout>,
is_leaf_pane: bool,
}
struct PaneAxisHandleLayout {
@ -934,6 +975,11 @@ mod element {
self
}
pub fn with_is_leaf_pane_mask(mut self, mask: Vec<bool>) -> Self {
self.is_leaf_pane_mask = mask;
self
}
fn compute_resize(
flexes: &Arc<Mutex<Vec<f32>>>,
e: &MouseMoveEvent,
@ -1150,10 +1196,14 @@ mod element {
child.prepaint_at(origin, window, cx);
origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis));
let is_leaf_pane = self.is_leaf_pane_mask.get(ix).copied().unwrap_or(true);
layout.children.push(PaneAxisChildLayout {
bounds: child_bounds,
element: child,
handle: None,
is_leaf_pane,
})
}
@ -1215,12 +1265,15 @@ mod element {
.apply_along(Axis::Horizontal, |val| val - Pixels(1.)),
};
if overlay_opacity.is_some() && self.active_pane_ix != Some(ix) {
if overlay_opacity.is_some()
&& child.is_leaf_pane
&& self.active_pane_ix != Some(ix)
{
window.paint_quad(gpui::fill(overlay_bounds, overlay_background));
}
if let Some(border) = overlay_border {
if self.active_pane_ix == Some(ix) {
if self.active_pane_ix == Some(ix) && child.is_leaf_pane {
window.paint_quad(gpui::quad(
overlay_bounds,
0.,