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);