Start work on making the agent thread flex w/ panes

Max Brunsfeld and Eric Holk created

Co-authored-by: Eric Holk <eric@zed.dev>

Change summary

crates/agent_ui/src/agent_panel.rs | 198 +++----
crates/workspace/src/dock.rs       |  60 ++
crates/workspace/src/pane_group.rs |   6 
crates/workspace/src/workspace.rs  | 800 ++++++++++++++++---------------
4 files changed, 543 insertions(+), 521 deletions(-)

Detailed changes

crates/agent_ui/src/agent_panel.rs 🔗

@@ -65,10 +65,9 @@ use extension_host::ExtensionStore;
 use fs::Fs;
 use git::repository::validate_worktree_directory;
 use gpui::{
-    Action, Animation, AnimationExt, AnyElement, AnyView, App, AsyncWindowContext, ClipboardItem,
-    Corner, DismissEvent, DragMoveEvent, Entity, EventEmitter, ExternalPaths, FocusHandle,
-    Focusable, KeyContext, MouseButton, Pixels, Subscription, Task, UpdateGlobal, WeakEntity,
-    deferred, prelude::*, pulsating_between,
+    Action, Animation, AnimationExt, AnyElement, App, AsyncWindowContext, ClipboardItem, Corner,
+    DismissEvent, Empty, Entity, EventEmitter, ExternalPaths, FocusHandle, Focusable, KeyContext,
+    Pixels, Subscription, Task, UpdateGlobal, WeakEntity, prelude::*, pulsating_between,
 };
 use language::LanguageRegistry;
 use language_model::{ConfigurationError, LanguageModelRegistry};
@@ -86,9 +85,8 @@ use ui::{
 };
 use util::ResultExt as _;
 use workspace::{
-    CollaboratorId, DraggedSelection, DraggedSidebar, DraggedTab, FocusWorkspaceSidebar,
-    MultiWorkspace, SIDEBAR_RESIZE_HANDLE_SIZE, ToggleWorkspaceSidebar, ToggleZoom,
-    ToolbarItemView, Workspace, WorkspaceId,
+    CollaboratorId, DraggedSelection, DraggedTab, FocusWorkspaceSidebar, MultiWorkspace,
+    ToggleWorkspaceSidebar, ToggleZoom, ToolbarItemView, Workspace, WorkspaceId,
     dock::{DockPosition, Panel, PanelEvent},
     multi_workspace_enabled,
 };
@@ -487,6 +485,9 @@ pub fn init(cx: &mut App) {
                                 sidebar.toggle(window, 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| {
@@ -499,6 +500,9 @@ 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());
                     }
                 });
         },
@@ -896,6 +900,7 @@ pub struct AgentPanel {
     last_configuration_error_telemetry: Option<String>,
     on_boarding_upsell_dismissed: AtomicBool,
     _active_view_observation: Option<Subscription>,
+    _sidebar_observation: Option<Subscription>,
     pub(crate) sidebar: Option<Entity<crate::sidebar::Sidebar>>,
 }
 
@@ -1225,14 +1230,21 @@ impl AgentPanel {
             last_configuration_error_telemetry: None,
             on_boarding_upsell_dismissed: AtomicBool::new(OnboardingUpsell::dismissed()),
             _active_view_observation: None,
+            _sidebar_observation: None,
             sidebar: None,
         };
 
         // Initial sync of agent servers from extensions
         panel.sync_agent_servers_from_extensions(cx);
 
-        cx.defer_in(window, move |this, window, cx| {
-            this.sidebar = find_or_create_sidebar_for_window(window, cx);
+        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()
+                .map(|sidebar| cx.observe(sidebar, |_, _, cx| cx.notify()));
             cx.notify();
         });
 
