Detailed changes
@@ -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,
// Default width when the agent panel is docked to the left or right.
"default_width": 640,
// Default height when the agent panel is docked to the bottom.
@@ -1643,6 +1647,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.
@@ -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,
@@ -26,6 +26,7 @@ pub struct AgentSettings {
pub enabled: bool,
pub button: bool,
pub dock: DockPosition,
+ pub flexible: bool,
pub default_width: Pixels,
pub default_height: Pixels,
pub default_model: Option<LanguageModelSelection>,
@@ -409,6 +410,7 @@ impl Settings for AgentSettings {
dock: agent.dock.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
@@ -3150,10 +3150,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<Self>) {
+ 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<Self>) {
if active
&& matches!(self.active_view, ActiveView::Uninitialized)
@@ -636,6 +636,7 @@ mod tests {
enabled: true,
button: true,
dock: DockPosition::Right,
+ flexible: true,
default_width: px(300.),
default_height: px(600.),
default_model: None,
@@ -880,6 +880,7 @@ impl VsCodeSettings {
scroll_multiplier: None,
toolbar: None,
show_count_badge: None,
+ flexible: None,
})
}
@@ -48,6 +48,10 @@ pub struct AgentSettingsContent {
///
/// Default: right
pub dock: Option<DockPosition>,
+ /// Whether the agent panel should use flexible (proportional) sizing.
+ ///
+ /// Default: true
+ pub flexible: Option<bool>,
/// Default width in pixels when the agent panel is docked to the left or right.
///
/// Default: 640
@@ -157,6 +161,10 @@ impl AgentSettingsContent {
self.dock = Some(dock);
}
+ 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)
}
@@ -129,6 +129,10 @@ pub struct TerminalSettingsContent {
/// Default: true
pub button: Option<bool>,
pub dock: Option<TerminalDockPosition>,
+ /// Whether the terminal panel should use flexible (proportional) sizing.
+ ///
+ /// Default: true
+ pub flexible: Option<bool>,
/// Default width when the terminal is docked to the left or right.
///
/// Default: 640
@@ -4952,7 +4952,7 @@ fn panels_page() -> SettingsPage {
]
}
- fn terminal_panel_section() -> [SettingsPageItem; 3] {
+ fn terminal_panel_section() -> [SettingsPageItem; 4] {
[
SettingsPageItem::SectionHeader("Terminal Panel"),
SettingsPageItem::SettingItem(SettingItem {
@@ -4968,6 +4968,19 @@ fn panels_page() -> SettingsPage {
metadata: None,
files: USER,
}),
+ SettingsPageItem::SettingItem(SettingItem {
+ title: "Terminal Panel Flexible Sizing",
+ description: "Whether the terminal panel should use flexible (proportional) sizing when docked to the left or right.",
+ field: Box::new(SettingField {
+ json_path: Some("terminal.flexible"),
+ pick: |settings_content| settings_content.terminal.as_ref()?.flexible.as_ref(),
+ write: |settings_content, value| {
+ settings_content.terminal.get_or_insert_default().flexible = value;
+ },
+ }),
+ metadata: None,
+ files: USER,
+ }),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Count Badge",
description: "Show a badge on the terminal panel icon with the count of open terminals.",
@@ -5628,7 +5641,7 @@ fn panels_page() -> SettingsPage {
]
}
- fn agent_panel_section() -> [SettingsPageItem; 5] {
+ fn agent_panel_section() -> [SettingsPageItem; 6] {
[
SettingsPageItem::SectionHeader("Agent Panel"),
SettingsPageItem::SettingItem(SettingItem {
@@ -5657,6 +5670,19 @@ fn panels_page() -> SettingsPage {
metadata: None,
files: USER,
}),
+ SettingsPageItem::SettingItem(SettingItem {
+ title: "Agent Panel Flexible Sizing",
+ description: "Whether the agent panel should use flexible (proportional) sizing when docked to the left or right.",
+ field: Box::new(SettingField {
+ json_path: Some("agent.flexible"),
+ pick: |settings_content| settings_content.agent.as_ref()?.flexible.as_ref(),
+ write: |settings_content, value| {
+ settings_content.agent.get_or_insert_default().flexible = value;
+ },
+ }),
+ metadata: None,
+ files: USER,
+ }),
SettingsPageItem::SettingItem(SettingItem {
title: "Agent Panel Default Width",
description: "Default width when the agent panel is docked to the left or right.",
@@ -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,
@@ -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<Self>) {
+ 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()
}
@@ -42,9 +42,19 @@ pub trait Panel: Focusable + EventEmitter<PanelEvent> + Render + Sized {
PanelSizeState::default()
}
fn size_state_changed(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {}
- 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<Self>,
+ ) {
+ }
fn icon(&self, window: &Window, cx: &App) -> Option<ui::IconName>;
fn icon_tooltip(&self, window: &Window, cx: &App) -> Option<&'static str>;
fn toggle_action(&self) -> Box<dyn Action>;
@@ -86,7 +96,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<ui::IconName>;
fn icon_tooltip(&self, window: &Window, cx: &App) -> Option<&'static str>;
fn toggle_action(&self, window: &Window, cx: &App) -> Box<dyn Action>;
@@ -172,8 +184,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<ui::IconName> {
@@ -284,7 +304,7 @@ impl DockPosition {
pub struct PanelSizeState {
pub size: Option<Pixels>,
#[serde(default)]
- pub flexible_size_ratio: Option<f32>,
+ pub flex: Option<f32>,
}
struct PanelEntry {
@@ -300,6 +320,25 @@ pub struct PanelButtons {
pub(crate) const PANEL_SIZE_STATE_KEY: &str = "dock_panel_size";
+fn resize_panel_entry(
+ position: DockPosition,
+ entry: &mut PanelEntry,
+ size: Option<Pixels>,
+ flex: Option<f32>,
+ window: &mut Window,
+ 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 {
+ entry.size_state.flex = flex;
+ } else {
+ entry.size_state.size = size;
+ }
+ entry.panel.size_state_changed(window, cx);
+ (entry.panel.panel_key(), entry.size_state)
+}
+
impl Dock {
pub fn new(
position: DockPosition,
@@ -762,17 +801,9 @@ impl Dock {
}
}
- pub fn panel_size(&self, panel: &dyn PanelHandle, window: &Window, cx: &App) -> Option<Pixels> {
- self.panel_entries
- .iter()
- .find(|entry| entry.panel.panel_id() == panel.panel_id())
- .map(|entry| self.resolved_panel_size(entry, window, cx))
- }
-
- pub fn active_panel_size(&self, window: &Window, cx: &App) -> Option<Pixels> {
+ pub fn active_panel_size(&self) -> Option<PanelSizeState> {
if self.is_open {
- self.active_panel_entry()
- .map(|entry| self.resolved_panel_size(entry, window, cx))
+ self.active_panel_entry().map(|entry| entry.size_state)
} else {
None
}
@@ -834,28 +865,58 @@ impl Dock {
}
}
+ pub fn toggle_panel_flexible_size(
+ &mut self,
+ panel: &dyn PanelHandle,
+ current_size: Option<Pixels>,
+ current_flex: Option<f32>,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
+ 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<Pixels>,
- ratio: Option<f32>,
+ flex: Option<f32>,
window: &mut Window,
cx: &mut Context<Self>,
) {
if let Some(index) = self.active_panel_index
&& let Some(entry) = self.panel_entries.get_mut(index)
{
- let size = size.map(|size| size.max(RESIZE_HANDLE_SIZE).round());
+ let (panel_key, size_state) =
+ resize_panel_entry(self.position, entry, size, flex, window, cx);
- if entry.panel.supports_flexible_size(window, cx) {
- entry.size_state.flexible_size_ratio = ratio;
- } else {
- entry.size_state.size = size;
- }
-
- let panel_key = entry.panel.panel_key();
- let size_state = entry.size_state;
let workspace = self.workspace.clone();
- entry.panel.size_state_changed(window, cx);
cx.defer(move |cx| {
if let Some(workspace) = workspace.upgrade() {
workspace.update(cx, |workspace, cx| {
@@ -870,22 +931,15 @@ impl Dock {
pub fn resize_all_panels(
&mut self,
size: Option<Pixels>,
- ratio: Option<f32>,
+ flex: Option<f32>,
window: &mut Window,
cx: &mut Context<Self>,
) {
- let mut size_states_to_persist = Vec::new();
-
- 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;
- } else {
- entry.size_state.size = size;
- }
- entry.panel.size_state_changed(window, cx);
- size_states_to_persist.push((entry.panel.panel_key(), entry.size_state));
- }
+ 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 workspace = self.workspace.clone();
cx.defer(move |cx| {
@@ -919,7 +973,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;
}
@@ -933,28 +988,6 @@ impl Dock {
}
}
- fn resolved_panel_size(&self, entry: &PanelEntry, window: &Window, cx: &App) -> Pixels {
- if self.position.axis() == Axis::Horizontal
- && entry.panel.supports_flexible_size(window, cx)
- {
- if let Some(workspace) = self.workspace.upgrade() {
- let workspace = workspace.read(cx);
- return resolve_panel_size(
- entry.size_state,
- entry.panel.as_ref(),
- self.position,
- workspace,
- window,
- cx,
- );
- }
- }
- entry
- .size_state
- .size
- .unwrap_or_else(|| entry.panel.default_size(window, cx))
- }
-
pub(crate) fn load_persisted_size_state(
workspace: &Workspace,
panel_key: &'static str,
@@ -974,41 +1007,10 @@ impl Dock {
}
}
-pub(crate) fn resolve_panel_size(
- size_state: PanelSizeState,
- panel: &dyn PanelHandle,
- position: DockPosition,
- workspace: &Workspace,
- window: &Window,
- cx: &App,
-) -> Pixels {
- if position.axis() == Axis::Horizontal && panel.supports_flexible_size(window, cx) {
- let ratio = size_state
- .flexible_size_ratio
- .or_else(|| workspace.default_flexible_dock_ratio(position));
-
- if let Some(ratio) = ratio {
- return workspace
- .flexible_dock_size(position, ratio, window, cx)
- .unwrap_or_else(|| {
- size_state
- .size
- .unwrap_or_else(|| panel.default_size(window, cx))
- });
- }
- }
-
- size_state
- .size
- .unwrap_or_else(|| panel.default_size(window, cx))
-}
-
impl Render for Dock {
- fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+ fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let dispatch_context = Self::dispatch_context();
if let Some(entry) = self.visible_entry() {
- let size = self.resolved_panel_size(entry, window, cx);
-
let position = self.position;
let create_resize_handle = || {
let handle = div()
@@ -1077,8 +1079,10 @@ impl Render for Dock {
.border_color(cx.theme().colors().border)
.overflow_hidden()
.map(|this| match self.position().axis() {
- Axis::Horizontal => this.w(size).h_full().flex_row(),
- Axis::Vertical => this.h(size).w_full().flex_col(),
+ // Width and height are always set on the workspace wrapper in
+ // render_dock, so fill whatever space the wrapper provides.
+ Axis::Horizontal => this.w_full().h_full().flex_row(),
+ Axis::Vertical => this.h_full().w_full().flex_col(),
})
.map(|this| match self.position() {
DockPosition::Left => this.border_r_1(),
@@ -1088,8 +1092,8 @@ impl Render for Dock {
.child(
div()
.map(|this| match self.position().axis() {
- Axis::Horizontal => this.min_w(size).h_full(),
- Axis::Vertical => this.min_h(size).w_full(),
+ Axis::Horizontal => this.w_full().h_full(),
+ Axis::Vertical => this.h_full().w_full(),
})
.child(
entry
@@ -1132,6 +1136,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()
@@ -1147,6 +1153,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 {
@@ -1175,20 +1185,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
})
})
@@ -1335,14 +1401,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>,
+ ) {
+ self.flexible = flexible;
+ }
+
fn icon(&self, _window: &Window, _: &App) -> Option<ui::IconName> {
None
}
@@ -148,7 +148,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,
@@ -2193,33 +2193,63 @@ impl Workspace {
did_set
}
- pub fn flexible_dock_size(
+ pub fn toggle_dock_panel_flexible_size(
&self,
- position: DockPosition,
- ratio: f32,
- window: &Window,
- cx: &App,
- ) -> Option<Pixels> {
- if position.axis() != Axis::Horizontal {
- return None;
- }
-
- let available_width = self.available_width_for_horizontal_dock(position, window, cx)?;
- Some((available_width * ratio.clamp(0.0, 1.0)).max(RESIZE_HANDLE_SIZE))
+ dock: &Entity<Dock>,
+ 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);
+ });
}
- pub fn resolved_dock_panel_size(
- &self,
- dock: &Dock,
- panel: &dyn PanelHandle,
- window: &Window,
- cx: &App,
- ) -> Pixels {
- let size_state = dock.stored_panel_size_state(panel).unwrap_or_default();
- dock::resolve_panel_size(size_state, panel, dock.position(), self, window, cx)
+ fn dock_size(&self, dock: &Dock, window: &Window, cx: &App) -> Option<Pixels> {
+ let panel = dock.active_panel()?;
+ let size_state = dock
+ .stored_panel_size_state(panel.as_ref())
+ .unwrap_or_default();
+ let position = dock.position();
+
+ let use_flex = panel.has_flexible_size(window, cx);
+
+ if position.axis() == Axis::Horizontal
+ && use_flex
+ && let Some(flex) = size_state.flex.or_else(|| self.default_dock_flex(position))
+ {
+ 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(
+ size_state
+ .size
+ .unwrap_or_else(|| panel.default_size(window, cx)),
+ )
}
- pub fn flexible_dock_ratio_for_size(
+ pub fn dock_flex_for_size(
&self,
position: DockPosition,
size: Pixels,
@@ -2230,45 +2260,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<Pixels> {
- let workspace_width = self.bounds.size.width;
- if workspace_width <= Pixels::ZERO {
- return None;
- }
-
+ ) -> Option<(Arc<dyn PanelHandle>, 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<f32> {
+ pub fn default_dock_flex(&self, position: DockPosition) -> Option<f32> {
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 {
@@ -2294,7 +2334,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
@@ -4892,10 +4932,7 @@ impl Workspace {
if let Some(dock_entity) = active_dock {
let dock = dock_entity.read(cx);
- let Some(panel_size) = dock
- .active_panel()
- .map(|panel| self.resolved_dock_panel_size(&dock, panel.as_ref(), window, cx))
- else {
+ let Some(panel_size) = self.dock_size(&dock, window, cx) else {
return;
};
match dock.position() {
@@ -7258,14 +7295,49 @@ impl Workspace {
leader_border_for_pane(follower_states, &pane, window, cx)
});
- Some(
- div()
- .flex()
- .flex_none()
- .overflow_hidden()
- .child(dock.clone())
- .children(leader_border),
- )
+ let mut container = div()
+ .flex()
+ .overflow_hidden()
+ .flex_none()
+ .child(dock.clone())
+ .children(leader_border);
+
+ // Apply sizing only when the dock is open. When closed the dock is still
+ // included in the element tree so its focus handle remains mounted — without
+ // this, toggle_panel_focus cannot focus the panel when the dock is closed.
+ let dock = dock.read(cx);
+ if let Some(panel) = dock.visible_panel() {
+ let size_state = dock.stored_panel_size_state(panel.as_ref());
+ if position.axis() == Axis::Horizontal {
+ 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);
+ style.flex_basis = Some(relative(0.).into());
+ } else {
+ let size = size_state
+ .and_then(|state| state.size)
+ .unwrap_or_else(|| panel.default_size(window, cx));
+ container = container.w(size);
+ }
+ } else {
+ let size = size_state
+ .and_then(|state| state.size)
+ .unwrap_or_else(|| panel.default_size(window, cx));
+ container = container.h(size);
+ }
+ }
+
+ Some(container)
}
pub fn for_window(window: &Window, cx: &App) -> Option<Entity<Workspace>> {
@@ -7335,18 +7407,17 @@ impl Workspace {
}
}
- fn adjust_dock_size_by_px(
+ fn resize_dock(
&mut self,
- panel_size: Pixels,
dock_pos: DockPosition,
- px: Pixels,
+ new_size: Pixels,
window: &mut Window,
cx: &mut Context<Self>,
) {
match dock_pos {
- DockPosition::Left => self.resize_left_dock(panel_size + px, window, cx),
- DockPosition::Right => self.resize_right_dock(panel_size + px, window, cx),
- DockPosition::Bottom => self.resize_bottom_dock(panel_size + px, window, cx),
+ DockPosition::Left => self.resize_left_dock(new_size, window, cx),
+ DockPosition::Right => self.resize_right_dock(new_size, window, cx),
+ DockPosition::Bottom => self.resize_bottom_dock(new_size, window, cx),
}
}
@@ -7363,15 +7434,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);
}
});
}
@@ -7387,15 +7458,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);
}
});
}
@@ -7790,14 +7861,10 @@ fn adjust_active_dock_size_by_px(
return;
};
let dock = active_dock.read(cx);
- let Some(panel_size) = dock
- .active_panel()
- .map(|panel| workspace.resolved_dock_panel_size(&dock, panel.as_ref(), window, cx))
- else {
+ let Some(panel_size) = workspace.dock_size(&dock, window, cx) else {
return;
};
- let dock_pos = dock.position();
- workspace.adjust_dock_size_by_px(panel_size, dock_pos, px, window, cx);
+ workspace.resize_dock(dock.position(), panel_size + px, window, cx);
}
fn adjust_open_docks_size_by_px(
@@ -7812,22 +7879,18 @@ fn adjust_open_docks_size_by_px(
.filter_map(|dock_entity| {
let dock = dock_entity.read(cx);
if dock.is_open() {
- let panel_size = dock.active_panel().map(|panel| {
- workspace.resolved_dock_panel_size(&dock, panel.as_ref(), window, cx)
- })?;
let dock_pos = dock.position();
- Some((panel_size, dock_pos, px))
+ let panel_size = workspace.dock_size(&dock, window, cx)?;
+ Some((dock_pos, panel_size + px))
} else {
None
}
})
.collect::<Vec<_>>();
- docks
- .into_iter()
- .for_each(|(panel_size, dock_pos, offset)| {
- workspace.adjust_dock_size_by_px(panel_size, dock_pos, offset, window, cx);
- });
+ for (position, new_size) in docks {
+ workspace.resize_dock(position, new_size, window, cx);
+ }
}
impl Focusable for Workspace {
@@ -12270,11 +12333,8 @@ mod tests {
let dock = workspace.right_dock().read(cx);
let workspace_width = workspace.bounds.size.width;
- let initial_width = dock
- .active_panel()
- .map(|panel| {
- workspace.resolved_dock_panel_size(&dock, panel.as_ref(), window, cx)
- })
+ let initial_width = workspace
+ .dock_size(&dock, window, cx)
.expect("flexible dock should have an initial width");
assert_eq!(initial_width, workspace_width / 2.);
@@ -12282,11 +12342,8 @@ mod tests {
workspace.resize_right_dock(px(300.), window, cx);
let dock = workspace.right_dock().read(cx);
- let resized_width = dock
- .active_panel()
- .map(|panel| {
- workspace.resolved_dock_panel_size(&dock, panel.as_ref(), window, cx)
- })
+ let resized_width = workspace
+ .dock_size(&dock, window, cx)
.expect("flexible dock should keep its resized width");
assert_eq!(resized_width, px(300.));
@@ -12306,9 +12363,8 @@ mod tests {
workspace.toggle_dock(DockPosition::Right, window, cx);
let dock = workspace.right_dock().read(cx);
- let reopened_width = dock
- .active_panel()
- .map(|panel| workspace.resolved_dock_panel_size(&dock, panel.as_ref(), window, cx))
+ let reopened_width = workspace
+ .dock_size(&dock, window, cx)
.expect("flexible dock should restore when reopened");
assert_eq!(reopened_width, resized_width);
@@ -12321,8 +12377,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
+ )
);
});
@@ -12335,9 +12394,8 @@ mod tests {
);
let dock = workspace.right_dock().read(cx);
- let split_width = dock
- .active_panel()
- .map(|panel| workspace.resolved_dock_panel_size(&dock, panel.as_ref(), window, cx))
+ let split_width = workspace
+ .dock_size(&dock, window, cx)
.expect("flexible dock should keep its user-resized proportion");
assert_eq!(split_width, px(300.));
@@ -12345,9 +12403,8 @@ mod tests {
workspace.bounds.size.width = px(1600.);
let dock = workspace.right_dock().read(cx);
- let resized_window_width = dock
- .active_panel()
- .map(|panel| workspace.resolved_dock_panel_size(&dock, panel.as_ref(), window, cx))
+ let resized_window_width = workspace
+ .dock_size(&dock, window, cx)
.expect("flexible dock should preserve proportional size on window resize");
assert_eq!(
@@ -12458,9 +12515,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| {
@@ -12481,9 +12536,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"
);
});
}
@@ -12517,9 +12572,8 @@ mod tests {
workspace.toggle_dock(DockPosition::Left, window, cx);
let left_dock = workspace.left_dock().read(cx);
- let left_width = left_dock
- .active_panel()
- .map(|p| workspace.resolved_dock_panel_size(&left_dock, p.as_ref(), window, cx))
+ let left_width = workspace
+ .dock_size(&left_dock, window, cx)
.expect("left dock should have an active panel");
assert_eq!(
@@ -12541,9 +12595,8 @@ mod tests {
);
let left_dock = workspace.left_dock().read(cx);
- let left_width = left_dock
- .active_panel()
- .map(|p| workspace.resolved_dock_panel_size(&left_dock, p.as_ref(), window, cx))
+ let left_width = workspace
+ .dock_size(&left_dock, window, cx)
.expect("left dock should still have an active panel after vertical split");
assert_eq!(
@@ -12562,15 +12615,13 @@ mod tests {
workspace.toggle_dock(DockPosition::Right, window, cx);
let right_dock = workspace.right_dock().read(cx);
- let right_width = right_dock
- .active_panel()
- .map(|p| workspace.resolved_dock_panel_size(&right_dock, p.as_ref(), window, cx))
+ let right_width = workspace
+ .dock_size(&right_dock, window, cx)
.expect("right dock should have an active panel");
let left_dock = workspace.left_dock().read(cx);
- let left_width = left_dock
- .active_panel()
- .map(|p| workspace.resolved_dock_panel_size(&left_dock, p.as_ref(), window, cx))
+ let left_width = workspace
+ .dock_size(&left_dock, window, cx)
.expect("left dock should still have an active panel");
let available_width = workspace.bounds.size.width - right_width;
@@ -12580,6 +12631,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);
@@ -12634,8 +12743,8 @@ mod tests {
panel_1.panel_id()
);
assert_eq!(
- left_dock.read(cx).active_panel_size(window, cx).unwrap(),
- px(300.)
+ workspace.dock_size(&left_dock.read(cx), window, cx),
+ Some(px(300.))
);
workspace.resize_left_dock(px(1337.), window, cx);
@@ -12668,7 +12777,12 @@ mod tests {
panel_1.panel_id()
);
assert_eq!(
- right_dock.read(cx).active_panel_size(window, cx).unwrap(),
+ right_dock
+ .read(cx)
+ .active_panel_size()
+ .unwrap()
+ .size
+ .unwrap(),
px(1337.)
);
@@ -12706,8 +12820,8 @@ mod tests {
panel_1.panel_id()
);
assert_eq!(
- left_dock.read(cx).active_panel_size(window, cx).unwrap(),
- px(1337.)
+ workspace.dock_size(&left_dock.read(cx), window, cx),
+ Some(px(1337.))
);
// And the right dock should be closed as it no longer has any panels.
assert!(!workspace.right_dock().read(cx).is_open());
@@ -12723,8 +12837,8 @@ mod tests {
// since the panel orientation changed from vertical to horizontal.
let bottom_dock = workspace.bottom_dock();
assert_eq!(
- bottom_dock.read(cx).active_panel_size(window, cx).unwrap(),
- px(300.),
+ workspace.dock_size(&bottom_dock.read(cx), window, cx),
+ Some(px(300.))
);
// Close bottom dock and move panel_1 back to the left.
bottom_dock.update(cx, |bottom_dock, cx| {