ArbitratorWindow.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. // Copyright (c) 2025 TerraByte Inc.
  2. //
  3. // This script is the View component for the Better Git tool. Its only
  4. // responsibility is to draw the UI based on the state provided by the
  5. // ArbitratorController.
  6. using System.Linq;
  7. using UnityEngine;
  8. using UnityEditor;
  9. namespace Terra.Arbitrator.GUI
  10. {
  11. public class ArbitratorWindow : EditorWindow
  12. {
  13. private ArbitratorController _controller;
  14. private string _commitMessage = "";
  15. private Vector2 _scrollPosition;
  16. private GUIStyle _evenRowStyle;
  17. private bool _stylesInitialized;
  18. [MenuItem("Version Control/Better Git")]
  19. public static void ShowWindow()
  20. {
  21. var window = GetWindow<ArbitratorWindow>();
  22. window.titleContent = new GUIContent("Better Git", EditorGUIUtility.IconContent("d_UnityEditor.VersionControl").image);
  23. }
  24. private void OnEnable()
  25. {
  26. // The window creates the controller and passes delegates for communication.
  27. _controller = new ArbitratorController(
  28. requestRepaint: Repaint,
  29. displayDialog: (heading, message) => EditorUtility.DisplayDialog(heading, message, "Yes", "No")
  30. );
  31. _controller.OnEnable();
  32. }
  33. private void InitializeStyles()
  34. {
  35. if (_stylesInitialized) return;
  36. _evenRowStyle = new GUIStyle();
  37. var texture = new Texture2D(1, 1);
  38. var color = EditorGUIUtility.isProSkin
  39. ? new Color(0.3f, 0.3f, 0.3f, 0.3f)
  40. : new Color(0.8f, 0.8f, 0.8f, 0.5f);
  41. texture.SetPixel(0, 0, color);
  42. texture.Apply();
  43. _evenRowStyle.normal.background = texture;
  44. _stylesInitialized = true;
  45. }
  46. private void OnGUI()
  47. {
  48. InitializeStyles();
  49. DrawToolbar();
  50. EditorGUILayout.Space();
  51. DrawMessageArea();
  52. DrawMainContent();
  53. }
  54. private void DrawToolbar()
  55. {
  56. EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
  57. EditorGUI.BeginDisabledGroup(_controller.IsLoading);
  58. if (GUILayout.Button(new GUIContent("Refresh", EditorGUIUtility.IconContent("Refresh").image), EditorStyles.toolbarButton, GUILayout.Width(80)))
  59. {
  60. _controller.Refresh();
  61. }
  62. if (GUILayout.Button(new GUIContent("Pull", EditorGUIUtility.IconContent("CollabPull").image), EditorStyles.toolbarButton, GUILayout.Width(80)))
  63. {
  64. _controller.Pull();
  65. }
  66. if (_controller.Changes is { Count: > 0 })
  67. {
  68. if (GUILayout.Button("Select All", EditorStyles.toolbarButton, GUILayout.Width(80)))
  69. {
  70. _controller.SetAllSelection(true);
  71. }
  72. if (GUILayout.Button("Deselect All", EditorStyles.toolbarButton, GUILayout.Width(80)))
  73. {
  74. _controller.SetAllSelection(false);
  75. }
  76. GUILayout.FlexibleSpace();
  77. var noChanges = !_controller.Changes.Any();
  78. EditorGUI.BeginDisabledGroup(noChanges);
  79. var originalColor = UnityEngine.GUI.backgroundColor;
  80. UnityEngine.GUI.backgroundColor = new Color(1f, 0.5f, 0.5f, 0.8f);
  81. if (GUILayout.Button(new GUIContent("Reset All", EditorGUIUtility.IconContent("d_TreeEditor.Trash").image), EditorStyles.toolbarButton, GUILayout.Width(100)))
  82. {
  83. EditorApplication.delayCall += _controller.ResetAll;
  84. }
  85. UnityEngine.GUI.backgroundColor = originalColor;
  86. EditorGUI.EndDisabledGroup();
  87. }
  88. EditorGUI.EndDisabledGroup();
  89. if (_controller.IsLoading)
  90. {
  91. GUILayout.FlexibleSpace();
  92. GUILayout.Label(_controller.LoadingMessage);
  93. }
  94. EditorGUILayout.EndHorizontal();
  95. }
  96. private void DrawMessageArea()
  97. {
  98. if (!string.IsNullOrEmpty(_controller.ErrorMessage))
  99. {
  100. EditorGUILayout.HelpBox(_controller.ErrorMessage, MessageType.Error);
  101. }
  102. else if (!string.IsNullOrEmpty(_controller.InfoMessage) && !_controller.IsLoading)
  103. {
  104. EditorGUILayout.HelpBox(_controller.InfoMessage, MessageType.Info);
  105. }
  106. }
  107. private void DrawMainContent()
  108. {
  109. if (_controller.IsLoading)
  110. {
  111. GUILayout.FlexibleSpace();
  112. EditorGUILayout.BeginHorizontal();
  113. GUILayout.FlexibleSpace();
  114. GUILayout.Label("Loading...", EditorStyles.largeLabel);
  115. GUILayout.FlexibleSpace();
  116. EditorGUILayout.EndHorizontal();
  117. GUILayout.FlexibleSpace();
  118. }
  119. else if (_controller.Changes != null && _controller.Changes.Any())
  120. {
  121. DrawChangesList();
  122. DrawCommitSection();
  123. }
  124. else
  125. {
  126. EditorGUILayout.HelpBox("You are up-to-date! No local changes detected.", MessageType.Info);
  127. }
  128. }
  129. private void DrawChangesList()
  130. {
  131. EditorGUILayout.BeginHorizontal(EditorStyles.helpBox);
  132. EditorGUILayout.LabelField("Commit", GUILayout.Width(45));
  133. EditorGUILayout.LabelField("Status", GUILayout.Width(50));
  134. EditorGUILayout.LabelField("File Path");
  135. EditorGUILayout.LabelField("Actions", GUILayout.Width(130));
  136. EditorGUILayout.EndHorizontal();
  137. _scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition, GUILayout.ExpandHeight(true));
  138. for (var i = 0; i < _controller.Changes.Count; i++)
  139. {
  140. var change = _controller.Changes[i];
  141. var rowStyle = i % 2 == 0 ? _evenRowStyle : GUIStyle.none;
  142. EditorGUILayout.BeginHorizontal(rowStyle);
  143. if (change.Status == LibGit2Sharp.ChangeKind.Conflicted)
  144. {
  145. EditorGUI.BeginDisabledGroup(true);
  146. EditorGUILayout.Toggle(false, GUILayout.Width(45));
  147. EditorGUI.EndDisabledGroup();
  148. }
  149. else
  150. {
  151. change.IsSelectedForCommit = EditorGUILayout.Toggle(change.IsSelectedForCommit, GUILayout.Width(45));
  152. }
  153. string status;
  154. Color statusColor;
  155. switch (change.Status)
  156. {
  157. case LibGit2Sharp.ChangeKind.Added: status = "[+]"; statusColor = Color.green; break;
  158. case LibGit2Sharp.ChangeKind.Deleted: status = "[-]"; statusColor = Color.red; break;
  159. case LibGit2Sharp.ChangeKind.Modified: status = "[M]"; statusColor = new Color(1.0f, 0.6f, 0.0f); break;
  160. case LibGit2Sharp.ChangeKind.Renamed: status = "[R]"; statusColor = new Color(0.6f, 0.6f, 1.0f); break;
  161. case LibGit2Sharp.ChangeKind.Conflicted: status = "[C]"; statusColor = Color.magenta; break;
  162. default: status = "[?]"; statusColor = Color.white; break;
  163. }
  164. var filePathDisplay = change.Status == LibGit2Sharp.ChangeKind.Renamed
  165. ? $"{change.OldFilePath} -> {change.FilePath}"
  166. : change.FilePath;
  167. var originalColor = UnityEngine.GUI.color;
  168. UnityEngine.GUI.color = statusColor;
  169. EditorGUILayout.LabelField(new GUIContent(status, change.Status.ToString()), GUILayout.Width(50));
  170. UnityEngine.GUI.color = originalColor;
  171. EditorGUILayout.LabelField(new GUIContent(filePathDisplay, filePathDisplay));
  172. EditorGUILayout.BeginHorizontal(GUILayout.Width(120));
  173. EditorGUI.BeginDisabledGroup(_controller.IsLoading);
  174. if (change.Status == LibGit2Sharp.ChangeKind.Conflicted)
  175. {
  176. if (GUILayout.Button("Resolve", GUILayout.Width(70)))
  177. {
  178. EditorApplication.delayCall += () => _controller.ResolveConflict(change);
  179. }
  180. }
  181. else
  182. {
  183. if (GUILayout.Button("Diff", GUILayout.Width(45)))
  184. {
  185. EditorApplication.delayCall += () => _controller.DiffFile(change);
  186. }
  187. }
  188. if (GUILayout.Button(new GUIContent("Reset", "Revert changes for this file"), GUILayout.Width(55)))
  189. {
  190. EditorApplication.delayCall += () => _controller.ResetFile(change);
  191. }
  192. EditorGUI.EndDisabledGroup();
  193. EditorGUILayout.EndHorizontal();
  194. EditorGUILayout.EndHorizontal();
  195. }
  196. EditorGUILayout.EndScrollView();
  197. }
  198. private void DrawCommitSection()
  199. {
  200. EditorGUILayout.Space(10);
  201. if (_controller.Changes.Any(c => c.Status == LibGit2Sharp.ChangeKind.Conflicted))
  202. {
  203. EditorGUILayout.HelpBox("You must resolve all conflicts before you can commit.", MessageType.Warning);
  204. return;
  205. }
  206. EditorGUILayout.LabelField("Commit & Push", EditorStyles.boldLabel);
  207. _commitMessage = EditorGUILayout.TextArea(_commitMessage, GUILayout.Height(60), GUILayout.ExpandWidth(true));
  208. var isPushDisabled = string.IsNullOrWhiteSpace(_commitMessage) || !_controller.Changes.Any(c => c.IsSelectedForCommit);
  209. EditorGUI.BeginDisabledGroup(isPushDisabled);
  210. if (GUILayout.Button("Commit & Push Selected Files", GUILayout.Height(40)))
  211. {
  212. // Pass the UI state to the controller.
  213. _controller.CommitAndPush(_commitMessage);
  214. _commitMessage = ""; // Clear on sending
  215. }
  216. EditorGUI.EndDisabledGroup();
  217. if (isPushDisabled)
  218. {
  219. EditorGUILayout.HelpBox("Please enter a commit message and select at least one file.", MessageType.Warning);
  220. }
  221. }
  222. }
  223. }