From 7f92e973a6eb171ae09f008e58e5146095e3002a Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 11 Mar 2026 17:24:54 -0700 Subject: [PATCH] Render flexible dock content adjacent to the fixed dock content --- crates/agent_ui/src/agent_panel.rs | 8 - crates/debugger_ui/src/session/running.rs | 2 - crates/terminal_view/src/terminal_panel.rs | 2 - crates/workspace/src/dock.rs | 46 ++-- crates/workspace/src/pane_group.rs | 242 ++------------------- crates/workspace/src/workspace.rs | 108 ++++----- 6 files changed, 106 insertions(+), 302 deletions(-) diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index fae5afa48959b6bd992c5a6cee2f1e9449b5f8f3..f85df064b2d8fb1e8ebe67c421d5687536924926 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -493,9 +493,6 @@ pub fn init(cx: &mut App) { window.focus(&pane_focus, cx); } } - // Explicitly notify the panel so the dock picks up - // the change to `has_main_element` via its observer. - panel.update(cx, |_, cx| cx.notify()); } }) .register_action(|workspace, _: &FocusWorkspaceSidebar, window, cx| { @@ -508,9 +505,6 @@ pub fn init(cx: &mut App) { sidebar.focus_or_unfocus(workspace, window, cx); }); } - // Explicitly notify the panel so the dock picks up - // any change to `has_main_element` via its observer. - panel.update(cx, |_, cx| cx.notify()); } }); }, @@ -1250,8 +1244,6 @@ impl AgentPanel { cx.defer_in(window, move |this, _window, cx| { this.sidebar = find_or_create_sidebar_for_window(_window, cx); - // Observe the sidebar so that when its open state changes, - // the panel (and thus the dock) is notified and re-rendered. this._sidebar_observation = this .sidebar .as_ref() diff --git a/crates/debugger_ui/src/session/running.rs b/crates/debugger_ui/src/session/running.rs index 8dc923780b55a62df4a5e1120bffbdf0acd05541..1df442ef88fada109b6b7ad6e3bb5cf63f0ea453 100644 --- a/crates/debugger_ui/src/session/running.rs +++ b/crates/debugger_ui/src/session/running.rs @@ -115,8 +115,6 @@ impl Render for RunningState { } else if let Some(active) = active { self.panes .render( - None, - None, None, &ActivePaneDecorator::new(active, &self.workspace), window, diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 479683e923d839b476c56b6a8dfdaa3bd995b75c..93b9e651191e791da8bbda35600c3db001b46d90 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -1362,8 +1362,6 @@ impl Render for TerminalPanel { .update(cx, |workspace, cx| { registrar.size_full().child(self.center.render( workspace.zoomed_item(), - None, - None, &workspace::PaneRenderContext { follower_states: &HashMap::default(), active_call: workspace.active_call(), diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 06af263d3c7e3fcb4ff6a73cfebc02829f4b95eb..28b5a4dbcd62033d750bb7585dc79d7e6aaba85c 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -823,14 +823,25 @@ impl Render for Dock { { let size = entry.panel.size(window, cx); + let panel_content = entry + .panel + .to_any() + .cached(StyleRefinement::default().v_flex().size_full()); + let position = self.position; - let create_resize_handle = || { + let create_resize_handle = |is_flex_content: bool| { let handle = div() .id("resize-handle") - .on_drag(DraggedDock(position), |dock, _, _, cx| { - cx.stop_propagation(); - cx.new(|_| dock.clone()) - }) + .on_drag( + DraggedDock { + position, + is_flex_content, + }, + |dock, _, _, cx| { + cx.stop_propagation(); + cx.new(|_| dock.clone()) + }, + ) .on_mouse_down( MouseButton::Left, cx.listener(|_, _: &MouseDownEvent, _, cx| { @@ -891,29 +902,22 @@ 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(), + Axis::Horizontal => this + .h_full() + .flex_row() + .child(div().w(size).h_full().child(panel_content)), + Axis::Vertical => this + .w_full() + .flex_col() + .child(div().h(size).w_full().child(panel_content)), }) .map(|this| match self.position() { DockPosition::Left => this.border_r_1(), DockPosition::Right => this.border_l_1(), DockPosition::Bottom => this.border_t_1(), }) - .child( - div() - .map(|this| match self.position().axis() { - Axis::Horizontal => this.min_w(size).h_full(), - Axis::Vertical => this.min_h(size).w_full(), - }) - .child( - entry - .panel - .to_any() - .cached(StyleRefinement::default().v_flex().size_full()), - ), - ) .when(self.resizable(cx), |this| { - this.child(create_resize_handle()) + this.child(create_resize_handle(false)) }) } else { div() diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 4f57b5994b0bb12c4335e53571461086ddd76ecb..692113620ac2bad3d745b9822f693b0effb1e278 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -27,7 +27,6 @@ const VERTICAL_MIN_SIZE: f32 = 100.; #[derive(Clone)] pub struct PaneGroup { pub root: Member, - pub state: PaneGroupState, pub is_center: bool, } @@ -40,7 +39,6 @@ impl PaneGroup { pub fn with_root(root: Member) -> Self { Self { root, - state: PaneGroupState::default(), is_center: false, } } @@ -64,7 +62,6 @@ impl PaneGroup { Member::Pane(pane) => { if pane == old_pane { self.root = Member::new_axis(old_pane.clone(), new_pane.clone(), direction); - self.state.reset_flexes(); true } else { false @@ -174,7 +171,6 @@ impl PaneGroup { Member::Axis(axis) => { if let Some(last_pane) = axis.remove(pane)? { self.root = last_pane; - self.state.reset_flexes(); } Ok(true) } @@ -223,46 +219,11 @@ impl PaneGroup { pub fn render( &self, zoomed: Option<&AnyWeakView>, - left_content: Option, - right_content: Option, render_cx: &dyn PaneLeaderDecorator, window: &mut Window, cx: &mut App, ) -> impl IntoElement { - let mut state = self.state.0.borrow_mut(); - if left_content.is_some() { - state.left_entry_is_active = true; - state.left_entry.get_or_insert(PaneAxisStateEntry { - flex: 1.0, - bounding_box: None, - }); - } else { - state.left_entry_is_active = false; - } - - if right_content.is_some() { - state.right_entry_is_active = true; - state.right_entry.get_or_insert(PaneAxisStateEntry { - flex: 1.0, - bounding_box: None, - }); - } else { - state.right_entry_is_active = false; - } - drop(state); - - self.root - .render( - 0, - zoomed, - left_content, - right_content, - Some(self.state.clone()), - render_cx, - window, - cx, - ) - .element + self.root.render(0, zoomed, render_cx, window, cx).element } pub fn panes(&self) -> Vec<&Entity> { @@ -546,66 +507,10 @@ impl Member { &self, basis: usize, zoomed: Option<&AnyWeakView>, - left_content: Option, - right_content: Option, - pane_group_state: Option, render_cx: &dyn PaneLeaderDecorator, window: &mut Window, cx: &mut App, ) -> PaneRenderResult { - if let Some(pane_group_state) = pane_group_state - && (left_content.is_some() || right_content.is_some()) - { - return match self { - Member::Axis(axis) if axis.axis == Axis::Horizontal => axis.render( - basis + 1, - zoomed, - Some(pane_group_state), - left_content, - right_content, - render_cx, - window, - cx, - ), - _ => { - let mut active_pane_ix = 0; - if left_content.is_some() { - active_pane_ix += 1; - } - if right_content.is_some() { - active_pane_ix += 1; - } - - let inner = self.render(0, zoomed, None, None, None, render_cx, window, cx); - - let mut children = Vec::new(); - children.extend(left_content.map(|content| content.into_any_element())); - children.push(inner.element); - children.extend(right_content.map(|content| content.into_any_element())); - - let element = pane_axis( - Axis::Horizontal, - 0, - PaneAxisState::with_flexes(vec![ - children.len() as f32 - - pane_group_state.total_flex(); - 1 - ]), - Some(pane_group_state), - render_cx.workspace().clone(), - ) - .with_active_pane(Some(active_pane_ix)) - .with_is_leaf_pane_mask(vec![true, matches!(self, Member::Pane(_)), true]) - .children(children) - .into_any_element(); - PaneRenderResult { - element, - contains_active_pane: inner.contains_active_pane, - } - } - }; - } - match self { Member::Pane(pane) => { if zoomed == Some(&pane.downgrade().into()) { @@ -643,9 +548,7 @@ impl Member { contains_active_pane: is_active, } } - Member::Axis(axis) => { - axis.render(basis + 1, zoomed, None, None, None, render_cx, window, cx) - } + Member::Axis(axis) => axis.render(basis + 1, zoomed, render_cx, window, cx), } } @@ -676,57 +579,17 @@ impl Member { #[derive(Debug, Clone)] pub struct PaneAxisState(Rc>); -#[derive(Default, Debug, Clone)] -pub struct PaneGroupState(Rc>); - #[derive(Debug)] struct PaneAxisStateInner { entries: Vec, } -#[derive(Default, Debug)] -struct PaneGroupStateInner { - left_entry: Option, - left_entry_is_active: bool, - right_entry: Option, - right_entry_is_active: bool, -} - #[derive(Clone, Copy, Debug)] struct PaneAxisStateEntry { flex: f32, bounding_box: Option>, } -impl PaneGroupState { - fn total_flex(&self) -> f32 { - let state = self.0.borrow(); - let left = state - .left_entry - .as_ref() - .filter(|_| state.left_entry_is_active) - .map_or(0., |e| e.flex); - let right = state - .right_entry - .as_ref() - .filter(|_| state.right_entry_is_active) - .map_or(0., |e| e.flex); - left + right - } - - fn reset_flexes(&self) { - let mut state = self.0.borrow_mut(); - if let Some(left_entry) = state.left_entry.as_mut() { - left_entry.flex = 1.0; - left_entry.bounding_box = None; - } - if let Some(right_entry) = state.right_entry.as_mut() { - right_entry.flex = 1.0; - right_entry.bounding_box = None; - } - } -} - impl PaneAxisState { pub fn new(member_count: usize) -> Self { Self(Rc::new(RefCell::new(PaneAxisStateInner { @@ -1094,9 +957,6 @@ impl PaneAxis { &self, basis: usize, zoomed: Option<&AnyWeakView>, - pane_group_state: Option, - left_content: Option, - right_content: Option, render_cx: &dyn PaneLeaderDecorator, window: &mut Window, cx: &mut App, @@ -1106,9 +966,11 @@ impl PaneAxis { let mut contains_active_pane = false; let mut is_leaf_pane = vec![false; self.members.len()]; - let rendered_children = left_content - .into_iter() - .chain(self.members.iter().enumerate().map(|(ix, member)| { + let rendered_children = self + .members + .iter() + .enumerate() + .map(|(ix, member)| { match member { Member::Pane(pane) => { is_leaf_pane[ix] = true; @@ -1122,29 +984,18 @@ impl PaneAxis { } } - let result = member.render( - (basis + ix) * 10, - zoomed, - None, - None, - None, - render_cx, - window, - cx, - ); + let result = member.render((basis + ix) * 10, zoomed, render_cx, window, cx); if result.contains_active_pane { contains_active_pane = true; } result.element.into_any_element() - })) - .chain(right_content) + }) .collect::>(); let element = pane_axis( self.axis, basis, self.state.clone(), - pane_group_state, render_cx.workspace().clone(), ) .with_is_leaf_pane_mask(is_leaf_pane) @@ -1159,35 +1010,6 @@ impl PaneAxis { } } -impl PaneAxisStateInner { - pub fn entries<'a>( - &'a mut self, - pane_group_state: Option<&'a mut PaneGroupStateInner>, - ) -> Vec<&'a mut PaneAxisStateEntry> { - let mut entries = Vec::new(); - if let Some(pane_group_state) = pane_group_state { - if let Some(left) = pane_group_state - .left_entry - .as_mut() - .filter(|_| pane_group_state.left_entry_is_active) - { - entries.push(left); - } - entries.extend(self.entries.iter_mut()); - if let Some(right) = pane_group_state - .right_entry - .as_mut() - .filter(|_| pane_group_state.right_entry_is_active) - { - entries.push(right); - } - } else { - entries.extend(self.entries.iter_mut()); - } - entries - } -} - #[derive(Clone, Copy, Debug, Deserialize, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum SplitDirection { @@ -1299,7 +1121,7 @@ pub mod element { use crate::Workspace; use crate::WorkspaceSettings; - use crate::pane_group::{PaneAxisStateEntry, PaneGroupState}; + use crate::pane_group::PaneAxisStateEntry; use super::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, PaneAxisState, VERTICAL_MIN_SIZE}; @@ -1309,14 +1131,12 @@ pub mod element { axis: Axis, basis: usize, state: PaneAxisState, - pane_group_state: Option, workspace: WeakEntity, ) -> PaneAxisElement { PaneAxisElement { axis, basis, state, - pane_group_state, children: SmallVec::new(), active_pane_ix: None, workspace, @@ -1328,7 +1148,6 @@ pub mod element { axis: Axis, basis: usize, state: PaneAxisState, - pane_group_state: Option, children: SmallVec<[AnyElement; 2]>, active_pane_ix: Option, workspace: WeakEntity, @@ -1365,7 +1184,6 @@ pub mod element { } fn compute_resize( - pane_group_state: Option<&PaneGroupState>, state: &PaneAxisState, e: &MouseMoveEvent, ix: usize, @@ -1376,36 +1194,33 @@ pub mod element { window: &mut Window, cx: &mut App, ) { - let mut state = state.0.borrow_mut(); - let mut group_state = pane_group_state.as_ref().map(|state| state.0.borrow_mut()); - let group_state = group_state.as_deref_mut(); - let mut entries = state.entries(group_state); + let entries = &mut state.0.borrow_mut().entries; let min_size = match axis { Axis::Horizontal => px(HORIZONTAL_MIN_SIZE), Axis::Vertical => px(VERTICAL_MIN_SIZE), }; - check_flex_values_in_bounds(&entries); + check_flex_values_in_bounds(entries); // Math to convert a flex value to a pixel value - let size = move |ix: usize, state: &[&mut PaneAxisStateEntry]| { - container_size.along(axis) * (state[ix].flex / state.len() as f32) + let size = move |ix: usize, entries: &[PaneAxisStateEntry]| { + container_size.along(axis) * (entries[ix].flex / entries.len() as f32) }; // Don't allow resizing to less than the minimum size, if elements are already too small - if min_size - px(1.) > size(ix, &entries) { + if min_size - px(1.) > size(ix, entries) { return; } // This is basically a "bucket" of pixel changes that need to be applied in response to this // mouse event. Probably a small, fractional number like 0.5 or 1.5 pixels let mut proposed_current_pixel_change = - (e.position - child_start).along(axis) - size(ix, &entries); + (e.position - child_start).along(axis) - size(ix, entries); // This takes a pixel change, and computes the flex changes that correspond to this pixel change // as well as the next one, for some reason let flex_changes = - |pixel_dx, target_ix: usize, next: isize, entries: &[&mut PaneAxisStateEntry]| { + |pixel_dx, target_ix: usize, next: isize, entries: &[PaneAxisStateEntry]| { let flex_change = pixel_dx / container_size.along(axis); let current_target_flex = entries[target_ix].flex + flex_change; let next_target_flex = @@ -1440,19 +1255,19 @@ pub mod element { }; let next_target_size = Pixels::max( - size(current_ix + 1, &entries) - proposed_current_pixel_change, + size(current_ix + 1, entries) - proposed_current_pixel_change, min_size, ); let current_target_size = Pixels::max( - size(current_ix, &entries) + size(current_ix + 1, &entries) - next_target_size, + size(current_ix, entries) + size(current_ix + 1, entries) - next_target_size, min_size, ); - let current_pixel_change = current_target_size - size(current_ix, &entries); + let current_pixel_change = current_target_size - size(current_ix, entries); let (current_target_flex, next_target_flex) = - flex_changes(current_pixel_change, current_ix, 1, &entries); + flex_changes(current_pixel_change, current_ix, 1, entries); entries[current_ix].flex = current_target_flex; entries[current_ix + 1].flex = next_target_flex; @@ -1548,14 +1363,11 @@ pub mod element { (state.clone(), state) }, ); - let mut state = self.state.0.borrow_mut(); - let mut group_state = self.pane_group_state.as_ref().map(|s| s.0.borrow_mut()); - let group_state = group_state.as_deref_mut(); - let mut entries = state.entries(group_state); + let entries = &mut self.state.0.borrow_mut().entries; let len = self.children.len(); debug_assert!(entries.len() == len); - check_flex_values_in_bounds(&entries); + check_flex_values_in_bounds(entries); let total_flex = len as f32; @@ -1702,7 +1514,6 @@ pub mod element { window.on_mouse_event({ let dragged_handle = layout.dragged_handle.clone(); let state = self.state.clone(); - let group_state = self.pane_group_state.clone(); let workspace = self.workspace.clone(); let handle_hitbox = handle.hitbox.clone(); move |e: &MouseDownEvent, phase, window, cx| { @@ -1710,9 +1521,6 @@ pub mod element { dragged_handle.replace(Some(ix)); if e.click_count >= 2 { state.reset_flexes(); - if let Some(group_state) = group_state.as_ref() { - group_state.reset_flexes(); - } workspace .update(cx, |this, cx| this.serialize_workspace(window, cx)) .log_err(); @@ -1727,14 +1535,12 @@ pub mod element { let workspace = self.workspace.clone(); let dragged_handle = layout.dragged_handle.clone(); let state = self.state.clone(); - let group_state = self.pane_group_state.clone(); let child_bounds = child.bounds; let axis = self.axis; move |e: &MouseMoveEvent, phase, window, cx| { let dragged_handle = dragged_handle.borrow(); if phase.bubble() && *dragged_handle == Some(ix) { Self::compute_resize( - group_state.as_ref(), &state, e, ix, @@ -1768,7 +1574,7 @@ pub mod element { } } - fn check_flex_values_in_bounds(inner: &[&mut PaneAxisStateEntry]) { + fn check_flex_values_in_bounds(inner: &[PaneAxisStateEntry]) { #[cfg(debug_assertions)] if (inner.iter().map(|e| e.flex).sum::() - inner.len() as f32).abs() >= 0.001 { panic!( diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index db3bb33e6c51bff676fc61821aa6c56e6b807b73..86e336cd25eaafec4326a5b735dfd06c85e66d2f 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -6974,9 +6974,11 @@ impl Workspace { dock: &Entity, window: &mut Window, cx: &mut App, - ) -> Option
{ + ) -> impl Iterator { + let mut results = [None, None]; + if self.zoomed_position == Some(position) { - return None; + return results.into_iter().flatten(); } let leader_border = dock.read(cx).active_panel().and_then(|panel| { @@ -6985,66 +6987,67 @@ impl Workspace { leader_border_for_pane(follower_states, &pane, window, cx) }); - Some( + results[0] = Some( div() .flex() .flex_none() .overflow_hidden() .child(dock.clone()) - .children(leader_border), - ) + .children(leader_border) + .into_any_element(), + ); + + results[1] = dock + .read(cx) + .visible_panel() + .cloned() + .and_then(|panel| panel.flex_content(window, cx)) + .map(|flex_content| { + div() + .h_full() + .flex_1() + .min_w(px(10.)) + .flex_grow() + .child(flex_content) + .into_any_element() + }); + + if position == DockPosition::Right { + results.swap(0, 1); + } + + results.into_iter().flatten() } - fn render_center_with_panel_elements( + fn render_center( &mut self, paddings: (Option
, Option
), window: &mut Window, cx: &mut App, ) -> AnyElement { - let left_panel_flex_content = (self.zoomed_position != Some(DockPosition::Left)) - .then(|| { - self.left_dock - .read(cx) - .visible_panel() - .cloned() - .and_then(|panel| panel.flex_content(window, cx)) - }) - .flatten(); - let right_panel_flex_content = (self.zoomed_position != Some(DockPosition::Right)) - .then(|| { - self.right_dock - .read(cx) - .visible_panel() - .cloned() - .and_then(|panel| panel.flex_content(window, cx)) - }) - .flatten(); - - let center_pane_group = h_flex() - .size_full() - .flex_1() - .when_some(paddings.0, |this, p| this.child(p.border_r_1())) - .child(self.center.render( - self.zoomed.as_ref(), - left_panel_flex_content, - right_panel_flex_content, - &PaneRenderContext { - follower_states: &self.follower_states, - active_call: self.active_call(), - active_pane: &self.active_pane, - app_state: &self.app_state, - project: &self.project, - workspace: &self.weak_self, - }, - window, - cx, - )) - .when_some(paddings.1, |this, p| this.child(p.border_l_1())) - .into_any_element(); - h_flex() .flex_1() - .child(center_pane_group) + .child( + h_flex() + .size_full() + .flex_1() + .when_some(paddings.0, |this, p| this.child(p.border_r_1())) + .child(self.center.render( + self.zoomed.as_ref(), + &PaneRenderContext { + follower_states: &self.follower_states, + active_call: self.active_call(), + active_pane: &self.active_pane, + app_state: &self.app_state, + project: &self.project, + workspace: &self.weak_self, + }, + window, + cx, + )) + .when_some(paddings.1, |this, p| this.child(p.border_l_1())) + .into_any_element(), + ) .into_any_element() } @@ -7585,7 +7588,10 @@ impl Focusable for Workspace { } #[derive(Clone)] -struct DraggedDock(DockPosition); +struct DraggedDock { + position: DockPosition, + is_flex_content: bool, +} impl Render for DraggedDock { fn render(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { @@ -7636,7 +7642,7 @@ impl Render for Workspace { .collect::>(); let bottom_dock_layout = WorkspaceSettings::get_global(cx).bottom_dock_layout; - let mut center_area = Some(self.render_center_with_panel_elements(paddings, window, cx)); + let mut center_area = Some(self.render_center(paddings, window, cx)); div() .relative() @@ -7724,7 +7730,7 @@ impl Render for Workspace { workspace.previous_dock_drag_coordinates = Some(e.event.position); - match e.drag(cx).0 { + match e.drag(cx).position { DockPosition::Left => { workspace.resize_left_dock( e.event.position.x