123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463 |
- using UnityEngine;
- using UnityEditor;
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Threading.Tasks;
- using System.Collections;
- using System.IO;
- namespace UnityVersionControl
- {
- #region Data Models
- [Serializable]
- public enum FileStatus
- {
- Added,
- Modified,
- Deleted,
- Renamed,
- Copied,
- Untracked,
- Synced
- }
- [Serializable]
- public class FileChangeInfo
- {
- public string relativePath;
- public FileStatus status;
- public string changeDescription;
-
- public FileChangeInfo(string path, FileStatus status, string description = "")
- {
- this.relativePath = path ?? "";
- this.status = status;
- this.changeDescription = description ?? "";
- }
- }
- [Serializable]
- public class CommitInfo
- {
- public string hash;
- public string author;
- public string message;
- public DateTime date;
- }
- public struct PullResult
- {
- public int filesChanged;
- public bool hasConflicts;
- public int conflictCount;
- }
- public enum NotificationType
- {
- Info,
- Success,
- Warning,
- Error
- }
- [Serializable]
- public struct FileMetadata
- {
- public string size;
- public string type;
- public DateTime lastModified;
- }
- [Serializable]
- public struct NotificationInfo
- {
- public string title;
- public string message;
- public NotificationType type;
- public float timestamp;
- public float duration;
- }
- public struct ThumbnailRequest
- {
- public string assetPath;
- public int index;
- public int priority;
- }
- #endregion
- /// <summary>
- /// Production-ready Unity Version Control Window with performance optimizations
- /// and comprehensive error handling
- /// </summary>
- public class VersionControlWindow : EditorWindow
- {
- #region Constants
- private const int ITEMS_PER_FRAME = 3;
- private const float ITEM_HEIGHT = 80f;
- private const int CACHE_SIZE_LIMIT = 100;
- private const float THUMBNAIL_SIZE = 56f;
- private const float NOTIFICATION_DURATION = 3f;
- private const float ANIMATION_SPEED = 2f;
- #endregion
- #region Core Fields
- [SerializeField] private Vector2 scrollPosition;
- [SerializeField] private int selectedTab = 0;
- [SerializeField] private int selectedChangeTab = 0;
- [SerializeField] private string commitMessage = "";
- [SerializeField] private bool isProcessing = false;
-
- private readonly string[] tabNames = { "Changes", "History", "Settings" };
- private readonly string[] changeTabNames = { "Added", "Modified", "Deleted", "Renamed" };
-
- private readonly List<FileChangeInfo> realChanges = new List<FileChangeInfo>();
- private readonly List<CommitInfo> commitHistory = new List<CommitInfo>();
-
- private FileSystemWatcher fileWatcher;
- #endregion
- #region Project Window Integration
- private static Dictionary<string, FileStatus> _assetStatusCache;
- public static Dictionary<string, FileStatus> assetStatusCache
- {
- get
- {
- if (_assetStatusCache == null)
- _assetStatusCache = new Dictionary<string, FileStatus>();
- return _assetStatusCache;
- }
- }
-
- private readonly Dictionary<FileStatus, Texture2D> statusIconCache = new Dictionary<FileStatus, Texture2D>();
- #endregion
- #region Performance System
- private ThumbnailCache thumbnailCache;
- private readonly Queue<ThumbnailRequest> thumbnailQueue = new Queue<ThumbnailRequest>();
- private bool isProcessingThumbnails = false;
- private int visibleStartIndex = 0;
- private int visibleEndIndex = 0;
- #endregion
- #region UI Enhancement Fields
- private readonly Dictionary<string, float> pulseTimers = new Dictionary<string, float>();
- private readonly Queue<NotificationInfo> notifications = new Queue<NotificationInfo>();
- private bool showQuickActions = true;
- private bool showMinimap = false;
- private Vector2 minimapScrollPosition;
-
- [SerializeField] private string globalSearchFilter = "";
- private readonly List<string> recentSearches = new List<string>();
- private bool showAdvancedFilters = false;
- private readonly HashSet<FileStatus> statusFilters = new HashSet<FileStatus>();
-
- private FileChangeInfo contextMenuTarget;
- private Vector2 contextMenuPosition;
- private bool showingContextMenu = false;
-
- private float frameTime = 0f;
- private int frameCount = 0;
- private float averageFrameTime = 16.67f;
- private double lastUpdateTime;
- #endregion
- #region 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 syncedColor = new Color(0.4f, 0.7f, 1f);
- #endregion
- #region Caches and Collections
- private readonly Dictionary<string, Texture2D> textureCache = new Dictionary<string, Texture2D>();
- private readonly Dictionary<string, FileMetadata> metadataCache = new Dictionary<string, FileMetadata>();
-
- private readonly Dictionary<string, Color> fileTypeColors = new Dictionary<string, Color>
- {
- [".cs"] = new Color(0.2f, 0.6f, 0.9f),
- [".unity"] = new Color(0.9f, 0.3f, 0.3f),
- [".prefab"] = new Color(0.3f, 0.7f, 0.9f),
- [".mat"] = new Color(0.8f, 0.4f, 0.8f),
- [".png"] = new Color(0.3f, 0.8f, 0.3f),
- [".jpg"] = new Color(0.3f, 0.8f, 0.3f),
- [".jpeg"] = new Color(0.3f, 0.8f, 0.3f),
- [".shader"] = new Color(0.9f, 0.7f, 0.2f),
- [".fbx"] = new Color(0.7f, 0.5f, 0.3f),
- [".wav"] = new Color(0.5f, 0.8f, 0.5f),
- [".mp4"] = new Color(0.8f, 0.6f, 0.2f),
- [".anim"] = new Color(0.6f, 0.4f, 0.8f),
- ["default"] = new Color(0.6f, 0.6f, 0.6f)
- };
-
- private readonly HashSet<string> trackedExtensions = new HashSet<string>
- {
- ".cs", ".unity", ".prefab", ".mat", ".png", ".jpg", ".jpeg", ".asset", ".shader", ".anim",
- ".fbx", ".obj", ".blend", ".wav", ".mp3", ".ogg", ".mp4", ".mov", ".txt", ".json", ".xml"
- };
- #endregion
- #region Unity Lifecycle
- [MenuItem("Window/Version Control/Version Control Window")]
- public static void ShowWindow()
- {
- var window = GetWindow<VersionControlWindow>();
- window.titleContent = new GUIContent("Version Control");
- window.minSize = new Vector2(450, 350);
- window.Initialize();
- }
- private void OnEnable()
- {
- try
- {
- Initialize();
- RegisterCallbacks();
- StartFileSystemWatcher();
- InitializeProjectWindowIcons();
- InitializeAssetStatusCache();
- InitializePerformanceFeatures();
- lastUpdateTime = EditorApplication.timeSinceStartup;
- }
- catch (Exception ex)
- {
- Debug.LogError($"Failed to initialize VersionControlWindow: {ex.Message}");
- }
- }
- private void OnDisable()
- {
- try
- {
- UnregisterCallbacks();
- StopFileSystemWatcher();
- CleanupProjectWindowIcons();
- CleanupPerformanceFeatures();
- CleanupCaches();
- }
- catch (Exception ex)
- {
- Debug.LogError($"Error during VersionControlWindow cleanup: {ex.Message}");
- }
- }
- private void OnGUI()
- {
- try
- {
- UpdateAnimations();
- ProcessThumbnailQueueImmediate();
- HandleKeyboardShortcuts();
- HandleContextMenu();
-
- DrawBackground();
- DrawEnhancedHeader();
- DrawEnhancedSearchBar();
- DrawTabs();
- EditorGUILayout.Space(8);
-
- switch (selectedTab)
- {
- case 0: DrawChangesTab(); break;
- case 1: DrawHistoryTab(); break;
- case 2: DrawSettingsTab(); break;
- }
-
- DrawMinimap();
- DrawNotifications();
- DrawContextMenu();
- }
- catch (Exception ex)
- {
- Debug.LogError($"Error in VersionControl OnGUI: {ex.Message}");
- DrawFallbackUI();
- }
- }
- #endregion
- #region Initialization
- private void Initialize()
- {
- RefreshCommitHistory();
- LoadCachedChanges();
- }
- private void InitializePerformanceFeatures()
- {
- thumbnailCache = new ThumbnailCache(CACHE_SIZE_LIMIT);
- lastUpdateTime = EditorApplication.timeSinceStartup;
- }
- private void LoadCachedChanges()
- {
- try
- {
- if (assetStatusCache?.Count > 0)
- {
- var changedAssets = assetStatusCache.Where(kvp => kvp.Value != FileStatus.Synced).ToList();
-
- foreach (var cachedAsset in changedAssets)
- {
- if (string.IsNullOrEmpty(cachedAsset.Key)) continue;
-
- if (!realChanges.Any(c => c.relativePath == cachedAsset.Key))
- {
- var description = GetStatusDescription(cachedAsset.Value);
- realChanges.Add(new FileChangeInfo(cachedAsset.Key, cachedAsset.Value, description));
- }
- }
-
- if (changedAssets.Count > 0)
- {
- Repaint();
- }
- }
- }
- catch (Exception ex)
- {
- Debug.LogError($"Error loading cached changes: {ex.Message}");
- }
- }
- private string GetStatusDescription(FileStatus status)
- {
- return status switch
- {
- FileStatus.Added => "Asset added",
- FileStatus.Modified => "Asset modified",
- FileStatus.Deleted => "Asset deleted",
- FileStatus.Renamed => "Asset renamed",
- _ => "Asset changed"
- };
- }
- #endregion
- #region Enhanced Header Drawing
- private void DrawEnhancedHeader()
- {
- DrawCard(() =>
- {
- using (new EditorGUILayout.HorizontalScope())
- {
- DrawStatusIcon();
- DrawMainTitle();
- DrawQuickStats();
- DrawQuickActionsToolbar();
- }
-
- if (showAdvancedFilters)
- {
- EditorGUILayout.Space(4);
- DrawAdvancedFilters();
- }
- }, 12);
- }
- private void DrawStatusIcon()
- {
- var iconRect = GUILayoutUtility.GetRect(32, 32);
- var pulse = GetPulseValue("status_icon");
- var iconColor = Color.Lerp(accentColor, successColor, pulse);
- var iconTexture = CreateRoundedTexture(iconColor, 32, 6);
-
- if (iconTexture != null)
- {
- GUI.DrawTexture(iconRect, iconTexture);
- }
-
- var statusIcon = IsFileWatcherActive() ? "⚡" : "⏸";
- var iconStyle = new GUIStyle(EditorStyles.centeredGreyMiniLabel)
- {
- normal = { textColor = Color.white },
- fontSize = 16,
- fontStyle = FontStyle.Bold
- };
- GUI.Label(iconRect, statusIcon, iconStyle);
-
- if (iconRect.Contains(Event.current.mousePosition))
- {
- var tooltip = IsFileWatcherActive() ?
- "Live tracking active - changes detected in real-time" :
- "File tracking offline - manual refresh required";
- GUI.tooltip = tooltip;
- }
- }
- private void DrawMainTitle()
- {
- GUILayout.Space(12);
-
- using (new EditorGUILayout.VerticalScope())
- {
- var titleStyle = new GUIStyle(EditorStyles.boldLabel)
- {
- fontSize = 16,
- normal = { textColor = Color.white }
- };
- EditorGUILayout.LabelField("Unity Version Control", titleStyle);
-
- var subtitle = GetDynamicSubtitle();
- var subtitleStyle = new GUIStyle(EditorStyles.label)
- {
- normal = { textColor = new Color(0.8f, 0.8f, 0.8f) },
- fontSize = 11
- };
- EditorGUILayout.LabelField(subtitle, subtitleStyle);
- }
- }
- private void DrawQuickStats()
- {
- GUILayout.FlexibleSpace();
-
- using (new EditorGUILayout.VerticalScope())
- {
- var perfColor = averageFrameTime < 20f ? successColor : (averageFrameTime < 33f ? warningColor : errorColor);
- var perfText = $"⚡ {averageFrameTime:F1}ms";
-
- var perfStyle = new GUIStyle(EditorStyles.label)
- {
- normal = { textColor = perfColor },
- fontSize = 9,
- alignment = TextAnchor.MiddleRight
- };
- EditorGUILayout.LabelField(perfText, perfStyle);
-
- var changeCount = GetFilteredChanges().Count;
- var countText = $"{changeCount} change{(changeCount != 1 ? "s" : "")}";
-
- var countStyle = new GUIStyle(EditorStyles.label)
- {
- normal = { textColor = changeCount > 0 ? warningColor : new Color(0.7f, 0.7f, 0.7f) },
- fontSize = 10,
- alignment = TextAnchor.MiddleRight
- };
- EditorGUILayout.LabelField(countText, countStyle);
- }
- }
- private void DrawQuickActionsToolbar()
- {
- if (!showQuickActions) return;
-
- GUILayout.Space(12);
-
- using (new EditorGUILayout.VerticalScope())
- {
- using (new EditorGUILayout.HorizontalScope())
- {
- DrawToolbarButton("🔄", "Refresh (F5)", RefreshAllChanges);
- DrawToolbarButton("⎇", "Branches (Ctrl+B)", TryOpenBranchManager);
- DrawToolbarButton("📋", "Diff (Ctrl+D)", ShowDiffForSelected);
-
- using (new EditorGUI.DisabledScope(isProcessing))
- {
- DrawToolbarButton("⬇", "Pull", () => _ = PullChangesAsync());
- }
- }
-
- using (new EditorGUILayout.HorizontalScope())
- {
- DrawToolbarButton("🔍", "Search", SetFocusToSearch);
- DrawToolbarButton("⚙", "Settings", () => selectedTab = 2);
- DrawToolbarButton("📊", "Minimap", () => showMinimap = !showMinimap);
- DrawToolbarButton("🎯", "Focus", FocusOnChanges);
- }
- }
- }
- private void DrawToolbarButton(string icon, string tooltip, System.Action action)
- {
- var buttonStyle = new GUIStyle(GUI.skin.button)
- {
- normal = { textColor = accentColor },
- fixedWidth = 24,
- fixedHeight = 24,
- fontSize = 12
- };
-
- if (GUILayout.Button(icon, buttonStyle))
- action?.Invoke();
-
- var lastRect = GUILayoutUtility.GetLastRect();
- if (lastRect.Contains(Event.current.mousePosition))
- {
- GUI.tooltip = tooltip;
- }
- }
- #endregion
- #region Enhanced Search and Filtering
- private void DrawEnhancedSearchBar()
- {
- using (new EditorGUILayout.HorizontalScope())
- {
- EditorGUILayout.LabelField("🔍", GUILayout.Width(20));
-
- GUI.SetNextControlName("GlobalSearch");
- var newFilter = EditorGUILayout.TextField(globalSearchFilter);
-
- if (newFilter != globalSearchFilter)
- {
- globalSearchFilter = newFilter;
- if (!string.IsNullOrEmpty(newFilter) && !recentSearches.Contains(newFilter))
- {
- recentSearches.Insert(0, newFilter);
- if (recentSearches.Count > 5)
- recentSearches.RemoveAt(5);
- }
- }
-
- if (GUILayout.Button("⚙", GUILayout.Width(25)))
- showAdvancedFilters = !showAdvancedFilters;
-
- if (GUILayout.Button("✕", GUILayout.Width(25)))
- globalSearchFilter = "";
- }
- }
- private void DrawAdvancedFilters()
- {
- using (new EditorGUILayout.HorizontalScope())
- {
- EditorGUILayout.LabelField("Filters:", GUILayout.Width(50));
-
- EditorGUILayout.LabelField("Status:", GUILayout.Width(45));
- foreach (FileStatus status in Enum.GetValues(typeof(FileStatus)))
- {
- if (status == FileStatus.Untracked || status == FileStatus.Synced) continue;
-
- var wasFiltered = statusFilters.Contains(status);
- var isFiltered = EditorGUILayout.Toggle(wasFiltered, GUILayout.Width(20));
-
- if (isFiltered != wasFiltered)
- {
- if (isFiltered) statusFilters.Add(status);
- else statusFilters.Remove(status);
- }
-
- var statusStyle = new GUIStyle(EditorStyles.label)
- {
- fontSize = 9,
- normal = { textColor = GetStatusColor(status) }
- };
- EditorGUILayout.LabelField(status.ToString().Substring(0, 1), statusStyle, GUILayout.Width(15));
- }
-
- GUILayout.FlexibleSpace();
-
- if (GUILayout.Button("Clear Filters", GUILayout.Width(80)))
- {
- statusFilters.Clear();
- globalSearchFilter = "";
- }
- }
- }
- private List<FileChangeInfo> GetFilteredChanges()
- {
- var changes = realChanges.Where(c => !string.IsNullOrEmpty(c.relativePath) && !c.relativePath.EndsWith(".meta")).ToList();
-
- if (!string.IsNullOrEmpty(globalSearchFilter))
- {
- changes = changes.Where(c =>
- c.relativePath.ToLower().Contains(globalSearchFilter.ToLower()) ||
- c.changeDescription.ToLower().Contains(globalSearchFilter.ToLower())
- ).ToList();
- }
-
- if (statusFilters.Count > 0)
- {
- changes = changes.Where(c => statusFilters.Contains(c.status)).ToList();
- }
-
- return changes;
- }
- #endregion
- #region Changes Tab Drawing
- private void DrawChangesTab()
- {
- EditorGUILayout.Space(8);
-
- DrawChangesHeader();
- DrawChangesTabs();
- DrawSelectedChangesContentOptimized();
-
- var totalChanges = GetFilteredChanges().Count;
- if (totalChanges > 0)
- DrawCommitSection();
- }
- private void DrawChangesHeader()
- {
- DrawCard(() =>
- {
- using (new EditorGUILayout.HorizontalScope())
- {
- var totalChanges = GetFilteredChanges().Count;
- DrawSectionHeader($"Pending Changes ({totalChanges})", "Ready to commit");
-
- GUILayout.FlexibleSpace();
-
- if (totalChanges > 0)
- {
- var revertButtonStyle = new GUIStyle(GUI.skin.button)
- {
- normal = { textColor = errorColor },
- fixedHeight = 24
- };
-
- if (GUILayout.Button("↶ REVERT ALL", revertButtonStyle, GUILayout.Width(100)))
- ShowRevertAllConfirmation();
- }
-
- var refreshButtonStyle = new GUIStyle(GUI.skin.button)
- {
- normal = { textColor = accentColor },
- fixedHeight = 24
- };
-
- if (GUILayout.Button("🔄 REFRESH", refreshButtonStyle, GUILayout.Width(80)))
- {
- RefreshAllChanges();
- }
- }
- }, 8);
- }
- private void DrawChangesTabs()
- {
- EditorGUILayout.Space(4);
-
- using (new EditorGUILayout.HorizontalScope())
- {
- for (int i = 0; i < changeTabNames.Length; i++)
- {
- var statusCount = GetStatusCount((FileStatus)i);
- var isSelected = selectedChangeTab == i;
- var buttonStyle = new GUIStyle(EditorStyles.toolbarButton);
-
- if (isSelected)
- {
- buttonStyle.normal.textColor = accentColor;
- buttonStyle.fontStyle = FontStyle.Bold;
- }
- else
- {
- buttonStyle.normal.textColor = new Color(0.7f, 0.7f, 0.7f);
- }
-
- buttonStyle.fontSize = 11;
- var tabLabel = statusCount > 0 ? $"{changeTabNames[i]} ({statusCount})" : changeTabNames[i];
-
- if (GUILayout.Button(tabLabel, buttonStyle))
- selectedChangeTab = i;
- }
- }
- }
- private void DrawSelectedChangesContentOptimized()
- {
- var selectedStatus = (FileStatus)selectedChangeTab;
- var filteredChanges = GetFilteredChanges().Where(c => c.status == selectedStatus).ToList();
-
- if (filteredChanges.Count == 0)
- {
- DrawEmptyChangesState(selectedStatus);
- return;
- }
- CalculateVisibleRange(filteredChanges.Count);
-
- using (var scrollView = new EditorGUILayout.ScrollViewScope(scrollPosition))
- {
- scrollPosition = scrollView.scrollPosition;
-
- DrawCard(() =>
- {
- // Virtual spacer for items above viewport
- if (visibleStartIndex > 0)
- {
- GUILayout.Space(visibleStartIndex * ITEM_HEIGHT);
- }
-
- // Draw only visible items
- for (int i = visibleStartIndex; i <= visibleEndIndex && i < filteredChanges.Count; i++)
- {
- var change = filteredChanges[i];
- DrawFileEntryOptimized(change, GetStatusColor(selectedStatus), i);
-
- if (i < visibleEndIndex && i < filteredChanges.Count - 1)
- {
- DrawSeparator(1f, 4f);
- }
- }
-
- // Virtual spacer for items below viewport
- var remainingItems = filteredChanges.Count - visibleEndIndex - 1;
- if (remainingItems > 0)
- {
- GUILayout.Space(remainingItems * ITEM_HEIGHT);
- }
- }, 12);
- }
- }
- private void CalculateVisibleRange(int totalItems)
- {
- var rect = GUILayoutUtility.GetLastRect();
- var scrollViewHeight = rect.height;
-
- if (scrollViewHeight <= 0) scrollViewHeight = 400;
-
- var visibleItems = Mathf.CeilToInt(scrollViewHeight / ITEM_HEIGHT) + 2;
-
- visibleStartIndex = Mathf.Max(0, Mathf.FloorToInt(scrollPosition.y / ITEM_HEIGHT) - 1);
- visibleEndIndex = Mathf.Min(totalItems - 1, visibleStartIndex + visibleItems);
- }
- private void DrawFileEntryOptimized(FileChangeInfo change, Color accentColor, int index)
- {
- var entryRect = EditorGUILayout.BeginVertical(GUILayout.Height(ITEM_HEIGHT));
-
- // Handle context menu
- if (Event.current.type == EventType.ContextClick && entryRect.Contains(Event.current.mousePosition))
- {
- contextMenuTarget = change;
- contextMenuPosition = Event.current.mousePosition;
- showingContextMenu = true;
- Event.current.Use();
- }
-
- using (new EditorGUILayout.HorizontalScope())
- {
- var thumbnail = GetOptimizedFileThumbnail(change.relativePath, index);
- DrawOptimizedFileThumbnail(change, thumbnail, THUMBNAIL_SIZE);
-
- GUILayout.Space(12);
-
- using (new EditorGUILayout.VerticalScope())
- {
- DrawFileInfo(change, accentColor);
- }
-
- GUILayout.FlexibleSpace();
-
- DrawFileActions(change, accentColor);
- }
-
- EditorGUILayout.EndVertical();
- }
- private void DrawFileInfo(FileChangeInfo change, Color accentColor)
- {
- var fileName = Path.GetFileName(change.relativePath);
- var fileNameStyle = new GUIStyle(EditorStyles.boldLabel)
- {
- fontSize = 13,
- normal = { textColor = Color.white }
- };
- EditorGUILayout.LabelField(fileName, fileNameStyle);
-
- var displayPath = change.relativePath.StartsWith("Assets/")
- ? change.relativePath.Substring(7)
- : change.relativePath;
- var pathStyle = new GUIStyle(EditorStyles.label)
- {
- normal = { textColor = new Color(0.7f, 0.7f, 0.7f) },
- fontSize = 11
- };
- EditorGUILayout.LabelField(displayPath, pathStyle);
-
- EditorGUILayout.Space(2);
-
- using (new EditorGUILayout.HorizontalScope())
- {
- var metadata = GetCachedFileMetadata(change.relativePath);
- DrawMetadataBadge(metadata.size, new Color(0.4f, 0.6f, 0.8f));
- GUILayout.Space(4);
- DrawMetadataBadge(metadata.type, new Color(0.6f, 0.4f, 0.8f));
- GUILayout.Space(4);
- DrawMetadataBadge(change.changeDescription, accentColor);
- }
- }
- private void DrawFileActions(FileChangeInfo change, Color accentColor)
- {
- using (new EditorGUILayout.VerticalScope(GUILayout.Width(80)))
- {
- if (change.status != FileStatus.Deleted)
- {
- var viewButtonStyle = new GUIStyle(GUI.skin.button)
- {
- normal = { textColor = accentColor },
- fixedHeight = 22
- };
-
- if (GUILayout.Button("👁 VIEW", viewButtonStyle))
- ViewFile(change);
-
- if (GUILayout.Button("📋 DIFF", viewButtonStyle))
- TryShowDiff(change.relativePath);
- }
-
- var revertButtonStyle = new GUIStyle(GUI.skin.button)
- {
- normal = { textColor = errorColor },
- fixedHeight = 22
- };
-
- if (GUILayout.Button("↶ REVERT", revertButtonStyle))
- RevertFile(change);
- }
- }
- private void DrawEmptyChangesState(FileStatus status)
- {
- DrawCard(() =>
- {
- var iconRect = GUILayoutUtility.GetRect(48, 48);
- var emptyTexture = CreateRoundedTexture(new Color(0.5f, 0.5f, 0.5f, 0.3f), 48, 12);
- if (emptyTexture != null)
- {
- GUI.DrawTexture(iconRect, emptyTexture);
- }
-
- EditorGUILayout.Space(8);
-
- var titleStyle = new GUIStyle(EditorStyles.boldLabel)
- {
- alignment = TextAnchor.MiddleCenter,
- normal = { textColor = new Color(0.7f, 0.7f, 0.7f) }
- };
- EditorGUILayout.LabelField($"No {status} Files", titleStyle);
-
- var subtitleStyle = new GUIStyle(EditorStyles.label)
- {
- alignment = TextAnchor.MiddleCenter,
- normal = { textColor = new Color(0.6f, 0.6f, 0.6f) },
- wordWrap = true
- };
-
- var message = GetEmptyStateMessage(status);
- EditorGUILayout.LabelField(message, subtitleStyle);
- }, 32);
- }
- private string GetEmptyStateMessage(FileStatus status)
- {
- return status switch
- {
- FileStatus.Added => "No new files have been added to the project.",
- FileStatus.Modified => "No existing files have been modified.",
- FileStatus.Deleted => "No files have been deleted from the project.",
- FileStatus.Renamed => "No files have been renamed or moved.",
- _ => "No changes detected."
- };
- }
- private int GetStatusCount(FileStatus status)
- {
- return GetFilteredChanges().Count(c => c.status == status);
- }
- private void DrawCommitSection()
- {
- EditorGUILayout.Space(8);
-
- DrawCard(() =>
- {
- DrawSectionHeader("Commit Changes", "Create a new commit with selected files");
-
- EditorGUILayout.Space(4);
-
- var messageStyle = new GUIStyle(EditorStyles.textArea)
- {
- wordWrap = true,
- fontSize = 12
- };
-
- EditorGUILayout.LabelField("Commit Message:", EditorStyles.boldLabel);
- commitMessage = EditorGUILayout.TextArea(commitMessage, messageStyle, GUILayout.Height(50));
-
- EditorGUILayout.Space(8);
-
- var canCommit = !string.IsNullOrEmpty(commitMessage.Trim()) && !isProcessing && GetFilteredChanges().Count > 0;
-
- using (new EditorGUI.DisabledScope(!canCommit))
- {
- var commitButtonStyle = new GUIStyle(GUI.skin.button)
- {
- normal = { textColor = Color.white },
- fixedHeight = 32,
- fontSize = 13,
- fontStyle = FontStyle.Bold
- };
-
- if (canCommit)
- {
- var commitBg = CreateRoundedTexture(successColor, 32, 6);
- if (commitBg != null)
- commitButtonStyle.normal.background = commitBg;
- }
-
- if (GUILayout.Button("🚀 COMMIT & PUSH", commitButtonStyle))
- _ = CommitAndPushAsync();
- }
- }, 12);
- }
- #endregion
- #region Optimized Thumbnail System
- private Texture2D GetOptimizedFileThumbnail(string assetPath, int index)
- {
- if (thumbnailCache != null && thumbnailCache.TryGet(assetPath, out var cachedThumbnail))
- {
- return cachedThumbnail;
- }
-
- var request = new ThumbnailRequest
- {
- assetPath = assetPath,
- index = index,
- priority = CalculatePriority(index)
- };
-
- if (!thumbnailQueue.Any(r => r.assetPath == assetPath))
- {
- thumbnailQueue.Enqueue(request);
- }
-
- return null;
- }
- private void ProcessThumbnailQueueImmediate()
- {
- if (isProcessingThumbnails || thumbnailQueue.Count == 0) return;
-
- isProcessingThumbnails = true;
- var processed = 0;
-
- while (thumbnailQueue.Count > 0 && processed < ITEMS_PER_FRAME)
- {
- var request = thumbnailQueue.Dequeue();
-
- try
- {
- var thumbnail = LoadThumbnailImmediate(request.assetPath);
- if (thumbnail != null && thumbnailCache != null)
- {
- thumbnailCache.Add(request.assetPath, thumbnail);
- Repaint();
- }
- }
- catch (Exception ex)
- {
- Debug.LogWarning($"Failed to load thumbnail for {request.assetPath}: {ex.Message}");
- }
-
- processed++;
- }
-
- isProcessingThumbnails = false;
- }
- private Texture2D LoadThumbnailImmediate(string assetPath)
- {
- try
- {
- var asset = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(assetPath);
- if (asset == null) return null;
- var extension = Path.GetExtension(assetPath).ToLower();
-
- if (IsVisualAsset(extension))
- {
- var preview = AssetPreview.GetAssetPreview(asset);
- if (preview != null) return preview;
-
- if (asset is Texture2D texture) return texture;
- if (asset is Sprite sprite) return sprite.texture;
- }
- return AssetPreview.GetMiniThumbnail(asset);
- }
- catch (Exception)
- {
- return null;
- }
- }
- private void DrawOptimizedFileThumbnail(FileChangeInfo change, Texture2D thumbnail, float size)
- {
- var thumbnailRect = GUILayoutUtility.GetRect(size, size);
-
- var bgTexture = CreateRoundedTexture(GetFileTypeColor(change.relativePath), (int)size, 8);
- if (bgTexture != null)
- {
- GUI.DrawTexture(thumbnailRect, bgTexture);
- }
-
- if (thumbnail != null)
- {
- var borderRect = new Rect(thumbnailRect.x + 2, thumbnailRect.y + 2, thumbnailRect.width - 4, thumbnailRect.height - 4);
- EditorGUI.DrawRect(borderRect, new Color(0.2f, 0.2f, 0.2f, 0.8f));
-
- var imageRect = new Rect(thumbnailRect.x + 3, thumbnailRect.y + 3, thumbnailRect.width - 6, thumbnailRect.height - 6);
- GUI.DrawTexture(imageRect, thumbnail, ScaleMode.ScaleToFit);
- }
- else
- {
- var iconStyle = new GUIStyle(EditorStyles.centeredGreyMiniLabel)
- {
- normal = { textColor = Color.white },
- fontSize = 18,
- fontStyle = FontStyle.Bold
- };
-
- GUI.Label(thumbnailRect, GetFileTypeIcon(change.relativePath), iconStyle);
-
- var loadingStyle = new GUIStyle(EditorStyles.centeredGreyMiniLabel)
- {
- normal = { textColor = new Color(1f, 1f, 1f, 0.5f) },
- fontSize = 8
- };
- var loadingRect = new Rect(thumbnailRect.x, thumbnailRect.y + thumbnailRect.height - 12, thumbnailRect.width, 12);
- GUI.Label(loadingRect, "⏳", loadingStyle);
- }
-
- DrawStatusIcon(change, thumbnailRect);
- }
- private void DrawStatusIcon(FileChangeInfo change, Rect thumbnailRect)
- {
- var statusSize = 16;
- var statusRect = new Rect(
- thumbnailRect.x + thumbnailRect.width - statusSize - 2,
- thumbnailRect.y + 2,
- statusSize,
- statusSize
- );
-
- if (statusIconCache.TryGetValue(change.status, out var statusIcon) && statusIcon != null)
- {
- GUI.DrawTexture(statusRect, statusIcon, ScaleMode.ScaleToFit);
- }
- else
- {
- var statusColor = GetStatusColor(change.status);
- var statusTexture = CreateRoundedTexture(statusColor, statusSize, statusSize / 2);
- if (statusTexture != null)
- {
- GUI.DrawTexture(statusRect, statusTexture);
- }
- }
- }
- private int CalculatePriority(int index)
- {
- var distanceFromVisible = Mathf.Min(
- Mathf.Abs(index - visibleStartIndex),
- Mathf.Abs(index - visibleEndIndex)
- );
- return 100 - distanceFromVisible;
- }
- private bool IsVisualAsset(string extension)
- {
- return extension switch
- {
- ".png" or ".jpg" or ".jpeg" or ".tga" or ".bmp" or ".gif" or ".psd" or ".tiff" => true,
- ".mat" or ".prefab" or ".fbx" or ".obj" or ".blend" => true,
- _ => false
- };
- }
- #endregion
- #region UI Enhancement Methods
- private void UpdateAnimations()
- {
- var currentTime = EditorApplication.timeSinceStartup;
- var deltaTime = (float)(currentTime - lastUpdateTime);
- lastUpdateTime = currentTime;
-
- // Update pulse timers
- var keysToUpdate = pulseTimers.Keys.ToList();
- foreach (var key in keysToUpdate)
- {
- pulseTimers[key] += deltaTime;
- if (pulseTimers[key] > ANIMATION_SPEED)
- pulseTimers[key] = 0f;
- }
-
- // Update performance monitoring
- frameTime = deltaTime * 1000f;
- frameCount++;
- if (frameCount % 10 == 0)
- {
- averageFrameTime = Mathf.Lerp(averageFrameTime, frameTime, 0.1f);
- }
- }
- private float GetPulseValue(string key)
- {
- if (!pulseTimers.ContainsKey(key))
- pulseTimers[key] = 0f;
-
- var time = pulseTimers[key];
- return (Mathf.Sin(time * Mathf.PI) + 1f) * 0.5f;
- }
- private void HandleKeyboardShortcuts()
- {
- if (Event.current.type != EventType.KeyDown) return;
-
- switch (Event.current.keyCode)
- {
- case KeyCode.F5:
- RefreshAllChanges();
- Event.current.Use();
- break;
- case KeyCode.B when Event.current.control:
- TryOpenBranchManager();
- Event.current.Use();
- break;
- case KeyCode.D when Event.current.control:
- ShowDiffForSelected();
- Event.current.Use();
- break;
- case KeyCode.F when Event.current.control:
- SetFocusToSearch();
- Event.current.Use();
- break;
- }
- }
- private void HandleContextMenu()
- {
- if (Event.current.type == EventType.MouseDown && Event.current.button == 0 && showingContextMenu)
- {
- showingContextMenu = false;
- contextMenuTarget = null;
- Event.current.Use();
- }
- }
- private string GetDynamicSubtitle()
- {
- var changeCount = GetFilteredChanges().Count;
-
- if (isProcessing)
- return "Processing...";
-
- if (changeCount == 0)
- return "Working directory clean • Ready for new changes";
-
- var addedCount = realChanges.Count(c => c.status == FileStatus.Added);
- var modifiedCount = realChanges.Count(c => c.status == FileStatus.Modified);
- var deletedCount = realChanges.Count(c => c.status == FileStatus.Deleted);
-
- var parts = new List<string>();
- if (addedCount > 0) parts.Add($"{addedCount} added");
- if (modifiedCount > 0) parts.Add($"{modifiedCount} modified");
- if (deletedCount > 0) parts.Add($"{deletedCount} deleted");
-
- return string.Join(" • ", parts) + " • Ready to commit";
- }
- private void SetFocusToSearch()
- {
- GUI.FocusControl("GlobalSearch");
- EditorGUIUtility.editingTextField = true;
- }
- private void FocusOnChanges()
- {
- selectedTab = 0;
- selectedChangeTab = 1;
- Repaint();
- }
- private void ShowDiffForSelected()
- {
- var filteredChanges = GetFilteredChanges();
- if (filteredChanges.Count > 0)
- {
- TryShowDiff(filteredChanges[0].relativePath);
- }
- else
- {
- ShowNotification("No files selected for diff", NotificationType.Warning);
- }
- }
- private void TryOpenBranchManager()
- {
- try
- {
- // Check if BranchAndStashManager exists using reflection
- var type = System.Type.GetType("UnityVersionControl.BranchAndStashManager");
- if (type != null)
- {
- var method = type.GetMethod("ShowWindow", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
- method?.Invoke(null, null);
- }
- else
- {
- ShowNotification("Branch Manager not available", NotificationType.Warning);
- }
- }
- catch (Exception ex)
- {
- Debug.LogWarning($"Could not open Branch Manager: {ex.Message}");
- ShowNotification("Branch Manager not available", NotificationType.Warning);
- }
- }
- private void TryShowDiff(string filePath)
- {
- try
- {
- // Check if FileDiffViewer exists using reflection
- var type = System.Type.GetType("UnityVersionControl.FileDiffViewer");
- if (type != null)
- {
- var method = type.GetMethod("ShowDiff", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
- method?.Invoke(null, new object[] { filePath });
- }
- else
- {
- ShowNotification("Diff Viewer not available", NotificationType.Warning);
- }
- }
- catch (Exception ex)
- {
- Debug.LogWarning($"Could not open Diff Viewer: {ex.Message}");
- ShowNotification("Diff Viewer not available", NotificationType.Warning);
- }
- }
- private void ShowNotification(string message, NotificationType type = NotificationType.Info, string title = "", float duration = NOTIFICATION_DURATION)
- {
- notifications.Enqueue(new NotificationInfo
- {
- title = string.IsNullOrEmpty(title) ? type.ToString() : title,
- message = message,
- type = type,
- timestamp = Time.realtimeSinceStartup,
- duration = duration
- });
- }
- #endregion
- #region Notification and Minimap Drawing
- private void DrawNotifications()
- {
- if (notifications.Count == 0) return;
-
- var notificationY = 50f;
- var notificationsToRemove = new List<NotificationInfo>();
-
- foreach (var notification in notifications)
- {
- if (Time.realtimeSinceStartup - notification.timestamp > notification.duration)
- {
- notificationsToRemove.Add(notification);
- continue;
- }
-
- DrawNotification(notification, notificationY);
- notificationY += 60f;
- }
-
- // Remove expired notifications
- foreach (var expired in notificationsToRemove)
- {
- var tempQueue = new Queue<NotificationInfo>();
- while (notifications.Count > 0)
- {
- var item = notifications.Dequeue();
- if (!item.Equals(expired))
- tempQueue.Enqueue(item);
- }
- while (tempQueue.Count > 0)
- {
- notifications.Enqueue(tempQueue.Dequeue());
- }
- }
- }
- private void DrawNotification(NotificationInfo notification, float y)
- {
- var notificationRect = new Rect(position.width - 320, y, 300, 50);
- var alpha = Mathf.Clamp01((notification.duration - (Time.realtimeSinceStartup - notification.timestamp)) / notification.duration);
-
- var bgColor = notification.type switch
- {
- NotificationType.Success => successColor,
- NotificationType.Warning => warningColor,
- NotificationType.Error => errorColor,
- _ => accentColor
- };
- bgColor.a = alpha * 0.9f;
-
- EditorGUI.DrawRect(notificationRect, bgColor);
-
- using (new GUILayout.AreaScope(notificationRect))
- {
- GUILayout.Space(8);
- using (new EditorGUILayout.HorizontalScope())
- {
- GUILayout.Space(8);
-
- var icon = notification.type switch
- {
- NotificationType.Success => "✓",
- NotificationType.Warning => "⚠",
- NotificationType.Error => "✕",
- _ => "ℹ"
- };
-
- EditorGUILayout.LabelField(icon, GUILayout.Width(20));
-
- using (new EditorGUILayout.VerticalScope())
- {
- var titleStyle = new GUIStyle(EditorStyles.boldLabel)
- {
- normal = { textColor = Color.white },
- fontSize = 11
- };
- EditorGUILayout.LabelField(notification.title, titleStyle);
-
- var messageStyle = new GUIStyle(EditorStyles.label)
- {
- normal = { textColor = new Color(1f, 1f, 1f, 0.8f) },
- fontSize = 9
- };
- EditorGUILayout.LabelField(notification.message, messageStyle);
- }
- }
- }
- }
- private void DrawMinimap()
- {
- if (!showMinimap) return;
-
- var minimapWidth = 150f;
- var minimapRect = new Rect(position.width - minimapWidth - 10, 100, minimapWidth, position.height - 150);
-
- GUI.Box(minimapRect, "", GUI.skin.window);
-
- using (new GUILayout.AreaScope(minimapRect))
- {
- EditorGUILayout.LabelField("Overview", EditorStyles.boldLabel);
-
- using (var scrollView = new EditorGUILayout.ScrollViewScope(minimapScrollPosition, GUILayout.Height(minimapRect.height - 40)))
- {
- minimapScrollPosition = scrollView.scrollPosition;
-
- var filteredChanges = GetFilteredChanges();
- foreach (var change in filteredChanges.Take(20))
- {
- var color = GetStatusColor(change.status);
- var fileName = Path.GetFileName(change.relativePath);
-
- var miniStyle = new GUIStyle(EditorStyles.miniLabel)
- {
- normal = { textColor = color }
- };
-
- if (GUILayout.Button(fileName, miniStyle, GUILayout.Height(12)))
- {
- ViewFile(change);
- }
- }
- }
- }
- }
- private void DrawContextMenu()
- {
- if (!showingContextMenu || contextMenuTarget == null) return;
-
- var menuWidth = 200f;
- var menuHeight = 120f;
- var menuRect = new Rect(contextMenuPosition.x, contextMenuPosition.y, menuWidth, menuHeight);
-
- if (menuRect.xMax > position.width)
- menuRect.x = position.width - menuWidth;
- if (menuRect.yMax > position.height)
- menuRect.y = position.height - menuHeight;
-
- GUI.Box(menuRect, "", GUI.skin.window);
-
- using (new GUILayout.AreaScope(menuRect))
- {
- GUILayout.Space(8);
-
- if (GUILayout.Button("📁 Show in Explorer"))
- {
- ShowFileInExplorer(contextMenuTarget.relativePath);
- CloseContextMenu();
- }
-
- if (GUILayout.Button("👁 View Diff"))
- {
- TryShowDiff(contextMenuTarget.relativePath);
- CloseContextMenu();
- }
-
- if (GUILayout.Button("↶ Revert Changes"))
- {
- RevertFile(contextMenuTarget);
- CloseContextMenu();
- }
-
- if (GUILayout.Button("📋 Copy Path"))
- {
- EditorGUIUtility.systemCopyBuffer = contextMenuTarget.relativePath;
- ShowNotification("Path copied to clipboard", NotificationType.Info);
- CloseContextMenu();
- }
-
- GUILayout.Space(4);
-
- if (GUILayout.Button("✕ Close"))
- {
- CloseContextMenu();
- }
- }
- }
- private void ShowFileInExplorer(string assetPath)
- {
- try
- {
- var fullPath = Path.Combine(Application.dataPath.Replace("Assets", ""), assetPath);
- if (File.Exists(fullPath))
- {
- EditorUtility.RevealInFinder(fullPath);
- }
- else
- {
- ShowNotification("File not found on disk", NotificationType.Error);
- }
- }
- catch (Exception ex)
- {
- Debug.LogError($"Error showing file in explorer: {ex.Message}");
- ShowNotification("Could not open file location", NotificationType.Error);
- }
- }
- private void CloseContextMenu()
- {
- showingContextMenu = false;
- contextMenuTarget = null;
- }
- #endregion
- #region Helper Methods and Drawing
- private void DrawBackground()
- {
- var rect = new Rect(0, 0, position.width, position.height);
- EditorGUI.DrawRect(rect, backgroundColor);
- }
- private void DrawCard(System.Action content, int padding = 12)
- {
- if (content == null) return;
-
- 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 = "")
- {
- EditorGUILayout.Space(4);
-
- var headerStyle = new GUIStyle(EditorStyles.boldLabel)
- {
- fontSize = 14,
- normal = { textColor = Color.white }
- };
-
- EditorGUILayout.LabelField(title, headerStyle);
-
- if (!string.IsNullOrEmpty(subtitle))
- {
- var subtitleStyle = new GUIStyle(EditorStyles.label)
- {
- normal = { textColor = new Color(0.8f, 0.8f, 0.8f) },
- fontSize = 11
- };
- EditorGUILayout.LabelField(subtitle, subtitleStyle);
- }
-
- EditorGUILayout.Space(4);
- }
- private void DrawSeparator(float thickness = 1f, float spacing = 8f)
- {
- EditorGUILayout.Space(spacing);
- var rect = EditorGUILayout.GetControlRect(false, thickness);
- EditorGUI.DrawRect(rect, new Color(0.5f, 0.5f, 0.5f, 0.3f));
- EditorGUILayout.Space(spacing);
- }
- private void DrawTabs()
- {
- EditorGUILayout.Space(8);
-
- using (new EditorGUILayout.HorizontalScope())
- {
- for (int i = 0; i < tabNames.Length; i++)
- {
- var isSelected = selectedTab == i;
- var buttonStyle = new GUIStyle(GUI.skin.button);
-
- if (isSelected)
- {
- buttonStyle.normal.background = Texture2D.whiteTexture;
- buttonStyle.normal.textColor = backgroundColor;
- buttonStyle.fontStyle = FontStyle.Bold;
- }
- else
- {
- buttonStyle.normal.textColor = new Color(0.8f, 0.8f, 0.8f);
- }
-
- buttonStyle.fixedHeight = 36;
-
- if (GUILayout.Button(tabNames[i].ToUpper(), buttonStyle))
- selectedTab = i;
- }
- }
- }
- private void DrawMetadataBadge(string text, Color color)
- {
- if (string.IsNullOrEmpty(text)) return;
-
- var badgeStyle = new GUIStyle(GUI.skin.box)
- {
- normal = { textColor = Color.white },
- fontSize = 9,
- padding = new RectOffset(6, 6, 2, 2),
- margin = new RectOffset(0, 0, 0, 0)
- };
-
- var badgeTexture = CreateRoundedTexture(color, 16, 4);
- if (badgeTexture != null)
- badgeStyle.normal.background = badgeTexture;
-
- GUILayout.Label(text.ToUpper(), badgeStyle);
- }
- 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;
- try
- {
- 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;
- }
- catch (Exception ex)
- {
- Debug.LogWarning($"Failed to create rounded texture: {ex.Message}");
- return null;
- }
- }
- private Color GetFileTypeColor(string filePath)
- {
- if (string.IsNullOrEmpty(filePath)) return fileTypeColors["default"];
-
- var extension = Path.GetExtension(filePath).ToLower();
- return fileTypeColors.TryGetValue(extension, out var color) ? color : fileTypeColors["default"];
- }
- private string GetFileTypeIcon(string filePath)
- {
- if (string.IsNullOrEmpty(filePath)) return "FILE";
-
- var extension = Path.GetExtension(filePath).ToLower();
- return extension switch
- {
- ".cs" => "C#",
- ".unity" => "SC",
- ".prefab" => "PF",
- ".mat" => "MT",
- ".png" or ".jpg" or ".jpeg" => "IMG",
- ".shader" => "SH",
- ".fbx" or ".obj" or ".blend" => "3D",
- ".wav" or ".mp3" or ".ogg" => "AUD",
- ".mp4" or ".mov" or ".avi" => "VID",
- ".anim" or ".controller" => "ANI",
- ".txt" or ".json" or ".xml" => "TXT",
- _ => "FILE"
- };
- }
- private Color GetStatusColor(FileStatus status)
- {
- return status switch
- {
- FileStatus.Added => successColor,
- FileStatus.Modified => warningColor,
- FileStatus.Deleted => errorColor,
- FileStatus.Renamed => accentColor,
- FileStatus.Synced => syncedColor,
- _ => Color.gray
- };
- }
- private FileMetadata GetCachedFileMetadata(string assetPath)
- {
- if (metadataCache.TryGetValue(assetPath, out var cached))
- {
- return cached;
- }
-
- var metadata = new FileMetadata
- {
- size = GetFileSize(assetPath),
- type = GetFileTypeDisplay(assetPath),
- lastModified = GetFileLastModified(assetPath)
- };
-
- metadataCache[assetPath] = metadata;
- return metadata;
- }
- private string GetFileSize(string assetPath)
- {
- try
- {
- if (string.IsNullOrEmpty(assetPath)) return "?";
-
- var fullPath = Path.Combine(Application.dataPath.Replace("Assets", ""), assetPath);
- if (File.Exists(fullPath))
- {
- var fileInfo = new FileInfo(fullPath);
- var bytes = fileInfo.Length;
-
- if (bytes < 1024) return $"{bytes}B";
- if (bytes < 1024 * 1024) return $"{bytes / 1024}KB";
- return $"{bytes / (1024 * 1024)}MB";
- }
- }
- catch (Exception ex)
- {
- Debug.LogWarning($"Failed to get file size for {assetPath}: {ex.Message}");
- }
- return "?";
- }
- private string GetFileTypeDisplay(string filePath)
- {
- if (string.IsNullOrEmpty(filePath)) return "FILE";
-
- var extension = Path.GetExtension(filePath).ToUpper().TrimStart('.');
- return string.IsNullOrEmpty(extension) ? "FILE" : extension;
- }
- private DateTime GetFileLastModified(string assetPath)
- {
- try
- {
- if (string.IsNullOrEmpty(assetPath)) return DateTime.MinValue;
-
- var fullPath = Path.Combine(Application.dataPath.Replace("Assets", ""), assetPath);
- if (File.Exists(fullPath))
- {
- return File.GetLastWriteTime(fullPath);
- }
- }
- catch (Exception ex)
- {
- Debug.LogWarning($"Failed to get last modified time for {assetPath}: {ex.Message}");
- }
- return DateTime.MinValue;
- }
- private void DrawFallbackUI()
- {
- EditorGUILayout.LabelField("Version Control - Error State", EditorStyles.boldLabel);
- if (GUILayout.Button("Restart"))
- {
- Close();
- ShowWindow();
- }
- }
- #endregion
- #region File Operations and Actions
- private void ViewFile(FileChangeInfo change)
- {
- try
- {
- if (change?.relativePath == null) return;
-
- if (change.status == FileStatus.Added)
- {
- AssetDatabase.Refresh();
- }
-
- var asset = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(change.relativePath);
- if (asset != null)
- {
- EditorUtility.FocusProjectWindow();
- EditorGUIUtility.PingObject(asset);
- }
- else
- {
- ShowNotification($"Asset not found: {Path.GetFileName(change.relativePath)}", NotificationType.Warning);
- }
- }
- catch (Exception ex)
- {
- Debug.LogError($"Error viewing file: {ex.Message}");
- ShowNotification("Could not view file", NotificationType.Error);
- }
- }
- private void RevertFile(FileChangeInfo change)
- {
- try
- {
- if (change?.relativePath == null) return;
-
- realChanges.Remove(change);
- UpdateAssetStatus(change.relativePath, FileStatus.Synced);
- ShowNotification($"Reverted {Path.GetFileName(change.relativePath)}", NotificationType.Success);
- Repaint();
- }
- catch (Exception ex)
- {
- Debug.LogError($"Error reverting file: {ex.Message}");
- ShowNotification("Could not revert file", NotificationType.Error);
- }
- }
- private void ShowRevertAllConfirmation()
- {
- if (EditorUtility.DisplayDialog(
- "Revert All Changes",
- "Are you sure you want to revert all pending changes?\n\nThis action cannot be undone.",
- "Revert All",
- "Cancel"))
- {
- RevertAllChanges();
- }
- }
- private void RevertAllChanges()
- {
- try
- {
- var count = realChanges.Count;
- foreach (var change in realChanges.ToList())
- {
- UpdateAssetStatus(change.relativePath, FileStatus.Synced);
- }
-
- realChanges.Clear();
- ShowNotification($"Reverted {count} changes", NotificationType.Success);
- Repaint();
- }
- catch (Exception ex)
- {
- Debug.LogError($"Error reverting all changes: {ex.Message}");
- ShowNotification("Could not revert all changes", NotificationType.Error);
- }
- }
- public void AddRealChange(string relativePath, FileStatus status, string description)
- {
- try
- {
- if (string.IsNullOrEmpty(relativePath)) return;
-
- realChanges.RemoveAll(c => c.relativePath == relativePath);
-
- var change = new FileChangeInfo(relativePath, status, description);
- realChanges.Insert(0, change);
-
- UpdateAssetStatus(relativePath, status);
-
- Repaint();
- }
- catch (Exception ex)
- {
- Debug.LogError($"Error adding real change: {ex.Message}");
- }
- }
- private void RefreshAllChanges()
- {
- try
- {
- AssetDatabase.Refresh();
- LoadCachedChanges();
- ShowNotification("Changes refreshed", NotificationType.Success);
- Repaint();
- }
- catch (Exception ex)
- {
- Debug.LogError($"Error during manual refresh: {ex.Message}");
- ShowNotification("Refresh failed", NotificationType.Error);
- }
- }
- #endregion
- #region Async Operations (Simplified)
- private async Task CommitAndPushAsync()
- {
- isProcessing = true;
-
- try
- {
- var changeCount = GetFilteredChanges().Count;
- ShowNotification($"Committing {changeCount} changes...", NotificationType.Info);
-
- await Task.Delay(1000);
-
- AddToCommitHistory();
-
- foreach (var change in realChanges.ToList())
- {
- UpdateAssetStatus(change.relativePath, FileStatus.Synced);
- }
-
- realChanges.Clear();
-
- await Task.Delay(1000);
-
- commitMessage = "";
- ShowNotification("Changes committed successfully!", NotificationType.Success);
- }
- catch (Exception e)
- {
- ShowNotification($"Commit failed: {e.Message}", NotificationType.Error);
- }
- finally
- {
- isProcessing = false;
- }
- }
- private async Task PullChangesAsync()
- {
- isProcessing = true;
- try
- {
- ShowNotification("Pulling changes from remote...", NotificationType.Info);
-
- await Task.Delay(1500);
-
- var pullResult = await SimulatePullOperation();
-
- if (pullResult.hasConflicts)
- {
- var shouldContinue = EditorUtility.DisplayDialog("Pull Conflicts Detected",
- $"Found {pullResult.conflictCount} conflicts that need resolution.\n\nOpen conflict resolver to continue?",
- "Resolve Conflicts", "Cancel Pull");
-
- if (shouldContinue)
- {
- TryOpenConflictResolver();
- }
- else
- {
- ShowNotification("Pull cancelled", NotificationType.Warning);
- }
- }
- else if (pullResult.filesChanged > 0)
- {
- await CompletePullOperation(pullResult);
- ShowNotification($"Pulled {pullResult.filesChanged} changes", NotificationType.Success);
- }
- else
- {
- ShowNotification("Already up to date", NotificationType.Info);
- }
- }
- catch (Exception e)
- {
- ShowNotification($"Pull failed: {e.Message}", NotificationType.Error);
- }
- finally
- {
- isProcessing = false;
- }
- }
- private void TryOpenConflictResolver()
- {
- try
- {
- var type = System.Type.GetType("UnityVersionControl.VersionControlConflictResolver");
- if (type != null)
- {
- var method = type.GetMethod("ShowWindow", new[] { typeof(System.Action) });
- method?.Invoke(null, new object[] { (System.Action)(() => {
- ShowNotification("Conflicts resolved successfully", NotificationType.Success);
- })});
- }
- else
- {
- ShowNotification("Conflict Resolver not available", NotificationType.Warning);
- }
- }
- catch (Exception ex)
- {
- Debug.LogWarning($"Could not open Conflict Resolver: {ex.Message}");
- ShowNotification("Conflict Resolver not available", NotificationType.Warning);
- }
- }
- private async Task CompletePullOperation(PullResult pullResult)
- {
- await Task.Delay(500);
-
- // Simulate some incoming changes
- var sampleFiles = new[]
- {
- "Assets/Scripts/EnemyAI.cs",
- "Assets/Prefabs/Weapon.prefab",
- "Assets/Materials/GroundMaterial.mat"
- };
-
- for (int i = 0; i < Math.Min(pullResult.filesChanged, sampleFiles.Length); i++)
- {
- var filePath = sampleFiles[i];
- var changeType = (FileStatus)(i % 3); // Rotate through Added, Modified, Deleted
-
- AddRealChange(filePath, changeType, "Updated from remote");
- }
-
- RefreshAllChanges();
- Repaint();
- }
- private async Task<PullResult> SimulatePullOperation()
- {
- await Task.Delay(1000);
-
- var random = new System.Random();
- var scenario = random.Next(0, 3);
-
- return scenario switch
- {
- 0 => new PullResult { filesChanged = 0, hasConflicts = false, conflictCount = 0 },
- 1 => new PullResult { filesChanged = random.Next(1, 5), hasConflicts = false, conflictCount = 0 },
- 2 => new PullResult { filesChanged = random.Next(3, 8), hasConflicts = true, conflictCount = random.Next(1, 3) },
- _ => new PullResult { filesChanged = 0, hasConflicts = false, conflictCount = 0 }
- };
- }
- private void AddToCommitHistory()
- {
- var newCommit = new CommitInfo
- {
- hash = Guid.NewGuid().ToString(),
- author = "Current User",
- message = commitMessage,
- date = DateTime.Now
- };
- commitHistory.Insert(0, newCommit);
- }
- #endregion
- #region Simplified Stubs for Missing Features
- private void DrawHistoryTab()
- {
- EditorGUILayout.Space(8);
-
- DrawCard(() =>
- {
- DrawSectionHeader("Commit History", "Recent project commits");
-
- if (commitHistory.Count == 0)
- {
- EditorGUILayout.LabelField("No commits yet", EditorStyles.centeredGreyMiniLabel);
- return;
- }
-
- using (var scrollView = new EditorGUILayout.ScrollViewScope(scrollPosition))
- {
- scrollPosition = scrollView.scrollPosition;
- foreach (var commit in commitHistory.Take(10))
- DrawCommit(commit);
- }
- }, 8);
- }
- private void DrawCommit(CommitInfo commit)
- {
- if (commit == null) return;
-
- DrawCard(() =>
- {
- using (new EditorGUILayout.HorizontalScope())
- {
- var hashText = string.IsNullOrEmpty(commit.hash) ? "unknown" : commit.hash.Substring(0, Math.Min(8, commit.hash.Length));
- DrawMetadataBadge(hashText, accentColor);
-
- GUILayout.Space(8);
-
- using (new EditorGUILayout.VerticalScope())
- {
- var messageStyle = new GUIStyle(EditorStyles.boldLabel)
- {
- normal = { textColor = Color.white }
- };
- EditorGUILayout.LabelField(commit.message ?? "No message", messageStyle);
-
- using (new EditorGUILayout.HorizontalScope())
- {
- var metaStyle = new GUIStyle(EditorStyles.label)
- {
- normal = { textColor = new Color(0.7f, 0.7f, 0.7f) },
- fontSize = 11
- };
-
- EditorGUILayout.LabelField($"by {commit.author ?? "Unknown"}", metaStyle);
- GUILayout.FlexibleSpace();
- EditorGUILayout.LabelField(commit.date.ToString("MMM dd, HH:mm"), metaStyle);
- }
- }
- }
- }, 8);
-
- EditorGUILayout.Space(4);
- }
- private void DrawSettingsTab()
- {
- EditorGUILayout.Space(8);
-
- DrawCard(() =>
- {
- DrawSectionHeader("Repository Configuration", "Set up your version control settings");
-
- EditorGUILayout.Space(8);
-
- EditorGUILayout.TextField("Repository Path", "");
- EditorGUILayout.TextField("User Name", "");
- EditorGUILayout.TextField("User Email", "");
- EditorGUILayout.Toggle("Auto-stage .meta files", true);
-
- EditorGUILayout.Space(16);
-
- using (new EditorGUILayout.HorizontalScope())
- {
- var initButtonStyle = new GUIStyle(GUI.skin.button)
- {
- fixedHeight = 32,
- normal = { textColor = successColor }
- };
-
- if (GUILayout.Button("⚡ INITIALIZE REPOSITORY", initButtonStyle))
- ShowNotification("Repository initialized", NotificationType.Success);
-
- GUILayout.Space(8);
-
- var cloneButtonStyle = new GUIStyle(GUI.skin.button)
- {
- fixedHeight = 32,
- normal = { textColor = accentColor }
- };
-
- if (GUILayout.Button("📥 CLONE REPOSITORY", cloneButtonStyle))
- ShowNotification("Clone dialog would open here", NotificationType.Info);
- }
- }, 16);
- }
- private void RefreshCommitHistory()
- {
- commitHistory.Clear();
-
- var sampleCommits = new[]
- {
- new CommitInfo { hash = "a1b2c3d4", author = "John Doe", message = "Added new player movement system", date = DateTime.Now.AddHours(-2) },
- new CommitInfo { hash = "e5f6g7h8", author = "Jane Smith", message = "Fixed enemy AI pathfinding bug", date = DateTime.Now.AddHours(-5) },
- new CommitInfo { hash = "i9j0k1l2", author = "Bob Johnson", message = "Updated UI design for main menu", date = DateTime.Now.AddDays(-1) }
- };
-
- commitHistory.AddRange(sampleCommits);
- }
- #endregion
- #region File System Integration (Simplified)
- private void RegisterCallbacks()
- {
- try
- {
- EditorApplication.projectChanged += OnProjectChanged;
- EditorApplication.hierarchyChanged += OnHierarchyChanged;
- }
- catch (Exception ex)
- {
- Debug.LogWarning($"Could not register callbacks: {ex.Message}");
- }
- }
- private void UnregisterCallbacks()
- {
- try
- {
- EditorApplication.projectChanged -= OnProjectChanged;
- EditorApplication.hierarchyChanged -= OnHierarchyChanged;
- }
- catch (Exception ex)
- {
- Debug.LogWarning($"Could not unregister callbacks: {ex.Message}");
- }
- }
- private void StartFileSystemWatcher()
- {
- // Simplified - just initialize tracking
- InitializeAssetStatusCache();
- }
- private void StopFileSystemWatcher()
- {
- try
- {
- if (fileWatcher != null)
- {
- fileWatcher.EnableRaisingEvents = false;
- fileWatcher.Dispose();
- fileWatcher = null;
- }
- }
- catch (Exception ex)
- {
- Debug.LogWarning($"Error stopping file watcher: {ex.Message}");
- }
- }
- private void InitializeProjectWindowIcons()
- {
- try
- {
- EditorApplication.projectWindowItemOnGUI += OnProjectWindowItemGUI;
- LoadStatusIcons();
- }
- catch (Exception ex)
- {
- Debug.LogWarning($"Could not initialize project window icons: {ex.Message}");
- }
- }
- private void CleanupProjectWindowIcons()
- {
- try
- {
- EditorApplication.projectWindowItemOnGUI -= OnProjectWindowItemGUI;
- }
- catch (Exception ex)
- {
- Debug.LogWarning($"Error cleaning up project window icons: {ex.Message}");
- }
- }
- private void LoadStatusIcons()
- {
- // Try to load icons, but don't fail if they don't exist
- try
- {
- statusIconCache[FileStatus.Added] = Resources.Load<Texture2D>("VersionControl/Icons/icon_added");
- statusIconCache[FileStatus.Modified] = Resources.Load<Texture2D>("VersionControl/Icons/icon_modified");
- statusIconCache[FileStatus.Deleted] = Resources.Load<Texture2D>("VersionControl/Icons/icon_deleted");
- statusIconCache[FileStatus.Renamed] = Resources.Load<Texture2D>("VersionControl/Icons/icon_renamed");
- statusIconCache[FileStatus.Synced] = Resources.Load<Texture2D>("VersionControl/Icons/icon_synced");
- }
- catch (Exception ex)
- {
- Debug.LogWarning($"Could not load status icons: {ex.Message}");
- }
- }
- private void OnProjectWindowItemGUI(string guid, Rect selectionRect)
- {
- try
- {
- var assetPath = AssetDatabase.GUIDToAssetPath(guid);
- if (string.IsNullOrEmpty(assetPath) || assetPath.EndsWith(".meta"))
- return;
-
- if (!IsTrackedAsset(assetPath))
- return;
- var status = GetAssetStatus(assetPath);
- if (status == FileStatus.Untracked)
- return;
- DrawProjectWindowStatusIcon(selectionRect, status, assetPath);
- }
- catch (Exception ex)
- {
- // Silently handle errors to avoid spamming console
- Debug.LogWarning($"Error in project window item GUI: {ex.Message}");
- }
- }
- private void DrawProjectWindowStatusIcon(Rect itemRect, FileStatus status, string assetPath)
- {
- if (!statusIconCache.TryGetValue(status, out var icon) || icon == null)
- return;
- var iconSize = 16f;
- var padding = 2f;
-
- var iconRect = new Rect(
- itemRect.xMax - iconSize - padding,
- itemRect.y + padding,
- iconSize,
- iconSize
- );
- GUI.DrawTexture(iconRect, icon, ScaleMode.ScaleToFit);
-
- if (iconRect.Contains(Event.current.mousePosition))
- {
- var tooltip = GetStatusTooltip(status, assetPath);
- GUI.tooltip = tooltip;
- }
- }
- private void InitializeAssetStatusCache()
- {
- try
- {
- var allAssets = AssetDatabase.GetAllAssetPaths()
- .Where(path => IsTrackedAsset(path))
- .ToArray();
- foreach (var assetPath in allAssets)
- {
- if (!assetStatusCache.ContainsKey(assetPath))
- {
- assetStatusCache[assetPath] = FileStatus.Synced;
- }
- }
- }
- catch (Exception ex)
- {
- Debug.LogWarning($"Error initializing asset status cache: {ex.Message}");
- }
- }
- private FileStatus GetAssetStatus(string assetPath)
- {
- var change = realChanges.FirstOrDefault(c => c.relativePath == assetPath);
- if (change != null)
- return change.status;
- return assetStatusCache.TryGetValue(assetPath, out var status) ? status : FileStatus.Synced;
- }
- private void UpdateAssetStatus(string assetPath, FileStatus status)
- {
- try
- {
- assetStatusCache[assetPath] = status;
- EditorApplication.RepaintProjectWindow();
- }
- catch (Exception ex)
- {
- Debug.LogWarning($"Error updating asset status: {ex.Message}");
- }
- }
- private bool IsTrackedAsset(string assetPath)
- {
- if (string.IsNullOrEmpty(assetPath) || assetPath.EndsWith(".meta"))
- return false;
- var extension = Path.GetExtension(assetPath).ToLower();
- return trackedExtensions.Contains(extension);
- }
- private string GetStatusTooltip(FileStatus status, string assetPath)
- {
- var fileName = Path.GetFileName(assetPath);
- return status switch
- {
- FileStatus.Added => $"Added: {fileName}",
- FileStatus.Modified => $"Modified: {fileName}",
- FileStatus.Deleted => $"Deleted: {fileName}",
- FileStatus.Renamed => $"Renamed: {fileName}",
- FileStatus.Synced => $"Synced: {fileName}",
- _ => fileName
- };
- }
- private bool IsFileWatcherActive()
- {
- return fileWatcher?.EnableRaisingEvents ?? false;
- }
- private void OnProjectChanged()
- {
- EditorApplication.delayCall += RefreshAllChanges;
- }
- private void OnHierarchyChanged()
- {
- try
- {
- var activeScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
- if (!string.IsNullOrEmpty(activeScene.path))
- {
- AddRealChange(activeScene.path, FileStatus.Modified, "Scene hierarchy changed");
- }
- }
- catch (Exception ex)
- {
- Debug.LogWarning($"Error in hierarchy changed: {ex.Message}");
- }
- }
- #endregion
- #region Performance Helper Classes
- private class ThumbnailCache
- {
- private readonly Dictionary<string, Texture2D> cache = new Dictionary<string, Texture2D>();
- private readonly Queue<string> accessOrder = new Queue<string>();
- private readonly int maxSize;
- public ThumbnailCache(int maxSize)
- {
- this.maxSize = maxSize;
- }
- public bool TryGet(string key, out Texture2D texture)
- {
- return cache.TryGetValue(key, out texture);
- }
- public void Add(string key, Texture2D texture)
- {
- if (cache.ContainsKey(key)) return;
-
- while (cache.Count >= maxSize && accessOrder.Count > 0)
- {
- var oldest = accessOrder.Dequeue();
- if (cache.TryGetValue(oldest, out var oldTexture))
- {
- cache.Remove(oldest);
- if (oldTexture != null) DestroyImmediate(oldTexture);
- }
- }
-
- cache[key] = texture;
- accessOrder.Enqueue(key);
- }
- public void Clear()
- {
- foreach (var texture in cache.Values)
- {
- if (texture != null) DestroyImmediate(texture);
- }
- cache.Clear();
- accessOrder.Clear();
- }
- }
- #endregion
- #region Cleanup
- private void CleanupPerformanceFeatures()
- {
- try
- {
- thumbnailCache?.Clear();
- thumbnailQueue.Clear();
- }
- catch (Exception ex)
- {
- Debug.LogWarning($"Error cleaning up performance features: {ex.Message}");
- }
- }
- private void CleanupCaches()
- {
- try
- {
- foreach (var texture in textureCache.Values)
- {
- if (texture != null) DestroyImmediate(texture);
- }
- textureCache.Clear();
-
- foreach (var texture in statusIconCache.Values)
- {
- if (texture != null) DestroyImmediate(texture);
- }
- statusIconCache.Clear();
-
- metadataCache.Clear();
- }
- catch (Exception ex)
- {
- Debug.LogWarning($"Error cleaning up caches: {ex.Message}");
- }
- }
- #endregion
- }
- }
|