sidebar: Move toggle to the status bar instead (#51916)

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Release Notes:

- N/A
This commit is contained in:
Danilo Leal authored and GitHub committed 2026-03-19 07:41:10 -03:00
1 parent a30e4d5228
commit b9beb94d36
10 files changed
+113 -111

No files matched your search

Generated
-1
View File
@@ -17772,7 +17772,6 @@ dependencies = [
"client",
"cloud_api_types",
"db",
"feature_flags",
"git_ui",
"gpui",
"icons",
@@ -32,7 +32,6 @@ pub struct PlatformTitleBar {
should_move: bool,
system_window_tabs: Entity<SystemWindowTabs>,
workspace_sidebar_open: bool,
sidebar_has_notifications: bool,
}
impl PlatformTitleBar {
@@ -47,7 +46,6 @@ impl PlatformTitleBar {
should_move: false,
system_window_tabs,
workspace_sidebar_open: false,
sidebar_has_notifications: false,
}
}
@@ -83,19 +81,6 @@ impl PlatformTitleBar {
cx.notify();
}
pub fn sidebar_has_notifications(&self) -> bool {
self.sidebar_has_notifications
}
pub fn set_sidebar_has_notifications(
&mut self,
has_notifications: bool,
cx: &mut Context<Self>,
) {
self.sidebar_has_notifications = has_notifications;
cx.notify();
}
pub fn is_multi_workspace_enabled(cx: &App) -> bool {
cx.has_flag::<AgentV2FeatureFlag>() && !DisableAiSettings::get_global(cx).disable_ai
}
+20 -19
View File
@@ -2551,23 +2551,19 @@ impl Sidebar {
this.pl(px(ui::utils::TRAFFIC_LIGHT_PADDING))
})
.pr_1p5()
.gap_1()
.border_b_1()
.border_color(cx.theme().colors().border)
.justify_between()
.child(self.render_sidebar_toggle_button(cx))
.justify_end()
.child(
h_flex()
.gap_0p5()
.child(
IconButton::new("archive", IconName::Archive)
.icon_size(IconSize::Small)
.tooltip(Tooltip::text("View Archived Threads"))
.on_click(cx.listener(|this, _, window, cx| {
this.show_archive(window, cx);
})),
)
.child(self.render_recent_projects_button(cx)),
),
IconButton::new("archive", IconName::Archive)
.icon_size(IconSize::Small)
.tooltip(Tooltip::text("View Archived Threads"))
.on_click(cx.listener(|this, _, window, cx| {
this.show_archive(window, cx);
})),
)
.child(self.render_recent_projects_button(cx)),
)
.when(!empty_state, |this| {
this.child(
@@ -2612,9 +2608,7 @@ impl Sidebar {
}
fn render_sidebar_toggle_button(&self, _cx: &mut Context<Self>) -> impl IntoElement {
let icon = IconName::ThreadsSidebarLeftOpen;
IconButton::new("sidebar-close-toggle", icon)
IconButton::new("sidebar-close-toggle", IconName::ThreadsSidebarLeftOpen)
.icon_size(IconSize::Small)
.tooltip(Tooltip::element(move |_window, cx| {
v_flex()
@@ -2816,6 +2810,13 @@ impl Render for Sidebar {
}),
SidebarView::Archive(archive_view) => this.child(archive_view.clone()),
})
.child(
h_flex()
.p_1()
.border_t_1()
.border_color(cx.theme().colors().border_variant)
.child(self.render_sidebar_toggle_button(cx)),
)
}
}
@@ -2874,8 +2875,8 @@ mod tests {
let multi_workspace = multi_workspace.clone();
let sidebar =
cx.update(|window, cx| cx.new(|cx| Sidebar::new(multi_workspace.clone(), window, cx)));
multi_workspace.update(cx, |mw, _cx| {
mw.register_sidebar(sidebar.clone());
multi_workspace.update(cx, |mw, cx| {
mw.register_sidebar(sidebar.clone(), cx);
});
cx.run_until_parked();
sidebar
-1
View File
@@ -38,7 +38,6 @@ chrono.workspace = true
client.workspace = true
cloud_api_types.workspace = true
db.workspace = true
feature_flags.workspace = true
git_ui.workspace = true
gpui = { workspace = true, features = ["screen-capture"] }
icons.workspace = true
+5 -57
View File
@@ -25,16 +25,14 @@ use auto_update::AutoUpdateStatus;
use call::ActiveCall;
use client::{Client, UserStore, zed_urls};
use cloud_api_types::Plan;
use feature_flags::{AgentV2FeatureFlag, FeatureFlagAppExt};
use gpui::{
Action, AnyElement, App, Context, Corner, Element, Empty, Entity, Focusable,
InteractiveElement, IntoElement, MouseButton, ParentElement, Render,
StatefulInteractiveElement, Styled, Subscription, WeakEntity, Window, actions, div,
};
use onboarding_banner::OnboardingBanner;
use project::{
DisableAiSettings, Project, git_store::GitStoreEvent, trusted_worktrees::TrustedWorktrees,
};
use project::{Project, git_store::GitStoreEvent, trusted_worktrees::TrustedWorktrees};
use remote::RemoteConnectionOptions;
use settings::Settings;
use settings::WorktreeId;
@@ -43,14 +41,13 @@ use std::sync::Arc;
use theme::ActiveTheme;
use title_bar_settings::TitleBarSettings;
use ui::{
Avatar, ButtonLike, ContextMenu, Divider, IconWithIndicator, Indicator, PopoverMenu,
PopoverMenuHandle, TintColor, Tooltip, prelude::*, utils::platform_title_bar_height,
Avatar, ButtonLike, ContextMenu, IconWithIndicator, Indicator, PopoverMenu, PopoverMenuHandle,
TintColor, Tooltip, prelude::*, utils::platform_title_bar_height,
};
use update_version::UpdateVersion;
use util::ResultExt;
use workspace::{
MultiWorkspace, ToggleWorkspaceSidebar, ToggleWorktreeSecurity, Workspace, WorkspaceId,
notifications::NotifyResultExt,
MultiWorkspace, ToggleWorktreeSecurity, Workspace, WorkspaceId, notifications::NotifyResultExt,
};
use zed_actions::OpenRemote;
@@ -198,7 +195,6 @@ impl Render for TitleBar {
let mut render_project_items = title_bar_settings.show_branch_name
|| title_bar_settings.show_project_items;
title_bar
.children(self.render_workspace_sidebar_toggle(window, cx))
.when_some(
self.application_menu.clone().filter(|_| !show_menus),
|title_bar, menu| {
@@ -402,19 +398,15 @@ impl TitleBar {
};
let is_open = multi_workspace.read(cx).sidebar_open();
let has_notifications = multi_workspace.read(cx).sidebar_has_notifications(cx);
platform_titlebar.update(cx, |titlebar, cx| {
titlebar.set_workspace_sidebar_open(is_open, cx);
titlebar.set_sidebar_has_notifications(has_notifications, cx);
});
let platform_titlebar = platform_titlebar.clone();
let subscription = cx.observe(&multi_workspace, move |mw, cx| {
let is_open = mw.read(cx).sidebar_open();
let has_notifications = mw.read(cx).sidebar_has_notifications(cx);
platform_titlebar.update(cx, |titlebar, cx| {
titlebar.set_workspace_sidebar_open(is_open, cx);
titlebar.set_sidebar_has_notifications(has_notifications, cx);
});
});
@@ -723,50 +715,6 @@ impl TitleBar {
)
}
fn render_workspace_sidebar_toggle(
&self,
_window: &mut Window,
cx: &mut Context<Self>,
) -> Option<AnyElement> {
if !cx.has_flag::<AgentV2FeatureFlag>() || DisableAiSettings::get_global(cx).disable_ai {
return None;
}
let is_sidebar_open = self.platform_titlebar.read(cx).is_workspace_sidebar_open();
if is_sidebar_open {
return None;
}
let has_notifications = self.platform_titlebar.read(cx).sidebar_has_notifications();
Some(
h_flex()
.h_full()
.gap_0p5()
.child(
IconButton::new(
"toggle-workspace-sidebar",
IconName::ThreadsSidebarLeftClosed,
)
.icon_size(IconSize::Small)
.when(has_notifications, |button| {
button
.indicator(Indicator::dot().color(Color::Accent))
.indicator_border_color(Some(cx.theme().colors().title_bar_background))
})
.tooltip(move |_, cx| {
Tooltip::for_action("Open Threads Sidebar", &ToggleWorkspaceSidebar, cx)
})
.on_click(|_, window, cx| {
window.dispatch_action(ToggleWorkspaceSidebar.boxed_clone(), cx);
}),
)
.child(Divider::vertical().color(ui::DividerColor::Border))
.into_any_element(),
)
}
fn render_project_name(
&self,
name: Option<SharedString>,
+26 -4
View File
@@ -169,7 +169,23 @@ impl MultiWorkspace {
}
}
pub fn register_sidebar<T: Sidebar>(&mut self, sidebar: Entity<T>) {
pub fn register_sidebar<T: Sidebar>(&mut self, sidebar: Entity<T>, cx: &mut Context<Self>) {
self._subscriptions
.push(cx.observe(&sidebar, |this, _, cx| {
let has_notifications = this.sidebar_has_notifications(cx);
let is_open = this.sidebar_open;
let show_toggle = this.multi_workspace_enabled(cx);
for workspace in &this.workspaces {
workspace.update(cx, |workspace, cx| {
workspace.set_workspace_sidebar_open(
is_open,
has_notifications,
show_toggle,
cx,
);
});
}
}));
self.sidebar = Some(Box::new(sidebar));
}
@@ -256,9 +272,11 @@ impl MultiWorkspace {
pub fn open_sidebar(&mut self, cx: &mut Context<Self>) {
self.sidebar_open = true;
let sidebar_focus_handle = self.sidebar.as_ref().map(|s| s.focus_handle(cx));
let has_notifications = self.sidebar_has_notifications(cx);
let show_toggle = self.multi_workspace_enabled(cx);
for workspace in &self.workspaces {
workspace.update(cx, |workspace, cx| {
workspace.set_workspace_sidebar_open(true, cx);
workspace.set_workspace_sidebar_open(true, has_notifications, show_toggle, cx);
workspace.set_sidebar_focus_handle(sidebar_focus_handle.clone());
});
}
@@ -268,9 +286,11 @@ impl MultiWorkspace {
fn close_sidebar(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.sidebar_open = false;
let has_notifications = self.sidebar_has_notifications(cx);
let show_toggle = self.multi_workspace_enabled(cx);
for workspace in &self.workspaces {
workspace.update(cx, |workspace, cx| {
workspace.set_workspace_sidebar_open(false, cx);
workspace.set_workspace_sidebar_open(false, has_notifications, show_toggle, cx);
workspace.set_sidebar_focus_handle(None);
});
}
@@ -367,8 +387,10 @@ impl MultiWorkspace {
} else {
if self.sidebar_open {
let sidebar_focus_handle = self.sidebar.as_ref().map(|s| s.focus_handle(cx));
let has_notifications = self.sidebar_has_notifications(cx);
let show_toggle = self.multi_workspace_enabled(cx);
workspace.update(cx, |workspace, cx| {
workspace.set_workspace_sidebar_open(true, cx);
workspace.set_workspace_sidebar_open(true, has_notifications, show_toggle, cx);
workspace.set_sidebar_focus_handle(sidebar_focus_handle);
});
}
+47 -7
View File
@@ -1,11 +1,11 @@
use crate::{ItemHandle, Pane};
use crate::{ItemHandle, Pane, ToggleWorkspaceSidebar};
use gpui::{
AnyView, App, Context, Decorations, Entity, IntoElement, ParentElement, Render, Styled,
Action, AnyView, App, Context, Decorations, Entity, IntoElement, ParentElement, Render, Styled,
Subscription, Window,
};
use std::any::TypeId;
use theme::CLIENT_SIDE_DECORATION_ROUNDING;
use ui::{h_flex, prelude::*};
use ui::{Divider, Indicator, Tooltip, prelude::*};
use util::ResultExt;
pub trait StatusItemView: Render {
@@ -35,6 +35,8 @@ pub struct StatusBar {
active_pane: Entity<Pane>,
_observe_active_pane: Subscription,
workspace_sidebar_open: bool,
sidebar_has_notifications: bool,
show_sidebar_toggle: bool,
}
impl Render for StatusBar {
@@ -43,8 +45,7 @@ impl Render for StatusBar {
.w_full()
.justify_between()
.gap(DynamicSpacing::Base08.rems(cx))
.py(DynamicSpacing::Base04.rems(cx))
.px(DynamicSpacing::Base06.rems(cx))
.p(DynamicSpacing::Base04.rems(cx))
.bg(cx.theme().colors().status_bar_background)
.map(|el| match window.window_decorations() {
Decorations::Server => el,
@@ -61,17 +62,21 @@ impl Render for StatusBar {
.border_b(px(1.0))
.border_color(cx.theme().colors().status_bar_background),
})
.child(self.render_left_tools())
.child(self.render_left_tools(cx))
.child(self.render_right_tools())
}
}
impl StatusBar {
fn render_left_tools(&self) -> impl IntoElement {
fn render_left_tools(&self, cx: &mut Context<Self>) -> impl IntoElement {
h_flex()
.gap_1()
.min_w_0()
.overflow_x_hidden()
.when(
self.show_sidebar_toggle && !self.workspace_sidebar_open,
|this| this.child(self.render_sidebar_toggle(cx)),
)
.children(self.left_items.iter().map(|item| item.to_any()))
}
@@ -82,6 +87,29 @@ impl StatusBar {
.overflow_x_hidden()
.children(self.right_items.iter().rev().map(|item| item.to_any()))
}
fn render_sidebar_toggle(&self, cx: &mut Context<Self>) -> impl IntoElement {
h_flex()
.gap_0p5()
.child(
IconButton::new(
"toggle-workspace-sidebar",
IconName::ThreadsSidebarLeftClosed,
)
.icon_size(IconSize::Small)
.when(self.sidebar_has_notifications, |this| {
this.indicator(Indicator::dot().color(Color::Accent))
.indicator_border_color(Some(cx.theme().colors().status_bar_background))
})
.tooltip(move |_, cx| {
Tooltip::for_action("Open Threads Sidebar", &ToggleWorkspaceSidebar, cx)
})
.on_click(|_, window, cx| {
window.dispatch_action(ToggleWorkspaceSidebar.boxed_clone(), cx);
}),
)
.child(Divider::vertical().color(ui::DividerColor::Border))
}
}
impl StatusBar {
@@ -94,6 +122,8 @@ impl StatusBar {
this.update_active_pane_item(window, cx)
}),
workspace_sidebar_open: false,
sidebar_has_notifications: false,
show_sidebar_toggle: false,
};
this.update_active_pane_item(window, cx);
this
@@ -104,6 +134,16 @@ impl StatusBar {
cx.notify();
}
pub fn set_sidebar_has_notifications(&mut self, has: bool, cx: &mut Context<Self>) {
self.sidebar_has_notifications = has;
cx.notify();
}
pub fn set_show_sidebar_toggle(&mut self, show: bool, cx: &mut Context<Self>) {
self.show_sidebar_toggle = show;
cx.notify();
}
pub fn add_left_item<T>(&mut self, item: Entity<T>, window: &mut Window, cx: &mut Context<Self>)
where
T: 'static + StatusItemView,
+9 -1
View File
@@ -2160,9 +2160,17 @@ impl Workspace {
&self.status_bar
}
pub fn set_workspace_sidebar_open(&self, open: bool, cx: &mut App) {
pub fn set_workspace_sidebar_open(
&self,
open: bool,
has_notifications: bool,
show_toggle: bool,
cx: &mut App,
) {
self.status_bar.update(cx, |status_bar, cx| {
status_bar.set_workspace_sidebar_open(open, cx);
status_bar.set_sidebar_has_notifications(has_notifications, cx);
status_bar.set_show_sidebar_toggle(show_toggle, cx);
});
}
+4 -4
View File
@@ -2659,8 +2659,8 @@ fn run_multi_workspace_sidebar_visual_tests(
.context("Failed to create sidebar")?;
multi_workspace_window
.update(cx, |multi_workspace, _window, _cx| {
multi_workspace.register_sidebar(sidebar.clone());
.update(cx, |multi_workspace, _window, cx| {
multi_workspace.register_sidebar(sidebar.clone(), cx);
})
.context("Failed to register sidebar")?;
@@ -3191,8 +3191,8 @@ edition = "2021"
.context("Failed to create sidebar")?;
workspace_window
.update(cx, |multi_workspace, _window, _cx| {
multi_workspace.register_sidebar(sidebar.clone());
.update(cx, |multi_workspace, _window, cx| {
multi_workspace.register_sidebar(sidebar.clone(), cx);
})
.context("Failed to register sidebar")?;
+2 -2
View File
@@ -397,8 +397,8 @@ pub fn initialize_workspace(
.update(cx, |_, window, cx| {
let sidebar =
cx.new(|cx| Sidebar::new(multi_workspace_handle.clone(), window, cx));
multi_workspace_handle.update(cx, |multi_workspace, _cx| {
multi_workspace.register_sidebar(sidebar);
multi_workspace_handle.update(cx, |multi_workspace, cx| {
multi_workspace.register_sidebar(sidebar, cx);
});
})
.ok();