From 78efbfdd7b7e7af6edb23445eded69fc93f6bafc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Gr=C3=BCndel?= Date: Fri, 14 Nov 2025 09:12:59 +0100 Subject: [PATCH] resizable, dock: Improve Panel adjust size logic on layout changed. (#1588) This is a follow up to #1560 I noticed a couple small issues with resizable panels and their use in docks after experimenting a lot: - Sometimes the resizable group would take about ~500ms to update after a resize. This was caused by the `StackPanel` not getting notified and therefore not redrawn when the `ResizableState` was notified, as well as the notify in `adjust_to_container_size` not actually firing properly. The latter was fixed by defering the notify, though I'm still not quite sure why that is necessary. - In a couple of places, the total size all the panel missmatches the container size, causing small glitches the first time a panel is resized. Update the code in those places to properly adjust all the panel sizes so they match the container size. --------- Co-authored-by: Jason Lee --- crates/ui/src/dock/stack_panel.rs | 12 +++--- crates/ui/src/resizable/mod.rs | 70 ++++++++++++++++++++++++------- crates/ui/src/resizable/panel.rs | 4 +- 3 files changed, 64 insertions(+), 22 deletions(-) diff --git a/crates/ui/src/dock/stack_panel.rs b/crates/ui/src/dock/stack_panel.rs index 870ae2e0..a2dc7960 100644 --- a/crates/ui/src/dock/stack_panel.rs +++ b/crates/ui/src/dock/stack_panel.rs @@ -56,10 +56,12 @@ impl StackPanel { pub fn new(axis: Axis, _: &mut Window, cx: &mut Context) -> Self { let state = cx.new(|_| ResizableState::default()); - // Bubble up the resize event. - let _subscriptions = vec![cx.subscribe(&state, |_, _, _: &ResizablePanelEvent, cx| { - cx.emit(PanelEvent::LayoutChanged) - })]; + let _subscriptions = vec![ + // Bubble up the resize event. + cx.subscribe(&state, |_, _, _: &ResizablePanelEvent, cx| { + cx.emit(PanelEvent::LayoutChanged) + }), + ]; Self { axis, @@ -253,7 +255,7 @@ impl StackPanel { Some(size) => size, None => { let state = self.state.read(cx); - (state.total_size() / (state.sizes().len() + 1) as f32).max(PANEL_MIN_SIZE) + (state.container_size() / (state.sizes().len() + 1) as f32).max(PANEL_MIN_SIZE) } }; diff --git a/crates/ui/src/resizable/mod.rs b/crates/ui/src/resizable/mod.rs index 9b19e02f..7d310fd5 100644 --- a/crates/ui/src/resizable/mod.rs +++ b/crates/ui/src/resizable/mod.rs @@ -1,6 +1,8 @@ use std::ops::Range; -use gpui::{px, Along, App, Axis, Bounds, Context, ElementId, EventEmitter, Pixels, Window}; +use gpui::{ + px, Along, App, Axis, Bounds, Context, ElementId, EventEmitter, IsZero, Pixels, Window, +}; use crate::PixelsExt; @@ -66,23 +68,56 @@ impl ResizableState { ..Default::default() }; + let size = size.unwrap_or(PANEL_MIN_SIZE); + + // We make sure that the size always sums up to the container size + // by reducing the size of all other panels first. + let container_size = self.container_size().max(px(1.)); + let total_leftover_size = (container_size - size).max(px(1.)); + + for (i, panel) in self.panels.iter_mut().enumerate() { + let ratio = self.sizes[i] / container_size; + self.sizes[i] = total_leftover_size * ratio; + panel.size = Some(self.sizes[i]); + } + if let Some(ix) = ix { self.panels.insert(ix, panel_state); - self.sizes.insert(ix, size.unwrap_or(PANEL_MIN_SIZE)); + self.sizes.insert(ix, size); } else { self.panels.push(panel_state); - self.sizes.push(size.unwrap_or(PANEL_MIN_SIZE)); + self.sizes.push(size); }; + cx.notify(); } - pub(crate) fn sync_panels_count(&mut self, axis: Axis, panels_count: usize) { + pub(crate) fn sync_panels_count( + &mut self, + axis: Axis, + panels_count: usize, + cx: &mut Context, + ) { + let mut changed = self.axis != axis; self.axis = axis; + if panels_count > self.panels.len() { let diff = panels_count - self.panels.len(); self.panels .extend(vec![ResizablePanelState::default(); diff]); self.sizes.extend(vec![PANEL_MIN_SIZE; diff]); + changed = true; + } + + if panels_count < self.panels.len() { + self.panels.truncate(panels_count); + self.sizes.truncate(panels_count); + changed = true; + } + + if changed { + // We need to make sure the total size is in line with the container size. + self.adjust_to_container_size(cx); } } @@ -114,7 +149,7 @@ impl ResizableState { self.resizing_panel_ix = Some(resizing_panel_ix - 1); } } - cx.notify(); + self.adjust_to_container_size(cx); } pub(crate) fn replace_panel( @@ -127,7 +162,7 @@ impl ResizableState { self.panels[panel_ix] = panel; self.sizes[panel_ix] = old_size; - cx.notify(); + self.adjust_to_container_size(cx); } pub(crate) fn clear(&mut self) { @@ -135,8 +170,9 @@ impl ResizableState { self.sizes.clear(); } - pub(crate) fn total_size(&self) -> Pixels { - self.sizes.iter().map(|s| s.as_f32()).sum::().into() + #[inline] + pub(crate) fn container_size(&self) -> Pixels { + self.bounds.size.along(self.axis) } pub(crate) fn done_resizing(&mut self, cx: &mut Context) { @@ -168,7 +204,7 @@ impl ResizableState { if ix >= old_sizes.len() - 1 { return; } - let container_size = self.bounds.size.along(self.axis); + let container_size = self.container_size(); self.sync_real_panel_sizes(cx); let move_changed = size - old_sizes[ix]; @@ -226,21 +262,25 @@ impl ResizableState { cx.notify(); } + /// Adjust panel sizes according to the container size. + /// /// When the container size changes, the panels should take up the same percentage as they did before. fn adjust_to_container_size(&mut self, cx: &mut Context) { - // At least 1px so we don't divide by zero. - let container_size = self.bounds.size.along(self.axis).max(px(1.)); - let total_size = self.total_size(); + if self.container_size().is_zero() { + return; + } + + let container_size = self.container_size(); + let total_size = px(self.sizes.iter().map(|s| s.as_f32()).sum::()); for i in 0..self.panels.len() { let size = self.sizes[i]; - let fractional_size = size / total_size; - let new_size = container_size * fractional_size; + let ratio = size / total_size; + let new_size = container_size * ratio; self.sizes[i] = new_size; self.panels[i].size = Some(new_size); } - cx.notify(); } } diff --git a/crates/ui/src/resizable/panel.rs b/crates/ui/src/resizable/panel.rs index 73684c5d..04f6383c 100644 --- a/crates/ui/src/resizable/panel.rs +++ b/crates/ui/src/resizable/panel.rs @@ -136,8 +136,8 @@ impl RenderOnce for ResizablePanelGroup { // Sync panels to the state let panels_count = self.children.len(); - state.update(cx, |state, _| { - state.sync_panels_count(self.axis, panels_count); + state.update(cx, |state, cx| { + state.sync_panels_count(self.axis, panels_count, cx); }); container