@@ -3103,6 +3115,9 @@ impl Panel for AgentPanel {
     }
 
     fn size(&self, window: &Window, cx: &App) -> Pixels {
+        if let Some(sidebar) = &self.sidebar {
+            return sidebar.read(cx).width(cx);
+        }
         let settings = AgentSettings::get_global(cx);
         match self.position(window, cx) {
             DockPosition::Left | DockPosition::Right => {
@@ -3113,6 +3128,10 @@ impl Panel for AgentPanel {
     }
 
     fn set_size(&mut self, size: Option<Pixels>, window: &mut Window, cx: &mut Context<Self>) {
+        if let Some(sidebar) = &self.sidebar {
+            sidebar.update(cx, |sidebar, cx| sidebar.set_width(size, cx));
+            return;
+        }
         match self.position(window, cx) {
             DockPosition::Left | DockPosition::Right => self.width = size,
             DockPosition::Bottom => self.height = size,
@@ -3166,6 +3185,22 @@ impl Panel for AgentPanel {
         self.zoomed = zoomed;
         cx.notify();
     }
+
+    fn render_center_element(
+        &mut self,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) -> Option<AnyElement> {
+        Some(self.render_content(window, cx).into_any_element())
+    }
+
+    fn has_center_element(&self, _window: &Window, _: &App) -> bool {
+        true
+    }
+
+    fn has_main_element(&self, window: &Window, cx: &App) -> bool {
+        sidebar_is_open(window, cx)
+    }
 }
 
 impl AgentPanel {
@@ -3609,17 +3644,6 @@ impl AgentPanel {
             })
     }
 
-    fn sidebar_info(&self, cx: &App) -> Option<(AnyView, Pixels, bool)> {
-        if !multi_workspace_enabled(cx) {
-            return None;
-        }
-        let sidebar = self.sidebar.as_ref()?;
-        let is_open = sidebar.read(cx).is_open();
-        let width = sidebar.read(cx).width(cx);
-        let view: AnyView = sidebar.clone().into();
-        Some((view, width, is_open))
-    }
-
     fn render_sidebar_toggle(&self, cx: &Context<Self>) -> Option<AnyElement> {
         if !multi_workspace_enabled(cx) {
             return None;
@@ -3649,65 +3673,6 @@ impl AgentPanel {
         )
     }
 
-    fn render_sidebar(&self, cx: &Context<Self>) -> Option<AnyElement> {
-        let (sidebar_view, sidebar_width, is_open) = self.sidebar_info(cx)?;
-        if !is_open {
-            return None;
-        }
-
-        let docked_right = agent_panel_dock_position(cx) == DockPosition::Right;
-        let sidebar = self.sidebar.as_ref()?.downgrade();
-
-        let resize_handle = deferred(
-            div()
-                .id("sidebar-resize-handle")
-                .absolute()
-                .when(docked_right, |this| {
-                    this.left(-SIDEBAR_RESIZE_HANDLE_SIZE / 2.)
-                })
-                .when(!docked_right, |this| {
-                    this.right(-SIDEBAR_RESIZE_HANDLE_SIZE / 2.)
-                })
-                .top(px(0.))
-                .h_full()
-                .w(SIDEBAR_RESIZE_HANDLE_SIZE)
-                .cursor_col_resize()
-                .on_drag(DraggedSidebar, |dragged, _, _, cx| {
-                    cx.stop_propagation();
-                    cx.new(|_| dragged.clone())
-                })
-                .on_mouse_down(MouseButton::Left, |_, _, cx| {
-                    cx.stop_propagation();
-                })
-                .on_mouse_up(MouseButton::Left, move |event, _, cx| {
-                    if event.click_count == 2 {
-                        sidebar
-                            .update(cx, |sidebar, cx| {
-                                sidebar.set_width(None, cx);
-                            })
-                            .ok();
-                        cx.stop_propagation();
-                    }
-                })
-                .occlude(),
-        );
-
-        Some(
-            div()
-                .id("sidebar-container")
-                .relative()
-                .h_full()
-                .w(sidebar_width)
-                .flex_shrink_0()
-                .when(docked_right, |this| this.border_l_1())
-                .when(!docked_right, |this| this.border_r_1())
-                .border_color(cx.theme().colors().border)
-                .child(sidebar_view)
-                .child(resize_handle)
-                .into_any_element(),
-        )
-    }
-
     fn render_toolbar(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
         let agent_server_store = self.project.read(cx).agent_server_store().clone();
         let focus_handle = self.focus_handle(cx);
@@ -4704,17 +4669,17 @@ impl AgentPanel {
     }
 }
 
-impl Render for AgentPanel {
-    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
-        // WARNING: Changes to this element hierarchy can have
-        // non-obvious implications to the layout of children.
-        //
-        // If you need to change it, please confirm:
-        // - The message editor expands (cmd-option-esc) correctly
-        // - When expanded, the buttons at the bottom of the panel are displayed correctly
-        // - Font size works as expected and can be changed with cmd-+/cmd-
-        // - Scrolling in all views works as expected
-        // - Files can be dropped into the panel
+impl AgentPanel {
+    // WARNING: Changes to this element hierarchy can have
+    // non-obvious implications to the layout of children.
+    //
+    // If you need to change it, please confirm:
+    // - The message editor expands (cmd-option-esc) correctly
+    // - When expanded, the buttons at the bottom of the panel are displayed correctly
+    // - Font size works as expected and can be changed with cmd-+/cmd-
+    // - Scrolling in all views works as expected
+    // - Files can be dropped into the panel
+    fn render_content(&mut self, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
         let content = v_flex()
             .relative()
             .size_full()
@@ -4802,44 +4767,33 @@ impl Render for AgentPanel {
             })
             .children(self.render_trial_end_upsell(window, cx));
 
-        let sidebar = self.render_sidebar(cx);
-        let has_sidebar = sidebar.is_some();
-        let docked_right = agent_panel_dock_position(cx) == DockPosition::Right;
-
-        let panel = h_flex()
-            .size_full()
-            .when(has_sidebar, |this| {
-                this.on_drag_move(cx.listener(
-                    move |this, e: &DragMoveEvent<DraggedSidebar>, _window, cx| {
-                        if let Some(sidebar) = &this.sidebar {
-                            let width = if docked_right {
-                                e.bounds.right() - e.event.position.x
-                            } else {
-                                e.event.position.x
-                            };
-                            sidebar.update(cx, |sidebar, cx| {
-                                sidebar.set_width(Some(width), cx);
-                            });
-                        }
-                    },
-                ))
-            })
-            .map(|this| {
-                if docked_right {
-                    this.child(content).children(sidebar)
-                } else {
-                    this.children(sidebar).child(content)
-                }
-            });
-
         match self.active_view.which_font_size_used() {
             WhichFontSize::AgentFont => {
                 WithRemSize::new(ThemeSettings::get_global(cx).agent_ui_font_size(cx))
                     .size_full()
-                    .child(panel)
+                    .child(content)
                     .into_any()
             }
-            _ => panel.into_any(),
+            _ => content.into_any(),
+        }
+    }
+}
+
+impl Render for AgentPanel {
+    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+        if multi_workspace_enabled(cx) {
+            // In multi-workspace mode, the main content is rendered as a
+            // center element via `render_center_element`. The dock only
+            // shows the sidebar when it is open.
+            if let Some(sidebar) = &self.sidebar {
+                if sidebar.read(cx).is_open() {
+                    return sidebar.clone().into_any_element();
+                }
+            }
+            Empty.into_any_element()
+        } else {
+            // In classic mode, the panel renders its full content in the dock.
+            self.render_content(window, cx)
         }
     }
 }

crates/workspace/src/dock.rs 🔗

@@ -5,10 +5,10 @@ use anyhow::Context as _;
 use client::proto;
 
 use gpui::{
-    Action, AnyView, App, Axis, Context, Corner, Entity, EntityId, EventEmitter, FocusHandle,
-    Focusable, IntoElement, KeyContext, MouseButton, MouseDownEvent, MouseUpEvent, ParentElement,
-    Render, SharedString, StyleRefinement, Styled, Subscription, WeakEntity, Window, deferred, div,
-    px,
+    Action, AnyElement, AnyView, App, Axis, Context, Corner, Entity, EntityId, EventEmitter,
+    FocusHandle, Focusable, IntoElement, KeyContext, MouseButton, MouseDownEvent, MouseUpEvent,
+    ParentElement, Render, SharedString, StyleRefinement, Styled, Subscription, WeakEntity, Window,
+    deferred, div, px,
 };
 use settings::SettingsStore;
 use std::sync::Arc;
@@ -59,6 +59,19 @@ pub trait Panel: Focusable + EventEmitter<PanelEvent> + Render + Sized {
     fn enabled(&self, _cx: &App) -> bool {
         true
     }
+    fn render_center_element(
+        &mut self,
+        _window: &mut Window,
+        _cx: &mut Context<Self>,
+    ) -> Option<AnyElement> {
+        None
+    }
+    fn has_center_element(&self, _window: &Window, _cx: &App) -> bool {
+        false
+    }
+    fn has_main_element(&self, _window: &Window, _cx: &App) -> bool {
+        true
+    }
 }
 
 pub trait PanelHandle: Send + Sync {
@@ -83,6 +96,9 @@ pub trait PanelHandle: Send + Sync {
     fn to_any(&self) -> AnyView;
     fn activation_priority(&self, cx: &App) -> u32;
     fn enabled(&self, cx: &App) -> bool;
+    fn center_element(&self, window: &Window, cx: &mut App) -> Option<AnyView>;
+    fn has_center_element(&self, window: &Window, cx: &App) -> bool;
+    fn has_main_element(&self, window: &Window, cx: &App) -> bool;
     fn move_to_next_position(&self, window: &mut Window, cx: &mut App) {
         let current_position = self.position(window, cx);
         let next_position = [
@@ -187,6 +203,32 @@ where
     fn enabled(&self, cx: &App) -> bool {
         self.read(cx).enabled(cx)
     }
+
+    fn center_element(&self, window: &Window, cx: &mut App) -> Option<AnyView> {
+        if !self.read(cx).has_center_element(window, cx) {
+            return None;
+        }
+        Some(cx.new(|_| PanelCenterView(self.clone())).into())
+    }
+
+    fn has_center_element(&self, window: &Window, cx: &App) -> bool {
+        self.read(cx).has_center_element(window, cx)
+    }
+
+    fn has_main_element(&self, window: &Window, cx: &App) -> bool {
+        self.read(cx).has_main_element(window, cx)
+    }
+}
+
+struct PanelCenterView<T: Panel>(Entity<T>);
+
+impl<T: Panel> Render for PanelCenterView<T> {
+    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+        self.0.update(cx, |this, cx| {
+            this.render_center_element(window, cx)
+                .unwrap_or_else(|| gpui::Empty.into_any_element())
+        })
+    }
 }
 
 impl From<&dyn PanelHandle> for AnyView {
@@ -690,6 +732,11 @@ impl Dock {
         Some(&entry.panel)
     }
 
+    pub fn visible_panel_center_element(&self, window: &Window, cx: &mut App) -> Option<AnyView> {
+        let entry = self.visible_entry()?;
+        entry.panel.center_element(window, cx)
+    }
+
     pub fn active_panel(&self) -> Option<&Arc<dyn PanelHandle>> {
         let panel_entry = self.active_panel_entry()?;
         Some(&panel_entry.panel)
@@ -783,7 +830,10 @@ impl Dock {
 impl Render for Dock {
     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() {
+        if let Some(entry) = self
+            .visible_entry()
+            .filter(|entry| entry.panel.has_main_element(window, cx))
+        {
             let size = entry.panel.size(window, cx);
 
             let position = self.position;

crates/workspace/src/pane_group.rs 🔗

@@ -1,7 +1,7 @@
+pub use crate::pane_group::element::{PaneAxisElement, pane_axis};
 use crate::{
     AnyActiveCall, AppState, CollaboratorId, FollowerState, Pane, ParticipantLocation, Workspace,
     WorkspaceSettings,
-    pane_group::element::pane_axis,
     workspace_settings::{PaneSplitDirectionHorizontal, PaneSplitDirectionVertical},
 };
 use anyhow::Result;
@@ -1035,7 +1035,7 @@ impl SplitDirection {
     }
 }
 
-mod element {
+pub mod element {
     use std::mem;
     use std::{cell::RefCell, iter, rc::Rc, sync::Arc};
 
@@ -1059,7 +1059,7 @@ mod element {
 
     const DIVIDER_SIZE: f32 = 1.0;
 
-    pub(super) fn pane_axis(
+    pub fn pane_axis(
         axis: Axis,
         basis: usize,
         flexes: Arc<Mutex<Vec<f32>>>,

crates/workspace/src/workspace.rs 🔗

@@ -51,12 +51,12 @@ use futures::{
     future::{Shared, try_join_all},
 };
 use gpui::{
-    Action, AnyEntity, AnyView, AnyWeakView, App, AsyncApp, AsyncWindowContext, Bounds, Context,
-    CursorStyle, Decorations, DragMoveEvent, Entity, EntityId, EventEmitter, FocusHandle,
-    Focusable, Global, HitboxBehavior, Hsla, KeyContext, Keystroke, ManagedView, MouseButton,
-    PathPromptOptions, Point, PromptLevel, Render, ResizeEdge, Size, Stateful, Subscription,
-    SystemWindowTabController, Task, Tiling, WeakEntity, WindowBounds, WindowHandle, WindowId,
-    WindowOptions, actions, canvas, point, relative, size, transparent_black,
+    Action, AnyElement, AnyEntity, AnyView, AnyWeakView, App, AsyncApp, AsyncWindowContext, Axis,
+    Bounds, Context, CursorStyle, Decorations, DragMoveEvent, Entity, EntityId, EventEmitter,
+    FocusHandle, Focusable, Global, HitboxBehavior, Hsla, KeyContext, Keystroke, ManagedView,
+    MouseButton, PathPromptOptions, Point, PromptLevel, Render, ResizeEdge, Size, Stateful,
+    Subscription, SystemWindowTabController, Task, Tiling, WeakEntity, WindowBounds, WindowHandle,
+    WindowId, WindowOptions, actions, canvas, point, relative, size, transparent_black,
 };
 pub use history_manager::*;
 pub use item::{
@@ -74,8 +74,9 @@ use notifications::{
 pub use pane::*;
 pub use pane_group::{
     ActivePaneDecorator, HANDLE_HITBOX_SIZE, Member, PaneAxis, PaneGroup, PaneRenderContext,
-    SplitDirection,
+    SplitDirection, pane_axis,
 };
+use parking_lot::Mutex;
 use persistence::{DB, SerializedWindowBounds, model::SerializedWorkspace};
 pub use persistence::{
     DB as WORKSPACE_DB, WorkspaceDb, delete_unloaded_items,
@@ -1336,6 +1337,8 @@ pub struct Workspace {
     last_open_dock_positions: Vec<DockPosition>,
     removing: bool,
     _panels_task: Option<Task<Result<()>>>,
+    center_element_flexes: Arc<Mutex<Vec<f32>>>,
+    center_element_bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
 }
 
 impl EventEmitter<Event> for Workspace {}
@@ -1741,6 +1744,8 @@ impl Workspace {
             scheduled_tasks: Vec::new(),
             last_open_dock_positions: Vec::new(),
             removing: false,
+            center_element_flexes: Arc::new(Mutex::new(Vec::new())),
+            center_element_bounding_boxes: Arc::new(Mutex::new(Vec::new())),
         }
     }
 
@@ -6996,6 +7001,113 @@ impl Workspace {
         )
     }
 
+    fn render_center_with_panel_elements(
+        &mut self,
+        paddings: (Option<Div>, Option<Div>),
+        window: &mut Window,
+        cx: &mut App,
+    ) -> AnyElement {
+        let left_visible_panel = if self.zoomed_position != Some(DockPosition::Left) {
+            self.left_dock.read(cx).visible_panel().cloned()
+        } else {
+            None
+        };
+        let left_center_element =
+            left_visible_panel.and_then(|panel| panel.center_element(window, cx));
+
+        let right_visible_panel = if self.zoomed_position != Some(DockPosition::Right) {
+            self.right_dock.read(cx).visible_panel().cloned()
+        } else {
+            None
+        };
+        let right_center_element =
+            right_visible_panel.and_then(|panel| panel.center_element(window, cx));
+
+        let center_pane_group = h_flex()
+            .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();
+
+        if left_center_element.is_none() && right_center_element.is_none() {
+            return h_flex()
+                .flex_1()
+                .child(center_pane_group)
+                .into_any_element();
+        }
+
+        let member_count =
+            1 + left_center_element.is_some() as usize + right_center_element.is_some() as usize;
+
+        {
+            let mut flexes = self.center_element_flexes.lock();
+            if flexes.len() != member_count {
+                *flexes = vec![1.; member_count];
+            }
+        }
+        {
+            let mut bounding_boxes = self.center_element_bounding_boxes.lock();
+            if bounding_boxes.len() != member_count {
+                *bounding_boxes = vec![None; member_count];
+            }
+        }
+
+        let axis_element = pane_axis(
+            Axis::Horizontal,
+            0,
+            self.center_element_flexes.clone(),
+            self.center_element_bounding_boxes.clone(),
+            self.weak_self.clone(),
+        );
+
+        let axis_element = if let Some(left_element) = left_center_element {
+            axis_element.child(
+                div()
+                    .flex_1()
+                    .size_full()
+                    .overflow_hidden()
+                    .child(left_element),
+            )
+        } else {
+            axis_element
+        };
+
+        let axis_element = axis_element.child(center_pane_group);
+
+        let axis_element = if let Some(right_element) = right_center_element {
+            axis_element.child(
+                div()
+                    .flex_1()
+                    .size_full()
+                    .overflow_hidden()
+                    .child(right_element),
+            )
+        } else {
+            axis_element
+        };
+
+        div()
+            .flex()
+            .flex_row()
+            .flex_1()
+            .size_full()
+            .child(axis_element)
+            .into_any_element()
+    }
+
     pub fn for_window(window: &Window, cx: &App) -> Option<Entity<Workspace>> {
         window
             .root::<MultiWorkspace>()
@@ -7584,6 +7696,8 @@ impl Render for Workspace {
             .collect::<Vec<_>>();
         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));
+
         div()
             .relative()
             .size_full()
@@ -7591,402 +7705,306 @@ impl Render for Workspace {
             .flex_col()
             .font(ui_font)
             .gap_0()
-                .justify_start()
-                .items_start()
-                .text_color(colors.text)
-                .overflow_hidden()
-                .children(self.titlebar_item.clone())
-                .on_modifiers_changed(move |_, _, cx| {
-                    for &id in &notification_entities {
-                        cx.notify(id);
-                    }
-                })
-                .child(
-                    div()
-                        .size_full()
-                        .relative()
-                        .flex_1()
-                        .flex()
-                        .flex_col()
-                        .child(
-                            div()
-                                .id("workspace")
-                                .bg(colors.background)
-                                .relative()
-                                .flex_1()
-                                .w_full()
-                                .flex()
-                                .flex_col()
-                                .overflow_hidden()
-                                .border_t_1()
-                                .border_b_1()
-                                .border_color(colors.border)
-                                .child({
-                                    let this = cx.entity();
-                                    canvas(
-                                        move |bounds, window, cx| {
-                                            this.update(cx, |this, cx| {
-                                                let bounds_changed = this.bounds != bounds;
-                                                this.bounds = bounds;
-
-                                                if bounds_changed {
-                                                    this.left_dock.update(cx, |dock, cx| {
-                                                        dock.clamp_panel_size(
-                                                            bounds.size.width,
-                                                            window,
-                                                            cx,
-                                                        )
-                                                    });
-
-                                                    this.right_dock.update(cx, |dock, cx| {
-                                                        dock.clamp_panel_size(
-                                                            bounds.size.width,
-                                                            window,
-                                                            cx,
-                                                        )
-                                                    });
-
-                                                    this.bottom_dock.update(cx, |dock, cx| {
-                                                        dock.clamp_panel_size(
-                                                            bounds.size.height,
-                                                            window,
-                                                            cx,
-                                                        )
-                                                    });
-                                                }
-                                            })
-                                        },
-                                        |_, _, _, _| {},
-                                    )
-                                    .absolute()
-                                    .size_full()
-                                })
-                                .when(self.zoomed.is_none(), |this| {
-                                    this.on_drag_move(cx.listener(
-                                        move |workspace,
-                                              e: &DragMoveEvent<DraggedDock>,
-                                              window,
-                                              cx| {
-                                            if workspace.previous_dock_drag_coordinates
-                                                != Some(e.event.position)
-                                            {
-                                                workspace.previous_dock_drag_coordinates =
-                                                    Some(e.event.position);
-
-                                                match e.drag(cx).0 {
-                                                    DockPosition::Left => {
-                                                        workspace.resize_left_dock(
-                                                            e.event.position.x
-                                                                - workspace.bounds.left(),
-                                                            window,
-                                                            cx,
-                                                        );
-                                                    }
-                                                    DockPosition::Right => {
-                                                        workspace.resize_right_dock(
-                                                            workspace.bounds.right()
-                                                                - e.event.position.x,
-                                                            window,
-                                                            cx,
-                                                        );
-                                                    }
-                                                    DockPosition::Bottom => {
-                                                        workspace.resize_bottom_dock(
-                                                            workspace.bounds.bottom()
-                                                                - e.event.position.y,
-                                                            window,
-                                                            cx,
-                                                        );
-                                                    }
-                                                };
-                                                workspace.serialize_workspace(window, cx);
-                                            }
-                                        },
-                                    ))
-
-                                })
-                                .child({
-                                    match bottom_dock_layout {
-                                        BottomDockLayout::Full => div()
-                                            .flex()
-                                            .flex_col()
-                                            .h_full()
-                                            .child(
-                                                div()
-                                                    .flex()
-                                                    .flex_row()
-                                                    .flex_1()
-                                                    .overflow_hidden()
-                                                    .children(self.render_dock(
-                                                        DockPosition::Left,
-                                                        &self.left_dock,
+            .justify_start()
+            .items_start()
+            .text_color(colors.text)
+            .overflow_hidden()
+            .children(self.titlebar_item.clone())
+            .on_modifiers_changed(move |_, _, cx| {
+                for &id in &notification_entities {
+                    cx.notify(id);
+                }
+            })
+            .child(
+                div()
+                    .size_full()
+                    .relative()
+                    .flex_1()
+                    .flex()
+                    .flex_col()
+                    .child(
+                        div()
+                            .id("workspace")
+                            .bg(colors.background)
+                            .relative()
+                            .flex_1()
+                            .w_full()
+                            .flex()
+                            .flex_col()
+                            .overflow_hidden()
+                            .border_t_1()
+                            .border_b_1()
+                            .border_color(colors.border)
+                            .child({
+                                let this = cx.entity();
+                                canvas(
+                                    move |bounds, window, cx| {
+                                        this.update(cx, |this, cx| {
+                                            let bounds_changed = this.bounds != bounds;
+                                            this.bounds = bounds;
+
+                                            if bounds_changed {
+                                                this.left_dock.update(cx, |dock, cx| {
+                                                    dock.clamp_panel_size(
+                                                        bounds.size.width,
                                                         window,
                                                         cx,
-                                                    ))
-
-                                                    .child(
-                                                        div()
-                                                            .flex()
-                                                            .flex_col()
-                                                            .flex_1()
-                                                            .overflow_hidden()
-                                                            .child(
-                                                                h_flex()
-                                                                    .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(),
-                                                                            )
-                                                                        },
-                                                                    ),
-                                                            ),
                                                     )
+                                                });
 
-                                                    .children(self.render_dock(
-                                                        DockPosition::Right,
-                                                        &self.right_dock,
+                                                this.right_dock.update(cx, |dock, cx| {
+                                                    dock.clamp_panel_size(
+                                                        bounds.size.width,
                                                         window,
                                                         cx,
-                                                    )),
-                                            )
-                                            .child(div().w_full().children(self.render_dock(
-                                                DockPosition::Bottom,
-                                                &self.bottom_dock,
-                                                window,
-                                                cx
-                                            ))),
-
-                                        BottomDockLayout::LeftAligned => div()
-                                            .flex()
-                                            .flex_row()
-                                            .h_full()
-                                            .child(
-                                                div()
-                                                    .flex()
-                                                    .flex_col()
-                                                    .flex_1()
-                                                    .h_full()
-                                                    .child(
-                                                        div()
-                                                            .flex()
-                                                            .flex_row()
-                                                            .flex_1()
-                                                            .children(self.render_dock(DockPosition::Left, &self.left_dock, window, cx))
-
-                                                            .child(
-                                                                div()
-                                                                    .flex()
-                                                                    .flex_col()
-                                                                    .flex_1()
-                                                                    .overflow_hidden()
-                                                                    .child(
-                                                                        h_flex()
-                                                                            .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())),
-                                                                    )
-                                                            )
-
-                                                    )
-                                                    .child(
-                                                        div()
-                                                            .w_full()
-                                                            .children(self.render_dock(DockPosition::Bottom, &self.bottom_dock, window, cx))
-                                                    ),
-                                            )
-                                            .children(self.render_dock(
-                                                DockPosition::Right,
-                                                &self.right_dock,
-                                                window,
-                                                cx,
-                                            )),
-
-                                        BottomDockLayout::RightAligned => div()
-                                            .flex()
-                                            .flex_row()
-                                            .h_full()
-                                            .children(self.render_dock(
-                                                DockPosition::Left,
-                                                &self.left_dock,
-                                                window,
-                                                cx,
-                                            ))
-
-                                            .child(
-                                                div()
-                                                    .flex()
-                                                    .flex_col()
-                                                    .flex_1()
-                                                    .h_full()
-                                                    .child(
-                                                        div()
-                                                            .flex()
-                                                            .flex_row()
-                                                            .flex_1()
-                                                            .child(
-                                                                div()
-                                                                    .flex()
-                                                                    .flex_col()
-                                                                    .flex_1()
-                                                                    .overflow_hidden()
-                                                                    .child(
-                                                                        h_flex()
-                                                                            .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())),
-                                                                    )
-                                                            )
-
-                                                            .children(self.render_dock(DockPosition::Right, &self.right_dock, window, cx))
                                                     )
-                                                    .child(
-                                                        div()
-                                                            .w_full()
-                                                            .children(self.render_dock(DockPosition::Bottom, &self.bottom_dock, window, cx))
-                                                    ),
-                                            ),
-
-                                        BottomDockLayout::Contained => div()
-                                            .flex()
-                                            .flex_row()
-                                            .h_full()
-                                            .children(self.render_dock(
-                                                DockPosition::Left,
-                                                &self.left_dock,
-                                                window,
-                                                cx,
-                                            ))
-
-                                            .child(
-                                                div()
-                                                    .flex()
-                                                    .flex_col()
-                                                    .flex_1()
-                                                    .overflow_hidden()
-                                                    .child(
-                                                        h_flex()
-                                                            .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())
-                                                            }),
+                                                });
+
+                                                this.bottom_dock.update(cx, |dock, cx| {
+                                                    dock.clamp_panel_size(
+                                                        bounds.size.height,
+                                                        window,
+                                                        cx,
                                                     )
-                                                    .children(self.render_dock(
-                                                        DockPosition::Bottom,
-                                                        &self.bottom_dock,
+                                                });
+                                            }
+                                        })
+                                    },
+                                    |_, _, _, _| {},
+                                )
+                                .absolute()
+                                .size_full()
+                            })
+                            .when(self.zoomed.is_none(), |this| {
+                                this.on_drag_move(cx.listener(
+                                    move |workspace, e: &DragMoveEvent<DraggedDock>, window, cx| {
+                                        if workspace.previous_dock_drag_coordinates
+                                            != Some(e.event.position)
+                                        {
+                                            workspace.previous_dock_drag_coordinates =
+                                                Some(e.event.position);
+
+                                            match e.drag(cx).0 {
+                                                DockPosition::Left => {
+                                                    workspace.resize_left_dock(
+                                                        e.event.position.x
+                                                            - workspace.bounds.left(),
                                                         window,
                                                         cx,
-                                                    )),
-                                            )
+                                                    );
+                                                }
+                                                DockPosition::Right => {
+                                                    workspace.resize_right_dock(
+                                                        workspace.bounds.right()
+                                                            - e.event.position.x,
+                                                        window,
+                                                        cx,
+                                                    );
+                                                }
+                                                DockPosition::Bottom => {
+                                                    workspace.resize_bottom_dock(
+                                                        workspace.bounds.bottom()
+                                                            - e.event.position.y,
+                                                        window,
+                                                        cx,
+                                                    );
+                                                }
+                                            };
+                                            workspace.serialize_workspace(window, cx);
+                                        }
+                                    },
+                                ))
+                            })
+                            .child({
+                                match bottom_dock_layout {
+                                    BottomDockLayout::Full => div()
+                                        .flex()
+                                        .flex_col()
+                                        .h_full()
+                                        .child(
+                                            div()
+                                                .flex()
+                                                .flex_row()
+                                                .flex_1()
+                                                .overflow_hidden()
+                                                .children(self.render_dock(
+                                                    DockPosition::Left,
+                                                    &self.left_dock,
+                                                    window,
+                                                    cx,
+                                                ))
+                                                .child(
+                                                    div()
+                                                        .flex()
+                                                        .flex_col()
+                                                        .flex_1()
+                                                        .overflow_hidden()
+                                                        .children(center_area.take()),
+                                                )
+                                                .children(self.render_dock(
+                                                    DockPosition::Right,
+                                                    &self.right_dock,
+                                                    window,
+                                                    cx,
+                                                )),
+                                        )
+                                        .child(div().w_full().children(self.render_dock(
+                                            DockPosition::Bottom,
+                                            &self.bottom_dock,
+                                            window,
+                                            cx,
+                                        ))),
+
+                                    BottomDockLayout::LeftAligned => div()
+                                        .flex()
+                                        .flex_row()
+                                        .h_full()
+                                        .child(
+                                            div()
+                                                .flex()
+                                                .flex_col()
+                                                .flex_1()
+                                                .h_full()
+                                                .child(
+                                                    div()
+                                                        .flex()
+                                                        .flex_row()
+                                                        .flex_1()
+                                                        .children(self.render_dock(
+                                                            DockPosition::Left,
+                                                            &self.left_dock,
+                                                            window,
+                                                            cx,
+                                                        ))
+                                                        .child(
+                                                            div()
+                                                                .flex()
+                                                                .flex_col()
+                                                                .flex_1()
+                                                                .overflow_hidden()
+                                                                .children(center_area.take()),
+                                                        ),
+                                                )
+                                                .child(div().w_full().children(self.render_dock(
+                                                    DockPosition::Bottom,
+                                                    &self.bottom_dock,
+                                                    window,
+                                                    cx,
+                                                ))),
+                                        )
+                                        .children(self.render_dock(
+                                            DockPosition::Right,
+                                            &self.right_dock,
+                                            window,
+                                            cx,
+                                        )),
+
+                                    BottomDockLayout::RightAligned => div()
+                                        .flex()
+                                        .flex_row()
+                                        .h_full()
+                                        .children(self.render_dock(
+                                            DockPosition::Left,
+                                            &self.left_dock,
+                                            window,
+                                            cx,
+                                        ))
+                                        .child(
+                                            div()
+                                                .flex()
+                                                .flex_col()
+                                                .flex_1()
+                                                .h_full()
+                                                .child(
+                                                    div()
+                                                        .flex()
+                                                        .flex_row()
+                                                        .flex_1()
+                                                        .child(
+                                                            div()
+                                                                .flex()
+                                                                .flex_col()
+                                                                .flex_1()
+                                                                .overflow_hidden()
+                                                                .children(center_area.take()),
+                                                        )
+                                                        .children(self.render_dock(
+                                                            DockPosition::Right,
+                                                            &self.right_dock,
+                                                            window,
+                                                            cx,
+                                                        )),
+                                                )
+                                                .child(div().w_full().children(self.render_dock(
+                                                    DockPosition::Bottom,
+                                                    &self.bottom_dock,
+                                                    window,
+                                                    cx,
+                                                ))),
+                                        ),
+
+                                    BottomDockLayout::Contained => div()
+                                        .flex()
+                                        .flex_row()
+                                        .h_full()
+                                        .children(self.render_dock(
+                                            DockPosition::Left,
+                                            &self.left_dock,
+                                            window,
+                                            cx,
+                                        ))
+                                        .child(
+                                            div()
+                                                .flex()
+                                                .flex_col()
+                                                .flex_1()
+                                                .overflow_hidden()
+                                                .children(center_area.take())
+                                                .children(self.render_dock(
+                                                    DockPosition::Bottom,
+                                                    &self.bottom_dock,
+                                                    window,
+                                                    cx,
+                                                )),
+                                        )
+                                        .children(self.render_dock(
+                                            DockPosition::Right,
+                                            &self.right_dock,
+                                            window,
+                                            cx,
+                                        )),
+                                }
+                            })
+                            .children(self.zoomed.as_ref().and_then(|view| {
+                                let zoomed_view = view.upgrade()?;
+                                let div = div()
+                                    .occlude()
+                                    .absolute()
+                                    .overflow_hidden()
+                                    .border_color(colors.border)
+                                    .bg(colors.background)
+                                    .child(zoomed_view)
+                                    .inset_0()
+                                    .shadow_lg();
+
+                                if !WorkspaceSettings::get_global(cx).zoomed_padding {
+                                    return Some(div);
+                                }
 
-                                            .children(self.render_dock(
-                                                DockPosition::Right,
-                                                &self.right_dock,
-                                                window,
-                                                cx,
-                                            )),
-                                    }
+                                Some(match self.zoomed_position {
+                                    Some(DockPosition::Left) => div.right_2().border_r_1(),
+                                    Some(DockPosition::Right) => div.left_2().border_l_1(),
+                                    Some(DockPosition::Bottom) => div.top_2().border_t_1(),
+                                    None => div.top_2().bottom_2().left_2().right_2().border_1(),
                                 })
-                                .children(self.zoomed.as_ref().and_then(|view| {
-                                    let zoomed_view = view.upgrade()?;
-                                    let div = div()
-                                        .occlude()
-                                        .absolute()
-                                        .overflow_hidden()
-                                        .border_color(colors.border)
-                                        .bg(colors.background)
-                                        .child(zoomed_view)
-                                        .inset_0()
-                                        .shadow_lg();
-
-                                    if !WorkspaceSettings::get_global(cx).zoomed_padding {
-                                       return Some(div);
-                                    }
-
-                                    Some(match self.zoomed_position {
-                                        Some(DockPosition::Left) => div.right_2().border_r_1(),
-                                        Some(DockPosition::Right) => div.left_2().border_l_1(),
-                                        Some(DockPosition::Bottom) => div.top_2().border_t_1(),
-                                        None => {
-                                            div.top_2().bottom_2().left_2().right_2().border_1()
-                                        }
-                                    })
-                                }))
-                                .children(self.render_notifications(window, cx)),
-                        )
-                        .when(self.status_bar_visible(cx), |parent| {
-                            parent.child(self.status_bar.clone())
-                        })
-                        .child(self.toast_layer.clone()),
-                )
+                            }))
+                            .children(self.render_notifications(window, cx)),
+                    )
+                    .when(self.status_bar_visible(cx), |parent| {
+                        parent.child(self.status_bar.clone())
+                    })
+                    .child(self.toast_layer.clone()),
+            )
     }
 }