diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 81e46b513b4b902aeebb1a912261826c0c4f30dc..ca8584fb1eb6dc2db6a08a528526ac76c37e860e 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -338,6 +338,15 @@ pub struct PanelButtons { pub(crate) const PANEL_SIZE_STATE_KEY: &str = "dock_panel_size"; +fn panel_uses_flexible_width( + position: DockPosition, + panel: &dyn PanelHandle, + window: &Window, + cx: &App, +) -> bool { + position.axis() == Axis::Horizontal && panel.has_flexible_size(window, cx) +} + fn resize_panel_entry( position: DockPosition, entry: &mut PanelEntry, @@ -347,8 +356,8 @@ fn resize_panel_entry( cx: &mut App, ) -> (&'static str, PanelSizeState) { let size = size.map(|size| size.max(RESIZE_HANDLE_SIZE).round()); - let use_flex = entry.panel.has_flexible_size(window, cx) && position.axis() == Axis::Horizontal; - if use_flex { + let uses_flexible_width = panel_uses_flexible_width(position, entry.panel.as_ref(), window, cx); + if uses_flexible_width { entry.size_state.flex = flex; } else { entry.size_state.size = size; @@ -960,11 +969,31 @@ impl Dock { window: &mut Window, cx: &mut Context, ) { - let size_states_to_persist: Vec<_> = self - .panel_entries - .iter_mut() - .map(|entry| resize_panel_entry(self.position, entry, size, flex, window, cx)) - .collect(); + let Some(active_panel_index) = self.active_panel_index else { + return; + }; + + let active_panel_uses_flexible_width = { + let Some(active_entry) = self.panel_entries.get(active_panel_index) else { + return; + }; + panel_uses_flexible_width(self.position, active_entry.panel.as_ref(), window, cx) + }; + let mut size_states_to_persist = Vec::new(); + for entry in &mut self.panel_entries { + if panel_uses_flexible_width(self.position, entry.panel.as_ref(), window, cx) + == active_panel_uses_flexible_width + { + size_states_to_persist.push(resize_panel_entry( + self.position, + entry, + size, + flex, + window, + cx, + )); + } + } let workspace = self.workspace.clone(); cx.defer(move |cx| { diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index c5f78eef6c4a7403589cb4e947326f9fe87ec610..0a5c1b61bdc919a6a5f56da6833b05e879e29894 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -98,8 +98,8 @@ impl PaneGroup { } } - pub fn width_fraction_for_pane(&self, pane: &Entity) -> Option { - self.root.width_fraction_for_pane(pane) + pub fn full_height_column_count(&self) -> usize { + self.root.full_height_column_count() } pub fn pane_at_pixel_position(&self, coordinate: Point) -> Option<&Entity> { @@ -307,10 +307,10 @@ impl Member { } } - fn width_fraction_for_pane(&self, pane: &Entity) -> Option { + fn full_height_column_count(&self) -> usize { match self { - Member::Pane(found) => (found == pane).then_some(1.0), - Member::Axis(axis) => axis.width_fraction_for_pane(pane), + Member::Pane(_) => 1, + Member::Axis(axis) => axis.full_height_column_count(), } } } @@ -901,38 +901,21 @@ impl PaneAxis { None } - fn width_fraction_for_pane(&self, pane: &Entity) -> Option { - let flexes = self.flexes.lock(); - let total_flex = flexes.iter().copied().sum::(); - - for (index, member) in self.members.iter().enumerate() { - let child_fraction = if total_flex > 0.0 { - flexes[index] / total_flex - } else { - 1.0 / self.members.len() as f32 - }; - - match member { - Member::Pane(found) => { - if found == pane { - return Some(match self.axis { - Axis::Horizontal => child_fraction, - Axis::Vertical => 1.0, - }); - } - } - Member::Axis(axis) => { - if let Some(descendant_fraction) = axis.width_fraction_for_pane(pane) { - return Some(match self.axis { - Axis::Horizontal => child_fraction * descendant_fraction, - Axis::Vertical => descendant_fraction, - }); - } - } - } + fn full_height_column_count(&self) -> usize { + match self.axis { + Axis::Horizontal => self + .members + .iter() + .map(Member::full_height_column_count) + .sum::() + .max(1), + Axis::Vertical => self + .members + .iter() + .map(Member::full_height_column_count) + .max() + .unwrap_or(1), } - - None } fn render( diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index ea443d2d6b7eb448300391e4d8af8826be3eff94..b4d236eef11cb2f46929275ba640b88ce77fe79b 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2302,18 +2302,19 @@ impl Workspace { return None; } let flex = flex.max(0.001); + let center_column_count = self.center_full_height_column_count(); let opposite = self.opposite_dock_panel_and_size_state(position, window, cx); if let Some(opposite_flex) = opposite.as_ref().and_then(|(_, s)| s.flex) { - // Both docks are flex items sharing the full workspace width. - let total_flex = flex + 1.0 + opposite_flex; + let total_flex = flex + center_column_count + opposite_flex; return Some((flex / total_flex * workspace_width).max(RESIZE_HANDLE_SIZE)); } else { - // Opposite dock is fixed-width; flex items share (W - fixed). let opposite_fixed = opposite .map(|(panel, s)| s.size.unwrap_or_else(|| panel.default_size(window, cx))) .unwrap_or_default(); let available = (workspace_width - opposite_fixed).max(RESIZE_HANDLE_SIZE); - return Some((flex / (flex + 1.0) * available).max(RESIZE_HANDLE_SIZE)); + return Some( + (flex / (flex + center_column_count) * available).max(RESIZE_HANDLE_SIZE), + ); } } @@ -2340,17 +2341,18 @@ impl Workspace { return None; } + let center_column_count = self.center_full_height_column_count(); let opposite = self.opposite_dock_panel_and_size_state(position, window, cx); if let Some(opposite_flex) = opposite.as_ref().and_then(|(_, s)| s.flex) { let size = size.clamp(px(0.), workspace_width - px(1.)); - Some((size * (1.0 + opposite_flex) / (workspace_width - size)).max(0.0)) + Some((size * (center_column_count + opposite_flex) / (workspace_width - size)).max(0.0)) } else { let opposite_width = opposite .map(|(panel, s)| s.size.unwrap_or_else(|| panel.default_size(window, cx))) .unwrap_or_default(); let available = (workspace_width - opposite_width).max(RESIZE_HANDLE_SIZE); let remaining = (available - size).max(px(1.)); - Some((size / remaining).max(0.0)) + Some((size * center_column_count / remaining).max(0.0)) } } @@ -2377,13 +2379,16 @@ impl Workspace { Some((panel.clone(), size_state)) } + fn center_full_height_column_count(&self) -> f32 { + self.center.full_height_column_count().max(1) as f32 + } + pub fn default_dock_flex(&self, position: DockPosition) -> Option { if position.axis() != Axis::Horizontal { return None; } - let pane = self.last_active_center_pane.clone()?.upgrade()?; - Some(self.center.width_fraction_for_pane(&pane).unwrap_or(1.0)) + Some(1.0) } pub fn is_edited(&self) -> bool { @@ -7484,7 +7489,7 @@ impl Workspace { None }; if let Some(grow) = flex_grow { - let grow = grow.max(0.001); + let grow = (grow / self.center_full_height_column_count()).max(0.001); let style = container.style(); style.flex_grow = Some(grow); style.flex_shrink = Some(1.0); @@ -12744,74 +12749,40 @@ mod tests { let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone()); workspace.update(cx, |workspace, _cx| { - workspace.bounds.size.width = px(800.); + workspace.set_random_database_id(); }); workspace.update_in(cx, |workspace, window, cx| { let panel = cx.new(|cx| TestPanel::new_flexible(DockPosition::Right, 100, cx)); - workspace.add_panel(panel, window, cx); + workspace.add_panel(panel.clone(), window, cx); workspace.toggle_dock(DockPosition::Right, window, cx); - }); - - let (panel, resized_width, ratio_basis_width) = - workspace.update_in(cx, |workspace, window, cx| { - let item = cx.new(|cx| { - TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]) - }); - workspace.add_item_to_active_pane(Box::new(item), None, true, window, cx); - - let dock = workspace.right_dock().read(cx); - let workspace_width = workspace.bounds.size.width; - let initial_width = workspace - .dock_size(&dock, window, cx) - .expect("flexible dock should have an initial width"); - - assert_eq!(initial_width, workspace_width / 2.); - - workspace.resize_right_dock(px(300.), window, cx); - - let dock = workspace.right_dock().read(cx); - let resized_width = workspace - .dock_size(&dock, window, cx) - .expect("flexible dock should keep its resized width"); - assert_eq!(resized_width, px(300.)); - - let panel = workspace - .right_dock() - .read(cx) - .visible_panel() - .expect("flexible dock should have a visible panel") - .panel_id(); - - (panel, resized_width, workspace_width) + let right_dock = workspace.right_dock().clone(); + right_dock.update(cx, |dock, cx| { + dock.set_panel_size_state( + &panel, + dock::PanelSizeState { + size: None, + flex: Some(1.0), + }, + cx, + ); }); + }); workspace.update_in(cx, |workspace, window, cx| { - workspace.toggle_dock(DockPosition::Right, window, cx); - workspace.toggle_dock(DockPosition::Right, window, cx); + let item = cx.new(|cx| { + TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]) + }); + workspace.add_item_to_active_pane(Box::new(item), None, true, window, cx); + workspace.bounds.size.width = px(1920.); let dock = workspace.right_dock().read(cx); - let reopened_width = workspace + let initial_width = workspace .dock_size(&dock, window, cx) - .expect("flexible dock should restore when reopened"); + .expect("flexible dock should have an initial width"); - assert_eq!(reopened_width, resized_width); - - let right_dock = workspace.right_dock().read(cx); - let flexible_panel = right_dock - .visible_panel() - .expect("flexible dock should still have a visible panel"); - assert_eq!(flexible_panel.panel_id(), panel); - assert_eq!( - right_dock - .stored_panel_size_state(flexible_panel.as_ref()) - .and_then(|size_state| size_state.flex), - Some( - resized_width.to_f64() as f32 - / (workspace.bounds.size.width - resized_width).to_f64() as f32 - ) - ); + assert_eq!(initial_width, px(960.)); }); workspace.update_in(cx, |workspace, window, cx| { @@ -12822,25 +12793,16 @@ mod tests { cx, ); - let dock = workspace.right_dock().read(cx); - let split_width = workspace - .dock_size(&dock, window, cx) - .expect("flexible dock should keep its user-resized proportion"); + let center_column_count = workspace.center.full_height_column_count(); + assert_eq!(center_column_count, 2); - assert_eq!(split_width, px(300.)); + let dock = workspace.right_dock().read(cx); + assert_eq!(workspace.dock_size(&dock, window, cx).unwrap(), px(640.)); - workspace.bounds.size.width = px(1600.); + workspace.bounds.size.width = px(2400.); let dock = workspace.right_dock().read(cx); - let resized_window_width = workspace - .dock_size(&dock, window, cx) - .expect("flexible dock should preserve proportional size on window resize"); - - assert_eq!( - resized_window_width, - workspace.bounds.size.width - * (resized_width.to_f64() as f32 / ratio_basis_width.to_f64() as f32) - ); + assert_eq!(workspace.dock_size(&dock, window, cx).unwrap(), px(800.)); }); } @@ -13012,9 +12974,31 @@ mod tests { ); }); - // Step 2: Split the center pane vertically (top/bottom). Vertical splits do not - // change horizontal width fractions, so the flexible panel stays at the same - // width as each half of the split. + // Step 2: Split the center pane left/right. The flexible panel is treated as one + // average center column, so with two center columns it should take one third of + // the workspace width. + workspace.update_in(cx, |workspace, window, cx| { + workspace.split_pane( + workspace.active_pane().clone(), + SplitDirection::Right, + window, + cx, + ); + + let left_dock = workspace.left_dock().read(cx); + let left_width = workspace + .dock_size(&left_dock, window, cx) + .expect("left dock should still have an active panel after horizontal split"); + + assert_eq!( + left_width, + workspace.bounds.size.width / 3., + "flexible left panel width should match the average center column width" + ); + }); + + // Step 3: Split the active center pane vertically (top/bottom). Vertical splits do + // not change the number of center columns, so the flexible panel width stays the same. workspace.update_in(cx, |workspace, window, cx| { workspace.split_pane( workspace.active_pane().clone(), @@ -13030,14 +13014,14 @@ mod tests { assert_eq!( left_width, - workspace.bounds.size.width / 2., - "flexible left panel width should match each vertically-split pane" + workspace.bounds.size.width / 3., + "flexible left panel width should still match the average center column width" ); }); - // Step 3: Open a fixed-width panel in the right dock. The right dock's default - // size reduces the available width, so the flexible left panel and the center - // panes all shrink proportionally to accommodate it. + // Step 4: Open a fixed-width panel in the right dock. The right dock's default + // size reduces the available width, so the flexible left panel keeps matching one + // average center column within the remaining space. workspace.update_in(cx, |workspace, window, cx| { let panel = cx.new(|cx| TestPanel::new(DockPosition::Right, 200, cx)); workspace.add_panel(panel, window, cx); @@ -13056,14 +13040,14 @@ mod tests { let available_width = workspace.bounds.size.width - right_width; assert_eq!( left_width, - available_width / 2., - "flexible left panel should shrink proportionally as the right dock takes space" + available_width / 3., + "flexible left panel should keep matching one average center column" ); }); - // Step 4: Toggle the right dock's panel to flexible. Now both docks use - // flex sizing and the workspace width is divided among left-flex, center - // (implicit flex 1.0), and right-flex. + // Step 5: Toggle the right dock's panel to flexible. Now both docks use + // column-equivalent flex sizing and the workspace width is divided among + // left-flex, two center columns, and right-flex. workspace.update_in(cx, |workspace, window, cx| { let right_dock = workspace.right_dock().clone(); let right_panel = right_dock @@ -13105,8 +13089,9 @@ mod tests { let left_flex = workspace .default_dock_flex(DockPosition::Left) .expect("left dock should have a default flex"); + let center_column_count = workspace.center.full_height_column_count() as f32; - let total_flex = left_flex + 1.0 + right_flex; + let total_flex = left_flex + center_column_count + right_flex; let expected_left = left_flex / total_flex * workspace.bounds.size.width; let expected_right = right_flex / total_flex * workspace.bounds.size.width; assert_eq!(