diff --git a/src/GitHub.Api/Git/GitBranch.cs b/src/GitHub.Api/Git/GitBranch.cs
index 733b845df..3e1c747d8 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; }
@@ -17,10 +17,6 @@ public struct GitBranch : ITreeData
public string tracking;
public bool isActive;
- public string Name { get { return name; } }
- public string Tracking { get { return tracking; } }
- public bool IsActive { get { return isActive; } }
-
public GitBranch(string name, string tracking, bool active)
{
Guard.ArgumentNotNullOrWhiteSpace(name, "name");
@@ -30,6 +26,10 @@ public GitBranch(string name, string tracking, bool active)
this.isActive = active;
}
+ public string Name => name;
+ public string Tracking => tracking;
+ public bool IsActive => isActive;
+
public override string ToString()
{
return $"{Name} Tracking? {Tracking} Active? {IsActive}";
diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/GitHub.Unity.csproj b/src/UnityExtension/Assets/Editor/GitHub.Unity/GitHub.Unity.csproj
index ac26b427b..a0b6dd2f1 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 @@
+
@@ -207,6 +208,10 @@
+
+
+
+
-
+
\ No newline at end of file
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 3fdcdc26b..5858502c4 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,
@@ -829,5 +830,79 @@ public static Texture2D DropdownListIcon
return dropdownListIcon;
}
}
+
+ private static Texture2D globeIcon;
+ public static Texture2D GlobeIcon
+ {
+ get
+ {
+ if (globeIcon == null)
+ {
+ globeIcon = Utility.GetIcon("globe.png", "globe@2x.png");
+ }
+ return globeIcon;
+ }
+ }
+
+ 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 5816c47cf..8b0e86bc2 100644
--- a/src/UnityExtension/Assets/Editor/GitHub.Unity/Misc/Utility.cs
+++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/Misc/Utility.cs
@@ -16,12 +16,38 @@ public static Texture2D GetIcon(string filename, string filename2x = "")
filename = filename2x;
}
+ Texture2D texture2D = null;
+
var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("GitHub.Unity.IconsAndLogos." + filename);
if (stream != null)
- return stream.ToTexture2D();
+ {
+ texture2D = stream.ToTexture2D();
+ }
+ else
+ {
+ var iconPath = EntryPoint.Environment.ExtensionInstallPath.Combine("IconsAndLogos", filename).ToString(SlashMode.Forward);
+ texture2D = AssetDatabase.LoadAssetAtPath(iconPath);
+ }
+
+ if (texture2D != null)
+ {
+ texture2D.hideFlags = HideFlags.HideAndDontSave;
+ }
+
+ return texture2D;
+ }
+
+ public static Texture2D GetTextureFromColor(Color color)
+ {
+ Color[] pix = new Color[1];
+ pix[0] = color;
+
+ Texture2D result = new Texture2D(1, 1);
+ result.hideFlags = HideFlags.HideAndDontSave;
+ result.SetPixels(pix);
+ result.Apply();
- var iconPath = EntryPoint.Environment.ExtensionInstallPath.Combine("IconsAndLogos", filename).ToString(SlashMode.Forward);
- return AssetDatabase.LoadAssetAtPath(iconPath);
+ return result;
}
}
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 7a2bd68d3..da903033d 100644
--- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs
+++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs
@@ -1,10 +1,10 @@
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Linq;
using GitHub.Unity.Helpers;
using UnityEditor;
using UnityEngine;
-using Debug = System.Diagnostics.Debug;
namespace GitHub.Unity
{
@@ -32,27 +32,26 @@ class BranchesView : Subview
private const string DeleteBranchTitle = "Delete Branch?";
private const string DeleteBranchButton = "Delete";
private const string CancelButtonLabel = "Cancel";
-
- private bool showLocalBranches = true;
- private bool showRemoteBranches = true;
+ private const string DeleteBranchContextMenuLabel = "Delete";
+ private const string SwitchBranchContextMenuLabel = "Switch";
+ private const string CheckoutBranchContextMenuLabel = "Checkout";
[NonSerialized] private int listID = -1;
- [NonSerialized] private BranchTreeNode newNodeSelection;
[NonSerialized] private BranchesMode targetMode;
- [SerializeField] private BranchTreeNode activeBranchNode;
- [SerializeField] private BranchTreeNode localRoot;
+ [SerializeField] private BranchesTree treeLocals;
+ [SerializeField] private BranchesTree treeRemotes;
[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 bool disableDelete;
+ [SerializeField] private bool disableCreate;
[SerializeField] private CacheUpdateEvent lastLocalAndRemoteBranchListChangedEvent;
[NonSerialized] private bool localAndRemoteBranchListHasUpdate;
- [SerializeField] private GitBranch[] localBranches;
- [SerializeField] private GitBranch[] remoteBranches;
+ [SerializeField] private List localBranches;
+ [SerializeField] private List remoteBranches;
public override void InitializeView(IView parent)
{
@@ -60,15 +59,13 @@ public override void InitializeView(IView parent)
targetMode = mode;
}
+
public override void OnEnable()
{
base.OnEnable();
+ UpdateTreeIcons();
AttachHandlers(Repository);
-
- if (Repository != null)
- {
- Repository.CheckLocalAndRemoteBranchListChangedEvent(lastLocalAndRemoteBranchListChangedEvent);
- }
+ Repository.CheckLocalAndRemoteBranchListChangedEvent(lastLocalAndRemoteBranchListChangedEvent);
}
public override void OnDisable()
@@ -99,12 +96,19 @@ private void MaybeUpdateData()
{
localAndRemoteBranchListHasUpdate = false;
- localBranches = Repository.LocalBranches.ToArray();
- remoteBranches = Repository.RemoteBranches.ToArray();
-
+ localBranches = Repository.LocalBranches.ToList();
+ remoteBranches = Repository.RemoteBranches.ToList();
- BuildTree(localBranches, remoteBranches);
+ BuildTree();
}
+
+ disableDelete = treeLocals.SelectedNode == null || treeLocals.SelectedNode.IsFolder || treeLocals.SelectedNode.IsActive;
+ disableCreate = treeLocals.SelectedNode == null || treeLocals.SelectedNode.IsFolder || treeLocals.SelectedNode.Level == 0;
+ }
+
+ public override void OnGUI()
+ {
+ Render();
}
private void AttachHandlers(IRepository repository)
@@ -114,234 +118,84 @@ private void AttachHandlers(IRepository repository)
private void DetachHandlers(IRepository repository)
{
-
repository.LocalAndRemoteBranchListChanged -= RepositoryOnLocalAndRemoteBranchListChanged;
}
- public override void OnGUI()
+ private void Render()
{
- OnEmbeddedGUI();
- }
+ listID = GUIUtility.GetControlID(FocusType.Keyboard);
+ GUILayout.BeginHorizontal();
+ {
+ OnButtonBarGUI();
+ }
+ GUILayout.EndHorizontal();
- public void OnEmbeddedGUI()
- {
+ var rect = GUILayoutUtility.GetLastRect();
scroll = GUILayout.BeginScrollView(scroll);
{
- listID = GUIUtility.GetControlID(FocusType.Keyboard);
-
- GUILayout.BeginHorizontal();
- {
- OnButtonBarGUI();
- }
- GUILayout.EndHorizontal();
-
- GUILayout.BeginVertical(Styles.CommitFileAreaStyle);
- {
- // 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();
+ OnTreeGUI(new Rect(0f, 0f, 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)
+ private void BuildTree()
{
- if (a.Name.Equals("master"))
+ if (treeLocals == null)
{
- return -1;
- }
+ treeLocals = new BranchesTree();
- if (b.Name.Equals("master"))
- {
- return 1;
- }
+ treeRemotes = new BranchesTree();
+ treeRemotes.IsRemote = true;
- return 0;
- }
+ UpdateTreeIcons();
+ }
- private void BuildTree(IEnumerable local, IEnumerable remote)
- {
- //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();
+ treeLocals.Load(localBranches.Cast(), LocalTitle);
+ treeRemotes.Load(remoteBranches.Cast(), RemoteTitle);
+ Redraw();
+ }
- // 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)
+ private void UpdateTreeIcons()
+ {
+ if (treeLocals != null)
{
- 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));
- }
- }
-
- // Build into tree
- BuildTree(localRoot, node);
+ treeLocals.UpdateIcons(Styles.ActiveBranchIcon, Styles.BranchIcon, Styles.FolderIcon, Styles.GlobeIcon);
}
- // Maintain list of remotes before building their roots, ignoring active state
- remotes.Clear();
- for (var index = 0; index < remoteBranches.Count; ++index)
+ if (treeRemotes != null)
{
- 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;
- }
- }
-
- // Build on the root of the remote, just like with locals
- BuildTree(remotes[remoteIndex].Root, node);
+ treeRemotes.UpdateIcons(Styles.ActiveBranchIcon, Styles.BranchIcon, Styles.FolderIcon, Styles.GlobeIcon);
}
-
- Redraw();
}
- private void BuildTree(BranchTreeNode parent, BranchTreeNode child)
+ private void UpdateTreeStyles()
{
- var firstSplit = child.Label.IndexOf('/');
-
- // No nesting needed here, this is just a straight add
- if (firstSplit < 0)
+ if (treeLocals != null && treeLocals.FolderStyle == null)
{
- parent.Children.Add(child);
- return;
+ treeLocals.FolderStyle = Styles.Foldout;
+ treeLocals.TreeNodeStyle = Styles.TreeNode;
+ treeLocals.ActiveTreeNodeStyle = Styles.TreeNodeActive;
}
- // 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)
+ if (treeRemotes != null && treeRemotes.FolderStyle == null)
{
- folder = new BranchTreeNode("", NodeType.Folder, false) { Label = folderName };
- parent.Children.Add(folder);
+ treeRemotes.FolderStyle = Styles.Foldout;
+ treeRemotes.TreeNodeStyle = Styles.TreeNode;
+ treeRemotes.ActiveTreeNodeStyle = Styles.TreeNodeActive;
}
-
- // 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 OnButtonBarGUI()
@@ -350,27 +204,25 @@ private void OnButtonBarGUI()
{
// 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 dialogMessage = string.Format(DeleteBranchMessageFormatString, selectedBranchName);
- if (EditorUtility.DisplayDialog(DeleteBranchTitle, dialogMessage, DeleteBranchButton, CancelButtonLabel))
- {
- GitClient.DeleteBranch(selectedBranchName, true).Start();
- }
+ DeleteLocalBranch(treeLocals.SelectedNode.Name);
}
}
EditorGUI.EndDisabledGroup();
// Create button
GUILayout.FlexibleSpace();
- if (GUILayout.Button(CreateBranchButton, EditorStyles.miniButton, GUILayout.ExpandWidth(false)))
+ EditorGUI.BeginDisabledGroup(disableCreate);
{
- targetMode = BranchesMode.Create;
+ if (GUILayout.Button(CreateBranchButton, EditorStyles.miniButton, GUILayout.ExpandWidth(false)))
+ {
+ targetMode = BranchesMode.Create;
+ }
}
+ EditorGUI.EndDisabledGroup();
}
// Branch name + cancel + create
else if (mode == BranchesMode.Create)
@@ -379,8 +231,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
@@ -426,21 +278,22 @@ private void OnButtonBarGUI()
// Effectuate create
if (createBranch)
{
- GitClient.CreateBranch(newBranchName, 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);
- }
+ 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);
+ }
})
.Start();
}
@@ -457,172 +310,186 @@ private void OnButtonBarGUI()
}
}
- private void OnTreeNodeGUI(BranchTreeNode node)
+ private void OnTreeGUI(Rect rect)
+ {
+ UpdateTreeStyles();
+
+ var initialRect = rect;
+ var treeHadFocus = treeLocals.SelectedNode != null;
+
+ rect = treeLocals.Render(initialRect, rect, scroll,
+ node =>{ },
+ node => {
+ if (node.IsFolder)
+ return;
+
+ if(node.IsActive)
+ return;
+
+ SwitchBranch(node.Name);
+ },
+ node => {
+ if (node.IsFolder)
+ return;
+
+ var menu = CreateContextMenuForLocalBranchNode(node);
+ menu.ShowAsContext();
+ });
+
+ 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;
+
+ rect = treeRemotes.Render(initialRect, rect, scroll,
+ node => { },
+ node => {
+ if (node.IsFolder)
+ return;
+
+ CheckoutRemoteBranch(node.Name);
+ },
+ node => {
+ if (node.IsFolder)
+ return;
+
+ var menu = CreateContextMenuForRemoteBranchNode(node);
+ menu.ShowAsContext();
+ });
+
+ if (treeHadFocus && treeRemotes.SelectedNode == null)
+ treeLocals.Focus();
+ else if (!treeHadFocus && treeRemotes.SelectedNode != null)
+ treeLocals.Blur();
+
+ 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 GenericMenu CreateContextMenuForLocalBranchNode(TreeNode node)
{
- // Content, style, and rects
+ var genericMenu = new GenericMenu();
- Texture2D iconContent;
+ var deleteGuiContent = new GUIContent(DeleteBranchContextMenuLabel);
+ var switchGuiContent = new GUIContent(SwitchBranchContextMenuLabel);
- if (node.Active == true)
+ if (node.IsActive)
{
- iconContent = Styles.ActiveBranchIcon;
+ genericMenu.AddDisabledItem(deleteGuiContent);
+ genericMenu.AddDisabledItem(switchGuiContent);
}
else
{
- if (node.Children.Count > 0)
- {
- iconContent = Styles.FolderIcon;
- }
- else
- {
- iconContent = Styles.BranchIcon;
- }
- }
+ genericMenu.AddItem(deleteGuiContent, false, () => {
+ DeleteLocalBranch(node.Name);
+ });
- 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);
+ genericMenu.AddItem(switchGuiContent, false, () => {
+ SwitchBranch(node.Name);
+ });
+ }
- var selected = selectedNode == node;
- var keyboardFocus = GUIUtility.keyboardControl == listID;
+ return genericMenu;
+ }
- // Selection highlight and favorite toggle
- if (selected)
- {
- if (Event.current.type == EventType.Repaint)
- {
- style.Draw(clickRect, GUIContent.none, false, false, true, keyboardFocus);
- }
- }
+ private GenericMenu CreateContextMenuForRemoteBranchNode(TreeNode node)
+ {
+ var genericMenu = new GenericMenu();
+
+ var checkoutGuiContent = new GUIContent(CheckoutBranchContextMenuLabel);
+
+ genericMenu.AddItem(checkoutGuiContent, false, () => {
+ CheckoutRemoteBranch(node.Name);
+ });
+
+ return genericMenu;
+ }
- // The actual icon and label
- if (Event.current.type == EventType.Repaint)
- {
- style.Draw(rect, content, false, false, selected, keyboardFocus);
- }
+ private void CheckoutRemoteBranch(string branch)
+ {
+ var indexOfFirstSlash = branch.IndexOf('/');
+ var originName = branch.Substring(0, indexOfFirstSlash);
+ var branchName = branch.Substring(indexOfFirstSlash + 1);
- // Children
- GUILayout.BeginHorizontal();
+ if (Repository.LocalBranches.Any(localBranch => localBranch.Name == branchName))
{
- GUILayout.Space(Styles.TreeIndentation);
- GUILayout.BeginVertical();
- {
- OnTreeNodeChildrenGUI(node);
- }
- GUILayout.EndVertical();
+ EditorUtility.DisplayDialog(WarningCheckoutBranchExistsTitle,
+ String.Format(WarningCheckoutBranchExistsMessage, branchName), WarningCheckoutBranchExistsOK);
}
- GUILayout.EndHorizontal();
-
- // Click selection of the node as well as branch switch
- if (Event.current.type == EventType.MouseDown && clickRect.Contains(Event.current.mousePosition))
+ else
{
- newNodeSelection = node;
- Event.current.Use();
+ var confirmCheckout = EditorUtility.DisplayDialog(ConfirmCheckoutBranchTitle,
+ String.Format(ConfirmCheckoutBranchMessage, branch, originName), ConfirmCheckoutBranchOK,
+ ConfirmCheckoutBranchCancel);
- if (Event.current.clickCount > 1 && mode == BranchesMode.Default)
+ if (confirmCheckout)
{
- if (node.Type == NodeType.LocalBranch)
- {
- if (EditorUtility.DisplayDialog(ConfirmSwitchTitle, String.Format(ConfirmSwitchMessage, node.Name), ConfirmSwitchOK, ConfirmSwitchCancel))
+ GitClient.CreateBranch(branchName, branch).FinallyInUI((success, e) => {
+ if (success)
{
- GitClient.SwitchBranch(node.Name)
- .FinallyInUI((success, e) =>
- {
- if (success)
- {
- Redraw();
- }
- else
- {
- EditorUtility.DisplayDialog(Localization.SwitchBranchTitle,
- String.Format(Localization.SwitchBranchFailedDescription, node.Name),
- Localization.Ok);
- }
- }).Start();
- }
- }
- else if (node.Type == NodeType.RemoteBranch)
- {
- var indexOfFirstSlash = selectedNode.Name.IndexOf('/');
- var originName = selectedNode.Name.Substring(0, indexOfFirstSlash);
- var branchName = selectedNode.Name.Substring(indexOfFirstSlash + 1);
-
- if (localBranches.Any(localBranch => localBranch.Name == branchName))
- {
- EditorUtility.DisplayDialog(WarningCheckoutBranchExistsTitle,
- String.Format(WarningCheckoutBranchExistsMessage, branchName),
- WarningCheckoutBranchExistsOK);
+ Redraw();
}
else
{
- var confirmCheckout = EditorUtility.DisplayDialog(ConfirmCheckoutBranchTitle,
- String.Format(ConfirmCheckoutBranchMessage, node.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, node.Name),
- Localization.Ok);
- }
- }).Start();
- }
+ EditorUtility.DisplayDialog(Localization.SwitchBranchTitle,
+ String.Format(Localization.SwitchBranchFailedDescription, branch), Localization.Ok);
}
- }
+ }).Start();
}
}
}
- private void OnTreeNodeChildrenGUI(BranchTreeNode node)
+ private void SwitchBranch(string branch)
{
- if (node == null || node.Children == null)
+ if (EditorUtility.DisplayDialog(ConfirmSwitchTitle, String.Format(ConfirmSwitchMessage, branch), ConfirmSwitchOK,
+ ConfirmSwitchCancel))
{
- return;
+ GitClient.SwitchBranch(branch).FinallyInUI((success, e) => {
+ if (success)
+ {
+ Redraw();
+ }
+ else
+ {
+ EditorUtility.DisplayDialog(Localization.SwitchBranchTitle,
+ String.Format(Localization.SwitchBranchFailedDescription, branch), Localization.Ok);
+ }
+ }).Start();
}
+ }
- for (var index = 0; index < node.Children.Count; ++index)
+ private void DeleteLocalBranch(string branch)
+ {
+ var dialogMessage = string.Format(DeleteBranchMessageFormatString, branch);
+ if (EditorUtility.DisplayDialog(DeleteBranchTitle, dialogMessage, DeleteBranchButton, CancelButtonLabel))
{
- // The actual GUI of the child
- OnTreeNodeGUI(node.Children[index]);
+ GitClient.DeleteBranch(branch, true).Start();
+ }
+ }
- // Keyboard navigation if this child is the current selection
- if (selectedNode == node.Children[index] && GUIUtility.keyboardControl == listID && Event.current.type == EventType.KeyDown)
- {
- 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;
+ private int CompareBranches(GitBranch a, GitBranch b)
+ {
+ if (a.Name.Equals("master"))
+ {
+ return -1;
+ }
- 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)
- {
- newNodeSelection = node.Children[index].Children[0];
- Event.current.Use();
- }
- }
+ if (b.Name.Equals("master"))
+ {
+ return 1;
}
+
+ return a.Name.CompareTo(b.Name);
}
public override bool IsBusy
@@ -642,34 +509,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;
- }
}
}
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..09a066402
--- /dev/null
+++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/TreeControl.cs
@@ -0,0 +1,541 @@
+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 TreeNodeDictionary : SerializableDictionary { }
+
+ [Serializable]
+ public abstract class Tree
+ {
+ public static float ItemHeight { get { return EditorGUIUtility.singleLineHeight; } }
+ public static float ItemSpacing { get { return EditorGUIUtility.standardVerticalSpacing; } }
+
+ [SerializeField] public Rect Margin = new Rect();
+ [SerializeField] public Rect Padding = new Rect();
+
+ [NonSerialized] public GUIStyle FolderStyle;
+ [NonSerialized] public GUIStyle TreeNodeStyle;
+ [NonSerialized] public GUIStyle ActiveTreeNodeStyle;
+
+ [SerializeField] private List nodes = new List();
+ [SerializeField] private TreeNode selectedNode = null;
+ [SerializeField] private TreeNode activeNode = null;
+ [SerializeField] private TreeNodeDictionary folders = new TreeNodeDictionary();
+
+ [NonSerialized] private Stack indents = new Stack();
+ [NonSerialized] private Action rightClickNextRender;
+ [NonSerialized] private TreeNode rightClickNextRenderNode;
+
+ 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; } }
+
+ public void Load(IEnumerable data, string title)
+ {
+ var collapsedFoldersEnumerable = folders.Where(pair => pair.Value.IsCollapsed).Select(pair => pair.Key);
+ var collapsedFolders = new HashSet(collapsedFoldersEnumerable);
+ string selectedNodeName = null;
+ if (SelectedNode != null)
+ {
+ selectedNodeName = SelectedNode.Name;
+ SelectedNode = null;
+ }
+
+ folders.Clear();
+ nodes.Clear();
+
+ var titleNode = new TreeNode()
+ {
+ Name = title,
+ Label = title,
+ Level = 0,
+ IsFolder = true
+ };
+ SetNodeIcon(titleNode);
+ nodes.Add(titleNode);
+
+ var hideChildren = false;
+ var hideChildrenBelowLevel = 0;
+
+ foreach (var d in data)
+ {
+ var parts = d.Name.Split('/');
+ for (int i = 0; i < parts.Length; i++)
+ {
+ var label = parts[i];
+ var level = i + 1;
+ var name = String.Join("/", parts, 0, level);
+ 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 = level,
+ IsFolder = isFolder
+ };
+
+ if (selectedNodeName != null && name == selectedNodeName)
+ {
+ SelectedNode = node;
+ }
+
+ if (node.IsActive)
+ {
+ activeNode = node;
+ }
+
+ if (hideChildren)
+ {
+ if (level <= hideChildrenBelowLevel)
+ {
+ hideChildren = false;
+ }
+ else
+ {
+ node.IsHidden = true;
+ }
+ }
+
+ SetNodeIcon(node);
+
+ nodes.Add(node);
+ if (isFolder)
+ {
+ if (collapsedFolders.Contains(name))
+ {
+ node.IsCollapsed = true;
+
+ if (!hideChildren)
+ {
+ hideChildren = true;
+ hideChildrenBelowLevel = level;
+ }
+ }
+
+ folders.Add(name, node);
+ }
+ }
+ }
+ }
+ }
+
+ public Rect Render(Rect containingRect, Rect rect, Vector2 scroll, Action singleClick = null, Action doubleClick = null, Action rightClick = null)
+ {
+ if (Event.current.type != EventType.Repaint)
+ {
+ if (rightClickNextRender != null)
+ {
+ rightClickNextRender.Invoke(rightClickNextRenderNode);
+ rightClickNextRender = null;
+ rightClickNextRenderNode = null;
+ }
+ }
+
+ var startDisplay = scroll.y;
+ var endDisplay = scroll.y + containingRect.height;
+
+ RequiresRepaint = false;
+ rect = new Rect(0f, rect.y, rect.width, ItemHeight);
+
+ var titleNode = nodes[0];
+ var selectionChanged = false;
+
+ var titleDisplay = !(rect.y > endDisplay || rect.yMax < startDisplay);
+ if (titleDisplay)
+ {
+ selectionChanged = titleNode.Render(rect, Styles.TreeIndentation, 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();
+ }
+
+ var changed = false;
+
+ var display = !(rect.y > endDisplay || rect.yMax < startDisplay);
+ if (display)
+ {
+ changed = node.Render(rect, Styles.TreeIndentation, 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, rightClick);
+ rect.y += ItemHeight + ItemSpacing;
+ }
+ }
+
+ Unindent();
+
+ 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)
+ {
+ 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, Action rightClick = 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;
+ var mouseButton = Event.current.button;
+
+ if (mouseButton == 0 && clickCount == 1 && singleClick != null)
+ {
+ singleClick(currentNode);
+ }
+ if (mouseButton == 0 && clickCount > 1 && doubleClick != null)
+ {
+ doubleClick(currentNode);
+ }
+ if (mouseButton == 1 && clickCount == 1 && rightClick != null)
+ {
+ rightClickNextRender = rightClick;
+ rightClickNextRenderNode = 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();
+ }
+
+ private void SetNodeIcon(TreeNode node)
+ {
+ node.Icon = GetNodeIcon(node);
+ node.Load();
+ }
+
+ protected abstract Texture2D GetNodeIcon(TreeNode node);
+
+ protected void LoadNodeIcons()
+ {
+ foreach (var treeNode in nodes)
+ {
+ SetNodeIcon(treeNode);
+ }
+ }
+ }
+
+ [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;
+ [NonSerialized] 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, GUIContent.none, 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)
+ {
+ var toggleRect = new Rect(nodeRect.x, nodeRect.y, style.border.horizontal, nodeRect.height);
+
+ EditorGUI.BeginChangeCheck();
+ GUI.Toggle(toggleRect, !IsCollapsed, GUIContent.none, 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);
+ }
+ }
+
+ [Serializable]
+ public class BranchesTree: Tree
+ {
+ [SerializeField] public bool IsRemote;
+
+ [NonSerialized] public Texture2D ActiveBranchIcon;
+ [NonSerialized] public Texture2D BranchIcon;
+ [NonSerialized] public Texture2D FolderIcon;
+ [NonSerialized] public Texture2D GlobeIcon;
+
+ protected override Texture2D GetNodeIcon(TreeNode node)
+ {
+ Texture2D nodeIcon;
+ if (node.IsActive)
+ {
+ nodeIcon = ActiveBranchIcon;
+ }
+ else if (node.IsFolder)
+ {
+ nodeIcon = IsRemote && node.Level == 1
+ ? GlobeIcon
+ : FolderIcon;
+ }
+ else
+ {
+ nodeIcon = BranchIcon;
+ }
+ return nodeIcon;
+ }
+
+
+ public void UpdateIcons(Texture2D activeBranchIcon, Texture2D branchIcon, Texture2D folderIcon, Texture2D globeIcon)
+ {
+ var needsLoad = ActiveBranchIcon == null || BranchIcon == null || FolderIcon == null || GlobeIcon == null;
+ if (needsLoad)
+ {
+ ActiveBranchIcon = activeBranchIcon;
+ BranchIcon = branchIcon;
+ FolderIcon = folderIcon;
+ GlobeIcon = globeIcon;
+
+ LoadNodeIcons();
+ }
+ }
+ }
+}
diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/Window.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/Window.cs
index 41e283aba..e741af05e 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");