123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474 |
- using System;
- using UnityEngine;
- using UnityEditor;
- using UnityEngine.SceneManagement;
- using System.Linq;
- using System.Collections.Generic;
- using GitMerge.Utilities;
- using Object = UnityEngine.Object;
- namespace GitMerge
- {
- /// <summary>
- /// The window that lets you perform merges on scenes and prefabs.
- /// </summary>
- public class GitMergeWindow : EditorWindow
- {
- private VCS vcs = new VCSGit();
-
- private const string EDITOR_PREFS_AUTOMERGE = "GitMerge_automerge";
- private const string EDITOR_PREFS_AUTOFOCUS = "GitMerge_autofocus";
-
- public static bool automerge { private set; get; }
- public static bool autofocus { private set; get; }
-
- private MergeManagerBase mergeManager;
- private MergeFilter filter = new MergeFilter();
- private MergeFilterBar filterBar = new MergeFilterBar();
- public bool mergeInProgress => mergeManager != null;
- private PageView pageView = new PageView();
- private Vector2 scrollPosition = Vector2.zero;
- private int tab = 0;
- private List<GameObjectMergeActions> mergeActionsFiltered;
- private Texture2D brokenLogo;
- private Texture2D fixedLogo;
-
- private static void OpenEditor()
- {
- GetWindow();
- }
- private static EditorWindow GetWindow()
- {
- var window = GetWindow(typeof(GitMergeWindow), false, "GitMerge");
- window.autoRepaintOnSceneChange = true;
- window.minSize = new Vector2(500, 100);
- return window;
- }
- public static void ResolveConflict(string path)
- {
- if (path.Contains(".unity") || path.Contains(".prefab"))
- {
- var mergeWindow = GetWindow() as GitMergeWindow;
- if (mergeWindow != null)
- {
- mergeWindow.InitializeMerge(path);
- }
- }
- throw new NotSupportedException($"Cannot resolve conflict for: {path}");
- }
- private void OnEnable()
- {
- brokenLogo = UnityEngine.Resources.Load<Texture2D>("chain-broken");
- fixedLogo = UnityEngine.Resources.Load<Texture2D>("check");
- pageView.NumElementsPerPage = 200;
- filterBar.filter = filter;
- filter.OnChanged += CacheMergeActions;
- Selection.selectionChanged += Repaint;
- EditorApplication.hierarchyWindowItemOnGUI += HighlightItems;
- LoadSettings();
- }
- private static void LoadSettings()
- {
- automerge = EditorPrefs.GetBool(EDITOR_PREFS_AUTOMERGE, true);
- autofocus = EditorPrefs.GetBool(EDITOR_PREFS_AUTOFOCUS, true);
- }
- void OnHierarchyChange()
- {
- // Repaint if we changed the scene
- this.Repaint();
- }
- // Always check for editor state changes, and abort the active merge process if needed
- private void Update()
- {
- if (MergeAction.inMergePhase &&
- (EditorApplication.isCompiling ||
- EditorApplication.isPlayingOrWillChangePlaymode))
- {
- ShowNotification(new GUIContent("Aborting merge due to editor state change."));
- AbortMerge(false);
- }
- }
- private void AbortMerge(bool showNotification = true)
- {
- mergeManager.AbortMerge(showNotification);
- mergeManager = null;
- }
- private void OnGUI()
- {
- Resources.DrawLogo();
- DrawTabButtons();
- EditorGUILayout.Space();
- DrawHorizontalDivider();
- EditorGUILayout.Space();
- switch (tab)
- {
- case 0:
- OnGUIStartMergeTab();
- break;
- default:
- OnGUISettingsTab();
- break;
- }
- }
- /// <summary>
- /// Tab that offers scene merging.
- /// </summary>
- private void OnGUIStartMergeTab()
- {
- if (!mergeInProgress)
- {
- DisplayPrefabMergeField();
- GUILayout.Space(20);
- DisplaySceneMergeButton();
- }
- else
- {
- DisplayMergeProcess();
- }
- }
- private void DisplaySceneMergeButton()
- {
- var activeScene = SceneManager.GetActiveScene();
- GUILayout.Label("Open Scene: " + activeScene.path);
- if (activeScene.path != "" &&
- !mergeInProgress &&
- GUILayout.Button("Start merging the open scene", GUILayout.Height(30)))
- {
- var manager = new MergeManagerScene(this, vcs);
- if (manager.TryInitializeMerge())
- {
- this.mergeManager = manager;
- CacheMergeActions();
- }
- }
- }
- private void DisplayPrefabMergeField()
- {
- if (!mergeInProgress)
- {
- var path = PathDetectingDragAndDropField("Drag a scene or prefab here to start merging", 80);
- if (path != null)
- {
- InitializeMerge(path);
- }
- }
- }
-
- private void HighlightItems(int instanceID, Rect selectionRect)
- {
- var target = EditorUtility.InstanceIDToObject(instanceID) as GameObject;
- Texture2D drawableLogo;
-
- if (target == null)
- {
- return;
- }
- bool isResolved = false;
- bool found = false;
-
- if (mergeManager is { allMergeActions: not null })
- {
- ObjectID targetId = ObjectID.GetFor(target);
-
- foreach (var mergeAction in mergeManager.allMergeActions)
- {
- ObjectID actionId = ObjectID.GetFor(mergeAction.ours);
-
- if (targetId.id == actionId.id)
- {
- found = true;
- isResolved = mergeAction.merged;
- break;
- }
- }
- }
- if (!found) return;
- if (!isResolved)
- {
- drawableLogo = brokenLogo;
- }
- else
- {
- drawableLogo = fixedLogo;
- }
- var iconSize = 16;
-
- var iconRect = new Rect(
- selectionRect.xMax - iconSize - 2,
- selectionRect.y + (selectionRect.height - iconSize) / 2f,
- iconSize,
- iconSize
- );
-
- GUI.DrawTexture(iconRect, drawableLogo, ScaleMode.ScaleToFit);
- }
- private void InitializeMerge(string path)
- {
- if (path == null) return;
- var asset = AssetDatabase.LoadAssetAtPath<Object>(path);
-
- if (IsPrefabAsset(asset))
- {
- var manager = new MergeManagerPrefab(this, vcs);
- if (manager.TryInitializeMerge(path))
- {
- this.mergeManager = manager;
- CacheMergeActions();
- }
- }
- else if (IsSceneAsset(asset))
- {
- var manager = new MergeManagerScene(this, vcs);
- if (manager.TryInitializeMerge(path))
- {
- this.mergeManager = manager;
- CacheMergeActions();
- }
- }
- }
- private static bool IsPrefabAsset(Object asset)
- {
- var assetType = asset.GetType();
- return assetType == typeof(GameObject) || assetType == typeof(BrokenPrefabAsset) ||
- assetType == typeof(DefaultAsset);
- }
- private static bool IsSceneAsset(Object asset)
- {
- var assetType = asset.GetType();
- return assetType == typeof(SceneAsset);
- }
- private static string PathDetectingDragAndDropField(string text, float height)
- {
- var currentEvent = Event.current;
- using (new GUIBackgroundColor(Color.black))
- {
- // Caching these sounds good on paper, but Unity tends to forget them randomly
- var content = EditorGUIUtility.IconContent("RectMask2D Icon", string.Empty);
- content.text = text;
-
- var buttonStyle = GUI.skin.GetStyle("Button");
- var style = new GUIStyle(GUI.skin.GetStyle("Box"));
- style.stretchWidth = true;
- style.normal.background = buttonStyle.normal.background;
- style.normal.textColor = buttonStyle.normal.textColor;
- style.alignment = TextAnchor.MiddleCenter;
- style.imagePosition = ImagePosition.ImageAbove;
- GUILayout.Box(content, style, GUILayout.Height(height));
- }
- var rect = GUILayoutUtility.GetLastRect();
- if (rect.Contains(currentEvent.mousePosition))
- {
- if (DragAndDrop.objectReferences.Length == 1)
- {
- switch (currentEvent.type)
- {
- case EventType.DragUpdated:
- var asset = DragAndDrop.objectReferences[0];
- if (IsPrefabAsset(asset) || IsSceneAsset(asset))
- {
- DragAndDrop.visualMode = DragAndDropVisualMode.Move;
- }
- break;
- case EventType.DragPerform:
- var path = AssetDatabase.GetAssetPath(DragAndDrop.objectReferences[0]);
- DragAndDrop.AcceptDrag();
- return path;
- }
- }
- }
- return null;
- }
- /// <summary>
- /// Tab that offers various settings for the tool.
- /// </summary>
- private void OnGUISettingsTab()
- {
- var vcsPath = vcs.GetExePath();
- var vcsPathNew = EditorGUILayout.TextField("Path to git.exe", vcsPath);
- if (vcsPath != vcsPathNew)
- {
- vcs.SetPath(vcsPathNew);
- }
- automerge = DisplaySettingsToggle(automerge,
- EDITOR_PREFS_AUTOMERGE,
- "Automerge",
- "(Automerge new/deleted GameObjects/Components upon merge start)");
-
- autofocus = DisplaySettingsToggle(autofocus,
- EDITOR_PREFS_AUTOFOCUS,
- "Auto Highlight",
- "(Highlight GameObjects when applying a MergeAction to it)");
- }
- private static bool DisplaySettingsToggle(bool value, string editorPrefsKey, string title, string description)
- {
- var newValue = EditorGUILayout.Toggle(title, value);
- if (value != newValue)
- {
- EditorPrefs.SetBool(editorPrefsKey, value);
- }
- GUILayout.Label(description);
- return newValue;
- }
- /// <summary>
- /// If no merge is in progress, draws the buttons to switch between tabs.
- /// Otherwise, draws the "abort merge" button.
- /// </summary>
- private void DrawTabButtons()
- {
- if (!mergeInProgress)
- {
- string[] tabs = { "Merge", "Settings" };
- tab = GUI.SelectionGrid(new Rect(72, 36, 300, 22), tab, tabs, 3);
- }
- else
- {
- GUI.backgroundColor = new Color(1, 0.4f, 0.4f, 1);
- if (GUI.Button(new Rect(72, 36, 300, 22), "Abort merge"))
- {
- mergeManager.AbortMerge();
- mergeManager = null;
- }
- // Confirm merge if possible
- if (mergeManager != null && mergeManager.isMergingDone)
- {
- GUI.backgroundColor = Color.green;
- if (GUI.Button(new Rect(400, 36, 300, 22), "Confirm merge"))
- {
- mergeManager.CompleteMerge();
- mergeManager = null;
- }
- }
- GUI.backgroundColor = Color.white;
- }
- }
-
- private void DrawHorizontalDivider()
- {
- var rect = EditorGUILayout.GetControlRect(false, 1);
- EditorGUI.DrawRect(rect, new Color(0.4f, 0.4f, 0.4f, 1f)); // Dark gray line
- }
- /// <summary>
- /// Displays all MergeActions and the "apply merge" button if a merge is in progress.
- /// </summary>
- private void DisplayMergeProcess()
- {
- DrawCommandBar();
- EditorGUILayout.Space();
- DrawHorizontalDivider();
- var done = DisplayMergeActions();
- // GUILayout.BeginHorizontal();
- // if (done && GUILayout.Button("Apply merge", GUILayout.Height(40)))
- // {
- // mergeManager.CompleteMerge();
- // mergeManager = null;
- // }
- // GUILayout.EndHorizontal();
- }
- /// <summary>
- /// Display extra commands to simplify merge process
- /// </summary>
- private void DrawCommandBar()
- {
- DrawQuickMergeSideSelectionCommands();
- filterBar.Draw();
- }
- /// <summary>
- /// Allow to select easily 'use ours' or 'use theirs' for all actions
- /// </summary>
- private void DrawQuickMergeSideSelectionCommands()
- {
- GUILayout.BeginHorizontal();
- {
- if (GUILayout.Button(new GUIContent("Use ours", "Use theirs for all. Do not apply merge automatically.")))
- {
- mergeManager.allMergeActions.ForEach((action) =>
- {
- action.UseOurs(true);
- });
- }
- if (GUILayout.Button(new GUIContent("Use theirs", "Use theirs for all. Do not apply merge automatically.")))
- {
- mergeManager.allMergeActions.ForEach((action) =>
- {
- action.UseTheirs(true);
- });
- }
- GUILayout.FlexibleSpace();
- }
- GUILayout.EndHorizontal();
- }
- /// <summary>
- /// Displays all GameObjectMergeActions.
- /// </summary>
- /// <returns>True, if all MergeActions are flagged as "merged".</returns>
- private bool DisplayMergeActions()
- {
- var textColor = GUI.skin.label.normal.textColor;
- GUI.skin.label.normal.textColor = Color.black;
- bool done = true;
- pageView.Draw(mergeActionsFiltered.Count, (index) =>
- {
- var actions = mergeActionsFiltered[index];
- actions.OnGUI();
- done = done && actions.merged;
- });
- GUI.skin.label.normal.textColor = textColor;
- return done;
- }
- private void CacheMergeActions()
- {
- if (filter.useFilter)
- {
- mergeActionsFiltered = mergeManager.allMergeActions.Where((actions) => filter.IsPassingFilter(actions)).ToList();
- }
- else
- {
- mergeActionsFiltered = mergeManager.allMergeActions;
- }
- mergeActionsFiltered = mergeManager.allMergeActions.Where((actions) => !actions.applied).ToList();
- }
- }
- }
|