From 5de9a24654447490f4c59ea643a15d1535305952 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Gr=C3=BCndel?= Date: Wed, 12 Nov 2025 14:18:05 +0100 Subject: [PATCH] resizable: Improve to avoid panels from improperly resizing on container resizing. (#1560) This pull request changes the internal behavior of the resizable panel to get rid of some weird behavior with many panels and resizing. Previously, when having multiple panels and dragging them into each other, panels that shouldn't be affected got improperly resized: https://github.com/user-attachments/assets/20d280c0-8a51-4e96-9c4a-3ade3deaca02 This pull request completely fixes this behavior: https://github.com/user-attachments/assets/7c207dd8-d0a0-47d3-8d9e-c7127f43fa73 In addition, dragging the container smaller and larger again wasn't idempotent: https://github.com/user-attachments/assets/ca54b039-fd79-4fa8-87cf-590c06dd2c18 This is also fixed by this pull request, replacing the behavior with more intuitive behavior, keeping the fractional size of all panels constant: https://github.com/user-attachments/assets/365d0a99-fd4b-4948-bc1b-bafdd826394a https://github.com/user-attachments/assets/b208b34f-18e7-42a4-825a-9dad421b7ad3 If desired, it would also be easy to only change the size of the panels that don't have a default size set when the container changes size. --------- Co-authored-by: Jason Lee --- crates/ui/src/resizable/mod.rs | 34 ++++++++++++++++++++++++++------ crates/ui/src/resizable/panel.rs | 22 ++++++++++++++++----- 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/crates/ui/src/resizable/mod.rs b/crates/ui/src/resizable/mod.rs index 3d6eb65a..9b19e02f 100644 --- a/crates/ui/src/resizable/mod.rs +++ b/crates/ui/src/resizable/mod.rs @@ -94,8 +94,13 @@ impl ResizableState { cx: &mut Context, ) { let size = bounds.size.along(self.axis); - self.sizes[panel_ix] = size; - self.panels[panel_ix].size = Some(size); + // This check is only necessary to stop the very first panel from resizing on its own + // it needs to be passed when the panel is freshly created so we get the initial size, + // but its also fine when it sometimes passes later. + if self.sizes[panel_ix].as_f32() == PANEL_MIN_SIZE.as_f32() { + self.sizes[panel_ix] = size; + self.panels[panel_ix].size = Some(size); + } self.panels[panel_ix].bounds = bounds; self.panels[panel_ix].size_range = size_range; cx.notify(); @@ -149,7 +154,7 @@ impl ResizableState { fn sync_real_panel_sizes(&mut self, _: &App) { for (i, panel) in self.panels.iter().enumerate() { - self.sizes[i] = panel.bounds.size.along(self.axis).floor(); + self.sizes[i] = panel.bounds.size.along(self.axis); } } @@ -163,7 +168,6 @@ impl ResizableState { if ix >= old_sizes.len() - 1 { return; } - let size = size.floor(); let container_size = self.bounds.size.along(self.axis); self.sync_real_panel_sizes(cx); @@ -193,7 +197,6 @@ impl ResizableState { } } else { let mut changed = new_size - size; - new_sizes[ix + 1] += old_sizes[ix] - new_size; new_sizes[ix] = new_size; while changed > px(0.) && ix > 0 { @@ -204,6 +207,8 @@ impl ResizableState { changed -= to_reduce; new_sizes[ix] -= to_reduce; } + + new_sizes[main_ix + 1] += old_sizes[main_ix] - size - changed; } // If total size exceeds container size, adjust the main panel @@ -217,10 +222,27 @@ impl ResizableState { let size = new_sizes[i]; self.panels[i].size = Some(size); } - self.sizes = new_sizes; cx.notify(); } + + /// 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(); + + 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; + + self.sizes[i] = new_size; + self.panels[i].size = Some(new_size); + } + + cx.notify(); + } } impl EventEmitter for ResizableState {} diff --git a/crates/ui/src/resizable/panel.rs b/crates/ui/src/resizable/panel.rs index d0b2a51a..73684c5d 100644 --- a/crates/ui/src/resizable/panel.rs +++ b/crates/ui/src/resizable/panel.rs @@ -4,9 +4,10 @@ use std::{ }; use gpui::{ - canvas, div, prelude::FluentBuilder, AnyElement, App, AppContext, Axis, Bounds, Context, - Element, ElementId, Empty, Entity, EventEmitter, InteractiveElement as _, IntoElement, IsZero, - MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Render, RenderOnce, Style, Styled, Window, + canvas, div, prelude::FluentBuilder, Along, AnyElement, App, AppContext, Axis, Bounds, Context, + Element, ElementId, Empty, Entity, EventEmitter, InteractiveElement as _, IntoElement, + IsZero as _, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Render, RenderOnce, Style, + Styled, Window, }; use crate::{h_flex, resizable::PANEL_MIN_SIZE, v_flex, AxisExt}; @@ -157,7 +158,18 @@ impl RenderOnce for ResizablePanelGroup { canvas( { let state = state.clone(); - move |bounds, _, cx| state.update(cx, |state, _| state.bounds = bounds) + move |bounds, _, cx| { + state.update(cx, |state, cx| { + let size_changed = state.bounds.size.along(self.axis) + != bounds.size.along(self.axis); + + state.bounds = bounds; + + if size_changed { + state.adjust_to_container_size(cx); + } + }) + } }, |_, _, _, _| {}, ) @@ -269,7 +281,7 @@ impl RenderOnce for ResizablePanel { .flex_basis(initial_size) }) .map(|this| match panel_state.size { - Some(size) => this.flex_basis(size), + Some(size) => this.flex_basis(size.min(size_range.end).max(size_range.start)), None => this, }) .child({