sidebar: Adjust design for the "Open Project" button (#51145)

This PR makes the "Open Project" button in the sidebar also open the
"Recent Projects" popover, while also anchoring that popover to the the
button on the sidebar instead.

Release Notes:

- N/A
This commit is contained in:
Danilo Leal 2026-03-09 22:01:40 -03:00 committed by GitHub
parent 147577496d
commit a26f0f8b60
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 151 additions and 25 deletions

1
Cargo.lock generated
View file

@ -15825,6 +15825,7 @@ dependencies = [
"language_model",
"menu",
"project",
"recent_projects",
"serde_json",
"settings",
"theme",

View file

@ -26,6 +26,7 @@ fs.workspace = true
gpui.workspace = true
menu.workspace = true
project.workspace = true
recent_projects.workspace = true
settings.workspace = true
theme.workspace = true
ui.workspace = true

View file

@ -12,20 +12,23 @@ use gpui::{
};
use menu::{Cancel, Confirm, SelectFirst, SelectLast, SelectNext, SelectPrevious};
use project::Event as ProjectEvent;
use recent_projects::RecentProjects;
use settings::Settings;
use std::collections::{HashMap, HashSet};
use std::mem;
use theme::{ActiveTheme, ThemeSettings};
use ui::utils::TRAFFIC_LIGHT_PADDING;
use ui::{
AgentThreadStatus, GradientFade, HighlightedLabel, IconButtonShape, KeyBinding, ListItem, Tab,
ThreadItem, Tooltip, WithScrollbar, prelude::*,
AgentThreadStatus, ButtonStyle, GradientFade, HighlightedLabel, IconButtonShape, KeyBinding,
ListItem, PopoverMenu, PopoverMenuHandle, Tab, ThreadItem, TintColor, Tooltip, WithScrollbar,
prelude::*,
};
use util::path_list::PathList;
use workspace::{
FocusWorkspaceSidebar, MultiWorkspace, MultiWorkspaceEvent, Sidebar as WorkspaceSidebar,
SidebarEvent, ToggleWorkspaceSidebar, Workspace,
};
use zed_actions::OpenRecent;
use zed_actions::editor::{MoveDown, MoveUp};
actions!(
@ -183,6 +186,7 @@ pub struct Sidebar {
active_entry_index: Option<usize>,
collapsed_groups: HashSet<PathList>,
expanded_groups: HashMap<PathList, usize>,
recent_projects_popover_handle: PopoverMenuHandle<RecentProjects>,
}
impl EventEmitter<SidebarEvent> for Sidebar {}
@ -278,6 +282,7 @@ impl Sidebar {
active_entry_index: None,
collapsed_groups: HashSet::new(),
expanded_groups: HashMap::new(),
recent_projects_popover_handle: PopoverMenuHandle::default(),
}
}
@ -1174,6 +1179,48 @@ impl Sidebar {
.into_any_element()
}
fn render_recent_projects_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
let workspace = self
.multi_workspace
.upgrade()
.map(|mw| mw.read(cx).workspace().downgrade());
let focus_handle = workspace
.as_ref()
.and_then(|ws| ws.upgrade())
.map(|w| w.read(cx).focus_handle(cx))
.unwrap_or_else(|| cx.focus_handle());
let popover_handle = self.recent_projects_popover_handle.clone();
PopoverMenu::new("sidebar-recent-projects-menu")
.with_handle(popover_handle)
.menu(move |window, cx| {
workspace.as_ref().map(|ws| {
RecentProjects::popover(ws.clone(), false, focus_handle.clone(), window, cx)
})
})
.trigger_with_tooltip(
IconButton::new("open-project", IconName::OpenFolder)
.icon_size(IconSize::Small)
.selected_style(ButtonStyle::Tinted(TintColor::Accent)),
|_window, cx| {
Tooltip::for_action(
"Recent Projects",
&OpenRecent {
create_new_window: false,
},
cx,
)
},
)
.anchor(gpui::Corner::TopLeft)
.offset(gpui::Point {
x: px(0.0),
y: px(2.0),
})
}
fn render_filter_input(&self, cx: &mut Context<Self>) -> impl IntoElement {
let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle {
@ -1315,6 +1362,14 @@ impl WorkspaceSidebar for Sidebar {
fn has_notifications(&self, _cx: &App) -> bool {
!self.contents.notified_threads.is_empty()
}
fn toggle_recent_projects_popover(&self, window: &mut Window, cx: &mut App) {
self.recent_projects_popover_handle.toggle(window, cx);
}
fn is_recent_projects_popover_deployed(&self) -> bool {
self.recent_projects_popover_handle.is_deployed()
}
}
impl Focusable for Sidebar {
@ -1412,27 +1467,7 @@ impl Render for Sidebar {
cx.emit(SidebarEvent::Close);
}))
})
.child(
IconButton::new("open-project", IconName::OpenFolder)
.icon_size(IconSize::Small)
.tooltip(|_window, cx| {
Tooltip::for_action(
"Open Project",
&workspace::Open {
create_new_window: false,
},
cx,
)
})
.on_click(|_event, window, cx| {
window.dispatch_action(
Box::new(workspace::Open {
create_new_window: false,
}),
cx,
);
}),
),
.child(self.render_recent_projects_button(cx)),
)
.child(
h_flex()

View file

@ -151,6 +151,7 @@ pub struct TitleBar {
user_store: Entity<UserStore>,
client: Arc<Client>,
workspace: WeakEntity<Workspace>,
multi_workspace: Option<WeakEntity<MultiWorkspace>>,
application_menu: Option<Entity<ApplicationMenu>>,
_subscriptions: Vec<Subscription>,
banner: Entity<OnboardingBanner>,
@ -188,7 +189,7 @@ impl Render for TitleBar {
.when(title_bar_settings.show_project_items, |title_bar| {
title_bar
.children(self.render_project_host(cx))
.child(self.render_project_name(cx))
.child(self.render_project_name(window, cx))
})
.when(title_bar_settings.show_branch_name, |title_bar| {
title_bar.children(self.render_project_branch(cx))
@ -389,6 +390,7 @@ impl TitleBar {
if let Some(this) = this.upgrade() {
this.update(cx, |this, _| {
this._subscriptions.push(subscription);
this.multi_workspace = Some(multi_workspace.downgrade());
});
}
});
@ -400,6 +402,7 @@ impl TitleBar {
platform_titlebar,
application_menu,
workspace: workspace.weak_handle(),
multi_workspace: None,
project,
user_store,
client,
@ -718,7 +721,11 @@ impl TitleBar {
)
}
pub fn render_project_name(&self, cx: &mut Context<Self>) -> impl IntoElement {
pub fn render_project_name(
&self,
window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let workspace = self.workspace.clone();
let name = self.effective_active_worktree(cx).map(|worktree| {
@ -734,6 +741,19 @@ impl TitleBar {
"Open Recent Project".to_string()
};
let is_sidebar_open = self.platform_titlebar.read(cx).is_workspace_sidebar_open();
if is_sidebar_open {
return self
.render_project_name_with_sidebar_popover(
window,
display_name,
is_project_selected,
cx,
)
.into_any_element();
}
let focus_handle = workspace
.upgrade()
.map(|w| w.read(cx).focus_handle(cx))
@ -773,6 +793,49 @@ impl TitleBar {
.into_any_element()
}
fn render_project_name_with_sidebar_popover(
&self,
_window: &Window,
display_name: String,
is_project_selected: bool,
cx: &mut Context<Self>,
) -> impl IntoElement {
let multi_workspace = self.multi_workspace.clone();
let is_popover_deployed = multi_workspace
.as_ref()
.and_then(|mw| mw.upgrade())
.map(|mw| mw.read(cx).is_recent_projects_popover_deployed(cx))
.unwrap_or(false);
Button::new("project_name_trigger", display_name)
.label_size(LabelSize::Small)
.when(self.worktree_count(cx) > 1, |this| {
this.icon(IconName::ChevronDown)
.icon_color(Color::Muted)
.icon_size(IconSize::XSmall)
})
.toggle_state(is_popover_deployed)
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
.when(!is_project_selected, |s| s.color(Color::Muted))
.tooltip(move |_window, cx| {
Tooltip::for_action(
"Recent Projects",
&zed_actions::OpenRecent {
create_new_window: false,
},
cx,
)
})
.on_click(move |_, window, cx| {
if let Some(mw) = multi_workspace.as_ref().and_then(|mw| mw.upgrade()) {
mw.update(cx, |mw, cx| {
mw.toggle_recent_projects_popover(window, cx);
});
}
})
}
pub fn render_project_branch(&self, cx: &mut Context<Self>) -> Option<impl IntoElement> {
let effective_worktree = self.effective_active_worktree(cx)?;
let repository = self.get_repository_for_worktree(&effective_worktree, cx)?;

View file

@ -50,6 +50,8 @@ pub trait Sidebar: EventEmitter<SidebarEvent> + Focusable + Render + Sized {
fn width(&self, cx: &App) -> Pixels;
fn set_width(&mut self, width: Option<Pixels>, cx: &mut Context<Self>);
fn has_notifications(&self, cx: &App) -> bool;
fn toggle_recent_projects_popover(&self, window: &mut Window, cx: &mut App);
fn is_recent_projects_popover_deployed(&self) -> bool;
}
pub trait SidebarHandle: 'static + Send + Sync {
@ -60,6 +62,8 @@ pub trait SidebarHandle: 'static + Send + Sync {
fn has_notifications(&self, cx: &App) -> bool;
fn to_any(&self) -> AnyView;
fn entity_id(&self) -> EntityId;
fn toggle_recent_projects_popover(&self, window: &mut Window, cx: &mut App);
fn is_recent_projects_popover_deployed(&self, cx: &App) -> bool;
}
#[derive(Clone)]
@ -100,6 +104,16 @@ impl<T: Sidebar> SidebarHandle for Entity<T> {
fn entity_id(&self) -> EntityId {
Entity::entity_id(self)
}
fn toggle_recent_projects_popover(&self, window: &mut Window, cx: &mut App) {
self.update(cx, |this, cx| {
this.toggle_recent_projects_popover(window, cx);
});
}
fn is_recent_projects_popover_deployed(&self, cx: &App) -> bool {
self.read(cx).is_recent_projects_popover_deployed()
}
}
pub struct MultiWorkspace {
@ -187,6 +201,18 @@ impl MultiWorkspace {
.map_or(false, |s| s.has_notifications(cx))
}
pub fn toggle_recent_projects_popover(&self, window: &mut Window, cx: &mut App) {
if let Some(sidebar) = &self.sidebar {
sidebar.toggle_recent_projects_popover(window, cx);
}
}
pub fn is_recent_projects_popover_deployed(&self, cx: &App) -> bool {
self.sidebar
.as_ref()
.map_or(false, |s| s.is_recent_projects_popover_deployed(cx))
}
pub fn multi_workspace_enabled(&self, cx: &App) -> bool {
cx.has_flag::<AgentV2FeatureFlag>() && !DisableAiSettings::get_global(cx).disable_ai
}