From b0a1bc16a0666eb4ff67dc3a1df762a5331ced3b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 30 Mar 2026 07:56:00 -0700 Subject: [PATCH] Allow agent and terminal panels to be either flexible or fixed (#52694) This PR adds the ability to change both the terminal and agent panels between fixed and flexible sizing using the status bar button right click menu. The value persists in your settings, similar to the dock position. I've also slightly tweaked the styling of the "Dock Left" and "Dock Right" items in the right-click menu, adding the current value as an item with a check beside it, to make it clear that it's a selectable option. Release Notes: - N/A --- assets/settings/default.json | 8 + crates/agent/src/tool_permissions.rs | 1 + crates/agent_settings/src/agent_settings.rs | 2 + crates/agent_ui/src/agent_panel.rs | 15 +- crates/agent_ui/src/agent_ui.rs | 1 + crates/settings/src/vscode_import.rs | 1 + crates/settings_content/src/agent.rs | 8 + crates/settings_content/src/terminal.rs | 4 + crates/terminal/src/terminal_settings.rs | 2 + crates/terminal_view/src/terminal_panel.rs | 14 ++ crates/workspace/src/dock.rs | 176 +++++++++++++++-- crates/workspace/src/workspace.rs | 205 +++++++++++++++----- 12 files changed, 365 insertions(+), 72 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index cabeeaf200..e076a6939d 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -943,6 +943,10 @@ "button": true, // Where to dock the agent panel. Can be 'left', 'right' or 'bottom'. "dock": "right", + // Whether the agent panel should use flexible (proportional) sizing. + // + // Default: true + "flexible": true, // Where to position the sidebar. Can be 'left', 'right', or 'follow_agent'. "sidebar_side": "follow_agent", // Default width when the agent panel is docked to the left or right. @@ -1651,6 +1655,10 @@ "shell": "system", // Where to dock terminals panel. Can be `left`, `right`, `bottom`. "dock": "bottom", + // Whether the terminal panel should use flexible (proportional) sizing. + // + // Default: true + "flexible": true, // Default width when the terminal is docked to the left or right. "default_width": 640, // Default height when the terminal is docked to the bottom. diff --git a/crates/agent/src/tool_permissions.rs b/crates/agent/src/tool_permissions.rs index b04db5ffd8..73b3ff842a 100644 --- a/crates/agent/src/tool_permissions.rs +++ b/crates/agent/src/tool_permissions.rs @@ -571,6 +571,7 @@ mod tests { enabled: true, button: true, dock: DockPosition::Right, + flexible: true, default_width: px(300.), default_height: px(600.), default_model: None, diff --git a/crates/agent_settings/src/agent_settings.rs b/crates/agent_settings/src/agent_settings.rs index 7d83be6ebc..258293ee65 100644 --- a/crates/agent_settings/src/agent_settings.rs +++ b/crates/agent_settings/src/agent_settings.rs @@ -27,6 +27,7 @@ pub struct AgentSettings { pub enabled: bool, pub button: bool, pub dock: DockPosition, + pub flexible: bool, pub sidebar_side: SidebarDockPosition, pub default_width: Pixels, pub default_height: Pixels, @@ -424,6 +425,7 @@ impl Settings for AgentSettings { sidebar_side: agent.sidebar_side.unwrap(), default_width: px(agent.default_width.unwrap()), default_height: px(agent.default_height.unwrap()), + flexible: agent.flexible.unwrap(), default_model: Some(agent.default_model.unwrap()), inline_assistant_model: agent.inline_assistant_model, inline_assistant_use_streaming_tools: agent diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index 8ab640a55e..8d6cac0e64 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -3157,10 +3157,23 @@ impl Panel for AgentPanel { } } - fn supports_flexible_size(&self, _window: &Window, _cx: &App) -> bool { + fn supports_flexible_size(&self) -> bool { true } + fn has_flexible_size(&self, _window: &Window, cx: &App) -> bool { + AgentSettings::get_global(cx).flexible + } + + fn set_flexible_size(&mut self, flexible: bool, _window: &mut Window, cx: &mut Context) { + settings::update_settings_file(self.fs.clone(), cx, move |settings, _| { + settings + .agent + .get_or_insert_default() + .set_flexible_size(flexible); + }); + } + fn set_active(&mut self, active: bool, window: &mut Window, cx: &mut Context) { if active && matches!(self.active_view, ActiveView::Uninitialized) diff --git a/crates/agent_ui/src/agent_ui.rs b/crates/agent_ui/src/agent_ui.rs index f8e73a6987..0e5eb39360 100644 --- a/crates/agent_ui/src/agent_ui.rs +++ b/crates/agent_ui/src/agent_ui.rs @@ -647,6 +647,7 @@ mod tests { enabled: true, button: true, dock: DockPosition::Right, + flexible: true, default_width: px(300.), default_height: px(600.), default_model: None, diff --git a/crates/settings/src/vscode_import.rs b/crates/settings/src/vscode_import.rs index 2d52fee639..fa6a9afbce 100644 --- a/crates/settings/src/vscode_import.rs +++ b/crates/settings/src/vscode_import.rs @@ -881,6 +881,7 @@ impl VsCodeSettings { scroll_multiplier: None, toolbar: None, show_count_badge: None, + flexible: None, }) } diff --git a/crates/settings_content/src/agent.rs b/crates/settings_content/src/agent.rs index 158cdc78d9..dc5395bba8 100644 --- a/crates/settings_content/src/agent.rs +++ b/crates/settings_content/src/agent.rs @@ -109,6 +109,10 @@ pub struct AgentSettingsContent { /// /// Default: right pub dock: Option, + /// Whether the agent panel should use flexible (proportional) sizing. + /// + /// Default: true + pub flexible: Option, /// Where to position the sidebar. /// /// Default: follow_agent @@ -230,6 +234,10 @@ impl AgentSettingsContent { self.sidebar_side = Some(position); } + pub fn set_flexible_size(&mut self, flexible: bool) { + self.flexible = Some(flexible); + } + pub fn set_model(&mut self, language_model: LanguageModelSelection) { self.default_model = Some(language_model) } diff --git a/crates/settings_content/src/terminal.rs b/crates/settings_content/src/terminal.rs index 83f3b32fdd..643dea18d1 100644 --- a/crates/settings_content/src/terminal.rs +++ b/crates/settings_content/src/terminal.rs @@ -129,6 +129,10 @@ pub struct TerminalSettingsContent { /// Default: true pub button: Option, pub dock: Option, + /// Whether the terminal panel should use flexible (proportional) sizing. + /// + /// Default: true + pub flexible: Option, /// Default width when the terminal is docked to the left or right. /// /// Default: 640 diff --git a/crates/terminal/src/terminal_settings.rs b/crates/terminal/src/terminal_settings.rs index 9e97a39812..ec784d466b 100644 --- a/crates/terminal/src/terminal_settings.rs +++ b/crates/terminal/src/terminal_settings.rs @@ -40,6 +40,7 @@ pub struct TerminalSettings { pub keep_selection_on_copy: bool, pub button: bool, pub dock: TerminalDockPosition, + pub flexible: bool, pub default_width: Pixels, pub default_height: Pixels, pub detect_venv: VenvSettings, @@ -110,6 +111,7 @@ impl settings::Settings for TerminalSettings { dock: user_content.dock.unwrap(), default_width: px(user_content.default_width.unwrap()), default_height: px(user_content.default_height.unwrap()), + flexible: user_content.flexible.unwrap(), detect_venv: project_content.detect_venv.unwrap(), scroll_multiplier: user_content.scroll_multiplier.unwrap(), max_scroll_history_lines: user_content.max_scroll_history_lines, diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 76f27f9465..a813a1adc5 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -1574,6 +1574,20 @@ impl Panel for TerminalPanel { } } + fn supports_flexible_size(&self) -> bool { + true + } + + fn has_flexible_size(&self, _window: &Window, cx: &App) -> bool { + TerminalSettings::get_global(cx).flexible + } + + fn set_flexible_size(&mut self, flexible: bool, _window: &mut Window, cx: &mut Context) { + settings::update_settings_file(self.fs.clone(), cx, move |settings, _| { + settings.terminal.get_or_insert_default().flexible = Some(flexible); + }); + } + fn is_zoomed(&self, _window: &Window, cx: &App) -> bool { self.active_pane.read(cx).is_zoomed() } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index e0870503b7..567d88228e 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -42,9 +42,19 @@ pub trait Panel: Focusable + EventEmitter + Render + Sized { PanelSizeState::default() } fn size_state_changed(&mut self, _window: &mut Window, _cx: &mut Context) {} - fn supports_flexible_size(&self, _window: &Window, _cx: &App) -> bool { + fn supports_flexible_size(&self) -> bool { false } + fn has_flexible_size(&self, _window: &Window, _cx: &App) -> bool { + false + } + fn set_flexible_size( + &mut self, + _flexible: bool, + _window: &mut Window, + _cx: &mut Context, + ) { + } fn icon(&self, window: &Window, cx: &App) -> Option; fn icon_tooltip(&self, window: &Window, cx: &App) -> Option<&'static str>; fn toggle_action(&self) -> Box; @@ -89,7 +99,9 @@ pub trait PanelHandle: Send + Sync { fn default_size(&self, window: &Window, cx: &App) -> Pixels; fn initial_size_state(&self, window: &Window, cx: &App) -> PanelSizeState; fn size_state_changed(&self, window: &mut Window, cx: &mut App); - fn supports_flexible_size(&self, window: &Window, cx: &App) -> bool; + fn supports_flexible_size(&self, cx: &App) -> bool; + fn has_flexible_size(&self, window: &Window, cx: &App) -> bool; + fn set_flexible_size(&self, flexible: bool, window: &mut Window, cx: &mut App); fn icon(&self, window: &Window, cx: &App) -> Option; fn icon_tooltip(&self, window: &Window, cx: &App) -> Option<&'static str>; fn toggle_action(&self, window: &Window, cx: &App) -> Box; @@ -176,8 +188,16 @@ where self.update(cx, |this, cx| this.size_state_changed(window, cx)) } - fn supports_flexible_size(&self, window: &Window, cx: &App) -> bool { - self.read(cx).supports_flexible_size(window, cx) + fn supports_flexible_size(&self, cx: &App) -> bool { + self.read(cx).supports_flexible_size() + } + + fn has_flexible_size(&self, window: &Window, cx: &App) -> bool { + self.read(cx).has_flexible_size(window, cx) + } + + fn set_flexible_size(&self, flexible: bool, window: &mut Window, cx: &mut App) { + self.update(cx, |this, cx| this.set_flexible_size(flexible, window, cx)) } fn icon(&self, window: &Window, cx: &App) -> Option { @@ -292,7 +312,7 @@ impl DockPosition { pub struct PanelSizeState { pub size: Option, #[serde(default)] - pub flexible_size_ratio: Option, + pub flex: Option, } struct PanelEntry { @@ -840,10 +860,48 @@ impl Dock { } } + pub fn toggle_panel_flexible_size( + &mut self, + panel: &dyn PanelHandle, + current_size: Option, + current_flex: Option, + window: &mut Window, + cx: &mut Context, + ) { + let Some(entry) = self + .panel_entries + .iter_mut() + .find(|entry| entry.panel.panel_id() == panel.panel_id()) + else { + return; + }; + let currently_flexible = entry.panel.has_flexible_size(window, cx); + if currently_flexible { + entry.size_state.size = current_size; + } else { + entry.size_state.flex = current_flex; + } + let panel_key = entry.panel.panel_key(); + let size_state = entry.size_state; + let workspace = self.workspace.clone(); + entry + .panel + .set_flexible_size(!currently_flexible, window, cx); + entry.panel.size_state_changed(window, cx); + cx.defer(move |cx| { + if let Some(workspace) = workspace.upgrade() { + workspace.update(cx, |workspace, cx| { + workspace.persist_panel_size_state(panel_key, size_state, cx); + }); + } + }); + cx.notify(); + } + pub fn resize_active_panel( &mut self, size: Option, - ratio: Option, + flex: Option, window: &mut Window, cx: &mut Context, ) { @@ -852,8 +910,9 @@ impl Dock { { let size = size.map(|size| size.max(RESIZE_HANDLE_SIZE).round()); - if entry.panel.supports_flexible_size(window, cx) { - entry.size_state.flexible_size_ratio = ratio; + let use_flex = entry.panel.has_flexible_size(window, cx); + if use_flex { + entry.size_state.flex = flex; } else { entry.size_state.size = size; } @@ -876,7 +935,7 @@ impl Dock { pub fn resize_all_panels( &mut self, size: Option, - ratio: Option, + flex: Option, window: &mut Window, cx: &mut Context, ) { @@ -884,8 +943,9 @@ impl Dock { for entry in &mut self.panel_entries { let size = size.map(|size| size.max(RESIZE_HANDLE_SIZE).round()); - if entry.panel.supports_flexible_size(window, cx) { - entry.size_state.flexible_size_ratio = ratio; + let use_flex = entry.panel.has_flexible_size(window, cx); + if use_flex { + entry.size_state.flex = flex; } else { entry.size_state.size = size; } @@ -925,7 +985,8 @@ impl Dock { pub fn clamp_panel_size(&mut self, max_size: Pixels, window: &Window, cx: &mut App) { let max_size = (max_size - RESIZE_HANDLE_SIZE).abs(); for entry in &mut self.panel_entries { - if entry.panel.supports_flexible_size(window, cx) { + let use_flexible = entry.panel.has_flexible_size(window, cx); + if use_flexible { continue; } @@ -1087,6 +1148,8 @@ impl Render for PanelButtons { DockPosition::Bottom | DockPosition::Right => (Corner::BottomRight, Corner::TopRight), }; + let dock_entity = self.dock.clone(); + let workspace = dock.workspace.clone(); let mut buttons: Vec<_> = dock .panel_entries .iter() @@ -1102,6 +1165,10 @@ impl Render for PanelButtons { .log_err()?; let name = entry.panel.persistent_name(); let panel = entry.panel.clone(); + let supports_flexible = panel.supports_flexible_size(cx); + let currently_flexible = panel.has_flexible_size(window, cx); + let dock_for_menu = dock_entity.clone(); + let workspace_for_menu = workspace.clone(); let is_active_button = Some(i) == active_index && is_open; let (action, tooltip) = if is_active_button { @@ -1130,20 +1197,76 @@ impl Render for PanelButtons { ]; ContextMenu::build(window, cx, |mut menu, _, cx| { + let mut has_position_entries = false; for position in POSITIONS { - if position != dock_position - && panel.position_is_valid(position, cx) - { + if panel.position_is_valid(position, cx) { + let is_current = position == dock_position; let panel = panel.clone(); - menu = menu.entry( + menu = menu.toggleable_entry( format!("Dock {}", position.label()), + is_current, + IconPosition::Start, None, move |window, cx| { - panel.set_position(position, window, cx); + if !is_current { + panel.set_position(position, window, cx); + } }, - ) + ); + has_position_entries = true; } } + if supports_flexible { + if has_position_entries { + menu = menu.separator(); + } + let panel_for_flex = panel.clone(); + let dock_for_flex = dock_for_menu.clone(); + let workspace_for_flex = workspace_for_menu.clone(); + menu = menu.toggleable_entry( + "Flex Width", + currently_flexible, + IconPosition::Start, + None, + move |window, cx| { + if !currently_flexible { + if let Some(ws) = workspace_for_flex.upgrade() { + ws.update(cx, |workspace, cx| { + workspace.toggle_dock_panel_flexible_size( + &dock_for_flex, + panel_for_flex.as_ref(), + window, + cx, + ); + }); + } + } + }, + ); + let panel_for_fixed = panel.clone(); + let dock_for_fixed = dock_for_menu.clone(); + let workspace_for_fixed = workspace_for_menu.clone(); + menu = menu.toggleable_entry( + "Fixed Width", + !currently_flexible, + IconPosition::Start, + None, + move |window, cx| { + if currently_flexible { + if let Some(ws) = workspace_for_fixed.upgrade() { + ws.update(cx, |workspace, cx| { + workspace.toggle_dock_panel_flexible_size( + &dock_for_fixed, + panel_for_fixed.as_ref(), + window, + cx, + ); + }); + } + } + }, + ); + } menu }) }) @@ -1290,14 +1413,27 @@ pub mod test { fn initial_size_state(&self, _window: &Window, _: &App) -> PanelSizeState { PanelSizeState { size: None, - flexible_size_ratio: None, + flex: None, } } - fn supports_flexible_size(&self, _window: &Window, _: &App) -> bool { + fn supports_flexible_size(&self) -> bool { self.flexible } + fn has_flexible_size(&self, _window: &Window, _: &App) -> bool { + self.flexible + } + + fn set_flexible_size( + &mut self, + flexible: bool, + _window: &mut Window, + _cx: &mut Context, + ) { + self.flexible = flexible; + } + fn icon(&self, _window: &Window, _: &App) -> Option { None } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 4328fb3e30..b9c243d495 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -150,7 +150,7 @@ pub use workspace_settings::{ }; use zed_actions::{Spawn, feedback::FileBugReport, theme::ToggleMode}; -use crate::{item::ItemBufferKind, notifications::NotificationId}; +use crate::{dock::PanelSizeState, item::ItemBufferKind, notifications::NotificationId}; use crate::{ persistence::{ SerializedAxis, @@ -2201,6 +2201,22 @@ impl Workspace { did_set } + pub fn toggle_dock_panel_flexible_size( + &self, + dock: &Entity, + panel: &dyn PanelHandle, + window: &mut Window, + cx: &mut App, + ) { + let position = dock.read(cx).position(); + let current_size = self.dock_size(&dock.read(cx), window, cx); + let current_flex = + current_size.and_then(|size| self.dock_flex_for_size(position, size, window, cx)); + dock.update(cx, |dock, cx| { + dock.toggle_panel_flexible_size(panel, current_size, current_flex, window, cx); + }); + } + fn dock_size(&self, dock: &Dock, window: &Window, cx: &App) -> Option { let panel = dock.active_panel()?; let size_state = dock @@ -2208,15 +2224,30 @@ impl Workspace { .unwrap_or_default(); let position = dock.position(); + let use_flex = panel.has_flexible_size(window, cx); + if position.axis() == Axis::Horizontal - && panel.supports_flexible_size(window, cx) - && let Some(ratio) = size_state - .flexible_size_ratio - .or_else(|| self.default_flexible_dock_ratio(position)) - && let Some(available_width) = - self.available_width_for_horizontal_dock(position, window, cx) + && use_flex + && let Some(flex) = size_state.flex.or_else(|| self.default_dock_flex(position)) { - return Some((available_width * ratio.clamp(0.0, 1.0)).max(RESIZE_HANDLE_SIZE)); + let workspace_width = self.bounds.size.width; + if workspace_width <= Pixels::ZERO { + return None; + } + let flex = flex.max(0.001); + 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; + 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)); + } } Some( @@ -2226,7 +2257,7 @@ impl Workspace { ) } - pub fn flexible_dock_ratio_for_size( + pub fn dock_flex_for_size( &self, position: DockPosition, size: Pixels, @@ -2237,45 +2268,55 @@ impl Workspace { return None; } - let available_width = self.available_width_for_horizontal_dock(position, window, cx)?; - let available_width = available_width.max(RESIZE_HANDLE_SIZE); - Some((size / available_width).clamp(0.0, 1.0)) - } - - fn available_width_for_horizontal_dock( - &self, - position: DockPosition, - window: &Window, - cx: &App, - ) -> Option { let workspace_width = self.bounds.size.width; if workspace_width <= Pixels::ZERO { return None; } + 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)) + } 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)) + } + } + + fn opposite_dock_panel_and_size_state( + &self, + position: DockPosition, + window: &Window, + cx: &App, + ) -> Option<(Arc, PanelSizeState)> { let opposite_position = match position { DockPosition::Left => DockPosition::Right, DockPosition::Right => DockPosition::Left, DockPosition::Bottom => return None, }; - let opposite_width = self - .dock_at_position(opposite_position) - .read(cx) - .stored_active_panel_size(window, cx) - .unwrap_or(Pixels::ZERO); - - Some((workspace_width - opposite_width).max(RESIZE_HANDLE_SIZE)) + let opposite_dock = self.dock_at_position(opposite_position).read(cx); + let panel = opposite_dock.visible_panel()?; + let mut size_state = opposite_dock + .stored_panel_size_state(panel.as_ref()) + .unwrap_or_default(); + if size_state.flex.is_none() && panel.has_flexible_size(window, cx) { + size_state.flex = self.default_dock_flex(opposite_position); + } + Some((panel.clone(), size_state)) } - pub fn default_flexible_dock_ratio(&self, position: DockPosition) -> Option { + 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()?; - let pane_fraction = self.center.width_fraction_for_pane(&pane).unwrap_or(1.0); - Some((pane_fraction / (1.0 + pane_fraction)).clamp(0.0, 1.0)) + Some(self.center.width_fraction_for_pane(&pane).unwrap_or(1.0)) } pub fn is_edited(&self) -> bool { @@ -2301,7 +2342,7 @@ impl Workspace { load_legacy_panel_size(T::panel_key(), dock_position, self, cx).map(|size| { let state = dock::PanelSizeState { size: Some(size), - flexible_size_ratio: None, + flex: None, }; self.persist_panel_size_state(T::panel_key(), state, cx); state @@ -7277,13 +7318,16 @@ impl Workspace { if let Some(panel) = dock.visible_panel() { let size_state = dock.stored_panel_size_state(panel.as_ref()); if position.axis() == Axis::Horizontal { - if let Some(ratio) = size_state - .and_then(|state| state.flexible_size_ratio) - .or_else(|| self.default_flexible_dock_ratio(position)) - && panel.supports_flexible_size(window, cx) - { - let ratio = ratio.clamp(0.001, 0.999); - let grow = ratio / (1.0 - ratio); + let use_flexible = panel.has_flexible_size(window, cx); + let flex_grow = if use_flexible { + size_state + .and_then(|state| state.flex) + .or_else(|| self.default_dock_flex(position)) + } else { + None + }; + if let Some(grow) = flex_grow { + let grow = grow.max(0.001); let style = container.style(); style.flex_grow = Some(grow); style.flex_shrink = Some(1.0); @@ -7399,15 +7443,15 @@ impl Workspace { } }); - let ratio = self.flexible_dock_ratio_for_size(DockPosition::Left, size, window, cx); + let flex_grow = self.dock_flex_for_size(DockPosition::Left, size, window, cx); self.left_dock.update(cx, |left_dock, cx| { if WorkspaceSettings::get_global(cx) .resize_all_panels_in_dock .contains(&DockPosition::Left) { - left_dock.resize_all_panels(Some(size), ratio, window, cx); + left_dock.resize_all_panels(Some(size), flex_grow, window, cx); } else { - left_dock.resize_active_panel(Some(size), ratio, window, cx); + left_dock.resize_active_panel(Some(size), flex_grow, window, cx); } }); } @@ -7423,15 +7467,15 @@ impl Workspace { size = workspace_width - left_dock_size } }); - let ratio = self.flexible_dock_ratio_for_size(DockPosition::Right, size, window, cx); + let flex_grow = self.dock_flex_for_size(DockPosition::Right, size, window, cx); self.right_dock.update(cx, |right_dock, cx| { if WorkspaceSettings::get_global(cx) .resize_all_panels_in_dock .contains(&DockPosition::Right) { - right_dock.resize_all_panels(Some(size), ratio, window, cx); + right_dock.resize_all_panels(Some(size), flex_grow, window, cx); } else { - right_dock.resize_active_panel(Some(size), ratio, window, cx); + right_dock.resize_active_panel(Some(size), flex_grow, window, cx); } }); } @@ -12346,8 +12390,11 @@ mod tests { assert_eq!( right_dock .stored_panel_size_state(flexible_panel.as_ref()) - .and_then(|size_state| size_state.flexible_size_ratio), - Some(resized_width.to_f64() as f32 / workspace.bounds.size.width.to_f64() as f32) + .and_then(|size_state| size_state.flex), + Some( + resized_width.to_f64() as f32 + / (workspace.bounds.size.width - resized_width).to_f64() as f32 + ) ); }); @@ -12481,9 +12528,7 @@ mod tests { persisted.size, None, "flexible panel should not persist a redundant pixel size" ); - let original_ratio = persisted - .flexible_size_ratio - .expect("flexible panel ratio should be persisted"); + let original_ratio = persisted.flex.expect("panel's flex should be persisted"); // Remove the panel and re-add: both size and ratio should be restored. workspace.update_in(cx, |workspace, window, cx| { @@ -12504,9 +12549,9 @@ mod tests { "re-added flexible panel should not have a persisted pixel size" ); assert_eq!( - size_state.flexible_size_ratio, + size_state.flex, Some(original_ratio), - "re-added flexible panel should restore persisted ratio" + "re-added flexible panel should restore persisted flex" ); }); } @@ -12599,6 +12644,64 @@ mod tests { "flexible left panel should shrink proportionally as the right dock takes space" ); }); + + // 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. + workspace.update_in(cx, |workspace, window, cx| { + let right_dock = workspace.right_dock().clone(); + let right_panel = right_dock + .read(cx) + .visible_panel() + .expect("right dock should have a visible panel") + .clone(); + workspace.toggle_dock_panel_flexible_size( + &right_dock, + right_panel.as_ref(), + window, + cx, + ); + + let right_dock = right_dock.read(cx); + let right_panel = right_dock + .visible_panel() + .expect("right dock should still have a visible panel"); + assert!( + right_panel.has_flexible_size(window, cx), + "right panel should now be flexible" + ); + + let right_size_state = right_dock + .stored_panel_size_state(right_panel.as_ref()) + .expect("right panel should have a stored size state after toggling"); + let right_flex = right_size_state + .flex + .expect("right panel should have a flex value after toggling"); + + 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"); + let right_width = workspace + .dock_size(&right_dock, window, cx) + .expect("right dock should still have an active panel"); + + let left_flex = workspace + .default_dock_flex(DockPosition::Left) + .expect("left dock should have a default flex"); + + let total_flex = left_flex + 1.0 + 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!( + left_width, expected_left, + "flexible left panel should share workspace width via flex ratios" + ); + assert_eq!( + right_width, expected_right, + "flexible right panel should share workspace width via flex ratios" + ); + }); } struct TestModal(FocusHandle);