From 8b7cc094575cdfc2342540dac87ceffd92fe8520 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 | 203 +++++++++++++++----- 12 files changed, 364 insertions(+), 71 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index cabeeaf2007dee5d7bd15aa295ffb265d55f183f..e076a6939d251b5a8e4b07985701c1a5610e161d 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 b04db5ffd886b0b4a3704d8c1443e0ef7358f3ec..73b3ff842ab6961b22815c902ce9ae79e60cd2e3 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 7d83be6ebcfe935d6650a85108966d02c7c95eec..258293ee65de1815fb91f7130a2bf5043f0f18f3 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 8ab640a55ef72ed29efbac21ee3b69cfc84c15dc..8d6cac0e647d78f9746b1dcf3d1966ec6b6653af 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 f8e73a69870ea693270990cb1253ea777331e094..0e5eb39360af434936c510a589c2d1f2b6fa74b2 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 2d52fee639f50b26ec115a69660a90492e7e85ef..fa6a9afbce1ef67097b11435f9481a133a0d563a 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 158cdc78d963847c3f2d814279e1417b723f7c2c..dc5395bba805a932ccc3f66f9004e01be4171d4e 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 83f3b32fdd14a6ee693f775b74022af4841af0a5..643dea18d106906d242ff21d0aadbc27492fd09b 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 9e97a398128c46e870c5b2485934a24a13be295b..ec784d466b1f97ba2e44231aaef7475d62981479 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 76f27f94658d738b522959f80354f9998bf2d89a..a813a1adc55fe5de75f5d9547839b15eb391192e 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 e0870503b7c64bb23218d897bc6b4828d315c8b8..567d88228e317d837dbd5e5cc2b9235c63b500d9 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 4328fb3e307295fb5123be79999a60285e208f4b..b9c243d49586c7087bb39711c2316ffee1e8d00b 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)) + 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 available_width_for_horizontal_dock( + fn opposite_dock_panel_and_size_state( &self, position: DockPosition, window: &Window, cx: &App, - ) -> Option { - let workspace_width = self.bounds.size.width; - if workspace_width <= Pixels::ZERO { - return None; - } - + ) -> 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);