From 10f5e157b83bbd9098aa77d7c7f129022a8e2874 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Tue, 17 Oct 2017 20:41:12 +0200 Subject: [PATCH 01/64] Update data before OnGUI whenever the repo event happens, and OnEnable when we're not in playmode --- .../Editor/GitHub.Unity/UI/BranchesView.cs | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs index 8c00a6f63..ed0de4c2f 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs @@ -43,6 +43,7 @@ class BranchesView : Subview [NonSerialized] private BranchTreeNode newNodeSelection; [NonSerialized] private BranchesMode targetMode; [NonSerialized] private bool favoritesHasChanged; + [NonSerialized] private bool branchesHaveChanged; [SerializeField] private BranchTreeNode activeBranchNode; [SerializeField] private BranchTreeNode localRoot; @@ -63,8 +64,11 @@ public override void OnEnable() { base.OnEnable(); AttachHandlers(Repository); - favoritesHasChanged = true; - Refresh(); + if (!Application.isPlaying) + { + favoritesHasChanged = true; + branchesHaveChanged = true; + } } public override void OnDisable() @@ -86,6 +90,12 @@ private void MaybeUpdateData() favoritesList = Manager.LocalSettings.Get(FavoritesSetting, new List()); favoritesHasChanged = false; } + + if (branchesHaveChanged) + { + UpdateBranches(); + branchesHaveChanged = false; + } } public override void OnRepositoryChanged(IRepository oldRepository) @@ -100,23 +110,28 @@ private void AttachHandlers(IRepository repository) if (repository == null) return; - repository.OnLocalBranchListChanged += RunUpdateBranchesOnMainThread; - repository.OnCurrentBranchChanged += HandleRepositoryBranchChangeEvent; - repository.OnCurrentRemoteChanged += HandleRepositoryBranchChangeEvent; + repository.OnLocalBranchListChanged += HandleDataUpdated; + repository.OnCurrentBranchChanged += HandleDataUpdated; + repository.OnCurrentRemoteChanged += HandleDataUpdated; } private void DetachHandlers(IRepository repository) { if (repository == null) return; - repository.OnLocalBranchListChanged -= RunUpdateBranchesOnMainThread; - repository.OnCurrentBranchChanged -= HandleRepositoryBranchChangeEvent; - repository.OnCurrentRemoteChanged -= HandleRepositoryBranchChangeEvent; + repository.OnLocalBranchListChanged -= HandleDataUpdated; + repository.OnCurrentBranchChanged -= HandleDataUpdated; + repository.OnCurrentRemoteChanged -= HandleDataUpdated; + } + + private void HandleDataUpdated() + { + branchesHaveChanged = true; } - private void HandleRepositoryBranchChangeEvent(string obj) + private void HandleDataUpdated(string obj) { - RunUpdateBranchesOnMainThread(); + branchesHaveChanged = true; } public override void Refresh() From b7bb749d86d27e7718f2cb3be49cb343cc161dbb Mon Sep 17 00:00:00 2001 From: Stanley Goldman Date: Tue, 17 Oct 2017 14:49:45 -0400 Subject: [PATCH 02/64] Removing unused method --- .../Assets/Editor/GitHub.Unity/UI/BranchesView.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs index ed0de4c2f..d7db8d5f7 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs @@ -140,12 +140,6 @@ public override void Refresh() UpdateBranches(); } - private void RunUpdateBranchesOnMainThread() - { - new ActionTask(TaskManager.Token, _ => UpdateBranches()) - .ScheduleUI(TaskManager); - } - public void UpdateBranches() { if (Repository == null) From 55883f2f5ef01736b27a9013e2a4a28989183ebb Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Tue, 17 Oct 2017 21:06:53 +0200 Subject: [PATCH 03/64] Redraw whenever data has changed --- src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs index d7db8d5f7..55d1d9f2c 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs @@ -132,6 +132,7 @@ private void HandleDataUpdated() private void HandleDataUpdated(string obj) { branchesHaveChanged = true; + new ActionTask(TaskManager.Token, Redraw) { Affinity = TaskAffinity.UI }.Start(); } public override void Refresh() From 4654cae0f8d9238abbe4ca4be04c316765cc1eb9 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Tue, 17 Oct 2017 21:13:52 +0200 Subject: [PATCH 04/64] Well duh --- .../Assets/Editor/GitHub.Unity/UI/BranchesView.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs index 55d1d9f2c..2d6b10117 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs @@ -127,12 +127,12 @@ private void DetachHandlers(IRepository repository) private void HandleDataUpdated() { branchesHaveChanged = true; + new ActionTask(TaskManager.Token, Redraw) { Affinity = TaskAffinity.UI }.Start(); } private void HandleDataUpdated(string obj) { - branchesHaveChanged = true; - new ActionTask(TaskManager.Token, Redraw) { Affinity = TaskAffinity.UI }.Start(); + HandleDataUpdated(); } public override void Refresh() From 205da5bca3f70d9ab2671d1456fd839f39c5204d Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Mon, 3 Jul 2017 20:33:36 +0200 Subject: [PATCH 05/64] Custom treeview control and cache manager --- .../Application/ApplicationManagerBase.cs | 8 + .../Application/IApplicationManager.cs | 1 + src/GitHub.Api/Cache/CacheManager.cs | 46 + src/GitHub.Api/Cache/IBranchCache.cs | 2 +- src/GitHub.Api/GitHub.Api.csproj | 1 + .../GitHub.Unity/IconsAndLogos/globe.png | 3 + .../GitHub.Unity/IconsAndLogos/globe@2x.png | 3 + .../Assets/Editor/GitHub.Unity/Misc/Styles.cs | 79 +- .../Editor/GitHub.Unity/Misc/Utility.cs | 12 + .../Services/AuthenticationService.cs | 1 - .../Editor/GitHub.Unity/UI/BranchesView.cs | 1029 +++++++++-------- 11 files changed, 719 insertions(+), 466 deletions(-) create mode 100644 src/GitHub.Api/Cache/CacheManager.cs create mode 100644 src/UnityExtension/Assets/Editor/GitHub.Unity/IconsAndLogos/globe.png create mode 100644 src/UnityExtension/Assets/Editor/GitHub.Unity/IconsAndLogos/globe@2x.png diff --git a/src/GitHub.Api/Application/ApplicationManagerBase.cs b/src/GitHub.Api/Application/ApplicationManagerBase.cs index d0eb0bc3e..d4cd6b412 100644 --- a/src/GitHub.Api/Application/ApplicationManagerBase.cs +++ b/src/GitHub.Api/Application/ApplicationManagerBase.cs @@ -12,6 +12,7 @@ abstract class ApplicationManagerBase : IApplicationManager protected static ILogging Logger { get; } = Logging.GetLogger(); private RepositoryManager repositoryManager; + private IBranchCache branchCache; public ApplicationManagerBase(SynchronizationContext synchronizationContext) { @@ -21,6 +22,7 @@ public ApplicationManagerBase(SynchronizationContext synchronizationContext) UIScheduler = TaskScheduler.FromCurrentSynchronizationContext(); ThreadingHelper.MainThreadScheduler = UIScheduler; TaskManager = new TaskManager(UIScheduler); + CacheManager = new CacheManager(); } protected void Initialize() @@ -80,6 +82,11 @@ private async Task SetupGit() } + public void SetupCache(IBranchCache bcache) + { + branchCache = bcache; + } + public ITask InitializeRepository() { Logger.Trace("Running Repository Initialize"); @@ -214,6 +221,7 @@ public void Dispose() public ISettings LocalSettings { get; protected set; } public ISettings SystemSettings { get; protected set; } public ISettings UserSettings { get; protected set; } + public CacheManager CacheManager { get; private set; } public IUsageTracker UsageTracker { get; protected set; } protected TaskScheduler UIScheduler { get; private set; } diff --git a/src/GitHub.Api/Application/IApplicationManager.cs b/src/GitHub.Api/Application/IApplicationManager.cs index fd59878a8..7ae10272f 100644 --- a/src/GitHub.Api/Application/IApplicationManager.cs +++ b/src/GitHub.Api/Application/IApplicationManager.cs @@ -15,6 +15,7 @@ public interface IApplicationManager : IDisposable ISettings LocalSettings { get; } ISettings UserSettings { get; } ITaskManager TaskManager { get; } + CacheManager CacheManager { get; } IGitClient GitClient { get; } IUsageTracker UsageTracker { get; } diff --git a/src/GitHub.Api/Cache/CacheManager.cs b/src/GitHub.Api/Cache/CacheManager.cs new file mode 100644 index 000000000..5e3cdaadc --- /dev/null +++ b/src/GitHub.Api/Cache/CacheManager.cs @@ -0,0 +1,46 @@ +using System; +using System.Linq; + +namespace GitHub.Unity +{ + public class CacheManager + { + private IBranchCache branchCache; + public IBranchCache BranchCache + { + get { return branchCache; } + set + { + if (branchCache == null) + branchCache = value; + } + } + + private Action onLocalBranchListChanged; + + public void SetupCache(IBranchCache branchCache, IRepository repository) + { + if (repository == null) + return; + + BranchCache = branchCache; + UpdateCache(repository); + if (onLocalBranchListChanged != null) + repository.OnLocalBranchListChanged -= onLocalBranchListChanged; + onLocalBranchListChanged = () => + { + if (!ThreadingHelper.InUIThread) + new ActionTask(TaskManager.Instance.Token, () => UpdateCache(repository)) { Affinity = TaskAffinity.UI }.Start(); + else + UpdateCache(repository); + }; + repository.OnLocalBranchListChanged += onLocalBranchListChanged; + } + + private void UpdateCache(IRepository repository) + { + BranchCache.LocalBranches = repository.LocalBranches.ToList(); + BranchCache.RemoteBranches = repository.RemoteBranches.ToList(); + } + } +} \ No newline at end of file diff --git a/src/GitHub.Api/Cache/IBranchCache.cs b/src/GitHub.Api/Cache/IBranchCache.cs index fce1b4c63..026d4f6bb 100644 --- a/src/GitHub.Api/Cache/IBranchCache.cs +++ b/src/GitHub.Api/Cache/IBranchCache.cs @@ -2,7 +2,7 @@ namespace GitHub.Unity { - interface IBranchCache + public interface IBranchCache { List LocalBranches { get; set; } List RemoteBranches { get; set; } diff --git a/src/GitHub.Api/GitHub.Api.csproj b/src/GitHub.Api/GitHub.Api.csproj index a87766cee..8539e9725 100644 --- a/src/GitHub.Api/GitHub.Api.csproj +++ b/src/GitHub.Api/GitHub.Api.csproj @@ -110,6 +110,7 @@ + diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/IconsAndLogos/globe.png b/src/UnityExtension/Assets/Editor/GitHub.Unity/IconsAndLogos/globe.png new file mode 100644 index 000000000..0b1353981 --- /dev/null +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/IconsAndLogos/globe.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b65bf8e330ede5edc0ae8d620a01f7003fbfdcd1053667c016a103b8ad49a934 +size 594 diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/IconsAndLogos/globe@2x.png b/src/UnityExtension/Assets/Editor/GitHub.Unity/IconsAndLogos/globe@2x.png new file mode 100644 index 000000000..792045838 --- /dev/null +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/IconsAndLogos/globe@2x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3051c3d5ba2726bf3b9877a44f41ec7f3a68a79afe40bf7fde0f65db1a542b18 +size 1318 diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/Misc/Styles.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/Misc/Styles.cs index a4c57790a..677cc12a3 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/Misc/Styles.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/Misc/Styles.cs @@ -27,13 +27,14 @@ class Styles MinCommitTreePadding = 20f, FoldoutWidth = 11f, FoldoutIndentation = -2f, + TreePadding = 12f, TreeIndentation = 12f, TreeRootIndentation = -5f, TreeVerticalSpacing = 3f, CommitIconSize = 16f, CommitIconHorizontalPadding = -5f, BranchListIndentation = 20f, - BranchListSeperation = 15f, + BranchListSeparation = 15f, RemotesTotalHorizontalMargin = 37f, RemotesNameRatio = .2f, RemotesUserRatio = .2f, @@ -195,7 +196,7 @@ public static GUIStyle Label label = new GUIStyle(GUI.skin.label); label.name = "CustomLabel"; - var hierarchyStyle = GUI.skin.FindStyle("PR Label"); + GUIStyle hierarchyStyle = GUI.skin.FindStyle("PR Label"); label.onNormal.background = hierarchyStyle.onNormal.background; label.onNormal.textColor = hierarchyStyle.onNormal.textColor; label.onFocused.background = hierarchyStyle.onFocused.background; @@ -798,5 +799,79 @@ public static Texture2D DropdownListIcon return dropdownListIcon; } } + + private static Texture2D rootFolderIcon; + public static Texture2D RootFolderIcon + { + get + { + if (rootFolderIcon == null) + { + rootFolderIcon = Utility.GetIcon("globe.png", "globe@2x.png"); + } + return rootFolderIcon; + } + } + + private static GUIStyle foldout; + public static GUIStyle Foldout + { + get + { + if (foldout == null) + { + foldout = new GUIStyle(EditorStyles.foldout); + foldout.name = "CustomFoldout"; + + foldout.focused.textColor = Color.white; + foldout.onFocused.textColor = Color.white; + foldout.focused.background = foldout.active.background; + foldout.onFocused.background = foldout.onActive.background; + } + + return foldout; + } + } + + private static GUIStyle treeNode; + public static GUIStyle TreeNode + { + get + { + if (treeNode == null) + { + treeNode = new GUIStyle(GUI.skin.label); + treeNode.name = "Custom TreeNode"; + + var color = new Color(62f / 255f, 125f / 255f, 231f / 255f); + var texture = Utility.GetTextureFromColor(color); + treeNode.focused.background = texture; + treeNode.onFocused.background = texture; + treeNode.focused.textColor = Color.white; + treeNode.onFocused.textColor = Color.white; + } + + return treeNode; + } + } + + private static GUIStyle treeNodeActive; + public static GUIStyle TreeNodeActive + { + get + { + if (treeNodeActive == null) + { + treeNodeActive = new GUIStyle(TreeNode); + treeNodeActive.name = "Custom TreeNode Active"; + treeNodeActive.fontStyle = FontStyle.Bold; + treeNodeActive.focused.textColor = Color.white; + treeNodeActive.active.textColor = Color.white; + } + + return treeNodeActive; + } + } + } } diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/Misc/Utility.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/Misc/Utility.cs index e645e5654..461d24415 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/Misc/Utility.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/Misc/Utility.cs @@ -23,6 +23,18 @@ public static Texture2D GetIcon(string filename, string filename2x = "") var iconPath = EntryPoint.Environment.ExtensionInstallPath.Combine("IconsAndLogos", filename).ToString(SlashMode.Forward); return AssetDatabase.LoadAssetAtPath(iconPath); } + + public static Texture2D GetTextureFromColor(Color color) + { + Color[] pix = new Color[1]; + pix[0] = color; + + Texture2D result = new Texture2D(1, 1); + result.SetPixels(pix); + result.Apply(); + + return result; + } } static class StreamExtensions diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/Services/AuthenticationService.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/Services/AuthenticationService.cs index c8564cfda..3d81553cf 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/Services/AuthenticationService.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/Services/AuthenticationService.cs @@ -1,5 +1,4 @@ using System; -using GitHub.Unity; namespace GitHub.Unity { diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs index 2d6b10117..ddccc1544 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using GitHub.Unity.Helpers; @@ -35,29 +36,24 @@ class BranchesView : Subview private const string DeleteBranchButton = "Delete"; private const string CancelButtonLabel = "Cancel"; - private bool showLocalBranches = true; - private bool showRemoteBranches = true; - - [NonSerialized] private List favorites = new List(); [NonSerialized] private int listID = -1; - [NonSerialized] private BranchTreeNode newNodeSelection; [NonSerialized] private BranchesMode targetMode; [NonSerialized] private bool favoritesHasChanged; - [NonSerialized] private bool branchesHaveChanged; + [NonSerialized] private List favoritesList; - [SerializeField] private BranchTreeNode activeBranchNode; - [SerializeField] private BranchTreeNode localRoot; + [SerializeField] private Tree treeLocals = new Tree(); + [SerializeField] private Tree treeRemotes = new Tree(); + [SerializeField] private Tree treeFavorites = new Tree(); [SerializeField] private BranchesMode mode = BranchesMode.Default; [SerializeField] private string newBranchName; - [SerializeField] private List remotes = new List(); [SerializeField] private Vector2 scroll; - [SerializeField] private BranchTreeNode selectedNode; - [SerializeField] private List favoritesList = new List(); + [SerializeField] private bool disableDelete; public override void InitializeView(IView parent) { base.InitializeView(parent); targetMode = mode; + Manager.CacheManager.SetupCache(BranchCache.Instance, Environment.Repository); } public override void OnEnable() @@ -67,7 +63,6 @@ public override void OnEnable() if (!Application.isPlaying) { favoritesHasChanged = true; - branchesHaveChanged = true; } } @@ -85,19 +80,21 @@ public override void OnDataUpdate() private void MaybeUpdateData() { + if (treeLocals == null || !treeLocals.IsInitialized) + { + BuildTree(BranchCache.Instance.LocalBranches, BranchCache.Instance.RemoteBranches); + } + if (favoritesHasChanged) { favoritesList = Manager.LocalSettings.Get(FavoritesSetting, new List()); favoritesHasChanged = false; } - if (branchesHaveChanged) - { - UpdateBranches(); - branchesHaveChanged = false; - } + disableDelete = treeLocals.SelectedNode == null || treeLocals.SelectedNode.IsFolder || treeLocals.SelectedNode.IsActive; } + public override void OnRepositoryChanged(IRepository oldRepository) { base.OnRepositoryChanged(oldRepository); @@ -105,6 +102,17 @@ public override void OnRepositoryChanged(IRepository oldRepository) AttachHandlers(Repository); } + public override void Refresh() + { + base.Refresh(); + RefreshBranchList(); + } + + public override void OnGUI() + { + Render(); + } + private void AttachHandlers(IRepository repository) { if (repository == null) @@ -126,7 +134,6 @@ private void DetachHandlers(IRepository repository) private void HandleDataUpdated() { - branchesHaveChanged = true; new ActionTask(TaskManager.Token, Redraw) { Affinity = TaskAffinity.UI }.Start(); } @@ -135,28 +142,18 @@ private void HandleDataUpdated(string obj) HandleDataUpdated(); } - public override void Refresh() - { - base.Refresh(); - UpdateBranches(); - } - - public void UpdateBranches() + private void RefreshBranchList() { - if (Repository == null) - return; - - BuildTree(Repository.LocalBranches, Repository.RemoteBranches); - } - - public override void OnGUI() - { - OnEmbeddedGUI(); + var localBranches = BranchCache.Instance.LocalBranches; + localBranches.Sort(CompareBranches); + var remoteBranches = BranchCache.Instance.RemoteBranches; + remoteBranches.Sort(CompareBranches); + BuildTree(localBranches, remoteBranches); } - public void OnEmbeddedGUI() + private void Render() { - scroll = GUILayout.BeginScrollView(scroll); + scroll = GUILayout.BeginScrollView(scroll, false, true); { listID = GUIUtility.GetControlID(FocusType.Keyboard); @@ -166,299 +163,44 @@ public void OnEmbeddedGUI() } GUILayout.EndHorizontal(); - GUILayout.BeginVertical(Styles.CommitFileAreaStyle); - { - // Favorites list - if (favorites.Count > 0) - { - GUILayout.Label(FavoritesTitle); - GUILayout.BeginHorizontal(); - { - GUILayout.BeginVertical(); - { - for (var index = 0; index < favorites.Count; ++index) - { - OnTreeNodeGUI(favorites[index]); - } - } - - GUILayout.EndVertical(); - } - - GUILayout.EndHorizontal(); - - GUILayout.Space(Styles.BranchListSeperation); - } - - // Local branches and "create branch" button - showLocalBranches = EditorGUILayout.Foldout(showLocalBranches, LocalTitle); - if (showLocalBranches) - { - GUILayout.BeginHorizontal(); - { - GUILayout.BeginVertical(); - { - OnTreeNodeChildrenGUI(localRoot); - } - GUILayout.EndVertical(); - } - GUILayout.EndHorizontal(); - } - - // Remotes - showRemoteBranches = EditorGUILayout.Foldout(showRemoteBranches, RemoteTitle); - if (showRemoteBranches) - { - GUILayout.BeginHorizontal(); - { - GUILayout.BeginVertical(); - for (var index = 0; index < remotes.Count; ++index) - { - var remote = remotes[index]; - GUILayout.Label(new GUIContent(remote.Name, Styles.FolderIcon), GUILayout.MaxHeight(EditorGUIUtility.singleLineHeight)); - - // Branches of the remote - GUILayout.BeginHorizontal(); - { - GUILayout.Space(Styles.TreeIndentation); - GUILayout.BeginVertical(); - { - OnTreeNodeChildrenGUI(remote.Root); - } - GUILayout.EndVertical(); - } - GUILayout.EndHorizontal(); - - GUILayout.Space(Styles.BranchListSeperation); - } - - GUILayout.EndVertical(); - } - GUILayout.EndHorizontal(); - } - - GUILayout.FlexibleSpace(); - } - GUILayout.EndVertical(); + var rect = GUILayoutUtility.GetLastRect(); + OnTreeGUI(new Rect(0f, rect.height + Styles.CommitAreaPadding, Position.width, Position.height - rect.height + Styles.CommitAreaPadding)); } - GUILayout.EndScrollView(); - - if (Event.current.type == EventType.Repaint) - { - // Effectuating selection - if (newNodeSelection != null) - { - selectedNode = newNodeSelection; - newNodeSelection = null; - GUIUtility.keyboardControl = listID; - Redraw(); - } - - // Effectuating mode switch - if (mode != targetMode) - { - mode = targetMode; - - if (mode == BranchesMode.Create) - { - selectedNode = activeBranchNode; - } - - Redraw(); - } - } - } - - private int CompareBranches(GitBranch a, GitBranch b) - { - if (IsFavorite(a.Name)) - { - return -1; - } - - if (IsFavorite(b.Name)) - { - return 1; - } - - if (a.Name.Equals("master")) - { - return -1; - } - - if (b.Name.Equals("master")) - { - return 1; - } - - return 0; - } - - private bool IsFavorite(string branchName) - { - return !String.IsNullOrEmpty(branchName) && favoritesList.Contains(branchName); } - private void BuildTree(IEnumerable local, IEnumerable remote) + private void BuildTree(List localBranches, List remoteBranches) { - //Clear the selected node - selectedNode = null; - - // Sort - var localBranches = new List(local); - var remoteBranches = new List(remote); localBranches.Sort(CompareBranches); remoteBranches.Sort(CompareBranches); - - // Prepare for tracking - var tracking = new List>(); - var localBranchNodes = new List(); - - // Prepare for updated favorites listing - favorites.Clear(); - - // Just build directly on the local root, keep track of active branch - localRoot = new BranchTreeNode("", NodeType.Folder, false); - for (var index = 0; index < localBranches.Count; ++index) - { - var branch = localBranches[index]; - var node = new BranchTreeNode(branch.Name, NodeType.LocalBranch, branch.IsActive); - localBranchNodes.Add(node); - - // Keep active node for quick reference - if (branch.IsActive) - { - activeBranchNode = node; - } - - // Add to tracking - if (!string.IsNullOrEmpty(branch.Tracking)) - { - var trackingIndex = !remoteBranches.Any() - ? -1 - : Enumerable.Range(0, remoteBranches.Count).FirstOrDefault(i => remoteBranches[i].Name.Equals(branch.Tracking)); - - if (trackingIndex > -1) - { - tracking.Add(new KeyValuePair(index, trackingIndex)); - } - } - - // Add to favorites - if (favoritesList.Contains(branch.Name)) - { - favorites.Add(node); - } - - // Build into tree - BuildTree(localRoot, node); - } - - // Maintain list of remotes before building their roots, ignoring active state - remotes.Clear(); - for (var index = 0; index < remoteBranches.Count; ++index) - { - var branch = remoteBranches[index]; - - // Remote name is always the first level - var remoteName = branch.Name.Substring(0, branch.Name.IndexOf('/')); - - // Get or create this remote - var remoteIndex = Enumerable.Range(1, remotes.Count + 1) - .FirstOrDefault(i => remotes.Count > i - 1 && remotes[i - 1].Name.Equals(remoteName)) - 1; - if (remoteIndex < 0) - { - remotes.Add(new Remote { Name = remoteName, Root = new BranchTreeNode("", NodeType.Folder, false) }); - remoteIndex = remotes.Count - 1; - } - - // Create the branch - var node = new BranchTreeNode(branch.Name, NodeType.RemoteBranch, false) { - Label = branch.Name.Substring(remoteName.Length + 1) - }; - - // Establish tracking link - for (var trackingIndex = 0; trackingIndex < tracking.Count; ++trackingIndex) - { - var pair = tracking[trackingIndex]; - - if (pair.Value == index) - { - localBranchNodes[pair.Key].Tracking = node; - } - } - - // Add to favorites - if (favoritesList.Contains(branch.Name)) - { - favorites.Add(node); - } - - // Build on the root of the remote, just like with locals - BuildTree(remotes[remoteIndex].Root, node); - } - + treeLocals = new Tree(); + treeLocals.ActiveNodeIcon = Styles.ActiveBranchIcon; + treeLocals.NodeIcon = Styles.BranchIcon; + treeLocals.RootFolderIcon = Styles.RootFolderIcon; + treeLocals.FolderIcon = Styles.FolderIcon; + + treeRemotes = new Tree(); + treeRemotes.ActiveNodeIcon = Styles.ActiveBranchIcon; + treeRemotes.NodeIcon = Styles.BranchIcon; + treeRemotes.RootFolderIcon = Styles.RootFolderIcon; + treeRemotes.FolderIcon = Styles.FolderIcon; + + treeLocals.Load(localBranches.Cast(), LocalTitle); + treeRemotes.Load(remoteBranches.Cast(), RemoteTitle); Redraw(); } - private void BuildTree(BranchTreeNode parent, BranchTreeNode child) - { - var firstSplit = child.Label.IndexOf('/'); - - // No nesting needed here, this is just a straight add - if (firstSplit < 0) - { - parent.Children.Add(child); - return; - } - - // Get or create the next folder level - var folderName = child.Label.Substring(0, firstSplit); - var folder = parent.Children.FirstOrDefault(f => f.Label.Equals(folderName)); - if (folder == null) - { - folder = new BranchTreeNode("", NodeType.Folder, false) { Label = folderName }; - parent.Children.Add(folder); - } - - // Pop the folder name from the front of the child label and add it to the folder - child.Label = child.Label.Substring(folderName.Length + 1); - BuildTree(folder, child); - } - - private void SetFavorite(BranchTreeNode branch, bool favorite) - { - if (string.IsNullOrEmpty(branch.Name)) - { - return; - } - - if (!favorite) - { - favorites.Remove(branch); - Manager.LocalSettings.Set(FavoritesSetting, favorites.Select(x => x.Name).ToList()); - } - else - { - favorites.Remove(branch); - favorites.Add(branch); - Manager.LocalSettings.Set(FavoritesSetting, favorites.Select(x => x.Name).ToList()); - } - } - private void OnButtonBarGUI() { if (mode == BranchesMode.Default) { // Delete button // If the current branch is selected, then do not enable the Delete button - var disableDelete = selectedNode == null || selectedNode.Type == NodeType.Folder || activeBranchNode == selectedNode; EditorGUI.BeginDisabledGroup(disableDelete); { if (GUILayout.Button(DeleteBranchButton, EditorStyles.miniButton, GUILayout.ExpandWidth(false))) { - var selectedBranchName = selectedNode.Name; + var selectedBranchName = treeLocals.SelectedNode.Name; var dialogMessage = string.Format(DeleteBranchMessageFormatString, selectedBranchName); if (EditorUtility.DisplayDialog(DeleteBranchTitle, dialogMessage, DeleteBranchButton, CancelButtonLabel)) { @@ -482,8 +224,8 @@ private void OnButtonBarGUI() { var createBranch = false; var cancelCreate = false; - var cannotCreate = selectedNode == null || - selectedNode.Type == NodeType.Folder || + var cannotCreate = treeLocals.SelectedNode == null || + treeLocals.SelectedNode.IsFolder || !Validation.IsBranchNameValid(newBranchName); // Create on return/enter or cancel on escape @@ -529,7 +271,7 @@ private void OnButtonBarGUI() // Effectuate create if (createBranch) { - GitClient.CreateBranch(newBranchName, selectedNode.Name) + GitClient.CreateBranch(newBranchName, treeLocals.SelectedNode.Name) .FinallyInUI((success, e) => { if (success) { @@ -560,191 +302,583 @@ private void OnButtonBarGUI() } } - private void OnTreeNodeGUI(BranchTreeNode node) + private void OnTreeGUI(Rect rect) + { + if (!treeLocals.IsInitialized) + RefreshBranchList(); + + if (treeLocals.FolderStyle == null) + { + treeLocals.FolderStyle = Styles.Foldout; + treeLocals.TreeNodeStyle = Styles.TreeNode; + treeLocals.ActiveTreeNodeStyle = Styles.TreeNodeActive; + treeRemotes.FolderStyle = Styles.Foldout; + treeRemotes.TreeNodeStyle = Styles.TreeNode; + treeRemotes.ActiveTreeNodeStyle = Styles.TreeNodeActive; + } + + var treeHadFocus = treeLocals.SelectedNode != null; + + rect = treeLocals.Render(rect, _ => { }, node => + { + if (EditorUtility.DisplayDialog(ConfirmSwitchTitle, String.Format(ConfirmSwitchMessage, node.Name), ConfirmSwitchOK, + ConfirmSwitchCancel)) + { + GitClient.SwitchBranch(node.Name) + .FinallyInUI((success, e) => + { + if (success) + { + Redraw(); + } + else + { + EditorUtility.DisplayDialog(Localization.SwitchBranchTitle, + String.Format(Localization.SwitchBranchFailedDescription, node.Name), + Localization.Ok); + } + }).Start(); + } + }); + + if (treeHadFocus && treeLocals.SelectedNode == null) + treeRemotes.Focus(); + else if (!treeHadFocus && treeLocals.SelectedNode != null) + treeRemotes.Blur(); + + if (treeLocals.RequiresRepaint) + Redraw(); + + treeHadFocus = treeRemotes.SelectedNode != null; + + rect.y += Styles.TreePadding; + + treeRemotes.Render(rect, _ => {}, selectedNode => + { + var indexOfFirstSlash = selectedNode.Name.IndexOf('/'); + var originName = selectedNode.Name.Substring(0, indexOfFirstSlash); + var branchName = selectedNode.Name.Substring(indexOfFirstSlash + 1); + + if (Repository.LocalBranches.Any(localBranch => localBranch.Name == branchName)) + { + EditorUtility.DisplayDialog(WarningCheckoutBranchExistsTitle, + String.Format(WarningCheckoutBranchExistsMessage, branchName), + WarningCheckoutBranchExistsOK); + } + else + { + var confirmCheckout = EditorUtility.DisplayDialog(ConfirmCheckoutBranchTitle, + String.Format(ConfirmCheckoutBranchMessage, selectedNode.Name, originName), + ConfirmCheckoutBranchOK, + ConfirmCheckoutBranchCancel); + + if (confirmCheckout) + { + GitClient + .CreateBranch(branchName, selectedNode.Name) + .FinallyInUI((success, e) => + { + if (success) + { + Redraw(); + } + else + { + EditorUtility.DisplayDialog(Localization.SwitchBranchTitle, + String.Format(Localization.SwitchBranchFailedDescription, selectedNode.Name), + Localization.Ok); + } + }) + .Start(); + } + } + }); + + if (treeHadFocus && treeRemotes.SelectedNode == null) + { + treeLocals.Focus(); + } + else if (!treeHadFocus && treeRemotes.SelectedNode != null) + { + treeLocals.Blur(); + } + + if (treeRemotes.RequiresRepaint) + Redraw(); + } + + private int CompareBranches(GitBranch a, GitBranch b) { - // Content, style, and rects + //if (IsFavorite(a.Name)) + //{ + // return -1; + //} - Texture2D iconContent; + //if (IsFavorite(b.Name)) + //{ + // return 1; + //} - if (node.Active == true) + if (a.Name.Equals("master")) { - iconContent = Styles.ActiveBranchIcon; + return -1; } - else + + if (b.Name.Equals("master")) { - if (node.Children.Count > 0) + return 1; + } + + return a.Name.CompareTo(b.Name); + } + + //private bool IsFavorite(string branchName) + //{ + // return !String.IsNullOrEmpty(branchName) && favoritesList.Contains(branchName); + //} + + //private void SetFavorite(TreeNode branch, bool favorite) + //{ + // if (string.IsNullOrEmpty(branch.Name)) + // { + // return; + // } + + // if (!favorite) + // { + // favorites.Remove(branch); + // Manager.LocalSettings.Set(FavoritesSetting, favorites.Select(x => x.Name).ToList()); + // } + // else + // { + // favorites.Remove(branch); + // favorites.Add(branch); + // Manager.LocalSettings.Set(FavoritesSetting, favorites.Select(x => x.Name).ToList()); + // } + //} + + + [Serializable] + public class Tree + { + [SerializeField] private List nodes = new List(); + [SerializeField] private TreeNode selectedNode = null; + [SerializeField] private TreeNode activeNode = null; + [SerializeField] public float ItemHeight = EditorGUIUtility.singleLineHeight; + [SerializeField] public float ItemSpacing = EditorGUIUtility.standardVerticalSpacing; + [SerializeField] public float Indentation = 12f; + [SerializeField] public Rect Margin = new Rect(); + [SerializeField] public Rect Padding = new Rect(); + [SerializeField] private List foldersKeys = new List(); + [SerializeField] public Texture2D ActiveNodeIcon; + [SerializeField] public Texture2D NodeIcon; + [SerializeField] public Texture2D FolderIcon; + [SerializeField] public Texture2D RootFolderIcon; + [SerializeField] public GUIStyle FolderStyle; + [SerializeField] public GUIStyle TreeNodeStyle; + [SerializeField] public GUIStyle ActiveTreeNodeStyle; + + [NonSerialized] + private Stack indents = new Stack(); + [NonSerialized] + private Hashtable folders; + + public bool IsInitialized { get { return nodes != null && nodes.Count > 0 && !String.IsNullOrEmpty(nodes[0].Name); } } + public bool RequiresRepaint { get; private set; } + + public TreeNode SelectedNode + { + get { - iconContent = Styles.FolderIcon; + if (selectedNode != null && String.IsNullOrEmpty(selectedNode.Name)) + selectedNode = null; + return selectedNode; } - else + private set + { + selectedNode = value; + } + } + + public TreeNode ActiveNode { get { return activeNode; } } + + private Hashtable Folders + { + get { - iconContent = Styles.BranchIcon; + if (folders == null) + { + folders = new Hashtable(); + for (int i = 0; i < foldersKeys.Count; i++) + { + folders.Add(foldersKeys[i], null); + } + } + return folders; } } - var content = new GUIContent(node.Label, iconContent); - var style = node.Active ? Styles.BoldLabel : Styles.Label; - var rect = GUILayoutUtility.GetRect(content, style, GUILayout.MaxHeight(EditorGUIUtility.singleLineHeight)); - var clickRect = new Rect(0f, rect.y, Position.width, rect.height); - var favoriteRect = new Rect(clickRect.xMax - clickRect.height * 2f, clickRect.y, clickRect.height, clickRect.height); + public void Load(IEnumerable data, string title) + { + foldersKeys.Clear(); + Folders.Clear(); + nodes.Clear(); + + var titleNode = new TreeNode() + { + Name = title, + Label = title, + Level = 0, + IsFolder = true + }; + titleNode.Load(); + nodes.Add(titleNode); + + foreach (var d in data) + { + var parts = d.Name.Split('/'); + for (int i = 0; i < parts.Length; i++) + { + var label = parts[i]; + var name = String.Join("/", parts, 0, i + 1); + var isFolder = i < parts.Length - 1; + var alreadyExists = Folders.ContainsKey(name); + if (!alreadyExists) + { + var node = new TreeNode() + { + Name = name, + IsActive = d.IsActive, + Label = label, + Level = i + 1, + IsFolder = isFolder + }; + + if (node.IsActive) + { + activeNode = node; + node.Icon = ActiveNodeIcon; + } + else if (node.IsFolder) + { + if (node.Level == 1) + node.Icon = RootFolderIcon; + else + node.Icon = FolderIcon; + } + else + { + node.Icon = NodeIcon; + } + + node.Load(); - var selected = selectedNode == node; - var keyboardFocus = GUIUtility.keyboardControl == listID; + nodes.Add(node); + if (isFolder) + { + Folders.Add(name, null); + } + } + } + } + foldersKeys = Folders.Keys.Cast().ToList(); + } - // Selection highlight and favorite toggle - if (selected) + public Rect Render(Rect rect, Action singleClick = null, Action doubleClick = null) { - if (Event.current.type == EventType.Repaint) + RequiresRepaint = false; + rect = new Rect(0f, rect.y, rect.width, ItemHeight); + + var titleNode = nodes[0]; + bool selectionChanged = titleNode.Render(rect, 0f, selectedNode == titleNode, FolderStyle, TreeNodeStyle, ActiveTreeNodeStyle); + + if (selectionChanged) { - style.Draw(clickRect, GUIContent.none, false, false, true, keyboardFocus); + ToggleNodeVisibility(0, titleNode); } - if (node.Type != NodeType.Folder) + RequiresRepaint = HandleInput(rect, titleNode, 0); + rect.y += ItemHeight + ItemSpacing; + + Indent(); + + int level = 1; + for (int i = 1; i < nodes.Count; i++) { - var favorite = IsFavorite(node.Name); - if (Event.current.type == EventType.Repaint) + var node = nodes[i]; + + if (node.Level > level && !node.IsHidden) { - GUI.DrawTexture(favoriteRect, favorite ? Styles.FavoriteIconOn : Styles.FavoriteIconOff); + Indent(); } - else if (Event.current.type == EventType.MouseDown && favoriteRect.Contains(Event.current.mousePosition)) + + var changed = node.Render(rect, Indentation, selectedNode == node, FolderStyle, TreeNodeStyle, ActiveTreeNodeStyle); + + if (node.IsFolder && changed) { - SetFavorite(node, !favorite); - Event.current.Use(); + // toggle visibility for all the nodes under this one + ToggleNodeVisibility(i, node); + } + + if (node.Level < level) + { + for (; node.Level > level && indents.Count > 1; level--) + { + Unindent(); + } + } + level = node.Level; + + if (!node.IsHidden) + { + RequiresRepaint = HandleInput(rect, node, i, singleClick, doubleClick); + rect.y += ItemHeight + ItemSpacing; } } + + Unindent(); + + foldersKeys = Folders.Keys.Cast().ToList(); + return rect; } - // Favorite status - else if (Event.current.type == EventType.Repaint && node.Type != NodeType.Folder && IsFavorite(node.Name)) + + public void Focus() { - GUI.DrawTexture(favoriteRect, Styles.FavoriteIconOn); + bool selectionChanged = false; + if (Event.current.type == EventType.KeyDown) + { + int directionY = Event.current.keyCode == KeyCode.UpArrow ? -1 : Event.current.keyCode == KeyCode.DownArrow ? 1 : 0; + int directionX = Event.current.keyCode == KeyCode.LeftArrow ? -1 : Event.current.keyCode == KeyCode.RightArrow ? 1 : 0; + if (directionY != 0 || directionX != 0) + { + if (directionY < 0 || directionY < 0) + { + SelectedNode = nodes[nodes.Count - 1]; + selectionChanged = true; + Event.current.Use(); + } + else if (directionY > 0 || directionX > 0) + { + SelectedNode = nodes[0]; + selectionChanged = true; + Event.current.Use(); + } + } + } + RequiresRepaint = selectionChanged; } - // The actual icon and label - if (Event.current.type == EventType.Repaint) + public void Blur() { - style.Draw(rect, content, false, false, selected, keyboardFocus); + SelectedNode = null; + RequiresRepaint = true; } - // Children - GUILayout.BeginHorizontal(); + private int ToggleNodeVisibility(int idx, TreeNode rootNode) { - GUILayout.Space(Styles.TreeIndentation); - GUILayout.BeginVertical(); + var rootNodeLevel = rootNode.Level; + rootNode.IsCollapsed = !rootNode.IsCollapsed; + idx++; + for (; idx < nodes.Count && nodes[idx].Level > rootNodeLevel; idx++) + { + nodes[idx].IsHidden = rootNode.IsCollapsed; + if (nodes[idx].IsFolder && !rootNode.IsCollapsed && nodes[idx].IsCollapsed) + { + var level = nodes[idx].Level; + for (idx++; idx < nodes.Count && nodes[idx].Level > level; idx++) { } + idx--; + } + } + if (SelectedNode != null && SelectedNode.IsHidden) { - OnTreeNodeChildrenGUI(node); + SelectedNode = rootNode; } - GUILayout.EndVertical(); + return idx; } - GUILayout.EndHorizontal(); - // Click selection of the node as well as branch switch - if (Event.current.type == EventType.MouseDown && clickRect.Contains(Event.current.mousePosition)) + private bool HandleInput(Rect rect, TreeNode currentNode, int index, Action singleClick = null, Action doubleClick = null) { - newNodeSelection = node; - Event.current.Use(); - - if (Event.current.clickCount > 1 && mode == BranchesMode.Default) + bool selectionChanged = false; + var clickRect = new Rect(0f, rect.y, rect.width, rect.height); + if (Event.current.type == EventType.MouseDown && clickRect.Contains(Event.current.mousePosition)) { - if (node.Type == NodeType.LocalBranch) + Event.current.Use(); + SelectedNode = currentNode; + selectionChanged = true; + var clickCount = Event.current.clickCount; + if (clickCount == 1 && singleClick != null) { - if (EditorUtility.DisplayDialog(ConfirmSwitchTitle, String.Format(ConfirmSwitchMessage, node.Name), ConfirmSwitchOK, ConfirmSwitchCancel)) - { - GitClient.SwitchBranch(node.Name) - .FinallyInUI((success, e) => - { - if (success) - { - Redraw(); - } - else - { - EditorUtility.DisplayDialog(Localization.SwitchBranchTitle, - String.Format(Localization.SwitchBranchFailedDescription, node.Name), - Localization.Ok); - } - }).Start(); - } + singleClick(currentNode); } - else if (node.Type == NodeType.RemoteBranch) + if (clickCount > 1 && doubleClick != null) { - var indexOfFirstSlash = selectedNode.Name.IndexOf('/'); - var originName = selectedNode.Name.Substring(0, indexOfFirstSlash); - var branchName = selectedNode.Name.Substring(indexOfFirstSlash + 1); + doubleClick(currentNode); + } + } - if (Repository.LocalBranches.Any(localBranch => localBranch.Name == branchName)) + // Keyboard navigation if this child is the current selection + if (currentNode == selectedNode && Event.current.type == EventType.KeyDown) + { + int directionY = Event.current.keyCode == KeyCode.UpArrow ? -1 : Event.current.keyCode == KeyCode.DownArrow ? 1 : 0; + int directionX = Event.current.keyCode == KeyCode.LeftArrow ? -1 : Event.current.keyCode == KeyCode.RightArrow ? 1 : 0; + if (directionY != 0 || directionX != 0) + { + if (directionY > 0) { - EditorUtility.DisplayDialog(WarningCheckoutBranchExistsTitle, - String.Format(WarningCheckoutBranchExistsMessage, branchName), - WarningCheckoutBranchExistsOK); + selectionChanged = SelectNext(index, false) != index; } - else + else if (directionY < 0) { - var confirmCheckout = EditorUtility.DisplayDialog(ConfirmCheckoutBranchTitle, - String.Format(ConfirmCheckoutBranchMessage, node.Name, originName), - ConfirmCheckoutBranchOK, ConfirmCheckoutBranchCancel); - - if (confirmCheckout) + selectionChanged = SelectPrevious(index, false) != index; + } + else if (directionX > 0) + { + if (currentNode.IsFolder && currentNode.IsCollapsed) { - GitClient.CreateBranch(branchName, selectedNode.Name) - .FinallyInUI((success, e) => - { - if (success) - { - Redraw(); - } - else - { - EditorUtility.DisplayDialog(Localization.SwitchBranchTitle, - String.Format(Localization.SwitchBranchFailedDescription, node.Name), - Localization.Ok); - } - }).Start(); + ToggleNodeVisibility(index, currentNode); + Event.current.Use(); + } + else + { + selectionChanged = SelectNext(index, true) != index; + } + } + else if (directionX < 0) + { + if (currentNode.IsFolder && !currentNode.IsCollapsed) + { + ToggleNodeVisibility(index, currentNode); + Event.current.Use(); + } + else + { + selectionChanged = SelectPrevious(index, true) != index; } } } } + return selectionChanged; + } + + private int SelectNext(int index, bool foldersOnly) + { + for (index++; index < nodes.Count; index++) + { + if (nodes[index].IsHidden) + continue; + if (!nodes[index].IsFolder && foldersOnly) + continue; + break; + } + + if (index < nodes.Count) + { + SelectedNode = nodes[index]; + Event.current.Use(); + } + else + { + SelectedNode = null; + } + return index; + } + + private int SelectPrevious(int index, bool foldersOnly) + { + for (index--; index >= 0; index--) + { + if (nodes[index].IsHidden) + continue; + if (!nodes[index].IsFolder && foldersOnly) + continue; + break; + } + + if (index >= 0) + { + SelectedNode = nodes[index]; + Event.current.Use(); + } + else + { + SelectedNode = null; + } + return index; + } + + private void Indent() + { + indents.Push(true); + } + + private void Unindent() + { + indents.Pop(); } } - private void OnTreeNodeChildrenGUI(BranchTreeNode node) + [Serializable] + public class TreeNode { - if (node == null || node.Children == null) + public string Name; + public string Label; + public int Level; + public bool IsFolder; + public bool IsCollapsed; + public bool IsHidden; + public bool IsActive; + public GUIContent content; + public Texture2D Icon; + + public void Load() { - return; + content = new GUIContent(Label, Icon); } - for (var index = 0; index < node.Children.Count; ++index) + public bool Render(Rect rect, float indentation, bool isSelected, GUIStyle folderStyle, GUIStyle nodeStyle, GUIStyle activeNodeStyle) { - // The actual GUI of the child - OnTreeNodeGUI(node.Children[index]); + if (IsHidden) + return false; - // Keyboard navigation if this child is the current selection - if (selectedNode == node.Children[index] && GUIUtility.keyboardControl == listID && Event.current.type == EventType.KeyDown) + GUIStyle style; + if (IsFolder) + { + style = folderStyle; + } + else { - int directionY = Event.current.keyCode == KeyCode.UpArrow ? -1 : Event.current.keyCode == KeyCode.DownArrow ? 1 : 0, - directionX = Event.current.keyCode == KeyCode.LeftArrow ? -1 : Event.current.keyCode == KeyCode.RightArrow ? 1 : 0; + style = IsActive ? activeNodeStyle : nodeStyle; + } - if (directionY < 0 && index > 0) - { - newNodeSelection = node.Children[index - 1]; - Event.current.Use(); - } - else if (directionY > 0 && index < node.Children.Count - 1) - { - newNodeSelection = node.Children[index + 1]; - Event.current.Use(); - } - else if (directionX < 0) - { - newNodeSelection = node; - Event.current.Use(); - } - else if (directionX > 0 && node.Children[index].Children.Count > 0) + bool changed = false; + var fillRect = rect; + var nodeRect = new Rect(Level * indentation, rect.y, rect.width, rect.height); + + if (Event.current.type == EventType.repaint) + { + nodeStyle.Draw(fillRect, "", false, false, false, isSelected); + if (IsFolder) + style.Draw(nodeRect, content, false, false, !IsCollapsed, isSelected); + else { - newNodeSelection = node.Children[index].Children[0]; - Event.current.Use(); + style.Draw(nodeRect, content, false, false, false, isSelected); } } + + if (IsFolder) + { + EditorGUI.BeginChangeCheck(); + GUI.Toggle(nodeRect, !IsCollapsed, "", GUIStyle.none); + changed = EditorGUI.EndChangeCheck(); + } + + return changed; + } + + public override string ToString() + { + return String.Format("name:{0} label:{1} level:{2} isFolder:{3} isCollapsed:{4} isHidden:{5} isActive:{6}", + Name, Label, Level, IsFolder, IsCollapsed, IsHidden, IsActive); } } @@ -765,34 +899,5 @@ private enum BranchesMode Default, Create } - - [Serializable] - private class BranchTreeNode - { - private readonly List children = new List(); - - public string Label; - public BranchTreeNode Tracking; - - public BranchTreeNode(string name, NodeType type, bool active) - { - Label = Name = name; - Type = type; - Active = active; - } - - public string Name { get; private set; } - public NodeType Type { get; private set; } - public bool Active { get; private set; } - - public IList Children { get { return children; } } - } - - private struct Remote - { - // TODO: Pull in and store more data from GitListRemotesTask - public string Name; - public BranchTreeNode Root; - } } } From 7cf80c05c62c5d3dd10b2a49fc0cc8994aa56ebf Mon Sep 17 00:00:00 2001 From: Stanley Goldman Date: Fri, 20 Oct 2017 11:29:13 -0400 Subject: [PATCH 06/64] treeLocals is never null --- .../Assets/Editor/GitHub.Unity/UI/BranchesView.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs index ddccc1544..705f16137 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs @@ -80,7 +80,7 @@ public override void OnDataUpdate() private void MaybeUpdateData() { - if (treeLocals == null || !treeLocals.IsInitialized) + if (!treeLocals.IsInitialized) { BuildTree(BranchCache.Instance.LocalBranches, BranchCache.Instance.RemoteBranches); } From 99c4ed264849e7b4218558b3863ca4185a5cc155 Mon Sep 17 00:00:00 2001 From: Stanley Goldman Date: Fri, 20 Oct 2017 11:29:33 -0400 Subject: [PATCH 07/64] Adding branchesHasChanged flag to signal rebuild of tree --- .../Assets/Editor/GitHub.Unity/UI/BranchesView.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs index 705f16137..efe7c0174 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs @@ -49,6 +49,9 @@ class BranchesView : Subview [SerializeField] private Vector2 scroll; [SerializeField] private bool disableDelete; + [NonSerialized] private bool branchesHasChanged; + + public override void InitializeView(IView parent) { base.InitializeView(parent); @@ -80,7 +83,7 @@ public override void OnDataUpdate() private void MaybeUpdateData() { - if (!treeLocals.IsInitialized) + if (!treeLocals.IsInitialized || branchesHasChanged) { BuildTree(BranchCache.Instance.LocalBranches, BranchCache.Instance.RemoteBranches); } @@ -134,7 +137,10 @@ private void DetachHandlers(IRepository repository) private void HandleDataUpdated() { - new ActionTask(TaskManager.Token, Redraw) { Affinity = TaskAffinity.UI }.Start(); + new ActionTask(TaskManager.Token, () => { + branchesHasChanged = true; + Redraw(); + }) { Affinity = TaskAffinity.UI }.Start(); } private void HandleDataUpdated(string obj) From 13fcec07c4b012b20e9355ac01a461b5ef9fb649 Mon Sep 17 00:00:00 2001 From: Stanley Goldman Date: Fri, 20 Oct 2017 11:46:56 -0400 Subject: [PATCH 08/64] Adding missing reset of branchesHasChanged --- src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs index efe7c0174..74dbd2708 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs @@ -85,6 +85,7 @@ private void MaybeUpdateData() { if (!treeLocals.IsInitialized || branchesHasChanged) { + branchesHasChanged = false; BuildTree(BranchCache.Instance.LocalBranches, BranchCache.Instance.RemoteBranches); } From 466507f04fd1258ec91c79c1b079ddba1d3785fe Mon Sep 17 00:00:00 2001 From: Stanley Goldman Date: Fri, 20 Oct 2017 12:18:12 -0400 Subject: [PATCH 09/64] Removing stray newline --- src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs index 74dbd2708..296d61a07 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs @@ -51,7 +51,6 @@ class BranchesView : Subview [NonSerialized] private bool branchesHasChanged; - public override void InitializeView(IView parent) { base.InitializeView(parent); From 4cabc1009a80a79314a3fdb8f0cd766a5662ab08 Mon Sep 17 00:00:00 2001 From: Stanley Goldman Date: Fri, 20 Oct 2017 15:59:09 -0400 Subject: [PATCH 10/64] Removing initialize of BranchCache from BranchView --- src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs index 296d61a07..e87d5e492 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs @@ -55,7 +55,6 @@ public override void InitializeView(IView parent) { base.InitializeView(parent); targetMode = mode; - Manager.CacheManager.SetupCache(BranchCache.Instance, Environment.Repository); } public override void OnEnable() From 8921bd886d588ac68df50b07ef85e8fbecd56312 Mon Sep 17 00:00:00 2001 From: Stanley Goldman Date: Wed, 1 Nov 2017 14:45:40 -0400 Subject: [PATCH 11/64] Fixes after merge --- .../Assets/Editor/GitHub.Unity/UI/BranchesView.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs index 94f047b4a..b5a670d41 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs @@ -156,7 +156,7 @@ private void Render() } GUILayout.EndHorizontal(); - GUILayout.Label(FavoritesTitle); + var rect = GUILayoutUtility.GetLastRect(); OnTreeGUI(new Rect(0f, rect.height + Styles.CommitAreaPadding, Position.width, Position.height - rect.height + Styles.CommitAreaPadding)); } GUILayout.EndScrollView(); From 733df51df402b8685df8f742ed6600064c1187dc Mon Sep 17 00:00:00 2001 From: Stanley Goldman Date: Wed, 1 Nov 2017 14:56:25 -0400 Subject: [PATCH 12/64] More fixes after the merge --- .../Editor/GitHub.Unity/UI/BranchesView.cs | 24 ++++++------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs index ee5d08262..6f0baa8f8 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs @@ -49,8 +49,8 @@ class BranchesView : Subview [SerializeField] private CacheUpdateEvent branchUpdateEvent; [NonSerialized] private bool branchCacheHasUpdate; - [SerializeField] private GitBranch[] localBranches; - [SerializeField] private GitBranch[] remoteBranches; + [SerializeField] private List localBranches; + [SerializeField] private List remoteBranches; public override void InitializeView(IView parent) { @@ -96,21 +96,14 @@ private void MaybeUpdateData() { branchCacheHasUpdate = false; - localBranches = Repository.LocalBranches.ToArray(); - remoteBranches = Repository.RemoteBranches.ToArray(); + localBranches = Repository.LocalBranches.ToList(); + remoteBranches = Repository.RemoteBranches.ToList(); BuildTree(localBranches, remoteBranches); } disableDelete = treeLocals.SelectedNode == null || treeLocals.SelectedNode.IsFolder || treeLocals.SelectedNode.IsActive; - - } - - public override void Refresh() - { - base.Refresh(); - RefreshBranchList(); } public override void OnGUI() @@ -168,8 +161,8 @@ private void BuildTree(List localBranches, List remoteBran treeRemotes.RootFolderIcon = Styles.RootFolderIcon; treeRemotes.FolderIcon = Styles.FolderIcon; - treeLocals.Load(localBranches.Cast(), LocalTitle); - treeRemotes.Load(remoteBranches.Cast(), RemoteTitle); + treeLocals.Load(localBranches, LocalTitle); + treeRemotes.Load(remoteBranches, RemoteTitle); Redraw(); } @@ -287,9 +280,6 @@ private void OnButtonBarGUI() private void OnTreeGUI(Rect rect) { - if (!treeLocals.IsInitialized) - RefreshBranchList(); - if (treeLocals.FolderStyle == null) { treeLocals.FolderStyle = Styles.Foldout; @@ -501,7 +491,7 @@ private Hashtable Folders } } - public void Load(IEnumerable data, string title) + public void Load(IEnumerable data, string title) { foldersKeys.Clear(); Folders.Clear(); From 72f6432a848c2e7aec932dd77de1ef477bf48343 Mon Sep 17 00:00:00 2001 From: Stanley Goldman Date: Thu, 2 Nov 2017 16:47:34 -0400 Subject: [PATCH 13/64] Removing some unused references to favorites --- .../Assets/Editor/GitHub.Unity/UI/BranchesView.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs index 1c2edfeb3..89a0577ea 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs @@ -36,11 +36,9 @@ class BranchesView : Subview [NonSerialized] private int listID = -1; [NonSerialized] private BranchesMode targetMode; - [NonSerialized] private List favoritesList; [SerializeField] private Tree treeLocals = new Tree(); [SerializeField] private Tree treeRemotes = new Tree(); - [SerializeField] private Tree treeFavorites = new Tree(); [SerializeField] private BranchesMode mode = BranchesMode.Default; [SerializeField] private string newBranchName; [SerializeField] private Vector2 scroll; From daed451099158811cf68b104effe15f3397c8eda Mon Sep 17 00:00:00 2001 From: Stanley Goldman Date: Thu, 2 Nov 2017 16:48:19 -0400 Subject: [PATCH 14/64] Removing unused function parameters --- .../Assets/Editor/GitHub.Unity/UI/BranchesView.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs index 89a0577ea..9d37417a8 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs @@ -101,8 +101,7 @@ private void MaybeUpdateData() localBranches = Repository.LocalBranches.ToList(); remoteBranches = Repository.RemoteBranches.ToList(); - - BuildTree(localBranches, remoteBranches); + BuildTree(); } disableDelete = treeLocals.SelectedNode == null || treeLocals.SelectedNode.IsFolder || treeLocals.SelectedNode.IsActive; @@ -147,7 +146,7 @@ private void Render() GUILayout.EndScrollView(); } - private void BuildTree(List localBranches, List remoteBranches) + private void BuildTree() { localBranches.Sort(CompareBranches); remoteBranches.Sort(CompareBranches); From 4d44b461859569dbaa20b33a5ad5fdd070c5698e Mon Sep 17 00:00:00 2001 From: Stanley Goldman Date: Fri, 3 Nov 2017 11:37:51 -0400 Subject: [PATCH 15/64] Code cleanup --- .../Assets/Editor/GitHub.Unity/UI/BranchesView.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs index 9d37417a8..24864879a 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs @@ -452,10 +452,8 @@ public class Tree [SerializeField] public GUIStyle TreeNodeStyle; [SerializeField] public GUIStyle ActiveTreeNodeStyle; - [NonSerialized] - private Stack indents = new Stack(); - [NonSerialized] - private Hashtable folders; + [NonSerialized] private Stack indents = new Stack(); + [NonSerialized] private Hashtable folders; public bool IsInitialized { get { return nodes != null && nodes.Count > 0 && !String.IsNullOrEmpty(nodes[0].Name); } } public bool RequiresRepaint { get; private set; } From 4a3db62c538d8f880f0bbb4e19c927a11daa72b4 Mon Sep 17 00:00:00 2001 From: Stanley Goldman Date: Fri, 3 Nov 2017 11:38:18 -0400 Subject: [PATCH 16/64] Preventing render if there are no nodes --- .../Assets/Editor/GitHub.Unity/UI/BranchesView.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs index 24864879a..2460f92f2 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs @@ -558,6 +558,9 @@ public void Load(IEnumerable data, string title) public Rect Render(Rect rect, Action singleClick = null, Action doubleClick = null) { + if (!nodes.Any()) + return rect; + RequiresRepaint = false; rect = new Rect(0f, rect.y, rect.width, ItemHeight); From 0b96c44397ad53e86f4b385f913346bd9fbacedd Mon Sep 17 00:00:00 2001 From: Stanley Goldman Date: Mon, 6 Nov 2017 13:43:48 -0500 Subject: [PATCH 17/64] Fixes needed after merge --- .../Assets/Editor/GitHub.Unity/UI/BranchesView.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs index c91b4a855..43535e72a 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs @@ -56,14 +56,14 @@ public override void InitializeView(IView parent) targetMode = mode; } - private void Repository_BranchCacheUpdated(CacheUpdateEvent cacheUpdateEvent) + private void RepositoryOnLocalAndRemoteBranchListChanged(CacheUpdateEvent cacheUpdateEvent) { - if (!branchUpdateEvent.Equals(cacheUpdateEvent)) + if (!lastLocalAndRemoteBranchListChangedEvent.Equals(cacheUpdateEvent)) { new ActionTask(TaskManager.Token, () => { - branchUpdateEvent = cacheUpdateEvent; - branchCacheHasUpdate = true; + lastLocalAndRemoteBranchListChangedEvent = cacheUpdateEvent; + localAndRemoteBranchListHasUpdate = true; Redraw(); }) { Affinity = TaskAffinity.UI }.Start(); From 98ca39f8871f4fe0b17fa6d6f1f6b18191e6d387 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Wed, 15 Nov 2017 17:07:29 +0100 Subject: [PATCH 18/64] Use ITreeData to represent data so that the Tree object can be reused in other views --- .../Assets/Editor/GitHub.Unity/UI/BranchesView.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs index 43535e72a..9a4608740 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs @@ -162,8 +162,8 @@ private void BuildTree() treeRemotes.RootFolderIcon = Styles.RootFolderIcon; treeRemotes.FolderIcon = Styles.FolderIcon; - treeLocals.Load(localBranches, LocalTitle); - treeRemotes.Load(remoteBranches, RemoteTitle); + treeLocals.Load(localBranches.Cast(), LocalTitle); + treeRemotes.Load(remoteBranches.Cast(), RemoteTitle); Redraw(); } @@ -490,7 +490,7 @@ private Hashtable Folders } } - public void Load(IEnumerable data, string title) + public void Load(IEnumerable data, string title) { foldersKeys.Clear(); Folders.Clear(); From 484d2ef135d47bb4aedf332590a1ba60c5d1bdf0 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Wed, 15 Nov 2017 20:06:47 +0100 Subject: [PATCH 19/64] Don't need this anymore, Load always happens before Render --- .../Assets/Editor/GitHub.Unity/UI/BranchesView.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs index 65e1b20eb..ea031e5bb 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs @@ -551,9 +551,6 @@ public void Load(IEnumerable data, string title) public Rect Render(Rect rect, Action singleClick = null, Action doubleClick = null) { - if (!nodes.Any()) - return rect; - RequiresRepaint = false; rect = new Rect(0f, rect.y, rect.width, ItemHeight); From 5d5b0c53497a8956787c708814f446761260affd Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Wed, 15 Nov 2017 20:43:50 +0100 Subject: [PATCH 20/64] A tree control. Fix scrolling. Isolate the treeview into its own TreeControl file so everyone can use it. Fix scrolling while we're at it. Context menus still need to be implemented. --- src/GitHub.Api/Git/GitBranch.cs | 2 +- .../Editor/GitHub.Unity/GitHub.Unity.csproj | 3 +- .../Editor/GitHub.Unity/UI/BranchesView.cs | 485 ++---------------- .../Editor/GitHub.Unity/UI/TreeControl.cs | 444 ++++++++++++++++ .../Assets/Editor/GitHub.Unity/UI/Window.cs | 9 + 5 files changed, 489 insertions(+), 454 deletions(-) create mode 100644 src/UnityExtension/Assets/Editor/GitHub.Unity/UI/TreeControl.cs diff --git a/src/GitHub.Api/Git/GitBranch.cs b/src/GitHub.Api/Git/GitBranch.cs index 733b845df..41cd21106 100644 --- a/src/GitHub.Api/Git/GitBranch.cs +++ b/src/GitHub.Api/Git/GitBranch.cs @@ -2,7 +2,7 @@ namespace GitHub.Unity { - interface ITreeData + public interface ITreeData { string Name { get; } bool IsActive { get; } diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/GitHub.Unity.csproj b/src/UnityExtension/Assets/Editor/GitHub.Unity/GitHub.Unity.csproj index ac26b427b..ee6504e3e 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/GitHub.Unity.csproj +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/GitHub.Unity.csproj @@ -103,6 +103,7 @@ + @@ -217,4 +218,4 @@ --> - + \ No newline at end of file diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs index ea031e5bb..666321b2a 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs @@ -5,7 +5,6 @@ using GitHub.Unity.Helpers; using UnityEditor; using UnityEngine; -using Debug = System.Diagnostics.Debug; namespace GitHub.Unity { @@ -123,18 +122,17 @@ private void DetachHandlers(IRepository repository) private void Render() { - scroll = GUILayout.BeginScrollView(scroll, false, true); + listID = GUIUtility.GetControlID(FocusType.Keyboard); + GUILayout.BeginHorizontal(); { - listID = GUIUtility.GetControlID(FocusType.Keyboard); - - GUILayout.BeginHorizontal(); - { - OnButtonBarGUI(); - } - GUILayout.EndHorizontal(); + OnButtonBarGUI(); + } + GUILayout.EndHorizontal(); - var rect = GUILayoutUtility.GetLastRect(); - OnTreeGUI(new Rect(0f, rect.height + Styles.CommitAreaPadding, Position.width, Position.height - rect.height + Styles.CommitAreaPadding)); + var rect = GUILayoutUtility.GetLastRect(); + scroll = GUILayout.BeginScrollView(scroll); + { + OnTreeGUI(new Rect(0f, 0f, Position.width, Position.height - rect.height + Styles.CommitAreaPadding)); } GUILayout.EndScrollView(); } @@ -242,20 +240,21 @@ private void OnButtonBarGUI() if (createBranch) { GitClient.CreateBranch(newBranchName, treeLocals.SelectedNode.Name) - .FinallyInUI((success, e) => { - if (success) - { - Redraw(); - } - else - { - var errorHeader = "fatal: "; - var errorMessage = e.Message.StartsWith(errorHeader) ? e.Message.Remove(0, errorHeader.Length) : e.Message; - - EditorUtility.DisplayDialog(CreateBranchTitle, - errorMessage, - Localization.Ok); - } + .FinallyInUI((success, e) => + { + if (success) + { + Redraw(); + } + else + { + var errorHeader = "fatal: "; + var errorMessage = e.Message.StartsWith(errorHeader) ? e.Message.Remove(0, errorHeader.Length) : e.Message; + + EditorUtility.DisplayDialog(CreateBranchTitle, + errorMessage, + Localization.Ok); + } }) .Start(); } @@ -274,6 +273,8 @@ private void OnButtonBarGUI() private void OnTreeGUI(Rect rect) { + var initialRect = rect; + if (treeLocals.FolderStyle == null) { treeLocals.FolderStyle = Styles.Foldout; @@ -286,7 +287,7 @@ private void OnTreeGUI(Rect rect) var treeHadFocus = treeLocals.SelectedNode != null; - rect = treeLocals.Render(rect, _ => { }, node => + rect = treeLocals.Render(rect, scroll, _ => { }, node => { if (EditorUtility.DisplayDialog(ConfirmSwitchTitle, String.Format(ConfirmSwitchMessage, node.Name), ConfirmSwitchOK, ConfirmSwitchCancel)) @@ -296,7 +297,7 @@ private void OnTreeGUI(Rect rect) { if (success) { - Redraw(); + Redraw(); } else { @@ -320,7 +321,7 @@ private void OnTreeGUI(Rect rect) rect.y += Styles.TreePadding; - treeRemotes.Render(rect, _ => {}, selectedNode => + treeRemotes.Render(rect, scroll, _ => {}, selectedNode => { var indexOfFirstSlash = selectedNode.Name.IndexOf('/'); var originName = selectedNode.Name.Substring(0, indexOfFirstSlash); @@ -347,7 +348,7 @@ private void OnTreeGUI(Rect rect) { if (success) { - Redraw(); + Redraw(); } else { @@ -372,6 +373,9 @@ private void OnTreeGUI(Rect rect) if (treeRemotes.RequiresRepaint) Redraw(); + + //Debug.LogFormat("reserving: {0} {1} {2}", rect.y - initialRect.y, rect.y, initialRect.y); + GUILayout.Space(rect.y - initialRect.y); } private int CompareBranches(GitBranch a, GitBranch b) @@ -424,429 +428,6 @@ private int CompareBranches(GitBranch a, GitBranch b) // } //} - - [Serializable] - public class Tree - { - [SerializeField] private List nodes = new List(); - [SerializeField] private TreeNode selectedNode = null; - [SerializeField] private TreeNode activeNode = null; - [SerializeField] public float ItemHeight = EditorGUIUtility.singleLineHeight; - [SerializeField] public float ItemSpacing = EditorGUIUtility.standardVerticalSpacing; - [SerializeField] public float Indentation = 12f; - [SerializeField] public Rect Margin = new Rect(); - [SerializeField] public Rect Padding = new Rect(); - [SerializeField] private List foldersKeys = new List(); - [SerializeField] public Texture2D ActiveNodeIcon; - [SerializeField] public Texture2D NodeIcon; - [SerializeField] public Texture2D FolderIcon; - [SerializeField] public Texture2D RootFolderIcon; - [SerializeField] public GUIStyle FolderStyle; - [SerializeField] public GUIStyle TreeNodeStyle; - [SerializeField] public GUIStyle ActiveTreeNodeStyle; - - [NonSerialized] private Stack indents = new Stack(); - [NonSerialized] private Hashtable folders; - - public bool IsInitialized { get { return nodes != null && nodes.Count > 0 && !String.IsNullOrEmpty(nodes[0].Name); } } - public bool RequiresRepaint { get; private set; } - - public TreeNode SelectedNode - { - get - { - if (selectedNode != null && String.IsNullOrEmpty(selectedNode.Name)) - selectedNode = null; - return selectedNode; - } - private set - { - selectedNode = value; - } - } - - public TreeNode ActiveNode { get { return activeNode; } } - - private Hashtable Folders - { - get - { - if (folders == null) - { - folders = new Hashtable(); - for (int i = 0; i < foldersKeys.Count; i++) - { - folders.Add(foldersKeys[i], null); - } - } - return folders; - } - } - - public void Load(IEnumerable data, string title) - { - foldersKeys.Clear(); - Folders.Clear(); - nodes.Clear(); - - var titleNode = new TreeNode() - { - Name = title, - Label = title, - Level = 0, - IsFolder = true - }; - titleNode.Load(); - nodes.Add(titleNode); - - foreach (var d in data) - { - var parts = d.Name.Split('/'); - for (int i = 0; i < parts.Length; i++) - { - var label = parts[i]; - var name = String.Join("/", parts, 0, i + 1); - var isFolder = i < parts.Length - 1; - var alreadyExists = Folders.ContainsKey(name); - if (!alreadyExists) - { - var node = new TreeNode() - { - Name = name, - IsActive = d.IsActive, - Label = label, - Level = i + 1, - IsFolder = isFolder - }; - - if (node.IsActive) - { - activeNode = node; - node.Icon = ActiveNodeIcon; - } - else if (node.IsFolder) - { - if (node.Level == 1) - node.Icon = RootFolderIcon; - else - node.Icon = FolderIcon; - } - else - { - node.Icon = NodeIcon; - } - - node.Load(); - - nodes.Add(node); - if (isFolder) - { - Folders.Add(name, null); - } - } - } - } - foldersKeys = Folders.Keys.Cast().ToList(); - } - - public Rect Render(Rect rect, Action singleClick = null, Action doubleClick = null) - { - RequiresRepaint = false; - rect = new Rect(0f, rect.y, rect.width, ItemHeight); - - var titleNode = nodes[0]; - bool selectionChanged = titleNode.Render(rect, 0f, selectedNode == titleNode, FolderStyle, TreeNodeStyle, ActiveTreeNodeStyle); - - if (selectionChanged) - { - ToggleNodeVisibility(0, titleNode); - } - - RequiresRepaint = HandleInput(rect, titleNode, 0); - rect.y += ItemHeight + ItemSpacing; - - Indent(); - - int level = 1; - for (int i = 1; i < nodes.Count; i++) - { - var node = nodes[i]; - - if (node.Level > level && !node.IsHidden) - { - Indent(); - } - - var changed = node.Render(rect, Indentation, selectedNode == node, FolderStyle, TreeNodeStyle, ActiveTreeNodeStyle); - - if (node.IsFolder && changed) - { - // toggle visibility for all the nodes under this one - ToggleNodeVisibility(i, node); - } - - if (node.Level < level) - { - for (; node.Level > level && indents.Count > 1; level--) - { - Unindent(); - } - } - level = node.Level; - - if (!node.IsHidden) - { - RequiresRepaint = HandleInput(rect, node, i, singleClick, doubleClick); - rect.y += ItemHeight + ItemSpacing; - } - } - - Unindent(); - - foldersKeys = Folders.Keys.Cast().ToList(); - return rect; - } - - public void Focus() - { - bool selectionChanged = false; - if (Event.current.type == EventType.KeyDown) - { - int directionY = Event.current.keyCode == KeyCode.UpArrow ? -1 : Event.current.keyCode == KeyCode.DownArrow ? 1 : 0; - int directionX = Event.current.keyCode == KeyCode.LeftArrow ? -1 : Event.current.keyCode == KeyCode.RightArrow ? 1 : 0; - if (directionY != 0 || directionX != 0) - { - if (directionY < 0 || directionY < 0) - { - SelectedNode = nodes[nodes.Count - 1]; - selectionChanged = true; - Event.current.Use(); - } - else if (directionY > 0 || directionX > 0) - { - SelectedNode = nodes[0]; - selectionChanged = true; - Event.current.Use(); - } - } - } - RequiresRepaint = selectionChanged; - } - - public void Blur() - { - SelectedNode = null; - RequiresRepaint = true; - } - - private int ToggleNodeVisibility(int idx, TreeNode rootNode) - { - var rootNodeLevel = rootNode.Level; - rootNode.IsCollapsed = !rootNode.IsCollapsed; - idx++; - for (; idx < nodes.Count && nodes[idx].Level > rootNodeLevel; idx++) - { - nodes[idx].IsHidden = rootNode.IsCollapsed; - if (nodes[idx].IsFolder && !rootNode.IsCollapsed && nodes[idx].IsCollapsed) - { - var level = nodes[idx].Level; - for (idx++; idx < nodes.Count && nodes[idx].Level > level; idx++) { } - idx--; - } - } - if (SelectedNode != null && SelectedNode.IsHidden) - { - SelectedNode = rootNode; - } - return idx; - } - - private bool HandleInput(Rect rect, TreeNode currentNode, int index, Action singleClick = null, Action doubleClick = null) - { - bool selectionChanged = false; - var clickRect = new Rect(0f, rect.y, rect.width, rect.height); - if (Event.current.type == EventType.MouseDown && clickRect.Contains(Event.current.mousePosition)) - { - Event.current.Use(); - SelectedNode = currentNode; - selectionChanged = true; - var clickCount = Event.current.clickCount; - if (clickCount == 1 && singleClick != null) - { - singleClick(currentNode); - } - if (clickCount > 1 && doubleClick != null) - { - doubleClick(currentNode); - } - } - - // Keyboard navigation if this child is the current selection - if (currentNode == selectedNode && Event.current.type == EventType.KeyDown) - { - int directionY = Event.current.keyCode == KeyCode.UpArrow ? -1 : Event.current.keyCode == KeyCode.DownArrow ? 1 : 0; - int directionX = Event.current.keyCode == KeyCode.LeftArrow ? -1 : Event.current.keyCode == KeyCode.RightArrow ? 1 : 0; - if (directionY != 0 || directionX != 0) - { - if (directionY > 0) - { - selectionChanged = SelectNext(index, false) != index; - } - else if (directionY < 0) - { - selectionChanged = SelectPrevious(index, false) != index; - } - else if (directionX > 0) - { - if (currentNode.IsFolder && currentNode.IsCollapsed) - { - ToggleNodeVisibility(index, currentNode); - Event.current.Use(); - } - else - { - selectionChanged = SelectNext(index, true) != index; - } - } - else if (directionX < 0) - { - if (currentNode.IsFolder && !currentNode.IsCollapsed) - { - ToggleNodeVisibility(index, currentNode); - Event.current.Use(); - } - else - { - selectionChanged = SelectPrevious(index, true) != index; - } - } - } - } - return selectionChanged; - } - - private int SelectNext(int index, bool foldersOnly) - { - for (index++; index < nodes.Count; index++) - { - if (nodes[index].IsHidden) - continue; - if (!nodes[index].IsFolder && foldersOnly) - continue; - break; - } - - if (index < nodes.Count) - { - SelectedNode = nodes[index]; - Event.current.Use(); - } - else - { - SelectedNode = null; - } - return index; - } - - private int SelectPrevious(int index, bool foldersOnly) - { - for (index--; index >= 0; index--) - { - if (nodes[index].IsHidden) - continue; - if (!nodes[index].IsFolder && foldersOnly) - continue; - break; - } - - if (index >= 0) - { - SelectedNode = nodes[index]; - Event.current.Use(); - } - else - { - SelectedNode = null; - } - return index; - } - - private void Indent() - { - indents.Push(true); - } - - private void Unindent() - { - indents.Pop(); - } - } - - [Serializable] - public class TreeNode - { - public string Name; - public string Label; - public int Level; - public bool IsFolder; - public bool IsCollapsed; - public bool IsHidden; - public bool IsActive; - public GUIContent content; - public Texture2D Icon; - - public void Load() - { - content = new GUIContent(Label, Icon); - } - - public bool Render(Rect rect, float indentation, bool isSelected, GUIStyle folderStyle, GUIStyle nodeStyle, GUIStyle activeNodeStyle) - { - if (IsHidden) - return false; - - GUIStyle style; - if (IsFolder) - { - style = folderStyle; - } - else - { - style = IsActive ? activeNodeStyle : nodeStyle; - } - - bool changed = false; - var fillRect = rect; - var nodeRect = new Rect(Level * indentation, rect.y, rect.width, rect.height); - - if (Event.current.type == EventType.repaint) - { - nodeStyle.Draw(fillRect, "", false, false, false, isSelected); - if (IsFolder) - style.Draw(nodeRect, content, false, false, !IsCollapsed, isSelected); - else - { - style.Draw(nodeRect, content, false, false, false, isSelected); - } - } - - if (IsFolder) - { - EditorGUI.BeginChangeCheck(); - GUI.Toggle(nodeRect, !IsCollapsed, "", GUIStyle.none); - changed = EditorGUI.EndChangeCheck(); - } - - return changed; - } - - public override string ToString() - { - return String.Format("name:{0} label:{1} level:{2} isFolder:{3} isCollapsed:{4} isHidden:{5} isActive:{6}", - Name, Label, Level, IsFolder, IsCollapsed, IsHidden, IsActive); - } - } - public override bool IsBusy { get { return false; } diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/TreeControl.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/TreeControl.cs new file mode 100644 index 000000000..6b6d05f71 --- /dev/null +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/TreeControl.cs @@ -0,0 +1,444 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; +using UnityEngine.Profiling; + +namespace GitHub.Unity +{ + [Serializable] + public class Tree + { + [SerializeField] private List nodes = new List(); + [SerializeField] private TreeNode selectedNode = null; + [SerializeField] private TreeNode activeNode = null; + [SerializeField] public float ItemHeight = EditorGUIUtility.singleLineHeight; + [SerializeField] public float ItemSpacing = EditorGUIUtility.standardVerticalSpacing; + [SerializeField] public float Indentation = 12f; + [SerializeField] public Rect Margin = new Rect(); + [SerializeField] public Rect Padding = new Rect(); + [SerializeField] private List foldersKeys = new List(); + [SerializeField] public Texture2D ActiveNodeIcon; + [SerializeField] public Texture2D NodeIcon; + [SerializeField] public Texture2D FolderIcon; + [SerializeField] public Texture2D RootFolderIcon; + [SerializeField] public GUIStyle FolderStyle; + [SerializeField] public GUIStyle TreeNodeStyle; + [SerializeField] public GUIStyle ActiveTreeNodeStyle; + + [NonSerialized] private Stack indents = new Stack(); + [NonSerialized] private Hashtable folders; + + public bool IsInitialized { get { return nodes != null && nodes.Count > 0 && !String.IsNullOrEmpty(nodes[0].Name); } } + public bool RequiresRepaint { get; private set; } + + public TreeNode SelectedNode + { + get + { + if (selectedNode != null && String.IsNullOrEmpty(selectedNode.Name)) + selectedNode = null; + return selectedNode; + } + private set + { + selectedNode = value; + } + } + + public TreeNode ActiveNode { get { return activeNode; } } + + private Hashtable Folders + { + get + { + if (folders == null) + { + folders = new Hashtable(); + for (int i = 0; i < foldersKeys.Count; i++) + { + folders.Add(foldersKeys[i], null); + } + } + return folders; + } + } + + public void Load(IEnumerable data, string title) + { + foldersKeys.Clear(); + Folders.Clear(); + nodes.Clear(); + + var titleNode = new TreeNode() + { + Name = title, + Label = title, + Level = 0, + IsFolder = true + }; + titleNode.Load(); + nodes.Add(titleNode); + + foreach (var d in data) + { + var parts = d.Name.Split('/'); + for (int i = 0; i < parts.Length; i++) + { + var label = parts[i]; + var name = String.Join("/", parts, 0, i + 1); + var isFolder = i < parts.Length - 1; + var alreadyExists = Folders.ContainsKey(name); + if (!alreadyExists) + { + var node = new TreeNode() + { + Name = name, + IsActive = d.IsActive, + Label = label, + Level = i + 1, + IsFolder = isFolder + }; + + if (node.IsActive) + { + activeNode = node; + node.Icon = ActiveNodeIcon; + } + else if (node.IsFolder) + { + if (node.Level == 1) + node.Icon = RootFolderIcon; + else + node.Icon = FolderIcon; + } + else + { + node.Icon = NodeIcon; + } + + node.Load(); + + nodes.Add(node); + if (isFolder) + { + Folders.Add(name, null); + } + } + } + } + foldersKeys = Folders.Keys.Cast().ToList(); + } + + public Rect Render(Rect rect, Vector2 scroll, Action singleClick = null, Action doubleClick = null) + { + Profiler.BeginSample("TreeControl"); + bool visible = true; + var availableHeight = rect.y + rect.height; + + RequiresRepaint = false; + rect = new Rect(0f, rect.y, rect.width, ItemHeight); + + var titleNode = nodes[0]; + bool selectionChanged = titleNode.Render(rect, 0f, selectedNode == titleNode, FolderStyle, TreeNodeStyle, ActiveTreeNodeStyle); + + if (selectionChanged) + { + ToggleNodeVisibility(0, titleNode); + } + + RequiresRepaint = HandleInput(rect, titleNode, 0); + rect.y += ItemHeight + ItemSpacing; + + Indent(); + + int level = 1; + int i = 1; + for (; i < nodes.Count; i++) + { + var node = nodes[i]; + + if (node.Level > level && !node.IsHidden) + { + Indent(); + } + + if (visible) + { + var changed = node.Render(rect, Indentation, selectedNode == node, FolderStyle, TreeNodeStyle, ActiveTreeNodeStyle); + + if (node.IsFolder && changed) + { + // toggle visibility for all the nodes under this one + ToggleNodeVisibility(i, node); + } + } + + if (node.Level < level) + { + for (; node.Level > level && indents.Count > 1; level--) + { + Unindent(); + } + } + level = node.Level; + + if (!node.IsHidden) + { + if (visible) + { + RequiresRepaint = HandleInput(rect, node, i, singleClick, doubleClick); + } + rect.y += ItemHeight + ItemSpacing; + } + } + + Unindent(); + + foldersKeys = Folders.Keys.Cast().ToList(); + Profiler.EndSample(); + return rect; + } + + public void Focus() + { + bool selectionChanged = false; + if (Event.current.type == EventType.KeyDown) + { + int directionY = Event.current.keyCode == KeyCode.UpArrow ? -1 : Event.current.keyCode == KeyCode.DownArrow ? 1 : 0; + int directionX = Event.current.keyCode == KeyCode.LeftArrow ? -1 : Event.current.keyCode == KeyCode.RightArrow ? 1 : 0; + if (directionY != 0 || directionX != 0) + { + if (directionY < 0 || directionY < 0) + { + SelectedNode = nodes[nodes.Count - 1]; + selectionChanged = true; + Event.current.Use(); + } + else if (directionY > 0 || directionX > 0) + { + SelectedNode = nodes[0]; + selectionChanged = true; + Event.current.Use(); + } + } + } + RequiresRepaint = selectionChanged; + } + + public void Blur() + { + SelectedNode = null; + RequiresRepaint = true; + } + + private int ToggleNodeVisibility(int idx, TreeNode rootNode) + { + var rootNodeLevel = rootNode.Level; + rootNode.IsCollapsed = !rootNode.IsCollapsed; + idx++; + for (; idx < nodes.Count && nodes[idx].Level > rootNodeLevel; idx++) + { + nodes[idx].IsHidden = rootNode.IsCollapsed; + if (nodes[idx].IsFolder && !rootNode.IsCollapsed && nodes[idx].IsCollapsed) + { + var level = nodes[idx].Level; + for (idx++; idx < nodes.Count && nodes[idx].Level > level; idx++) { } + idx--; + } + } + if (SelectedNode != null && SelectedNode.IsHidden) + { + SelectedNode = rootNode; + } + return idx; + } + + private bool HandleInput(Rect rect, TreeNode currentNode, int index, Action singleClick = null, Action doubleClick = null) + { + bool selectionChanged = false; + var clickRect = new Rect(0f, rect.y, rect.width, rect.height); + if (Event.current.type == EventType.MouseDown && clickRect.Contains(Event.current.mousePosition)) + { + Event.current.Use(); + SelectedNode = currentNode; + selectionChanged = true; + var clickCount = Event.current.clickCount; + if (clickCount == 1 && singleClick != null) + { + singleClick(currentNode); + } + if (clickCount > 1 && doubleClick != null) + { + doubleClick(currentNode); + } + } + + // Keyboard navigation if this child is the current selection + if (currentNode == selectedNode && Event.current.type == EventType.KeyDown) + { + int directionY = Event.current.keyCode == KeyCode.UpArrow ? -1 : Event.current.keyCode == KeyCode.DownArrow ? 1 : 0; + int directionX = Event.current.keyCode == KeyCode.LeftArrow ? -1 : Event.current.keyCode == KeyCode.RightArrow ? 1 : 0; + if (directionY != 0 || directionX != 0) + { + if (directionY > 0) + { + selectionChanged = SelectNext(index, false) != index; + } + else if (directionY < 0) + { + selectionChanged = SelectPrevious(index, false) != index; + } + else if (directionX > 0) + { + if (currentNode.IsFolder && currentNode.IsCollapsed) + { + ToggleNodeVisibility(index, currentNode); + Event.current.Use(); + } + else + { + selectionChanged = SelectNext(index, true) != index; + } + } + else if (directionX < 0) + { + if (currentNode.IsFolder && !currentNode.IsCollapsed) + { + ToggleNodeVisibility(index, currentNode); + Event.current.Use(); + } + else + { + selectionChanged = SelectPrevious(index, true) != index; + } + } + } + } + return selectionChanged; + } + + private int SelectNext(int index, bool foldersOnly) + { + for (index++; index < nodes.Count; index++) + { + if (nodes[index].IsHidden) + continue; + if (!nodes[index].IsFolder && foldersOnly) + continue; + break; + } + + if (index < nodes.Count) + { + SelectedNode = nodes[index]; + Event.current.Use(); + } + else + { + SelectedNode = null; + } + return index; + } + + private int SelectPrevious(int index, bool foldersOnly) + { + for (index--; index >= 0; index--) + { + if (nodes[index].IsHidden) + continue; + if (!nodes[index].IsFolder && foldersOnly) + continue; + break; + } + + if (index >= 0) + { + SelectedNode = nodes[index]; + Event.current.Use(); + } + else + { + SelectedNode = null; + } + return index; + } + + private void Indent() + { + indents.Push(true); + } + + private void Unindent() + { + indents.Pop(); + } + } + + [Serializable] + public class TreeNode + { + public string Name; + public string Label; + public int Level; + public bool IsFolder; + public bool IsCollapsed; + public bool IsHidden; + public bool IsActive; + public GUIContent content; + public Texture2D Icon; + + public void Load() + { + content = new GUIContent(Label, Icon); + } + + public bool Render(Rect rect, float indentation, bool isSelected, GUIStyle folderStyle, GUIStyle nodeStyle, GUIStyle activeNodeStyle) + { + if (IsHidden) + return false; + + GUIStyle style; + if (IsFolder) + { + style = folderStyle; + } + else + { + style = IsActive ? activeNodeStyle : nodeStyle; + } + + bool changed = false; + var fillRect = rect; + var nodeRect = new Rect(Level * indentation, rect.y, rect.width, rect.height); + + if (Event.current.type == EventType.repaint) + { + nodeStyle.Draw(fillRect, "", false, false, false, isSelected); + if (IsFolder) + style.Draw(nodeRect, content, false, false, !IsCollapsed, isSelected); + else + { + style.Draw(nodeRect, content, false, false, false, isSelected); + } + } + + if (IsFolder) + { + EditorGUI.BeginChangeCheck(); + GUI.Toggle(nodeRect, !IsCollapsed, "", GUIStyle.none); + changed = EditorGUI.EndChangeCheck(); + } + + return changed; + } + + public override string ToString() + { + return String.Format("name:{0} label:{1} level:{2} isFolder:{3} isCollapsed:{4} isHidden:{5} isActive:{6}", + Name, Label, Level, IsFolder, IsCollapsed, IsHidden, IsActive); + } + } +} diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/Window.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/Window.cs index 0b2a23af8..332b8b28c 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/Window.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/Window.cs @@ -58,6 +58,15 @@ public static void GitHub_CommandLine() EntryPoint.ApplicationManager.ProcessManager.RunCommandLineWindow(NPath.CurrentDirectory); } +#if DEBUG + [MenuItem("GitHub/Select Window")] + public static void GitHub_SelectWindow() + { + var window = Resources.FindObjectsOfTypeAll(typeof(Window)).FirstOrDefault() as Window; + Selection.activeObject = window; + } +#endif + public static void ShowWindow(IApplicationManager applicationManager) { var type = typeof(EditorWindow).Assembly.GetType("UnityEditor.InspectorWindow"); From b7430694449107e6e7ed453ea8cfcbe083d97220 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Thu, 16 Nov 2017 16:32:38 +0100 Subject: [PATCH 21/64] Icons needs to be embedded in the project --- .../Assets/Editor/GitHub.Unity/GitHub.Unity.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/GitHub.Unity.csproj b/src/UnityExtension/Assets/Editor/GitHub.Unity/GitHub.Unity.csproj index ee6504e3e..a0b6dd2f1 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/GitHub.Unity.csproj +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/GitHub.Unity.csproj @@ -208,6 +208,10 @@ + + + +