using UnityEngine;
using UnityEditor;
using System;
using System.Collections.Generic;
using System.Linq;
namespace UnityVersionControl
{
///
/// Enhanced Visual Conflict Resolution Window for Unity Version Control
/// Provides rich visual comparison and resolution for different asset types
///
public class VersionControlConflictResolver : EditorWindow
{
#region Fields
private Vector2 conflictListScrollPosition;
private Vector2 viewerScrollPosition;
private Vector2 leftScrollPosition;
private Vector2 rightScrollPosition;
private int selectedConflictIndex = 0;
private readonly List mockConflicts = new List();
private ConflictViewer currentViewer;
// Enhanced visual comparison
private int selectedViewMode = 0;
private readonly string[] viewModeNames = { "Side by Side", "3D Preview", "Properties", "Overlay" };
private float splitRatio = 0.5f;
private bool isDraggingSplitter = false;
// 3D Preview system
private Camera previewCameraLeft;
private Camera previewCameraRight;
private RenderTexture leftRenderTexture;
private RenderTexture rightRenderTexture;
private GameObject leftPreviewObject;
private GameObject rightPreviewObject;
private GameObject previewEnvironment;
private Light previewLight;
// UI State
private bool showLineNumbers = true;
private bool showWhitespace = false;
private bool wordWrap = false;
private string searchText = "";
private Vector3 cameraRotation = new Vector3(15f, -30f, 0f);
private float cameraDistance = 3f;
// Static callback for when conflicts are resolved
private static System.Action onConflictsResolved;
// Visual constants
private static readonly Color backgroundColor = new Color(0.22f, 0.22f, 0.22f);
private static readonly Color cardColor = new Color(0.28f, 0.28f, 0.28f);
private static readonly Color accentColor = new Color(0.3f, 0.7f, 1f);
private static readonly Color successColor = new Color(0.3f, 0.8f, 0.3f);
private static readonly Color warningColor = new Color(1f, 0.8f, 0.2f);
private static readonly Color errorColor = new Color(1f, 0.4f, 0.4f);
private static readonly Color localColor = new Color(0.4f, 0.8f, 0.4f);
private static readonly Color remoteColor = new Color(0.4f, 0.6f, 1f);
private static readonly Color conflictColor = new Color(1f, 0.6f, 0.2f);
private static readonly Color selectedColor = new Color(0.2f, 0.5f, 0.8f, 0.3f);
// Caches
private readonly Dictionary textureCache = new Dictionary();
private readonly Dictionary materialCache = new Dictionary();
private readonly Dictionary prefabCache = new Dictionary();
private readonly List currentPropertyDifferences = new List();
#endregion
#region Unity Lifecycle
[MenuItem("Window/Version Control/Conflict Resolver", false, 1)]
public static void ShowWindow()
{
var window = GetWindow();
window.minSize = new Vector2(1000, 700);
window.Initialize();
}
public static void ShowWindow(System.Action onResolved)
{
onConflictsResolved = onResolved;
var window = GetWindow("Visual Conflict Resolver");
window.minSize = new Vector2(1000, 700);
window.Initialize();
}
private void OnEnable()
{
Initialize();
Setup3DPreviewSystem();
}
private void OnDisable()
{
CleanupTextures();
Cleanup3DPreviewSystem();
// Clean up callback to prevent memory leaks
if (onConflictsResolved != null)
{
Debug.LogWarning("Conflict resolver closed without resolving conflicts - cleaning up callback");
onConflictsResolved = null;
}
}
private void OnGUI()
{
try
{
HandleEvents();
DrawBackground();
DrawEnhancedHeader();
DrawMainLayout();
}
catch (Exception ex)
{
Debug.LogError($"Error in ConflictResolver OnGUI: {ex.Message}");
DrawFallbackUI();
}
}
#endregion
#region Initialization
private void Initialize()
{
CreateMockConflicts();
if (mockConflicts.Count > 0)
{
SelectConflict(0);
}
}
private void CreateMockConflicts()
{
try
{
mockConflicts.Clear();
// Script conflict
mockConflicts.Add(new MockConflictInfo
{
fileName = "PlayerController.cs",
assetType = ConflictAssetType.Script,
conflictType = ConflictType.PropertyValueDifference,
description = "Method implementation differs",
localVersion = "public float speed = 5.0f;\npublic void Move() {\n // Local implementation\n transform.Translate(Vector3.forward * speed);\n}",
remoteVersion = "public float speed = 7.0f;\npublic void Move() {\n // Remote implementation\n rb.velocity = Vector3.forward * speed;\n}",
resolution = ConflictResolution.Unresolved
});
// Prefab conflict
mockConflicts.Add(new MockConflictInfo
{
fileName = "PlayerPrefab.prefab",
assetType = ConflictAssetType.Prefab,
conflictType = ConflictType.ComponentAdded,
description = "Components differ between versions",
localVersion = "Components: Transform, MeshRenderer, Rigidbody, BoxCollider",
remoteVersion = "Components: Transform, MeshRenderer, Rigidbody, AudioSource, ParticleSystem",
resolution = ConflictResolution.Unresolved
});
// Material conflict
mockConflicts.Add(new MockConflictInfo
{
fileName = "PlayerMaterial.mat",
assetType = ConflictAssetType.Material,
conflictType = ConflictType.PropertyValueDifference,
description = "Material properties have different values",
localVersion = "Albedo: Red (1,0,0), Metallic: 0.5, Smoothness: 0.8",
remoteVersion = "Albedo: Blue (0,0,1), Metallic: 0.2, Smoothness: 0.6",
resolution = ConflictResolution.Unresolved
});
// Scene conflict
mockConflicts.Add(new MockConflictInfo
{
fileName = "MainScene.unity",
assetType = ConflictAssetType.Scene,
conflictType = ConflictType.GameObjectMoved,
description = "GameObject positions and lighting differ",
localVersion = "Camera: (0,10,0), Lighting: Realtime GI",
remoteVersion = "Camera: (5,10,-5), Lighting: Baked GI",
resolution = ConflictResolution.Unresolved
});
// Texture conflict
mockConflicts.Add(new MockConflictInfo
{
fileName = "CharacterTexture.png",
assetType = ConflictAssetType.Texture,
conflictType = ConflictType.AssetReplaced,
description = "Different texture versions",
localVersion = "Local texture: 512x512, RGB format",
remoteVersion = "Remote texture: 1024x1024, RGBA format",
resolution = ConflictResolution.Unresolved
});
}
catch (Exception ex)
{
Debug.LogError($"Error creating mock conflicts: {ex.Message}");
}
}
private void CleanupTextures()
{
foreach (var texture in textureCache.Values)
{
if (texture != null) DestroyImmediate(texture);
}
textureCache.Clear();
}
#endregion
#region 3D Preview System
private void Setup3DPreviewSystem()
{
try
{
// Create preview environment
previewEnvironment = new GameObject("ConflictResolver_PreviewEnvironment");
previewEnvironment.hideFlags = HideFlags.HideAndDontSave;
// Setup lighting
var lightGO = new GameObject("PreviewLight");
lightGO.transform.SetParent(previewEnvironment.transform);
lightGO.hideFlags = HideFlags.HideAndDontSave;
previewLight = lightGO.AddComponent();
previewLight.type = LightType.Directional;
previewLight.intensity = 1.0f;
previewLight.color = Color.white;
previewLight.transform.rotation = Quaternion.Euler(50f, -30f, 0f);
// Setup left camera
var leftCameraGO = new GameObject("PreviewCamera_Left");
leftCameraGO.transform.SetParent(previewEnvironment.transform);
leftCameraGO.hideFlags = HideFlags.HideAndDontSave;
previewCameraLeft = leftCameraGO.AddComponent();
previewCameraLeft.clearFlags = CameraClearFlags.SolidColor;
previewCameraLeft.backgroundColor = new Color(0.1f, 0.1f, 0.1f);
previewCameraLeft.cullingMask = 1 << 30; // Layer 30 for left previews
// Setup right camera
var rightCameraGO = new GameObject("PreviewCamera_Right");
rightCameraGO.transform.SetParent(previewEnvironment.transform);
rightCameraGO.hideFlags = HideFlags.HideAndDontSave;
previewCameraRight = rightCameraGO.AddComponent();
previewCameraRight.clearFlags = CameraClearFlags.SolidColor;
previewCameraRight.backgroundColor = new Color(0.1f, 0.1f, 0.1f);
previewCameraRight.cullingMask = 1 << 31; // Layer 31 for right previews
// Create render textures
leftRenderTexture = new RenderTexture(256, 256, 16);
rightRenderTexture = new RenderTexture(256, 256, 16);
previewCameraLeft.targetTexture = leftRenderTexture;
previewCameraRight.targetTexture = rightRenderTexture;
// Position cameras
UpdateCameraPositions();
}
catch (Exception ex)
{
Debug.LogError($"Failed to setup 3D preview system: {ex.Message}");
}
}
private void UpdateCameraPositions()
{
if (previewCameraLeft != null && previewCameraRight != null)
{
var position = Quaternion.Euler(cameraRotation) * Vector3.back * cameraDistance;
previewCameraLeft.transform.position = position + Vector3.left * 2f;
previewCameraLeft.transform.LookAt(Vector3.left * 2f);
previewCameraRight.transform.position = position + Vector3.right * 2f;
previewCameraRight.transform.LookAt(Vector3.right * 2f);
}
}
private void Cleanup3DPreviewSystem()
{
try
{
if (leftRenderTexture != null)
{
leftRenderTexture.Release();
DestroyImmediate(leftRenderTexture);
leftRenderTexture = null;
}
if (rightRenderTexture != null)
{
rightRenderTexture.Release();
DestroyImmediate(rightRenderTexture);
rightRenderTexture = null;
}
if (leftPreviewObject != null)
{
DestroyImmediate(leftPreviewObject);
leftPreviewObject = null;
}
if (rightPreviewObject != null)
{
DestroyImmediate(rightPreviewObject);
rightPreviewObject = null;
}
if (previewEnvironment != null)
{
DestroyImmediate(previewEnvironment);
previewEnvironment = null;
}
previewCameraLeft = null;
previewCameraRight = null;
previewLight = null;
}
catch (Exception ex)
{
Debug.LogError($"Failed to cleanup 3D preview system: {ex.Message}");
}
}
private void RefreshPreviewObjects()
{
if (selectedViewMode == 1 && selectedConflictIndex >= 0 && selectedConflictIndex < mockConflicts.Count)
{
var conflict = mockConflicts[selectedConflictIndex];
LoadPreviewObjects(conflict);
}
}
private void LoadPreviewObjects(MockConflictInfo conflict)
{
try
{
// Clean up existing preview objects
if (leftPreviewObject != null)
DestroyImmediate(leftPreviewObject);
if (rightPreviewObject != null)
DestroyImmediate(rightPreviewObject);
if (conflict.assetType == ConflictAssetType.Prefab)
{
leftPreviewObject = CreateMockPrefab(true);
rightPreviewObject = CreateMockPrefab(false);
// Position objects
leftPreviewObject.transform.position = Vector3.left * 2f;
rightPreviewObject.transform.position = Vector3.right * 2f;
// Set layers
SetLayerRecursively(leftPreviewObject, 30);
SetLayerRecursively(rightPreviewObject, 31);
}
else if (conflict.assetType == ConflictAssetType.Material)
{
// Create sphere previews for materials
leftPreviewObject = GameObject.CreatePrimitive(PrimitiveType.Sphere);
rightPreviewObject = GameObject.CreatePrimitive(PrimitiveType.Sphere);
leftPreviewObject.hideFlags = HideFlags.HideAndDontSave;
rightPreviewObject.hideFlags = HideFlags.HideAndDontSave;
leftPreviewObject.transform.position = Vector3.left * 2f;
rightPreviewObject.transform.position = Vector3.right * 2f;
// Apply different materials
var leftRenderer = leftPreviewObject.GetComponent();
var rightRenderer = rightPreviewObject.GetComponent();
leftRenderer.material.color = Color.red;
rightRenderer.material.color = Color.blue;
SetLayerRecursively(leftPreviewObject, 30);
SetLayerRecursively(rightPreviewObject, 31);
}
}
catch (Exception ex)
{
Debug.LogError($"Failed to load preview objects: {ex.Message}");
}
}
private GameObject CreateMockPrefab(bool isLocal)
{
var prefab = GameObject.CreatePrimitive(PrimitiveType.Cube);
prefab.hideFlags = HideFlags.HideAndDontSave;
prefab.name = isLocal ? "LocalPrefab" : "RemotePrefab";
if (isLocal)
{
prefab.transform.localScale = Vector3.one;
var renderer = prefab.GetComponent();
renderer.material.color = Color.red;
}
else
{
prefab.transform.localScale = Vector3.one * 1.2f;
var renderer = prefab.GetComponent();
renderer.material.color = Color.blue;
// Add additional component to show difference
var audioSource = prefab.AddComponent();
}
return prefab;
}
private void SetLayerRecursively(GameObject obj, int layer)
{
obj.layer = layer;
foreach (Transform child in obj.transform)
{
SetLayerRecursively(child.gameObject, layer);
}
}
#endregion
#region Enhanced GUI Drawing
private void DrawBackground()
{
var rect = new Rect(0, 0, position.width, position.height);
EditorGUI.DrawRect(rect, backgroundColor);
}
private void DrawEnhancedHeader()
{
DrawCard(() =>
{
using (new EditorGUILayout.HorizontalScope())
{
var iconRect = GUILayoutUtility.GetRect(24, 24);
var iconTexture = CreateRoundedTexture(errorColor, 24, 4);
GUI.DrawTexture(iconRect, iconTexture);
GUILayout.Space(8);
using (new EditorGUILayout.VerticalScope())
{
var titleStyle = new GUIStyle(EditorStyles.boldLabel);
titleStyle.fontSize = 14;
titleStyle.normal.textColor = Color.white;
EditorGUILayout.LabelField("Visual Conflict Resolution", titleStyle);
var subtitleStyle = new GUIStyle(EditorStyles.label);
subtitleStyle.normal.textColor = new Color(0.8f, 0.8f, 0.8f);
subtitleStyle.fontSize = 11;
EditorGUILayout.LabelField($"Resolve {mockConflicts.Count(c => c.resolution == ConflictResolution.Unresolved)} conflicts to continue", subtitleStyle);
}
GUILayout.FlexibleSpace();
DrawViewModeSelector();
GUILayout.Space(8);
DrawResolutionButtons();
}
}, 12);
}
private void DrawViewModeSelector()
{
using (new EditorGUILayout.VerticalScope())
{
EditorGUILayout.LabelField("View Mode:", EditorStyles.miniLabel);
var newViewMode = GUILayout.Toolbar(selectedViewMode, viewModeNames, GUILayout.Width(300));
if (newViewMode != selectedViewMode)
{
selectedViewMode = newViewMode;
RefreshPreviewObjects();
GenerateCurrentPropertyDifferences();
Repaint();
}
}
}
private void DrawResolutionButtons()
{
var unresolvedCount = mockConflicts.Count(c => c.resolution == ConflictResolution.Unresolved);
var canProceed = unresolvedCount == 0;
using (new EditorGUI.DisabledScope(!canProceed))
{
var proceedStyle = new GUIStyle(GUI.skin.button);
proceedStyle.normal.textColor = Color.white;
proceedStyle.fixedHeight = 28;
proceedStyle.fontStyle = FontStyle.Bold;
if (canProceed)
{
proceedStyle.normal.background = CreateRoundedTexture(successColor, 28, 4);
}
if (GUILayout.Button("✓ APPLY RESOLUTIONS", proceedStyle, GUILayout.Width(140)))
{
ApplyResolutions();
}
}
GUILayout.Space(8);
var cancelStyle = new GUIStyle(GUI.skin.button);
cancelStyle.normal.textColor = errorColor;
cancelStyle.fixedHeight = 28;
if (GUILayout.Button("✕ CANCEL PULL", cancelStyle, GUILayout.Width(100)))
{
Close();
}
}
private void DrawMainLayout()
{
EditorGUILayout.Space(8);
using (new EditorGUILayout.HorizontalScope())
{
// Left panel - Conflict list
DrawConflictList();
GUILayout.Space(8);
// Right panel - Conflict viewer
DrawConflictViewer();
}
}
private void DrawConflictList()
{
using (new EditorGUILayout.VerticalScope(GUILayout.Width(280)))
{
DrawCard(() =>
{
DrawSectionHeader("Conflicting Files", $"{mockConflicts.Count} files need resolution");
EditorGUILayout.Space(8);
using (var scrollView = new EditorGUILayout.ScrollViewScope(conflictListScrollPosition))
{
conflictListScrollPosition = scrollView.scrollPosition;
for (int i = 0; i < mockConflicts.Count; i++)
{
DrawConflictListItem(mockConflicts[i], i);
if (i < mockConflicts.Count - 1)
EditorGUILayout.Space(4);
}
}
}, 12);
}
}
private void DrawConflictListItem(MockConflictInfo conflict, int index)
{
var isSelected = selectedConflictIndex == index;
var itemRect = EditorGUILayout.BeginVertical();
if (isSelected)
{
EditorGUI.DrawRect(itemRect, selectedColor);
}
if (GUI.Button(itemRect, "", GUIStyle.none))
{
SelectConflict(index);
}
GUILayout.Space(8);
using (new EditorGUILayout.HorizontalScope())
{
GUILayout.Space(8);
// Asset type icon
var iconSize = 20;
var iconRect = GUILayoutUtility.GetRect(iconSize, iconSize);
var iconColor = GetAssetTypeColor(conflict.assetType);
var iconTexture = CreateRoundedTexture(iconColor, iconSize, iconSize / 2);
GUI.DrawTexture(iconRect, iconTexture);
var iconStyle = new GUIStyle(EditorStyles.centeredGreyMiniLabel);
iconStyle.normal.textColor = Color.white;
iconStyle.fontSize = 8;
iconStyle.fontStyle = FontStyle.Bold;
GUI.Label(iconRect, GetAssetTypeIcon(conflict.assetType), iconStyle);
GUILayout.Space(8);
using (new EditorGUILayout.VerticalScope())
{
var fileNameStyle = new GUIStyle(EditorStyles.boldLabel);
fileNameStyle.fontSize = 12;
fileNameStyle.normal.textColor = Color.white;
EditorGUILayout.LabelField(conflict.fileName, fileNameStyle);
var descStyle = new GUIStyle(EditorStyles.label);
descStyle.normal.textColor = new Color(0.7f, 0.7f, 0.7f);
descStyle.fontSize = 10;
descStyle.wordWrap = true;
EditorGUILayout.LabelField(conflict.description, descStyle);
}
GUILayout.FlexibleSpace();
// Resolution status
var statusSize = 16;
var statusRect = GUILayoutUtility.GetRect(statusSize, statusSize);
var statusColor = conflict.resolution switch
{
ConflictResolution.Unresolved => errorColor,
ConflictResolution.UseLocal => localColor,
ConflictResolution.UseRemote => remoteColor,
_ => warningColor
};
var statusTexture = CreateRoundedTexture(statusColor, statusSize, statusSize / 2);
GUI.DrawTexture(statusRect, statusTexture);
var statusIcon = conflict.resolution switch
{
ConflictResolution.Unresolved => "!",
ConflictResolution.UseLocal => "L",
ConflictResolution.UseRemote => "R",
_ => "?"
};
var statusStyle = new GUIStyle(EditorStyles.centeredGreyMiniLabel);
statusStyle.normal.textColor = Color.white;
statusStyle.fontSize = 8;
statusStyle.fontStyle = FontStyle.Bold;
GUI.Label(statusRect, statusIcon, statusStyle);
GUILayout.Space(8);
}
GUILayout.Space(8);
EditorGUILayout.EndVertical();
}
private void DrawConflictViewer()
{
using (new EditorGUILayout.VerticalScope())
{
if (selectedConflictIndex >= 0 && selectedConflictIndex < mockConflicts.Count)
{
var selectedConflict = mockConflicts[selectedConflictIndex];
DrawCard(() =>
{
DrawConflictViewerContent(selectedConflict);
}, 12);
}
else
{
DrawCard(() =>
{
var emptyStyle = new GUIStyle(EditorStyles.centeredGreyMiniLabel);
emptyStyle.normal.textColor = new Color(0.6f, 0.6f, 0.6f);
emptyStyle.fontSize = 14;
EditorGUILayout.LabelField("Select a conflict to view details", emptyStyle);
}, 32);
}
}
}
private void DrawConflictViewerContent(MockConflictInfo conflict)
{
DrawSectionHeader($"{conflict.fileName} Conflict", $"{conflict.assetType} • {conflict.conflictType}");
EditorGUILayout.Space(8);
// Enhanced conflict viewer based on selected mode
switch (selectedViewMode)
{
case 0: DrawSideBySideComparison(conflict); break;
case 1: Draw3DPreviewComparison(conflict); break;
case 2: DrawPropertiesComparison(conflict); break;
case 3: DrawOverlayComparison(conflict); break;
}
EditorGUILayout.Space(16);
DrawResolutionControls(conflict);
}
private void DrawSideBySideComparison(MockConflictInfo conflict)
{
var viewerRect = EditorGUILayout.BeginHorizontal(GUILayout.Height(400));
// Left panel
var leftWidth = viewerRect.width * splitRatio;
using (new EditorGUILayout.VerticalScope(GUILayout.Width(leftWidth)))
{
DrawVersionPanel(conflict, true, "LOCAL VERSION");
}
// Splitter
DrawSplitter();
// Right panel
using (new EditorGUILayout.VerticalScope())
{
DrawVersionPanel(conflict, false, "REMOTE VERSION");
}
EditorGUILayout.EndHorizontal();
}
private void DrawVersionPanel(MockConflictInfo conflict, bool isLocal, string title)
{
var headerColor = isLocal ? localColor : remoteColor;
var content = isLocal ? conflict.localVersion : conflict.remoteVersion;
var headerStyle = new GUIStyle(EditorStyles.boldLabel);
headerStyle.normal.textColor = headerColor;
headerStyle.alignment = TextAnchor.MiddleCenter;
EditorGUILayout.LabelField(title, headerStyle);
EditorGUILayout.Space(4);
// Enhanced content based on asset type
switch (conflict.assetType)
{
case ConflictAssetType.Material:
DrawMaterialPreview(conflict, isLocal);
break;
case ConflictAssetType.Prefab:
DrawPrefabPreview(conflict, isLocal);
break;
case ConflictAssetType.Texture:
DrawTexturePreview(conflict, isLocal);
break;
case ConflictAssetType.Scene:
DrawScenePreview(conflict, isLocal);
break;
case ConflictAssetType.Script:
DrawScriptPreview(conflict, isLocal);
break;
default:
DrawGenericPreview(content, headerColor);
break;
}
}
private void DrawMaterialPreview(MockConflictInfo conflict, bool isLocal)
{
// Material sphere preview
var previewRect = GUILayoutUtility.GetRect(200, 150);
EditorGUI.DrawRect(previewRect, new Color(0.1f, 0.1f, 0.1f));
// Mock material sphere
var centerRect = new Rect(previewRect.x + 50, previewRect.y + 25, 100, 100);
var sphereColor = isLocal ? new Color(0.8f, 0.2f, 0.2f) : new Color(0.2f, 0.2f, 0.8f);
// Draw gradient to simulate sphere lighting
for (int i = 0; i < 50; i++)
{
var t = i / 50f;
var currentColor = Color.Lerp(sphereColor * 1.5f, sphereColor * 0.3f, t);
var rect = new Rect(centerRect.x + i, centerRect.y + i * 0.5f, centerRect.width - i * 2, centerRect.height - i);
EditorGUI.DrawRect(rect, currentColor);
}
// Material properties
EditorGUILayout.Space(8);
var materialData = isLocal ?
"Albedo: Red (1,0,0)\nMetallic: 0.5\nSmoothness: 0.8\nEmission: Off" :
"Albedo: Blue (0,0,1)\nMetallic: 0.2\nSmoothness: 0.6\nEmission: On";
var propStyle = new GUIStyle(EditorStyles.textArea);
propStyle.fontSize = 10;
propStyle.normal.textColor = new Color(0.9f, 0.9f, 0.9f);
EditorGUILayout.TextArea(materialData, propStyle, GUILayout.Height(60));
}
private void DrawPrefabPreview(MockConflictInfo conflict, bool isLocal)
{
// Hierarchy view
var hierarchyRect = GUILayoutUtility.GetRect(200, 200);
EditorGUI.DrawRect(hierarchyRect, new Color(0.15f, 0.15f, 0.15f));
GUILayout.BeginArea(hierarchyRect);
GUILayout.Space(8);
if (isLocal)
{
DrawHierarchyItem("Player", 0, true);
DrawHierarchyItem("Mesh Renderer", 1, false);
DrawHierarchyItem("Box Collider", 1, false);
DrawHierarchyItem("Rigidbody", 1, false);
DrawHierarchyItem("Player Controller", 1, false);
}
else
{
DrawHierarchyItem("Player", 0, true);
DrawHierarchyItem("Mesh Renderer", 1, false);
DrawHierarchyItem("Box Collider", 1, false);
DrawHierarchyItem("Rigidbody", 1, false);
DrawHierarchyItem("Audio Source", 1, true); // New component
DrawHierarchyItem("Particle System", 1, true); // New component
DrawHierarchyItem("Player Controller", 1, false);
}
GUILayout.EndArea();
}
private void DrawHierarchyItem(string name, int indent, bool isHighlighted)
{
using (new EditorGUILayout.HorizontalScope())
{
GUILayout.Space(indent * 16 + 8);
var itemStyle = new GUIStyle(EditorStyles.label);
itemStyle.fontSize = 10;
itemStyle.normal.textColor = isHighlighted ? conflictColor : Color.white;
var icon = indent == 0 ? "🎮" : "⚙️";
EditorGUILayout.LabelField($"{icon} {name}", itemStyle, GUILayout.Height(16));
}
}
private void DrawTexturePreview(MockConflictInfo conflict, bool isLocal)
{
var previewRect = GUILayoutUtility.GetRect(200, 150);
EditorGUI.DrawRect(previewRect, new Color(0.1f, 0.1f, 0.1f));
// Mock texture preview with checkerboard pattern
var textureRect = new Rect(previewRect.x + 25, previewRect.y + 25, 150, 100);
var textureColor = isLocal ?
new Color(0.8f, 0.6f, 0.4f) : // Brown texture
new Color(0.4f, 0.6f, 0.8f); // Blue texture
// Draw checkerboard pattern
for (int x = 0; x < 15; x++)
{
for (int y = 0; y < 10; y++)
{
var checkerColor = ((x + y) % 2 == 0) ? textureColor : textureColor * 0.7f;
var checkerRect = new Rect(textureRect.x + x * 10, textureRect.y + y * 10, 10, 10);
EditorGUI.DrawRect(checkerRect, checkerColor);
}
}
// Texture info
EditorGUILayout.Space(8);
var textureInfo = isLocal ?
"Size: 512x512\nFormat: RGB24\nMipmaps: Yes\nFilter: Bilinear" :
"Size: 1024x1024\nFormat: RGBA32\nMipmaps: Yes\nFilter: Trilinear";
var infoStyle = new GUIStyle(EditorStyles.textArea);
infoStyle.fontSize = 10;
infoStyle.normal.textColor = new Color(0.9f, 0.9f, 0.9f);
EditorGUILayout.TextArea(textureInfo, infoStyle, GUILayout.Height(60));
}
private void DrawScenePreview(MockConflictInfo conflict, bool isLocal)
{
var previewRect = GUILayoutUtility.GetRect(200, 150);
EditorGUI.DrawRect(previewRect, new Color(0.05f, 0.05f, 0.1f)); // Dark blue background
// Mock scene elements
var cameraPos = isLocal ? new Vector2(50, 50) : new Vector2(100, 80);
var lightPos = isLocal ? new Vector2(150, 30) : new Vector2(120, 40);
// Draw ground plane
var groundRect = new Rect(previewRect.x + 20, previewRect.y + 120, previewRect.width - 40, 20);
EditorGUI.DrawRect(groundRect, new Color(0.3f, 0.5f, 0.3f));
// Draw camera
var cameraRect = new Rect(previewRect.x + cameraPos.x, previewRect.y + cameraPos.y, 20, 20);
EditorGUI.DrawRect(cameraRect, Color.yellow);
// Draw light
var lightRect = new Rect(previewRect.x + lightPos.x, previewRect.y + lightPos.y, 15, 15);
EditorGUI.DrawRect(lightRect, Color.white);
// Scene info
EditorGUILayout.Space(8);
var sceneInfo = isLocal ?
"Camera: (0,10,0)\nLighting: Realtime GI\nSkybox: Default\nFog: Disabled" :
"Camera: (5,10,-5)\nLighting: Baked GI\nSkybox: Procedural\nFog: Enabled";
var infoStyle = new GUIStyle(EditorStyles.textArea);
infoStyle.fontSize = 10;
infoStyle.normal.textColor = new Color(0.9f, 0.9f, 0.9f);
EditorGUILayout.TextArea(sceneInfo, infoStyle, GUILayout.Height(60));
}
private void DrawScriptPreview(MockConflictInfo conflict, bool isLocal)
{
var content = isLocal ? conflict.localVersion : conflict.remoteVersion;
using (var scrollView = new EditorGUILayout.ScrollViewScope(isLocal ? leftScrollPosition : rightScrollPosition, GUILayout.Height(200)))
{
if (isLocal) leftScrollPosition = scrollView.scrollPosition;
else rightScrollPosition = scrollView.scrollPosition;
var codeStyle = new GUIStyle(EditorStyles.textArea);
codeStyle.wordWrap = wordWrap;
codeStyle.richText = false;
codeStyle.font = EditorGUIUtility.Load("Consolas") as Font ?? GUI.skin.font;
codeStyle.fontSize = 10;
var lines = content.Split('\n');
for (int i = 0; i < lines.Length; i++)
{
using (new EditorGUILayout.HorizontalScope())
{
if (showLineNumbers)
{
var lineNumStyle = new GUIStyle(EditorStyles.label);
lineNumStyle.normal.textColor = new Color(0.5f, 0.5f, 0.5f);
lineNumStyle.fontSize = 9;
lineNumStyle.alignment = TextAnchor.MiddleRight;
EditorGUILayout.LabelField((i + 1).ToString(), lineNumStyle, GUILayout.Width(30));
}
EditorGUILayout.LabelField(lines[i], codeStyle, GUILayout.Height(16));
}
}
}
// Code options
EditorGUILayout.Space(4);
using (new EditorGUILayout.HorizontalScope())
{
showLineNumbers = EditorGUILayout.Toggle("Lines", showLineNumbers, GUILayout.Width(60));
wordWrap = EditorGUILayout.Toggle("Wrap", wordWrap, GUILayout.Width(60));
}
}
private void DrawGenericPreview(string content, Color borderColor)
{
var rect = EditorGUILayout.BeginVertical();
EditorGUI.DrawRect(rect, new Color(borderColor.r, borderColor.g, borderColor.b, 0.1f));
GUILayout.Space(8);
using (new EditorGUILayout.HorizontalScope())
{
GUILayout.Space(8);
using (new EditorGUILayout.VerticalScope())
{
var contentStyle = new GUIStyle(EditorStyles.textArea);
contentStyle.wordWrap = true;
contentStyle.normal.textColor = new Color(0.9f, 0.9f, 0.9f);
EditorGUILayout.TextArea(content, contentStyle, GUILayout.Height(200));
}
GUILayout.Space(8);
}
GUILayout.Space(8);
EditorGUILayout.EndVertical();
}
private void Draw3DPreviewComparison(MockConflictInfo conflict)
{
if (conflict.assetType == ConflictAssetType.Prefab || conflict.assetType == ConflictAssetType.Scene || conflict.assetType == ConflictAssetType.Material)
{
EditorGUILayout.LabelField("3D Preview Comparison", EditorStyles.boldLabel);
EditorGUILayout.Space(8);
using (new EditorGUILayout.HorizontalScope())
{
// Left 3D preview
using (new EditorGUILayout.VerticalScope())
{
EditorGUILayout.LabelField("LOCAL VERSION", EditorStyles.centeredGreyMiniLabel);
Draw3DPreviewPanel(true);
}
GUILayout.Space(8);
// Right 3D preview
using (new EditorGUILayout.VerticalScope())
{
EditorGUILayout.LabelField("REMOTE VERSION", EditorStyles.centeredGreyMiniLabel);
Draw3DPreviewPanel(false);
}
}
// 3D Controls
EditorGUILayout.Space(8);
Draw3DControls();
}
else
{
EditorGUILayout.HelpBox("3D Preview is only available for Prefabs, Scenes, and Materials.", MessageType.Info);
DrawSideBySideComparison(conflict);
}
}
private void Draw3DPreviewPanel(bool isLocal)
{
var previewRect = GUILayoutUtility.GetRect(300, 200);
EditorGUI.DrawRect(previewRect, new Color(0.1f, 0.1f, 0.1f));
// Draw render texture if available
var renderTexture = isLocal ? leftRenderTexture : rightRenderTexture;
if (renderTexture != null)
{
GUI.DrawTexture(previewRect, renderTexture, ScaleMode.ScaleToFit);
}
else
{
// Fallback: mock 3D preview
var centerX = previewRect.x + previewRect.width * 0.5f;
var centerY = previewRect.y + previewRect.height * 0.5f;
var cubeSize = isLocal ? 40 : 50;
var cubeColor = isLocal ? new Color(0.8f, 0.2f, 0.2f) : new Color(0.2f, 0.2f, 0.8f);
var cubeRect = new Rect(centerX - cubeSize/2, centerY - cubeSize/2, cubeSize, cubeSize);
EditorGUI.DrawRect(cubeRect, cubeColor);
// Draw simple perspective lines to simulate 3D
var offset = 10;
var topRect = new Rect(centerX - cubeSize/2 + offset, centerY - cubeSize/2 - offset, cubeSize, cubeSize);
EditorGUI.DrawRect(topRect, new Color(cubeColor.r + 0.2f, cubeColor.g + 0.2f, cubeColor.b + 0.2f));
}
// Preview info
var infoStyle = new GUIStyle(EditorStyles.centeredGreyMiniLabel);
infoStyle.normal.textColor = Color.white;
infoStyle.fontSize = 10;
var infoRect = new Rect(previewRect.x, previewRect.y + previewRect.height - 20, previewRect.width, 20);
GUI.Label(infoRect, isLocal ? "Local 3D Preview" : "Remote 3D Preview", infoStyle);
}
private void Draw3DControls()
{
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("Reset Camera", GUILayout.Width(100)))
{
cameraRotation = new Vector3(15f, -30f, 0f);
cameraDistance = 3f;
UpdateCameraPositions();
}
GUILayout.Space(8);
if (GUILayout.Button("Focus on Differences", GUILayout.Width(150)))
{
// Focus camera on differences
Debug.Log("Focusing on differences");
}
GUILayout.FlexibleSpace();
EditorGUILayout.LabelField("Wireframe:", GUILayout.Width(70));
var showWireframe = EditorGUILayout.Toggle(false, GUILayout.Width(20));
}
EditorGUILayout.Space(4);
// Camera controls
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.LabelField("Camera:", GUILayout.Width(60));
cameraDistance = EditorGUILayout.Slider("Distance", cameraDistance, 1f, 10f);
var newRotationY = EditorGUILayout.Slider("Rotation", cameraRotation.y, -180f, 180f);
if (Math.Abs(newRotationY - cameraRotation.y) > 0.1f)
{
cameraRotation.y = newRotationY;
UpdateCameraPositions();
}
}
}
private void DrawPropertiesComparison(MockConflictInfo conflict)
{
EditorGUILayout.LabelField("Property-by-Property Comparison", EditorStyles.boldLabel);
EditorGUILayout.Space(8);
// Search bar
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.LabelField("Search:", GUILayout.Width(50));
searchText = EditorGUILayout.TextField(searchText);
if (GUILayout.Button("Clear", GUILayout.Width(50)))
searchText = "";
}
EditorGUILayout.Space(4);
using (var scrollView = new EditorGUILayout.ScrollViewScope(viewerScrollPosition, GUILayout.Height(300)))
{
viewerScrollPosition = scrollView.scrollPosition;
var properties = currentPropertyDifferences;
if (!string.IsNullOrEmpty(searchText))
{
properties = properties.Where(p => p.propertyName.ToLower().Contains(searchText.ToLower())).ToList();
}
foreach (var prop in properties)
{
DrawPropertyDifference(prop);
EditorGUILayout.Space(4);
}
if (properties.Count == 0)
{
EditorGUILayout.LabelField("No properties found", EditorStyles.centeredGreyMiniLabel);
}
}
}
private void DrawPropertyDifference(PropertyDifference prop)
{
var propRect = EditorGUILayout.BeginHorizontal();
// Background color based on change type
Color bgColor = prop.changeType switch
{
PropertyChangeType.Added => new Color(0.2f, 0.6f, 0.2f, 0.3f),
PropertyChangeType.Removed => new Color(0.6f, 0.2f, 0.2f, 0.3f),
PropertyChangeType.Modified => new Color(0.6f, 0.4f, 0.2f, 0.3f),
_ => Color.clear
};
if (bgColor != Color.clear)
{
EditorGUI.DrawRect(propRect, bgColor);
}
GUILayout.Space(8);
// Property name
var nameStyle = new GUIStyle(EditorStyles.boldLabel);
nameStyle.normal.textColor = Color.white;
nameStyle.fontSize = 11;
EditorGUILayout.LabelField(prop.propertyName, nameStyle, GUILayout.Width(200));
// Change type icon
var changeIcon = prop.changeType switch
{
PropertyChangeType.Added => "+",
PropertyChangeType.Removed => "-",
PropertyChangeType.Modified => "⟳",
_ => "?"
};
var iconStyle = new GUIStyle(EditorStyles.label);
iconStyle.normal.textColor = prop.changeType switch
{
PropertyChangeType.Added => Color.green,
PropertyChangeType.Removed => Color.red,
PropertyChangeType.Modified => Color.yellow,
_ => Color.white
};
EditorGUILayout.LabelField(changeIcon, iconStyle, GUILayout.Width(20));
// Values comparison
using (new EditorGUILayout.VerticalScope())
{
if (!string.IsNullOrEmpty(prop.localValue))
{
var localStyle = new GUIStyle(EditorStyles.label);
localStyle.normal.textColor = localColor;
localStyle.fontSize = 10;
EditorGUILayout.LabelField($"Local: {prop.localValue}", localStyle);
}
if (!string.IsNullOrEmpty(prop.remoteValue))
{
var remoteStyle = new GUIStyle(EditorStyles.label);
remoteStyle.normal.textColor = remoteColor;
remoteStyle.fontSize = 10;
EditorGUILayout.LabelField($"Remote: {prop.remoteValue}", remoteStyle);
}
}
GUILayout.FlexibleSpace();
// Individual resolution choice
if (prop.changeType == PropertyChangeType.Modified)
{
using (new EditorGUILayout.VerticalScope(GUILayout.Width(100)))
{
var localSelected = prop.resolution == ConflictResolution.UseLocal;
var remoteSelected = prop.resolution == ConflictResolution.UseRemote;
var localStyle = new GUIStyle(GUI.skin.button);
if (localSelected) localStyle.normal.background = CreateRoundedTexture(localColor, 18, 4);
if (GUILayout.Button("Use Local", localStyle, GUILayout.Height(18)))
{
prop.resolution = ConflictResolution.UseLocal;
Debug.Log($"Using local value for {prop.propertyName}");
}
var remoteStyle = new GUIStyle(GUI.skin.button);
if (remoteSelected) remoteStyle.normal.background = CreateRoundedTexture(remoteColor, 18, 4);
if (GUILayout.Button("Use Remote", remoteStyle, GUILayout.Height(18)))
{
prop.resolution = ConflictResolution.UseRemote;
Debug.Log($"Using remote value for {prop.propertyName}");
}
}
}
GUILayout.Space(8);
EditorGUILayout.EndHorizontal();
}
private void DrawOverlayComparison(MockConflictInfo conflict)
{
if (conflict.assetType == ConflictAssetType.Texture)
{
DrawTextureOverlay(conflict);
}
else if (conflict.assetType == ConflictAssetType.Material)
{
DrawMaterialOverlay(conflict);
}
else
{
EditorGUILayout.HelpBox("Overlay comparison is only available for Textures and Materials.", MessageType.Info);
DrawSideBySideComparison(conflict);
}
}
private void DrawTextureOverlay(MockConflictInfo conflict)
{
EditorGUILayout.LabelField("Texture Overlay Comparison", EditorStyles.boldLabel);
EditorGUILayout.Space(8);
var overlayRect = GUILayoutUtility.GetRect(400, 300);
EditorGUI.DrawRect(overlayRect, new Color(0.1f, 0.1f, 0.1f));
// Mock overlay - checkerboard showing differences
for (int x = 0; x < 40; x++)
{
for (int y = 0; y < 30; y++)
{
var isLocalPixel = (x + y) % 3 == 0;
var isDifferent = (x + y) % 7 == 0;
Color pixelColor;
if (isDifferent)
{
pixelColor = conflictColor; // Show differences
}
else if (isLocalPixel)
{
pixelColor = new Color(0.8f, 0.2f, 0.2f, 0.7f); // Local
}
else
{
pixelColor = new Color(0.2f, 0.2f, 0.8f, 0.7f); // Remote
}
var pixelRect = new Rect(overlayRect.x + x * 10, overlayRect.y + y * 10, 10, 10);
EditorGUI.DrawRect(pixelRect, pixelColor);
}
}
// Overlay controls
EditorGUILayout.Space(8);
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.LabelField("Blend Mode:", GUILayout.Width(80));
var blendMode = EditorGUILayout.Popup(0, new[] { "Normal", "Difference", "Overlay" }, GUILayout.Width(100));
GUILayout.Space(16);
EditorGUILayout.LabelField("Opacity:", GUILayout.Width(60));
var opacity = EditorGUILayout.Slider(0.5f, 0f, 1f, GUILayout.Width(100));
}
}
private void DrawMaterialOverlay(MockConflictInfo conflict)
{
EditorGUILayout.LabelField("Material Property Overlay", EditorStyles.boldLabel);
EditorGUILayout.Space(8);
// Side-by-side material spheres with difference highlighting
using (new EditorGUILayout.HorizontalScope())
{
// Local material sphere
var leftRect = GUILayoutUtility.GetRect(150, 150);
EditorGUI.DrawRect(leftRect, new Color(0.1f, 0.1f, 0.1f));
var leftSphere = new Rect(leftRect.x + 25, leftRect.y + 25, 100, 100);
EditorGUI.DrawRect(leftSphere, new Color(0.8f, 0.2f, 0.2f));
GUILayout.Space(16);
// Remote material sphere
var rightRect = GUILayoutUtility.GetRect(150, 150);
EditorGUI.DrawRect(rightRect, new Color(0.1f, 0.1f, 0.1f));
var rightSphere = new Rect(rightRect.x + 25, rightRect.y + 25, 100, 100);
EditorGUI.DrawRect(rightSphere, new Color(0.2f, 0.2f, 0.8f));
}
// Property differences highlight
EditorGUILayout.Space(8);
EditorGUILayout.LabelField("Differences:", EditorStyles.boldLabel);
DrawPropertyDifference(new PropertyDifference
{
propertyName = "Albedo Color",
localValue = "Red (1,0,0)",
remoteValue = "Blue (0,0,1)",
changeType = PropertyChangeType.Modified
});
}
private void DrawSplitter()
{
var splitterRect = GUILayoutUtility.GetRect(4, 400);
EditorGUI.DrawRect(splitterRect, new Color(0.5f, 0.5f, 0.5f, 0.5f));
EditorGUIUtility.AddCursorRect(splitterRect, MouseCursor.ResizeHorizontal);
if (Event.current.type == EventType.MouseDown && splitterRect.Contains(Event.current.mousePosition))
{
isDraggingSplitter = true;
Event.current.Use();
}
if (isDraggingSplitter)
{
if (Event.current.type == EventType.MouseDrag)
{
splitRatio = Mathf.Clamp(Event.current.mousePosition.x / position.width, 0.2f, 0.8f);
Event.current.Use();
Repaint();
}
else if (Event.current.type == EventType.MouseUp)
{
isDraggingSplitter = false;
Event.current.Use();
}
}
}
private void DrawResolutionControls(MockConflictInfo conflict)
{
using (new EditorGUILayout.HorizontalScope())
{
GUILayout.FlexibleSpace();
var localButtonStyle = new GUIStyle(GUI.skin.button);
localButtonStyle.normal.textColor = Color.white;
localButtonStyle.fixedHeight = 32;
localButtonStyle.fontSize = 12;
localButtonStyle.fontStyle = FontStyle.Bold;
if (conflict.resolution == ConflictResolution.UseLocal)
{
localButtonStyle.normal.background = CreateRoundedTexture(localColor, 32, 4);
}
if (GUILayout.Button("◀ USE LOCAL", localButtonStyle, GUILayout.Width(120)))
{
SetConflictResolution(selectedConflictIndex, ConflictResolution.UseLocal);
}
GUILayout.Space(8);
var remoteButtonStyle = new GUIStyle(GUI.skin.button);
remoteButtonStyle.normal.textColor = Color.white;
remoteButtonStyle.fixedHeight = 32;
remoteButtonStyle.fontSize = 12;
remoteButtonStyle.fontStyle = FontStyle.Bold;
if (conflict.resolution == ConflictResolution.UseRemote)
{
remoteButtonStyle.normal.background = CreateRoundedTexture(remoteColor, 32, 4);
}
if (GUILayout.Button("USE REMOTE ▶", remoteButtonStyle, GUILayout.Width(120)))
{
SetConflictResolution(selectedConflictIndex, ConflictResolution.UseRemote);
}
GUILayout.FlexibleSpace();
}
}
#endregion
#region Helper Methods
private void DrawCard(System.Action content, int padding = 12)
{
var rect = EditorGUILayout.BeginVertical();
EditorGUI.DrawRect(rect, cardColor);
GUILayout.Space(padding);
EditorGUILayout.BeginHorizontal();
GUILayout.Space(padding);
EditorGUILayout.BeginVertical();
content?.Invoke();
EditorGUILayout.EndVertical();
GUILayout.Space(padding);
EditorGUILayout.EndHorizontal();
GUILayout.Space(padding);
EditorGUILayout.EndVertical();
}
private void DrawSectionHeader(string title, string subtitle = "", Color? color = null)
{
var headerStyle = new GUIStyle(EditorStyles.boldLabel);
headerStyle.fontSize = 13;
headerStyle.normal.textColor = color ?? Color.white;
EditorGUILayout.LabelField(title, headerStyle);
if (!string.IsNullOrEmpty(subtitle))
{
var subtitleStyle = new GUIStyle(EditorStyles.label);
subtitleStyle.normal.textColor = new Color(0.7f, 0.7f, 0.7f);
subtitleStyle.fontSize = 10;
EditorGUILayout.LabelField(subtitle, subtitleStyle);
}
}
private Texture2D CreateRoundedTexture(Color color, int size = 64, int cornerRadius = 8)
{
var key = $"{color}_{size}_{cornerRadius}";
if (textureCache.TryGetValue(key, out var cachedTexture) && cachedTexture != null)
return cachedTexture;
var texture = new Texture2D(size, size);
var pixels = new Color[size * size];
for (int y = 0; y < size; y++)
{
for (int x = 0; x < size; x++)
{
float distanceToCorner = float.MaxValue;
if (x < cornerRadius && y < cornerRadius)
distanceToCorner = Vector2.Distance(new Vector2(x, y), new Vector2(cornerRadius, cornerRadius));
else if (x >= size - cornerRadius && y < cornerRadius)
distanceToCorner = Vector2.Distance(new Vector2(x, y), new Vector2(size - cornerRadius - 1, cornerRadius));
else if (x < cornerRadius && y >= size - cornerRadius)
distanceToCorner = Vector2.Distance(new Vector2(x, y), new Vector2(cornerRadius, size - cornerRadius - 1));
else if (x >= size - cornerRadius && y >= size - cornerRadius)
distanceToCorner = Vector2.Distance(new Vector2(x, y), new Vector2(size - cornerRadius - 1, size - cornerRadius - 1));
pixels[y * size + x] = (distanceToCorner <= cornerRadius ||
(x >= cornerRadius && x < size - cornerRadius) ||
(y >= cornerRadius && y < size - cornerRadius)) ? color : Color.clear;
}
}
texture.SetPixels(pixels);
texture.Apply();
textureCache[key] = texture;
return texture;
}
private Color GetAssetTypeColor(ConflictAssetType assetType)
{
return assetType switch
{
ConflictAssetType.Script => new Color(0.2f, 0.6f, 0.9f),
ConflictAssetType.Prefab => new Color(0.3f, 0.7f, 0.9f),
ConflictAssetType.Material => new Color(0.8f, 0.4f, 0.8f),
ConflictAssetType.Scene => new Color(0.9f, 0.3f, 0.3f),
ConflictAssetType.Texture => new Color(0.3f, 0.8f, 0.3f),
ConflictAssetType.ScriptableObject => new Color(0.9f, 0.7f, 0.2f),
ConflictAssetType.Animation => new Color(1f, 0.6f, 0.2f),
_ => new Color(0.6f, 0.6f, 0.6f)
};
}
private string GetAssetTypeIcon(ConflictAssetType assetType)
{
return assetType switch
{
ConflictAssetType.Script => "C#",
ConflictAssetType.Prefab => "PF",
ConflictAssetType.Material => "MT",
ConflictAssetType.Scene => "SC",
ConflictAssetType.Texture => "TX",
ConflictAssetType.ScriptableObject => "SO",
ConflictAssetType.Animation => "AN",
_ => "??"
};
}
private void SelectConflict(int index)
{
selectedConflictIndex = index;
GenerateCurrentPropertyDifferences();
RefreshPreviewObjects();
Repaint();
}
private void SetConflictResolution(int conflictIndex, ConflictResolution resolution)
{
try
{
if (conflictIndex >= 0 && conflictIndex < mockConflicts.Count)
{
mockConflicts[conflictIndex].resolution = resolution;
Debug.Log($"Set resolution for {mockConflicts[conflictIndex].fileName}: {resolution}");
Repaint();
}
else
{
Debug.LogError($"Invalid conflict index: {conflictIndex}");
}
}
catch (Exception ex)
{
Debug.LogError($"Error setting conflict resolution: {ex.Message}");
}
}
private void GenerateCurrentPropertyDifferences()
{
currentPropertyDifferences.Clear();
if (selectedConflictIndex >= 0 && selectedConflictIndex < mockConflicts.Count)
{
var conflict = mockConflicts[selectedConflictIndex];
currentPropertyDifferences.AddRange(GeneratePropertyDifferences(conflict));
}
}
private List GeneratePropertyDifferences(MockConflictInfo conflict)
{
return conflict.assetType switch
{
ConflictAssetType.Material => GenerateMaterialProperties(),
ConflictAssetType.Prefab => GeneratePrefabProperties(),
ConflictAssetType.Texture => GenerateTextureProperties(),
ConflictAssetType.Scene => GenerateSceneProperties(),
ConflictAssetType.Script => GenerateScriptProperties(),
_ => GenerateGenericProperties()
};
}
private List GenerateMaterialProperties()
{
return new List
{
new PropertyDifference
{
propertyName = "Albedo Color",
localValue = "Red (1.0, 0.0, 0.0, 1.0)",
remoteValue = "Blue (0.0, 0.0, 1.0, 1.0)",
changeType = PropertyChangeType.Modified
},
new PropertyDifference
{
propertyName = "Metallic",
localValue = "0.5",
remoteValue = "0.2",
changeType = PropertyChangeType.Modified
},
new PropertyDifference
{
propertyName = "Smoothness",
localValue = "0.8",
remoteValue = "0.6",
changeType = PropertyChangeType.Modified
},
new PropertyDifference
{
propertyName = "Emission",
localValue = "",
remoteValue = "Enabled",
changeType = PropertyChangeType.Added
}
};
}
private List GeneratePrefabProperties()
{
return new List
{
new PropertyDifference
{
propertyName = "Transform.Scale",
localValue = "(1.0, 1.0, 1.0)",
remoteValue = "(1.2, 1.2, 1.2)",
changeType = PropertyChangeType.Modified
},
new PropertyDifference
{
propertyName = "BoxCollider.Size",
localValue = "(1.0, 1.0, 1.0)",
remoteValue = "(1.1, 1.1, 1.1)",
changeType = PropertyChangeType.Modified
},
new PropertyDifference
{
propertyName = "AudioSource",
localValue = "",
remoteValue = "Component Added",
changeType = PropertyChangeType.Added
},
new PropertyDifference
{
propertyName = "ParticleSystem",
localValue = "",
remoteValue = "Component Added",
changeType = PropertyChangeType.Added
}
};
}
private List GenerateTextureProperties()
{
return new List
{
new PropertyDifference
{
propertyName = "Texture Size",
localValue = "512x512",
remoteValue = "1024x1024",
changeType = PropertyChangeType.Modified
},
new PropertyDifference
{
propertyName = "Format",
localValue = "RGB24",
remoteValue = "RGBA32",
changeType = PropertyChangeType.Modified
},
new PropertyDifference
{
propertyName = "Filter Mode",
localValue = "Bilinear",
remoteValue = "Trilinear",
changeType = PropertyChangeType.Modified
}
};
}
private List GenerateSceneProperties()
{
return new List
{
new PropertyDifference
{
propertyName = "Camera Position",
localValue = "(0, 10, 0)",
remoteValue = "(5, 10, -5)",
changeType = PropertyChangeType.Modified
},
new PropertyDifference
{
propertyName = "Lighting Mode",
localValue = "Realtime GI",
remoteValue = "Baked GI",
changeType = PropertyChangeType.Modified
},
new PropertyDifference
{
propertyName = "Skybox",
localValue = "Default",
remoteValue = "Procedural",
changeType = PropertyChangeType.Modified
},
new PropertyDifference
{
propertyName = "Fog",
localValue = "",
remoteValue = "Enabled",
changeType = PropertyChangeType.Added
}
};
}
private List GenerateScriptProperties()
{
return new List
{
new PropertyDifference
{
propertyName = "speed variable",
localValue = "5.0f",
remoteValue = "7.0f",
changeType = PropertyChangeType.Modified
},
new PropertyDifference
{
propertyName = "Move() method",
localValue = "transform.Translate()",
remoteValue = "rb.velocity =",
changeType = PropertyChangeType.Modified
}
};
}
private List GenerateGenericProperties()
{
return new List
{
new PropertyDifference
{
propertyName = "Generic Property",
localValue = "Local Value",
remoteValue = "Remote Value",
changeType = PropertyChangeType.Modified
}
};
}
private void HandleEvents()
{
if (Event.current.type == EventType.Repaint)
{
// Handle any repaint-related operations
if (selectedViewMode == 1 && previewCameraLeft != null && previewCameraRight != null)
{
// Render 3D previews if needed
previewCameraLeft.Render();
previewCameraRight.Render();
}
}
}
private void ApplyResolutions()
{
try
{
Debug.Log("Applying conflict resolutions:");
foreach (var conflict in mockConflicts)
{
Debug.Log($" {conflict.fileName}: {conflict.resolution}");
}
EditorUtility.DisplayDialog("Resolutions Applied",
"All conflicts have been resolved. Pull operation will now continue.",
"OK");
// Call the callback to notify the main window that conflicts are resolved
var callback = onConflictsResolved;
onConflictsResolved = null; // Clear the callback first to prevent issues
callback?.Invoke();
Close();
}
catch (Exception ex)
{
Debug.LogError($"Error applying resolutions: {ex.Message}");
EditorUtility.DisplayDialog("Error",
$"Failed to apply resolutions: {ex.Message}",
"OK");
}
}
private void DrawFallbackUI()
{
EditorGUILayout.LabelField("Visual Conflict Resolver - Error State", EditorStyles.boldLabel);
if (GUILayout.Button("Restart"))
{
Close();
ShowWindow();
}
}
#endregion
#region Data Models
[Serializable]
public class MockConflictInfo
{
public string fileName;
public ConflictAssetType assetType;
public ConflictType conflictType;
public string description;
public string localVersion;
public string remoteVersion;
public ConflictResolution resolution;
}
[Serializable]
public class PropertyDifference
{
public string propertyName;
public string localValue;
public string remoteValue;
public PropertyChangeType changeType;
public ConflictResolution resolution = ConflictResolution.Unresolved;
}
public enum PropertyChangeType
{
Added,
Removed,
Modified
}
public enum ConflictAssetType
{
Script,
Prefab,
Material,
Scene,
Texture,
ScriptableObject,
Animation,
Audio,
Model,
Unknown
}
public enum ConflictType
{
PropertyValueDifference,
PropertyAdded,
PropertyRemoved,
ComponentAdded,
ComponentRemoved,
ComponentPropertyChanged,
ChildObjectAdded,
ChildObjectRemoved,
ChildObjectReordered,
GameObjectMoved,
GameObjectRenamed,
SceneSettingsChanged,
AssetReplaced,
AssetMetadataChanged
}
public enum ConflictResolution
{
Unresolved,
UseLocal,
UseRemote,
Manual,
Merged
}
public abstract class ConflictViewer
{
public abstract bool CanHandle(ConflictAssetType assetType);
public abstract void DrawConflictView(Rect area, MockConflictInfo conflict);
public abstract ConflictResolution GetUserChoice();
}
#endregion
}
}