From b9beb94d36a9b8aabb90e90937caef05e73e1e95 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Thu, 19 Mar 2026 07:41:10 -0300 Subject: [PATCH] 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 --- Cargo.lock | 1 - .../src/platform_title_bar.rs | 15 ----- crates/sidebar/src/sidebar.rs | 39 ++++++------ crates/title_bar/Cargo.toml | 1 - crates/title_bar/src/title_bar.rs | 62 ++----------------- crates/workspace/src/multi_workspace.rs | 30 +++++++-- crates/workspace/src/status_bar.rs | 54 +++++++++++++--- crates/workspace/src/workspace.rs | 10 ++- crates/zed/src/visual_test_runner.rs | 8 +-- crates/zed/src/zed.rs | 4 +- 10 files changed, 113 insertions(+), 111 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fd8c847109e113f0e31fc59b39051d853e099ac6..9c5e544c8acd74403eb7bb92fab40a30f6fec371 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17772,7 +17772,6 @@ dependencies = [ "client", "cloud_api_types", "db", - "feature_flags", "git_ui", "gpui", "icons", diff --git a/crates/platform_title_bar/src/platform_title_bar.rs b/crates/platform_title_bar/src/platform_title_bar.rs index 7053fe89e7fdc6ece9ad50fdd8facaf31dba3086..f315aa411896c5fd80e83da5602000a2b24c2719 100644 --- a/crates/platform_title_bar/src/platform_title_bar.rs +++ b/crates/platform_title_bar/src/platform_title_bar.rs @@ -32,7 +32,6 @@ pub struct PlatformTitleBar { should_move: bool, system_window_tabs: Entity, 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.sidebar_has_notifications = has_notifications; - cx.notify(); - } - pub fn is_multi_workspace_enabled(cx: &App) -> bool { cx.has_flag::() && !DisableAiSettings::get_global(cx).disable_ai } diff --git a/crates/sidebar/src/sidebar.rs b/crates/sidebar/src/sidebar.rs index f90f94ae7b0e5698e203cd58b8597208e8ab689d..c92250f1ea87180c833a864cb071b66889bcea75 100644 --- a/crates/sidebar/src/sidebar.rs +++ b/crates/sidebar/src/sidebar.rs @@ -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) -> 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 diff --git a/crates/title_bar/Cargo.toml b/crates/title_bar/Cargo.toml index cdac434f4e6a247e69e218875f7c17ac39380e14..ef59ada28baa878d2cfc37ba52b4912e261274e8 100644 --- a/crates/title_bar/Cargo.toml +++ b/crates/title_bar/Cargo.toml @@ -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 diff --git a/crates/title_bar/src/title_bar.rs b/crates/title_bar/src/title_bar.rs index ccdf34bee36688db7113a5aea646c30587d6baec..c5232a67189949e703a282f9894c71302c2f223a 100644 --- a/crates/title_bar/src/title_bar.rs +++ b/crates/title_bar/src/title_bar.rs @@ -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, - ) -> Option { - if !cx.has_flag::() || 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, diff --git a/crates/workspace/src/multi_workspace.rs b/crates/workspace/src/multi_workspace.rs index 65ce1f17d76d14e565a4b8761bae14b9ad5f7aa3..a7a7d50dca59727ebe81b7f55c10adc8ef8638f9 100644 --- a/crates/workspace/src/multi_workspace.rs +++ b/crates/workspace/src/multi_workspace.rs @@ -169,7 +169,23 @@ impl MultiWorkspace { } } - pub fn register_sidebar(&mut self, sidebar: Entity) { + pub fn register_sidebar(&mut self, sidebar: Entity, cx: &mut Context) { + 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.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.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); }); } diff --git a/crates/workspace/src/status_bar.rs b/crates/workspace/src/status_bar.rs index cd492f5b92be74ada112ac85dcbacab4f215a874..52c993d90179a4854cb62a9bb92c80901e8fee9c 100644 --- a/crates/workspace/src/status_bar.rs +++ b/crates/workspace/src/status_bar.rs @@ -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, _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) -> 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) -> 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.sidebar_has_notifications = has; + cx.notify(); + } + + pub fn set_show_sidebar_toggle(&mut self, show: bool, cx: &mut Context) { + self.show_sidebar_toggle = show; + cx.notify(); + } + pub fn add_left_item(&mut self, item: Entity, window: &mut Window, cx: &mut Context) where T: 'static + StatusItemView, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index c87634d4f7db10a430d26d558ba987604842835f..79c2aedc9da40518da32c215dd7edef6a4fa6ef8 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -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); }); } diff --git a/crates/zed/src/visual_test_runner.rs b/crates/zed/src/visual_test_runner.rs index 701413281a0156e9e4015dbedba690257df2fb04..af95c8dae2a3cdd4391603a0b30e17669a337a43 100644 --- a/crates/zed/src/visual_test_runner.rs +++ b/crates/zed/src/visual_test_runner.rs @@ -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")?; diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 7624a027a2c4fc59f14bab025b9061e070116f76..10d8c0d5974fc1c3a097a3d09c34f107f7840877 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -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();