From fc896cbccde7084c4f0024dab2a01f56ff3094d7 Mon Sep 17 00:00:00 2001 From: Federico Zivolo Date: Mon, 22 Jan 2024 12:07:51 +0100 Subject: [PATCH 1/4] fix: git branches dropdown scroll and navigation --- .../Views/ToolbarBranchPicker.swift | 71 +++++++++---------- 1 file changed, 33 insertions(+), 38 deletions(-) diff --git a/CodeEdit/Features/CodeEditUI/Views/ToolbarBranchPicker.swift b/CodeEdit/Features/CodeEditUI/Views/ToolbarBranchPicker.swift index fde39952c..cf8f8ef93 100644 --- a/CodeEdit/Features/CodeEditUI/Views/ToolbarBranchPicker.swift +++ b/CodeEdit/Features/CodeEditUI/Views/ToolbarBranchPicker.swift @@ -31,52 +31,47 @@ struct ToolbarBranchPicker: View { } var body: some View { - HStack(alignment: .center, spacing: 5) { - if currentBranch != nil { - Image.checkout - .font(.title3) - .imageScale(.large) - .foregroundColor(controlActive == .inactive ? inactiveColor : .primary) - } else { - Image(systemName: "folder.fill.badge.gearshape") - .font(.title3) - .imageScale(.medium) - .foregroundColor(controlActive == .inactive ? inactiveColor : .accentColor) + Menu(content: { + if let sourceControlManager = workspaceFileManager?.sourceControlManager { + PopoverView(sourceControlManager: sourceControlManager) } - VStack(alignment: .leading, spacing: 2) { - Text(title) - .font(.headline) - .foregroundColor(controlActive == .inactive ? inactiveColor : .primary) - .frame(height: 16) - .help(title) - if let currentBranch { - ZStack(alignment: .trailing) { - Text(currentBranch.name) - .padding(.trailing) - if isHovering { - Image(systemName: "chevron.down") + }, label: { + HStack(alignment: .center, spacing: 5) { + if currentBranch != nil { + Image.checkout + .font(.title3) + .imageScale(.large) + .foregroundColor(controlActive == .inactive ? inactiveColor : .primary) + } else { + Image(systemName: "folder.fill.badge.gearshape") + .font(.title3) + .imageScale(.medium) + .foregroundColor(controlActive == .inactive ? inactiveColor : .accentColor) + } + VStack(alignment: .leading, spacing: 2) { + Text(title) + .font(.headline) + .foregroundColor(controlActive == .inactive ? inactiveColor : .primary) + .frame(height: 16) + .help(title) + if let currentBranch { + ZStack(alignment: .trailing) { + Text(currentBranch.name) + .padding(.trailing) + if isHovering { + Image(systemName: "chevron.down") + } } + .font(.subheadline) + .foregroundColor(controlActive == .inactive ? inactiveColor : .secondary) + .frame(height: 11) } - .font(.subheadline) - .foregroundColor(controlActive == .inactive ? inactiveColor : .secondary) - .frame(height: 11) } } - } - .contentShape(Rectangle()) - .onTapGesture { - if currentBranch != nil { - displayPopover.toggle() - } - } + }) .onHover { active in isHovering = active } - .popover(isPresented: $displayPopover, arrowEdge: .bottom) { - if let sourceControlManager = workspaceFileManager?.sourceControlManager { - PopoverView(sourceControlManager: sourceControlManager) - } - } .onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { (_) in Task { await sourceControlManager?.refreshCurrentBranch() From 1b0b1291f078b23f65a463e26fc7c8df9370280a Mon Sep 17 00:00:00 2001 From: Federico Zivolo Date: Mon, 22 Jan 2024 16:03:38 +0100 Subject: [PATCH 2/4] fix: show project title --- .../Views/ToolbarBranchPicker.swift | 64 +++++++++---------- 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/CodeEdit/Features/CodeEditUI/Views/ToolbarBranchPicker.swift b/CodeEdit/Features/CodeEditUI/Views/ToolbarBranchPicker.swift index cf8f8ef93..e9afe531e 100644 --- a/CodeEdit/Features/CodeEditUI/Views/ToolbarBranchPicker.swift +++ b/CodeEdit/Features/CodeEditUI/Views/ToolbarBranchPicker.swift @@ -31,44 +31,40 @@ struct ToolbarBranchPicker: View { } var body: some View { - Menu(content: { - if let sourceControlManager = workspaceFileManager?.sourceControlManager { - PopoverView(sourceControlManager: sourceControlManager) + HStack(alignment: .center, spacing: 5) { + if currentBranch != nil { + Image.checkout + .font(.title3) + .imageScale(.large) + .foregroundColor(controlActive == .inactive ? inactiveColor : .primary) + } else { + Image(systemName: "folder.fill.badge.gearshape") + .font(.title3) + .imageScale(.medium) + .foregroundColor(controlActive == .inactive ? inactiveColor : .accentColor) } - }, label: { - HStack(alignment: .center, spacing: 5) { - if currentBranch != nil { - Image.checkout - .font(.title3) - .imageScale(.large) - .foregroundColor(controlActive == .inactive ? inactiveColor : .primary) - } else { - Image(systemName: "folder.fill.badge.gearshape") - .font(.title3) - .imageScale(.medium) - .foregroundColor(controlActive == .inactive ? inactiveColor : .accentColor) - } - VStack(alignment: .leading, spacing: 2) { - Text(title) - .font(.headline) - .foregroundColor(controlActive == .inactive ? inactiveColor : .primary) - .frame(height: 16) - .help(title) - if let currentBranch { - ZStack(alignment: .trailing) { - Text(currentBranch.name) - .padding(.trailing) - if isHovering { - Image(systemName: "chevron.down") - } + VStack(alignment: .leading, spacing: 2) { + Text(title) + .font(.headline) + .foregroundColor(controlActive == .inactive ? inactiveColor : .primary) + .frame(height: 16) + .help(title) + if let currentBranch { + Menu(content: { + if let sourceControlManager = workspaceFileManager?.sourceControlManager { + PopoverView(sourceControlManager: sourceControlManager) } - .font(.subheadline) - .foregroundColor(controlActive == .inactive ? inactiveColor : .secondary) - .frame(height: 11) - } + }, label: { + Text(currentBranch.name) + .font(.subheadline) + .foregroundColor(controlActive == .inactive ? inactiveColor : .secondary) + .frame(height: 11) + }) + .buttonStyle(.borderless) + .padding(.leading, -3) } } - }) + } .onHover { active in isHovering = active } From f15c2a462b2e6a8abbac9f04c5077a1fad53717c Mon Sep 17 00:00:00 2001 From: Federico Zivolo Date: Mon, 22 Jan 2024 12:00:51 +0100 Subject: [PATCH 3/4] fix: git branches dropdown --- CodeEdit.xcodeproj/project.pbxproj | 4 ++ .../Views/ToolbarBranchPicker.swift | 51 +++++++++++++++++-- .../Git/Client/Models/GitBranchesGroup.swift | 16 ++++++ 3 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 CodeEdit/Features/Git/Client/Models/GitBranchesGroup.swift diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index 62cf29b93..4a711cc83 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -370,6 +370,7 @@ 85773E1E2A3E0A1F00C5D926 /* SettingsSearchResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85773E1D2A3E0A1F00C5D926 /* SettingsSearchResult.swift */; }; 85CD0C5F2A10CC3200E531FD /* URL+isImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85CD0C5E2A10CC3200E531FD /* URL+isImage.swift */; }; 85E4122A2A46C8CA00183F2B /* LocationsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85E412292A46C8CA00183F2B /* LocationsSettings.swift */; }; + 9D36E1BF2B5E7D7500443C41 /* GitBranchesGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D36E1BE2B5E7D7500443C41 /* GitBranchesGroup.swift */; }; B6041F4D29D7A4E9000F3454 /* SettingsPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6041F4C29D7A4E9000F3454 /* SettingsPageView.swift */; }; B6041F5229D7D6D6000F3454 /* SettingsWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6041F5129D7D6D6000F3454 /* SettingsWindow.swift */; }; B607181D2B0C5BE3009CDAB4 /* GitClient+Stash.swift in Sources */ = {isa = PBXBuildFile; fileRef = B607181C2B0C5BE3009CDAB4 /* GitClient+Stash.swift */; }; @@ -885,6 +886,7 @@ 85773E1D2A3E0A1F00C5D926 /* SettingsSearchResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSearchResult.swift; sourceTree = ""; }; 85CD0C5E2A10CC3200E531FD /* URL+isImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+isImage.swift"; sourceTree = ""; }; 85E412292A46C8CA00183F2B /* LocationsSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationsSettings.swift; sourceTree = ""; }; + 9D36E1BE2B5E7D7500443C41 /* GitBranchesGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitBranchesGroup.swift; sourceTree = ""; }; B6041F4C29D7A4E9000F3454 /* SettingsPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsPageView.swift; sourceTree = ""; }; B6041F5129D7D6D6000F3454 /* SettingsWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsWindow.swift; sourceTree = ""; }; B607181C2B0C5BE3009CDAB4 /* GitClient+Stash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GitClient+Stash.swift"; sourceTree = ""; }; @@ -1951,6 +1953,7 @@ 04BA7C0A2AE2A2D100584E1C /* GitBranch.swift */, B65B10F12B07D34F002852CF /* GitRemote.swift */, B607181F2B0C6CE7009CDAB4 /* GitStashEntry.swift */, + 9D36E1BE2B5E7D7500443C41 /* GitBranchesGroup.swift */, ); path = Models; sourceTree = ""; @@ -3251,6 +3254,7 @@ B6F0517D29D9E4B100D72287 /* TerminalSettingsView.swift in Sources */, 587B9E8C29301D8F00AC7927 /* GitHubOpenness.swift in Sources */, 5894E59729FEF7740077E59C /* CEWorkspaceFile+Recursion.swift in Sources */, + 9D36E1BF2B5E7D7500443C41 /* GitBranchesGroup.swift in Sources */, 587B9E8229301D8F00AC7927 /* GitHubPreviewHeader.swift in Sources */, 611191FC2B08CCB800D4459B /* SearchIndexer+AsyncController.swift in Sources */, 58F2EB02292FB2B0004A9BDE /* Loopable.swift in Sources */, diff --git a/CodeEdit/Features/CodeEditUI/Views/ToolbarBranchPicker.swift b/CodeEdit/Features/CodeEditUI/Views/ToolbarBranchPicker.swift index e9afe531e..6600de4dd 100644 --- a/CodeEdit/Features/CodeEditUI/Views/ToolbarBranchPicker.swift +++ b/CodeEdit/Features/CodeEditUI/Views/ToolbarBranchPicker.swift @@ -111,11 +111,43 @@ struct ToolbarBranchPicker: View { let branches = sourceControlManager.branches .filter({ $0.isLocal && $0 != sourceControlManager.currentBranch }) + let branchesGroups = branches.reduce(into: [String: GitBranchesGroup]()) { result, branch in + guard let branchPrefix = branch.name.components(separatedBy: "/").first else { + return + } + + result[ + branchPrefix, + default: GitBranchesGroup(name: branchPrefix, branches: []) + ].branches.append(branch) + } + if !branches.isEmpty { VStack(alignment: .leading, spacing: 0) { headerLabel("Branches") - ForEach(branches, id: \.self) { branch in - BranchCell(sourceControlManager: sourceControlManager, branch: branch) + ForEach(branchesGroups.keys.sorted(), id: \.self) { branchGroupPrefix in + if let group = branchesGroups[branchGroupPrefix] { + if !group.shouldNest { + BranchCell( + sourceControlManager: sourceControlManager, + branch: group.branches.first! + ) + } else { + Menu(content: { + ForEach(group.branches, id: \.self) { branch in + BranchCell( + sourceControlManager: sourceControlManager, + branch: branch, + title: String( + branch.name.suffix(branch.name.count - branchGroupPrefix.count - 1) + ) + ) + } + }, label: { + Text(group.name) + }) + } + } } } } @@ -142,8 +174,21 @@ struct ToolbarBranchPicker: View { struct BranchCell: View { let sourceControlManager: SourceControlManager var branch: GitBranch + let title: String? var active: Bool = false + init( + sourceControlManager: SourceControlManager, + branch: GitBranch, + active: Bool = false, + title: String? = nil + ) { + self.sourceControlManager = sourceControlManager + self.branch = branch + self.active = active + self.title = title + } + @Environment(\.dismiss) private var dismiss @@ -155,7 +200,7 @@ struct ToolbarBranchPicker: View { } label: { HStack { Label { - Text(branch.name) + Text(self.title ?? branch.name) .frame(maxWidth: .infinity, alignment: .leading) } icon: { Image.checkout diff --git a/CodeEdit/Features/Git/Client/Models/GitBranchesGroup.swift b/CodeEdit/Features/Git/Client/Models/GitBranchesGroup.swift new file mode 100644 index 000000000..6c05097f6 --- /dev/null +++ b/CodeEdit/Features/Git/Client/Models/GitBranchesGroup.swift @@ -0,0 +1,16 @@ +// +// GitBranchesGroup.swift +// CodeEdit +// +// Created by Federico Zivolo on 22/01/24. +// + +import Foundation + +struct GitBranchesGroup: Hashable { + let name: String + var branches: [GitBranch] + var shouldNest: Bool { + branches.first?.name.hasPrefix(name + "/") ?? false + } +} From 7c7a8a7df42c25a6cdf08bd6480fb2fd02f5563b Mon Sep 17 00:00:00 2001 From: Federico Zivolo Date: Wed, 24 Jan 2024 14:10:50 +0100 Subject: [PATCH 4/4] fix: branch selector icons --- .../Views/ToolbarBranchPicker.swift | 40 ++++++------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/CodeEdit/Features/CodeEditUI/Views/ToolbarBranchPicker.swift b/CodeEdit/Features/CodeEditUI/Views/ToolbarBranchPicker.swift index 6600de4dd..ea505fe15 100644 --- a/CodeEdit/Features/CodeEditUI/Views/ToolbarBranchPicker.swift +++ b/CodeEdit/Features/CodeEditUI/Views/ToolbarBranchPicker.swift @@ -33,7 +33,7 @@ struct ToolbarBranchPicker: View { var body: some View { HStack(alignment: .center, spacing: 5) { if currentBranch != nil { - Image.checkout + Image.branch .font(.title3) .imageScale(.large) .foregroundColor(controlActive == .inactive ? inactiveColor : .primary) @@ -103,7 +103,7 @@ struct ToolbarBranchPicker: View { var body: some View { VStack(alignment: .leading) { if let currentBranch = sourceControlManager.currentBranch { - VStack(alignment: .leading, spacing: 0) { + Section { headerLabel("Current Branch") BranchCell(sourceControlManager: sourceControlManager, branch: currentBranch, active: true) } @@ -123,7 +123,7 @@ struct ToolbarBranchPicker: View { } if !branches.isEmpty { - VStack(alignment: .leading, spacing: 0) { + Section { headerLabel("Branches") ForEach(branchesGroups.keys.sorted(), id: \.self) { branchGroupPrefix in if let group = branchesGroups[branchGroupPrefix] { @@ -144,7 +144,10 @@ struct ToolbarBranchPicker: View { ) } }, label: { - Text(group.name) + HStack { + Image(systemName: "folder") + Text(group.name) + } }) } } @@ -173,9 +176,9 @@ struct ToolbarBranchPicker: View { /// A Button Cell that represents a branch in the branch picker struct BranchCell: View { let sourceControlManager: SourceControlManager - var branch: GitBranch + let branch: GitBranch + let active: Bool let title: String? - var active: Bool = false init( sourceControlManager: SourceControlManager, @@ -192,37 +195,18 @@ struct ToolbarBranchPicker: View { @Environment(\.dismiss) private var dismiss - @State private var isHovering: Bool = false - var body: some View { Button { switchBranch() } label: { HStack { - Label { - Text(self.title ?? branch.name) - .frame(maxWidth: .infinity, alignment: .leading) - } icon: { - Image.checkout - .imageScale(.large) - } - .foregroundColor(isHovering ? .white : .secondary) if active { Image(systemName: "checkmark.circle.fill") - .foregroundColor(isHovering ? .white : .green) + } else { + Image.branch } + Text(self.title ?? branch.name) } - .contentShape(Rectangle()) - } - .buttonStyle(.plain) - .padding(.horizontal) - .padding(.vertical, 10) - .background( - EffectView.selectionBackground(isHovering) - ) - .clipShape(RoundedRectangle(cornerRadius: 4)) - .onHover { active in - isHovering = active } }