diff --git a/crates/collab_ui/src/branches_button.rs b/crates/collab_ui/src/branches_button.rs new file mode 100644 index 0000000000000000000000000000000000000000..7acc7a8ed3210fec9298c108645048edf86b20b0 --- /dev/null +++ b/crates/collab_ui/src/branches_button.rs @@ -0,0 +1,165 @@ +use context_menu::{ContextMenu, ContextMenuItem}; +use gpui::{ + elements::*, + platform::{CursorStyle, MouseButton}, + AnyElement, Element, Entity, View, ViewContext, ViewHandle, WeakViewHandle, +}; +use settings::Settings; +use workspace::Workspace; + +pub struct BranchesButton { + workspace: WeakViewHandle, + popup_menu: ViewHandle, +} + +impl Entity for BranchesButton { + type Event = (); +} + +impl View for BranchesButton { + fn ui_name() -> &'static str { + "BranchesButton" + } + + fn render(&mut self, cx: &mut ViewContext) -> AnyElement { + let Some(workspace) = self.workspace.upgrade(cx) else { + return Empty::new().into_any(); + }; + + let project = workspace.read(cx).project().read(cx); + let only_one_worktree = project.visible_worktrees(cx).count() == 1; + let branches_count: usize = project + .visible_worktrees(cx) + .map(|worktree_handle| worktree_handle.read(cx).snapshot().git_entries().count()) + .sum(); + let branch_caption: String = if only_one_worktree { + project + .visible_worktrees(cx) + .next() + .unwrap() + .read(cx) + .snapshot() + .root_git_entry() + .and_then(|entry| entry.branch()) + .map(|branch| branch.to_string()) + .unwrap_or_else(|| "".to_owned()) + } else { + branches_count.to_string() + }; + let is_popup_menu_visible = self.popup_menu.read(cx).visible(); + + let theme = cx.global::().theme.clone(); + + Stack::new() + .with_child( + MouseEventHandler::::new(0, cx, { + let theme = theme.clone(); + move |state, _cx| { + let style = theme + .workspace + .titlebar + .toggle_contacts_button + .style_for(state, is_popup_menu_visible); + + Flex::row() + .with_child( + Svg::new("icons/version_control_branch_12.svg") + .with_color(style.color) + .constrained() + .with_width(style.icon_width) + .aligned() + // .constrained() + // .with_width(style.button_width) + // .with_height(style.button_width) + // .contained() + // .with_style(style.container) + .into_any_named("version-control-branch-icon"), + ) + .with_child( + Label::new(branch_caption, theme.workspace.titlebar.title.clone()) + .contained() + .with_style(style.container) + .aligned(), + ) + .constrained() + .with_height(style.button_width) + .contained() + .with_style(style.container) + } + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, move |_, this, cx| { + this.deploy_branches_menu(cx); + }) + .with_tooltip::( + 0, + "Branches".into(), + None, + theme.tooltip.clone(), + cx, + ), + ) + .with_child( + ChildView::new(&self.popup_menu, cx) + .aligned() + .bottom() + .left(), + ) + .into_any_named("branches-button") + } +} + +impl BranchesButton { + pub fn new(workspace: ViewHandle, cx: &mut ViewContext) -> Self { + cx.observe(&workspace, |_, _, cx| cx.notify()).detach(); + Self { + workspace: workspace.downgrade(), + popup_menu: cx.add_view(|cx| { + let mut menu = ContextMenu::new(cx); + menu.set_position_mode(OverlayPositionMode::Local); + menu + }), + } + } + + pub fn deploy_branches_menu(&mut self, cx: &mut ViewContext) { + let mut menu_options = vec![]; + + if let Some(workspace) = self.workspace.upgrade(cx) { + let project = workspace.read(cx).project().read(cx); + + let worktrees_with_branches = project + .visible_worktrees(cx) + .map(|worktree_handle| { + worktree_handle + .read(cx) + .snapshot() + .git_entries() + .filter_map(|entry| { + entry.branch().map(|branch| { + let repo_name = entry.work_directory(); + if let Some(name) = repo_name.file_name() { + (name.to_string_lossy().to_string(), branch) + } else { + ("WORKTREE ROOT".into(), branch) + } + }) + }) + .collect::>() + }) + .flatten(); + + let context_menu_items = worktrees_with_branches.map(|(repo_name, branch_name)| { + let caption = format!("{} / {}", repo_name, branch_name); + ContextMenuItem::handler(caption.to_owned(), move |_| { + println!("{}", caption); + }) + }); + menu_options.extend(context_menu_items); + } + + self.popup_menu.update(cx, |menu, cx| { + menu.show(Default::default(), AnchorCorner::TopLeft, menu_options, cx); + }); + } +} diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index c0734388b1512b2e9cac5014c460bf1a8c09650b..21a864d4e8943f6b8e7c00d30e9f9dc5358a5ffb 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -1,3 +1,4 @@ +mod branches_button; mod collab_titlebar_item; mod contact_finder; mod contact_list; @@ -9,6 +10,7 @@ mod notifications; mod project_shared_notification; mod sharing_status_indicator; +pub use branches_button::BranchesButton; use call::ActiveCall; pub use collab_titlebar_item::{CollabTitlebarItem, ToggleContactsMenu}; use gpui::{actions, AppContext, Task}; diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index fed98d49edc8e55979617206c045ee46e37d2455..b6de263d2a3d8b3e53c82d439b488c3faeeb20da 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -137,6 +137,10 @@ impl RepositoryEntry { pub fn branch(&self) -> Option> { self.branch.clone() } + + pub fn work_directory(&self) -> Arc { + self.work_directory.0.clone() + } } /// This path corresponds to the 'content path' (the folder that contains the .git) @@ -1459,6 +1463,10 @@ impl Snapshot { .map(|entry| entry.to_owned()) } + pub fn git_entries(&self) -> impl Iterator { + self.repository_entries.values() + } + pub fn scan_id(&self) -> usize { self.scan_id }