// Copyright (c) 2025 TerraByte Inc. // // This script is the View component for the Better Git tool. Its only // responsibility is to draw the UI based on the state provided by the // ArbitratorController. using System.Linq; using UnityEngine; using UnityEditor; namespace Terra.Arbitrator.GUI { public class ArbitratorWindow : EditorWindow { private ArbitratorController _controller; private string _commitMessage = ""; private Vector2 _scrollPosition; private GUIStyle _evenRowStyle; private bool _stylesInitialized; [MenuItem("Version Control/Better Git")] public static void ShowWindow() { var window = GetWindow(); window.titleContent = new GUIContent("Better Git", EditorGUIUtility.IconContent("d_UnityEditor.VersionControl").image); } private void OnEnable() { _controller = new ArbitratorController( requestRepaint: Repaint, displayDialog: (heading, message) => EditorUtility.DisplayDialog(heading, message, "Yes", "No") ); _controller.OnEnable(); } private void InitializeStyles() { if (_stylesInitialized) return; _evenRowStyle = new GUIStyle(); var texture = new Texture2D(1, 1); var color = EditorGUIUtility.isProSkin ? new Color(0.3f, 0.3f, 0.3f, 0.3f) : new Color(0.8f, 0.8f, 0.8f, 0.5f); texture.SetPixel(0, 0, color); texture.Apply(); _evenRowStyle.normal.background = texture; _stylesInitialized = true; } private void OnGUI() { InitializeStyles(); DrawToolbar(); EditorGUILayout.Space(); DrawMessageArea(); DrawMainContent(); } private void DrawToolbar() { EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); EditorGUI.BeginDisabledGroup(_controller.IsLoading); if (GUILayout.Button(new GUIContent("Refresh", EditorGUIUtility.IconContent("Refresh").image), EditorStyles.toolbarButton, GUILayout.Width(80))) { _controller.Refresh(); } if (GUILayout.Button(new GUIContent("Pull", EditorGUIUtility.IconContent("CollabPull").image), EditorStyles.toolbarButton, GUILayout.Width(80))) { _controller.Pull(); } if (_controller.Changes is { Count: > 0 }) { if (GUILayout.Button("Select All", EditorStyles.toolbarButton, GUILayout.Width(80))) { _controller.SetAllSelection(true); } if (GUILayout.Button("Deselect All", EditorStyles.toolbarButton, GUILayout.Width(80))) { _controller.SetAllSelection(false); } GUILayout.FlexibleSpace(); var noChanges = !_controller.Changes.Any(); EditorGUI.BeginDisabledGroup(noChanges); var originalColor = UnityEngine.GUI.backgroundColor; UnityEngine.GUI.backgroundColor = new Color(1f, 0.5f, 0.5f, 0.8f); if (GUILayout.Button(new GUIContent("Reset All", EditorGUIUtility.IconContent("d_TreeEditor.Trash").image), EditorStyles.toolbarButton, GUILayout.Width(100))) { EditorApplication.delayCall += _controller.ResetAll; } UnityEngine.GUI.backgroundColor = originalColor; EditorGUI.EndDisabledGroup(); } EditorGUI.EndDisabledGroup(); if (_controller.IsLoading) { GUILayout.FlexibleSpace(); GUILayout.Label(_controller.LoadingMessage); } EditorGUILayout.EndHorizontal(); } private void DrawMessageArea() { if (!string.IsNullOrEmpty(_controller.ErrorMessage)) { EditorGUILayout.HelpBox(_controller.ErrorMessage, MessageType.Error); } else if (!string.IsNullOrEmpty(_controller.InfoMessage) && !_controller.IsLoading) { EditorGUILayout.HelpBox(_controller.InfoMessage, MessageType.Info); } } private void DrawMainContent() { if (_controller.IsLoading) { GUILayout.FlexibleSpace(); EditorGUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); GUILayout.Label("Loading...", EditorStyles.largeLabel); GUILayout.FlexibleSpace(); EditorGUILayout.EndHorizontal(); GUILayout.FlexibleSpace(); } else if (_controller.Changes != null && _controller.Changes.Any()) { DrawChangesList(); DrawCommitSection(); } else { EditorGUILayout.HelpBox("You are up-to-date! No local changes detected.", MessageType.Info); } } private void DrawChangesList() { EditorGUILayout.BeginHorizontal(EditorStyles.helpBox); EditorGUILayout.LabelField("Commit", GUILayout.Width(45)); EditorGUILayout.LabelField("Status", GUILayout.Width(50)); EditorGUILayout.LabelField("File Path"); EditorGUILayout.LabelField("Actions", GUILayout.Width(130)); EditorGUILayout.EndHorizontal(); _scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition, GUILayout.ExpandHeight(true)); for (var i = 0; i < _controller.Changes.Count; i++) { var change = _controller.Changes[i]; var rowStyle = i % 2 == 0 ? _evenRowStyle : GUIStyle.none; EditorGUILayout.BeginHorizontal(rowStyle); if (change.Status == LibGit2Sharp.ChangeKind.Conflicted) { EditorGUI.BeginDisabledGroup(true); EditorGUILayout.Toggle(false, GUILayout.Width(45)); EditorGUI.EndDisabledGroup(); } else { change.IsSelectedForCommit = EditorGUILayout.Toggle(change.IsSelectedForCommit, GUILayout.Width(45)); } string status; Color statusColor; switch (change.Status) { case LibGit2Sharp.ChangeKind.Added: status = "[+]"; statusColor = Color.green; break; case LibGit2Sharp.ChangeKind.Deleted: status = "[-]"; statusColor = Color.red; break; case LibGit2Sharp.ChangeKind.Modified: status = "[M]"; statusColor = new Color(1.0f, 0.6f, 0.0f); break; case LibGit2Sharp.ChangeKind.Renamed: status = "[R]"; statusColor = new Color(0.6f, 0.6f, 1.0f); break; case LibGit2Sharp.ChangeKind.Conflicted: status = "[C]"; statusColor = Color.magenta; break; default: status = "[?]"; statusColor = Color.white; break; } var filePathDisplay = change.Status == LibGit2Sharp.ChangeKind.Renamed ? $"{change.OldFilePath} -> {change.FilePath}" : change.FilePath; var originalColor = UnityEngine.GUI.color; UnityEngine.GUI.color = statusColor; EditorGUILayout.LabelField(new GUIContent(status, change.Status.ToString()), GUILayout.Width(50)); UnityEngine.GUI.color = originalColor; EditorGUILayout.LabelField(new GUIContent(filePathDisplay, filePathDisplay)); EditorGUILayout.BeginHorizontal(GUILayout.Width(120)); EditorGUI.BeginDisabledGroup(_controller.IsLoading); if (change.Status == LibGit2Sharp.ChangeKind.Conflicted) { if (GUILayout.Button("Resolve", GUILayout.Width(70))) { EditorApplication.delayCall += () => _controller.ResolveConflict(change); } } else { if (GUILayout.Button("Diff", GUILayout.Width(45))) { EditorApplication.delayCall += () => _controller.DiffFile(change); } } if (GUILayout.Button(new GUIContent("Reset", "Revert changes for this file"), GUILayout.Width(55))) { EditorApplication.delayCall += () => _controller.ResetFile(change); } EditorGUI.EndDisabledGroup(); EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal(); } EditorGUILayout.EndScrollView(); } private void DrawCommitSection() { EditorGUILayout.Space(10); if (_controller.Changes.Any(c => c.Status == LibGit2Sharp.ChangeKind.Conflicted)) { EditorGUILayout.HelpBox("You must resolve all conflicts before you can commit.", MessageType.Warning); return; } EditorGUILayout.LabelField("Commit & Push", EditorStyles.boldLabel); _commitMessage = EditorGUILayout.TextArea(_commitMessage, GUILayout.Height(60), GUILayout.ExpandWidth(true)); var isPushDisabled = string.IsNullOrWhiteSpace(_commitMessage) || !_controller.Changes.Any(c => c.IsSelectedForCommit); EditorGUI.BeginDisabledGroup(isPushDisabled); if (GUILayout.Button("Commit & Push Selected Files", GUILayout.Height(40))) { _controller.CommitAndPush(_commitMessage); _commitMessage = ""; } EditorGUI.EndDisabledGroup(); if (isPushDisabled) { EditorGUILayout.HelpBox("Please enter a commit message and select at least one file.", MessageType.Warning); } } } }