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 <huacnlee@gmail.com>
255 lines
7.9 KiB
Rust
255 lines
7.9 KiB
Rust
use std::ops::Range;
|
|
|
|
use gpui::{px, Along, App, Axis, Bounds, Context, ElementId, EventEmitter, Pixels, Window};
|
|
|
|
use crate::PixelsExt;
|
|
|
|
mod panel;
|
|
mod resize_handle;
|
|
pub use panel::*;
|
|
pub(crate) use resize_handle::*;
|
|
|
|
pub(crate) const PANEL_MIN_SIZE: Pixels = px(100.);
|
|
|
|
/// Create a [`ResizablePanelGroup`] with horizontal resizing
|
|
pub fn h_resizable(id: impl Into<ElementId>) -> ResizablePanelGroup {
|
|
ResizablePanelGroup::new(id).axis(Axis::Horizontal)
|
|
}
|
|
|
|
/// Create a [`ResizablePanelGroup`] with vertical resizing
|
|
pub fn v_resizable(id: impl Into<ElementId>) -> ResizablePanelGroup {
|
|
ResizablePanelGroup::new(id).axis(Axis::Vertical)
|
|
}
|
|
|
|
/// Create a [`ResizablePanel`].
|
|
pub fn resizable_panel() -> ResizablePanel {
|
|
ResizablePanel::new()
|
|
}
|
|
|
|
/// State for a [`ResizablePanel`]
|
|
#[derive(Debug, Clone)]
|
|
pub struct ResizableState {
|
|
/// The `axis` will sync to actual axis of the ResizablePanelGroup in use.
|
|
axis: Axis,
|
|
panels: Vec<ResizablePanelState>,
|
|
sizes: Vec<Pixels>,
|
|
pub(crate) resizing_panel_ix: Option<usize>,
|
|
bounds: Bounds<Pixels>,
|
|
}
|
|
|
|
impl Default for ResizableState {
|
|
fn default() -> Self {
|
|
Self {
|
|
axis: Axis::Horizontal,
|
|
panels: vec![],
|
|
sizes: vec![],
|
|
resizing_panel_ix: None,
|
|
bounds: Bounds::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ResizableState {
|
|
/// Get the size of the panels.
|
|
pub fn sizes(&self) -> &Vec<Pixels> {
|
|
&self.sizes
|
|
}
|
|
|
|
pub(crate) fn insert_panel(
|
|
&mut self,
|
|
size: Option<Pixels>,
|
|
ix: Option<usize>,
|
|
cx: &mut Context<Self>,
|
|
) {
|
|
let panel_state = ResizablePanelState {
|
|
size,
|
|
..Default::default()
|
|
};
|
|
|
|
if let Some(ix) = ix {
|
|
self.panels.insert(ix, panel_state);
|
|
self.sizes.insert(ix, size.unwrap_or(PANEL_MIN_SIZE));
|
|
} else {
|
|
self.panels.push(panel_state);
|
|
self.sizes.push(size.unwrap_or(PANEL_MIN_SIZE));
|
|
};
|
|
cx.notify();
|
|
}
|
|
|
|
pub(crate) fn sync_panels_count(&mut self, axis: Axis, panels_count: usize) {
|
|
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]);
|
|
}
|
|
}
|
|
|
|
pub(crate) fn update_panel_size(
|
|
&mut self,
|
|
panel_ix: usize,
|
|
bounds: Bounds<Pixels>,
|
|
size_range: Range<Pixels>,
|
|
cx: &mut Context<Self>,
|
|
) {
|
|
let size = bounds.size.along(self.axis);
|
|
// 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();
|
|
}
|
|
|
|
pub(crate) fn remove_panel(&mut self, panel_ix: usize, cx: &mut Context<Self>) {
|
|
self.panels.remove(panel_ix);
|
|
self.sizes.remove(panel_ix);
|
|
if let Some(resizing_panel_ix) = self.resizing_panel_ix {
|
|
if resizing_panel_ix > panel_ix {
|
|
self.resizing_panel_ix = Some(resizing_panel_ix - 1);
|
|
}
|
|
}
|
|
cx.notify();
|
|
}
|
|
|
|
pub(crate) fn replace_panel(
|
|
&mut self,
|
|
panel_ix: usize,
|
|
panel: ResizablePanelState,
|
|
cx: &mut Context<Self>,
|
|
) {
|
|
let old_size = self.sizes[panel_ix];
|
|
|
|
self.panels[panel_ix] = panel;
|
|
self.sizes[panel_ix] = old_size;
|
|
cx.notify();
|
|
}
|
|
|
|
pub(crate) fn clear(&mut self) {
|
|
self.panels.clear();
|
|
self.sizes.clear();
|
|
}
|
|
|
|
pub(crate) fn total_size(&self) -> Pixels {
|
|
self.sizes.iter().map(|s| s.as_f32()).sum::<f32>().into()
|
|
}
|
|
|
|
pub(crate) fn done_resizing(&mut self, cx: &mut Context<Self>) {
|
|
self.resizing_panel_ix = None;
|
|
cx.emit(ResizablePanelEvent::Resized);
|
|
}
|
|
|
|
fn panel_size_range(&self, ix: usize) -> Range<Pixels> {
|
|
let Some(panel) = self.panels.get(ix) else {
|
|
return PANEL_MIN_SIZE..Pixels::MAX;
|
|
};
|
|
|
|
panel.size_range.clone()
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// The `ix`` is the index of the panel to resize,
|
|
/// and the `size` is the new size for the panel.
|
|
fn resize_panel(&mut self, ix: usize, size: Pixels, _: &mut Window, cx: &mut Context<Self>) {
|
|
let old_sizes = self.sizes.clone();
|
|
|
|
let mut ix = ix;
|
|
// Only resize the left panels.
|
|
if ix >= old_sizes.len() - 1 {
|
|
return;
|
|
}
|
|
let container_size = self.bounds.size.along(self.axis);
|
|
self.sync_real_panel_sizes(cx);
|
|
|
|
let move_changed = size - old_sizes[ix];
|
|
if move_changed == px(0.) {
|
|
return;
|
|
}
|
|
|
|
let size_range = self.panel_size_range(ix);
|
|
let new_size = size.clamp(size_range.start, size_range.end);
|
|
let is_expand = move_changed > px(0.);
|
|
|
|
let main_ix = ix;
|
|
let mut new_sizes = old_sizes.clone();
|
|
|
|
if is_expand {
|
|
let mut changed = new_size - old_sizes[ix];
|
|
new_sizes[ix] = new_size;
|
|
|
|
while changed > px(0.) && ix < old_sizes.len() - 1 {
|
|
ix += 1;
|
|
let size_range = self.panel_size_range(ix);
|
|
let available_size = (new_sizes[ix] - size_range.start).max(px(0.));
|
|
let to_reduce = changed.min(available_size);
|
|
new_sizes[ix] -= to_reduce;
|
|
changed -= to_reduce;
|
|
}
|
|
} else {
|
|
let mut changed = new_size - size;
|
|
new_sizes[ix] = new_size;
|
|
|
|
while changed > px(0.) && ix > 0 {
|
|
ix -= 1;
|
|
let size_range = self.panel_size_range(ix);
|
|
let available_size = (new_sizes[ix] - size_range.start).max(px(0.));
|
|
let to_reduce = changed.min(available_size);
|
|
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
|
|
let total_size: Pixels = new_sizes.iter().map(|s| s.as_f32()).sum::<f32>().into();
|
|
if total_size > container_size {
|
|
let overflow = total_size - container_size;
|
|
new_sizes[main_ix] = (new_sizes[main_ix] - overflow).max(size_range.start);
|
|
}
|
|
|
|
for (i, _) in old_sizes.iter().enumerate() {
|
|
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<Self>) {
|
|
// 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<ResizablePanelEvent> for ResizableState {}
|
|
|
|
#[derive(Debug, Clone, Default)]
|
|
pub(crate) struct ResizablePanelState {
|
|
pub size: Option<Pixels>,
|
|
pub size_range: Range<Pixels>,
|
|
bounds: Bounds<Pixels>,
|
|
}
|