Init
This commit is contained in:
@@ -0,0 +1,411 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
[InitializeOnLoad]
|
||||
public static class GitBranchToolbar
|
||||
{
|
||||
private const string ToolbarElementName = "GitBranchToolbar";
|
||||
private const double RefreshInterval = 2.0;
|
||||
private const double ToolbarCheckInterval = 0.25;
|
||||
private const float Height = 18f;
|
||||
|
||||
private static VisualElement toolbarElement;
|
||||
private static string cachedBranchName = "...";
|
||||
private static GitState cachedState = GitState.Unknown;
|
||||
|
||||
private static double nextRefreshTime;
|
||||
private static double nextToolbarCheckTime;
|
||||
|
||||
private enum GitState
|
||||
{
|
||||
Unknown,
|
||||
NoRepository,
|
||||
Branch,
|
||||
DetachedHead
|
||||
}
|
||||
|
||||
static GitBranchToolbar()
|
||||
{
|
||||
RefreshBranchInfo(force: true);
|
||||
|
||||
EditorApplication.update += OnEditorUpdate;
|
||||
EditorApplication.delayCall += AddToolbarUI;
|
||||
AssemblyReloadEvents.afterAssemblyReload += OnAfterAssemblyReload;
|
||||
}
|
||||
|
||||
private static void OnAfterAssemblyReload()
|
||||
{
|
||||
EditorApplication.delayCall += AddToolbarUI;
|
||||
}
|
||||
|
||||
private static void OnEditorUpdate()
|
||||
{
|
||||
if (EditorApplication.timeSinceStartup >= nextRefreshTime)
|
||||
{
|
||||
RefreshBranchInfo();
|
||||
}
|
||||
|
||||
EnsureToolbarExists();
|
||||
}
|
||||
|
||||
private static void EnsureToolbarExists()
|
||||
{
|
||||
if (EditorApplication.timeSinceStartup < nextToolbarCheckTime)
|
||||
return;
|
||||
|
||||
nextToolbarCheckTime = EditorApplication.timeSinceStartup + ToolbarCheckInterval;
|
||||
|
||||
var toolbarType = typeof(Editor).Assembly.GetType("UnityEditor.Toolbar");
|
||||
if (toolbarType == null)
|
||||
return;
|
||||
|
||||
var toolbars = Resources.FindObjectsOfTypeAll(toolbarType);
|
||||
if (toolbars == null || toolbars.Length == 0)
|
||||
return;
|
||||
|
||||
foreach (var toolbar in toolbars)
|
||||
{
|
||||
TryInjectIntoToolbar(toolbarType, toolbar);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddToolbarUI()
|
||||
{
|
||||
var toolbarType = typeof(Editor).Assembly.GetType("UnityEditor.Toolbar");
|
||||
if (toolbarType == null)
|
||||
return;
|
||||
|
||||
var toolbars = Resources.FindObjectsOfTypeAll(toolbarType);
|
||||
if (toolbars == null || toolbars.Length == 0)
|
||||
return;
|
||||
|
||||
foreach (var toolbar in toolbars)
|
||||
{
|
||||
TryInjectIntoToolbar(toolbarType, toolbar);
|
||||
}
|
||||
}
|
||||
|
||||
private static void TryInjectIntoToolbar(Type toolbarType, UnityEngine.Object toolbar)
|
||||
{
|
||||
var rootField = toolbarType.GetField("m_Root", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (rootField == null)
|
||||
return;
|
||||
|
||||
if (rootField.GetValue(toolbar) is not VisualElement root)
|
||||
return;
|
||||
|
||||
var rightContainer = root.Q("ToolbarZoneRightAlign");
|
||||
if (rightContainer == null)
|
||||
return;
|
||||
|
||||
var existing = rightContainer.Q<IMGUIContainer>(ToolbarElementName);
|
||||
if (existing != null)
|
||||
{
|
||||
toolbarElement = existing;
|
||||
return;
|
||||
}
|
||||
|
||||
var newElement = new IMGUIContainer(DrawGUI);
|
||||
newElement.name = ToolbarElementName;
|
||||
newElement.style.flexShrink = 0;
|
||||
newElement.style.flexGrow = 0;
|
||||
newElement.style.minWidth = 70;
|
||||
newElement.style.maxWidth = 130;
|
||||
newElement.style.marginLeft = 4;
|
||||
newElement.style.marginRight = 0;
|
||||
newElement.style.overflow = Overflow.Hidden;
|
||||
newElement.style.alignSelf = Align.FlexEnd;
|
||||
|
||||
rightContainer.Add(newElement);
|
||||
toolbarElement = newElement;
|
||||
}
|
||||
|
||||
private static void DrawGUI()
|
||||
{
|
||||
RefreshBranchInfo();
|
||||
|
||||
var indicatorColor = GetBackgroundColor(cachedState, cachedBranchName);
|
||||
var textColor = GetTextColor(cachedState);
|
||||
|
||||
string displayText = GetShortBranchName(cachedBranchName, cachedState);
|
||||
|
||||
Rect rect = GUILayoutUtility.GetRect(
|
||||
GUIContent.none,
|
||||
GUIStyle.none,
|
||||
GUILayout.Height(Height),
|
||||
GUILayout.MinWidth(80),
|
||||
GUILayout.MaxWidth(220)
|
||||
);
|
||||
|
||||
const float dotSize = 8f;
|
||||
const float leftPadding = 4f;
|
||||
const float gap = 6f;
|
||||
const float rightPadding = 0f;
|
||||
|
||||
Rect dotRect = new Rect(
|
||||
rect.x + leftPadding,
|
||||
rect.y + Mathf.Round((rect.height - dotSize) * 0.5f),
|
||||
dotSize,
|
||||
dotSize
|
||||
);
|
||||
|
||||
Rect labelRect = new Rect(
|
||||
dotRect.xMax + gap,
|
||||
rect.y,
|
||||
rect.width - leftPadding - dotSize - gap - rightPadding,
|
||||
rect.height
|
||||
);
|
||||
|
||||
GUI.Label(
|
||||
labelRect,
|
||||
new GUIContent(displayText, cachedBranchName),
|
||||
GetLabelStyle(textColor)
|
||||
);
|
||||
|
||||
EditorGUI.DrawRect(dotRect, indicatorColor);
|
||||
|
||||
EditorGUIUtility.AddCursorRect(rect, MouseCursor.Link);
|
||||
|
||||
if (Event.current.type == EventType.MouseDown && rect.Contains(Event.current.mousePosition))
|
||||
{
|
||||
ShowContextMenu();
|
||||
Event.current.Use();
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetShortBranchName(string branchName, GitState state)
|
||||
{
|
||||
if (state == GitState.DetachedHead)
|
||||
return "DETACHED";
|
||||
|
||||
if (state == GitState.NoRepository)
|
||||
return "No Repo";
|
||||
|
||||
if (state == GitState.Unknown)
|
||||
return "...";
|
||||
|
||||
if (string.IsNullOrWhiteSpace(branchName))
|
||||
return "...";
|
||||
|
||||
const int maxLen = 18;
|
||||
|
||||
if (branchName.Length <= maxLen)
|
||||
return branchName;
|
||||
|
||||
return branchName.Substring(0, maxLen - 1) + "…";
|
||||
}
|
||||
|
||||
private static void DrawBackground(Rect rect, Color color)
|
||||
{
|
||||
var oldColor = GUI.color;
|
||||
GUI.color = color;
|
||||
GUI.Box(rect, GUIContent.none, EditorStyles.toolbarButton);
|
||||
GUI.color = oldColor;
|
||||
}
|
||||
|
||||
private static GUIStyle GetLabelStyle(Color textColor)
|
||||
{
|
||||
var style = new GUIStyle(EditorStyles.label)
|
||||
{
|
||||
alignment = TextAnchor.MiddleLeft,
|
||||
fontStyle = FontStyle.Bold,
|
||||
clipping = TextClipping.Clip,
|
||||
padding = new RectOffset(0, 0, 0, 0),
|
||||
margin = new RectOffset(0, 0, 0, 0),
|
||||
fixedHeight = (int)Height
|
||||
};
|
||||
|
||||
style.normal.textColor = textColor;
|
||||
style.hover.textColor = textColor;
|
||||
style.active.textColor = textColor;
|
||||
style.focused.textColor = textColor;
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
private static GUIContent GetDisplayContent()
|
||||
{
|
||||
return cachedState switch
|
||||
{
|
||||
GitState.Branch => new GUIContent($"{cachedBranchName + "TEST TEST TEST"}", cachedBranchName),
|
||||
GitState.DetachedHead => new GUIContent("DETACHED"),
|
||||
GitState.NoRepository => new GUIContent("No Repo"),
|
||||
_ => new GUIContent("...")
|
||||
};
|
||||
}
|
||||
|
||||
private static Color GetBackgroundColor(GitState state, string branchName)
|
||||
{
|
||||
string normalized = (branchName ?? string.Empty).Trim();
|
||||
|
||||
return state switch
|
||||
{
|
||||
GitState.NoRepository => new Color(0.25f, 0.25f, 0.25f, 1f),
|
||||
GitState.DetachedHead => new Color(0.45f, 0.30f, 0.10f, 1f),
|
||||
|
||||
GitState.Branch when normalized.Equals("main", StringComparison.OrdinalIgnoreCase)
|
||||
=> Color.red,
|
||||
|
||||
GitState.Branch when normalized.Equals("develop", StringComparison.OrdinalIgnoreCase)
|
||||
=> Color.blue,
|
||||
|
||||
GitState.Branch when normalized.StartsWith("feature-", StringComparison.OrdinalIgnoreCase)
|
||||
=> Color.green,
|
||||
|
||||
GitState.Branch when normalized.StartsWith("feature/", StringComparison.OrdinalIgnoreCase)
|
||||
=> new Color(0.12f, 0.28f, 0.45f, 1f),
|
||||
|
||||
GitState.Branch when normalized.StartsWith("hotfix-", StringComparison.OrdinalIgnoreCase)
|
||||
=> Color.yellow,
|
||||
|
||||
GitState.Branch when normalized.StartsWith("hotfix/", StringComparison.OrdinalIgnoreCase)
|
||||
=> new Color(0.45f, 0.20f, 0.10f, 1f),
|
||||
|
||||
GitState.Branch when normalized.StartsWith("bugfix-", StringComparison.OrdinalIgnoreCase)
|
||||
=> Color.gray,
|
||||
|
||||
GitState.Branch => new Color(0.18f, 0.35f, 0.22f, 1f),
|
||||
_ => new Color(0.22f, 0.22f, 0.22f, 1f)
|
||||
};
|
||||
}
|
||||
|
||||
private static Color GetTextColor(GitState state)
|
||||
{
|
||||
return state switch
|
||||
{
|
||||
GitState.NoRepository => new Color(0.85f, 0.85f, 0.85f),
|
||||
_ => Color.white
|
||||
};
|
||||
}
|
||||
|
||||
private static void ShowContextMenu()
|
||||
{
|
||||
var menu = new GenericMenu();
|
||||
|
||||
menu.AddDisabledItem(new GUIContent($"Branch: {cachedBranchName}"));
|
||||
|
||||
if (cachedState == GitState.Branch || cachedState == GitState.DetachedHead)
|
||||
{
|
||||
menu.AddItem(new GUIContent("Copy Branch Name"), false, () =>
|
||||
{
|
||||
EditorGUIUtility.systemCopyBuffer = cachedBranchName;
|
||||
Debug.Log($"Copied Git branch name: {cachedBranchName}");
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
menu.AddDisabledItem(new GUIContent("Copy Branch Name"));
|
||||
}
|
||||
|
||||
menu.AddSeparator("");
|
||||
|
||||
menu.AddItem(new GUIContent("Refresh"), false, () =>
|
||||
{
|
||||
RefreshBranchInfo(force: true);
|
||||
});
|
||||
|
||||
menu.ShowAsContext();
|
||||
}
|
||||
|
||||
private static void RefreshBranchInfo(bool force = false)
|
||||
{
|
||||
if (!force && EditorApplication.timeSinceStartup < nextRefreshTime)
|
||||
return;
|
||||
|
||||
nextRefreshTime = EditorApplication.timeSinceStartup + RefreshInterval;
|
||||
|
||||
try
|
||||
{
|
||||
string projectRoot = Path.GetFullPath(Path.Combine(Application.dataPath, ".."));
|
||||
string gitDir = FindGitDirectory(projectRoot);
|
||||
|
||||
if (string.IsNullOrEmpty(gitDir))
|
||||
{
|
||||
cachedBranchName = "No Git";
|
||||
cachedState = GitState.NoRepository;
|
||||
return;
|
||||
}
|
||||
|
||||
string headPath = Path.Combine(gitDir, "HEAD");
|
||||
|
||||
if (!File.Exists(headPath))
|
||||
{
|
||||
cachedBranchName = "No Git";
|
||||
cachedState = GitState.NoRepository;
|
||||
return;
|
||||
}
|
||||
|
||||
string headContent = File.ReadAllText(headPath).Trim();
|
||||
|
||||
if (headContent.StartsWith("ref:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
const string headsPrefix = "refs/heads/";
|
||||
int headsIndex = headContent.IndexOf(headsPrefix, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (headsIndex >= 0)
|
||||
{
|
||||
cachedBranchName = headContent.Substring(headsIndex + headsPrefix.Length).Trim();
|
||||
}
|
||||
else
|
||||
{
|
||||
cachedBranchName = headContent
|
||||
.Replace("ref:", string.Empty)
|
||||
.Trim()
|
||||
.Split('/')
|
||||
.LastOrDefault() ?? "unknown";
|
||||
}
|
||||
|
||||
cachedState = GitState.Branch;
|
||||
}
|
||||
else
|
||||
{
|
||||
cachedBranchName = "DETACHED";
|
||||
cachedState = GitState.DetachedHead;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
cachedBranchName = "Git ?";
|
||||
cachedState = GitState.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
private static string FindGitDirectory(string startDirectory)
|
||||
{
|
||||
var dir = new DirectoryInfo(startDirectory);
|
||||
|
||||
while (dir != null)
|
||||
{
|
||||
string gitPath = Path.Combine(dir.FullName, ".git");
|
||||
|
||||
if (Directory.Exists(gitPath))
|
||||
return gitPath;
|
||||
|
||||
if (File.Exists(gitPath))
|
||||
{
|
||||
string content = File.ReadAllText(gitPath).Trim();
|
||||
const string prefix = "gitdir:";
|
||||
|
||||
if (content.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string relativePath = content.Substring(prefix.Length).Trim();
|
||||
string resolvedPath = Path.GetFullPath(Path.Combine(dir.FullName, relativePath));
|
||||
|
||||
if (Directory.Exists(resolvedPath))
|
||||
return resolvedPath;
|
||||
}
|
||||
}
|
||||
|
||||
dir = dir.Parent;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9e93a5acbb3e741409543d32ad2762d0
|
||||
Reference in New Issue
Block a user