//#define UNITY_5 using UnityEditor; using UnityEngine; using System; using System.Collections.Generic; using System.Linq; using Perforce.P4; namespace P4Connect { public class Icons { const float MinHeight = 16.0f; const float MinWidth = 32.0f; const float MaxWidth = 96.0f; const float IconMinSize = 8.0f; const float IconMaxSize = 24.0f; const float MarginX = 1.0f; const float MarginY = 0.0f; enum Corner { BottomRight = 0, BottomLeft, TopLeft, TopRight, Center } static Texture IconHasLatest; static Texture IconOutOfDate; static Texture IconCheckedOutLocal; static Texture IconCheckedOutOther; static Texture IconMarkedForAddLocal; static Texture IconMarkedForAddOther; static Texture IconMarkedForDeleteLocal; static Texture IconMarkedForDeleteOther; static Texture IconMoveAdd; static Texture IconMoveDelete; static Texture IconMissingFile; static Texture IconOtherFile; static Texture IconPairFileOnly; static Texture IconPairMetaOnly; static Texture IconPairFileAndMeta; static Texture IconNeedsResolve; static Texture IconFolderMetaOutOfDate; static Texture IconFolderMetaModifiedLocal; static Texture IconLockedLocal; static Texture IconLockedOther; static Texture IconChangeOther; static Texture IconChangeLocal; static Texture IconChangeShelvedOther; static Texture IconChangeShelvedLocal; enum UpdateState { None, ProjectWindowChanged, TriggerProjectWindowChanged, } static UpdateState _State; static string _CurrentFolder; static HashSet _FilesToUpdate = new HashSet(); static UnityEngine.Object[] allTextures; static AssetBundle iconbundle = null; public static string PackName = "P4Connect.icons"; public static string PackFolder = "Assets/P4Connect/Editor"; public static void Initialize() { string iconsPath = Utils.AssetPathToFullPath(PackFolder + "/" + PackName); if (iconbundle != null) { iconbundle.Unload(true); } iconbundle = AssetBundle.LoadFromFile(iconsPath); // iconbundle = AssetBundle.CreateFromFile(iconsPath); if (iconbundle != null) { #if UNITY_5 allTextures = iconbundle.LoadAllAssets(typeof(Texture)); #else if (Utils.IsRunningUnity5()) // Use reflection to call "LoadAllAssets" at runtime. Running in Unity5 built in Unity 4 :) { System.Reflection.MethodInfo _LoadAllAssets = typeof(AssetBundle).GetMethod("LoadAllAssets", new Type[]{ typeof(Type) }); object result = _LoadAllAssets.Invoke(iconbundle, new object[] { typeof(Texture) }); allTextures = (UnityEngine.Object[]) result; } else { allTextures = iconbundle.LoadAll(typeof(Texture)); } #endif iconbundle.Unload(false); } else { Debug.Log("Failed to load " + iconsPath + " bundle"); } if (allTextures != null) { IconHasLatest = GetTexture("Has-Latest"); IconOutOfDate = GetTexture("Out-of-date"); IconCheckedOutLocal = GetTexture("Checked-out-Local"); IconCheckedOutOther = GetTexture("Checked-out-Other"); IconMarkedForAddLocal = GetTexture("Marked-for-add-Local"); IconMarkedForAddOther = GetTexture("Marked-for-add-Other"); IconMarkedForDeleteLocal = GetTexture("Marked-for-delete-Local"); IconMarkedForDeleteOther = GetTexture("Marked-for-delete-Other"); IconMoveAdd = GetTexture("Move-Add"); IconMoveDelete = GetTexture("Move-Delete"); IconMissingFile = GetTexture("missing-file"); IconOtherFile = GetTexture("other-file"); IconPairFileOnly = GetTexture("has-file"); IconPairMetaOnly = GetTexture("has-meta"); IconPairFileAndMeta = GetTexture("has-file-and-meta"); IconNeedsResolve = GetTexture("needs-resolve"); IconFolderMetaOutOfDate = GetTexture("foldermeta-out-of-date"); IconFolderMetaModifiedLocal = GetTexture("foldermeta-modified-local"); IconLockedLocal = GetTexture("locked-local"); IconLockedOther = GetTexture("locked-other"); IconChangeOther = GetTexture("change-other"); IconChangeLocal = GetTexture("change-local"); IconChangeShelvedOther = GetTexture("change-other"); // need change-shelved-other FIXME IconChangeShelvedLocal = GetTexture("change-local"); // need change-shelved-local FIXME _State = UpdateState.None; _FilesToUpdate = new HashSet(); UpdateDisplay(); } } // During development icons can be placed here: Later they should get moved into the assetbundle public static string AssetIconsFolder = "Assets/P4Connect/Icons/"; /// /// Given an Icon name (with extension). See if it is in the Icon Asset we provide, and if not there, look on the filesystem for a "loose" texture. /// /// /// public static Texture GetIcon(string name) { CheckInit(); var tex = GetTexture(System.IO.Path.GetFileNameWithoutExtension(name)); if (tex == null) { try { #if Unity_4_0 tex = (Resources.LoadAssetAtPath(AssetIconsFolder + name, typeof(Texture2D)) as Texture2D); #else tex = AssetDatabase.LoadAssetAtPath(AssetIconsFolder + name, typeof(Texture2D)) as Texture2D; #endif if (tex == null) { Debug.Log("Couldn't find loose image: " + name + " at: " + AssetIconsFolder + name); } } catch (Exception ex) { Debug.LogException(ex); } } if (tex != null) return tex; return new Texture(); // Better than returning null? } private static Texture GetTexture(string name) { Texture icon = null; foreach(var o in allTextures) { icon = (Texture) o; if (String.Equals(icon.name, name, StringComparison.CurrentCultureIgnoreCase)) { return icon; } } return null; } // // Hook up all the callback functions into editorApplication // public static void UpdateDisplay() { EditorApplication.projectWindowItemOnGUI -= OnProjectWindowItem; EditorApplication.update -= OnUpdate; AssetStatusCache.OnAssetStatusChanged -= OnOperationPerformed; if (Config.ValidConfiguration && Config.DisplayStatusIcons) { EditorApplication.projectWindowItemOnGUI += OnProjectWindowItem; EditorApplication.update += OnUpdate; AssetStatusCache.OnAssetStatusChanged += OnOperationPerformed; } _State = UpdateState.TriggerProjectWindowChanged; EditorApplication.RepaintProjectWindow(); } static void OnProjectWindowItem(String aGuid, Rect aSelectionRect) { // What kind of asset is this? string assetPath = AssetDatabase.GUIDToAssetPath(aGuid); DrawItemIcons(assetPath, aSelectionRect, aSelectionRect.height <= 16.0f); if (_State == UpdateState.ProjectWindowChanged) { _FilesToUpdate.Add(assetPath); } } static void OnUpdate() { // Don't update icons while in play mode if (Config.ValidConfiguration && Config.DisplayStatusIcons && !EditorApplication.isPlayingOrWillChangePlaymode) { if (_State == UpdateState.TriggerProjectWindowChanged) { _FilesToUpdate.Clear(); _State = UpdateState.ProjectWindowChanged; // Force a refresh AssetDatabase.Refresh(); } else if (_State == UpdateState.ProjectWindowChanged) { // Now we update the state of all the flags HashSet validFiles = new HashSet(); foreach (var file in _FilesToUpdate) { if (file != "" && file != "Assets") { validFiles.Add(file); } } //Debug.Log("BeforeSelection: " + Logger.StringArrayToString(validFiles.ToArray())); var paths = Utils.GetDeepSelectedEffectivePaths(); // Get all local files from within the selection var selected = Utils.ExplodeLocalPaths(paths.AssetToLocalPaths()).LocalToAssetPaths(); //Debug.Log("FromSelection: " + Logger.StringArrayToString(selected.ToArray())); validFiles.UnionWith(selected); // Merge in the list of assets from the current folder //var sel = Selection.GetFiltered(typeof(UnityEngine.Object), SelectionMode.Assets); //if (sel != null && sel.Length > 0) //{ // string assetPath = AssetDatabase.GetAssetPath(sel[0]); // string assetFullPath = Utils.AssetPathToFullPath(assetPath); // string folderFullPath; // if (Utils.IsDirectory(assetPath)) // folderFullPath = assetFullPath; // else // folderFullPath = System.IO.Path.GetDirectoryName(assetFullPath); // foreach (var name in System.IO.Directory.GetFileSystemEntries(folderFullPath)) // { // string subAssetPath = Utils.FullPathToAssetPath(name); // if (!validFiles.Contains(subAssetPath)) // validFiles.Add(subAssetPath); // } //} //Debug.Log("UpdateAssetData: " + Logger.StringArrayToString(validFiles.StripDirectories().ToArray())); AssetStatusCache.UpdateAssetData(validFiles.StripDirectories().ToList()); _FilesToUpdate.Clear(); _State = UpdateState.None; AssetDatabase.Refresh(); } else if (_State == UpdateState.None) { // Detect change in selection var selection = Selection.GetFiltered(typeof(UnityEngine.Object), SelectionMode.Assets); if (selection != null && selection.Length > 0) { string selectionFolder = AssetDatabase.GetAssetPath(selection[0]); if (!Utils.IsDirectory(selectionFolder)) { selectionFolder = System.IO.Path.GetDirectoryName(selectionFolder); } if (_CurrentFolder != selectionFolder) { _CurrentFolder = selectionFolder; _FilesToUpdate.Clear(); _State = UpdateState.ProjectWindowChanged; } } } } } static void OnOperationPerformed(PerforceConnection aConnection) { // Refresh the project view _State = UpdateState.TriggerProjectWindowChanged; } public static void DrawItemIcons(String aAssetPath, Rect aSelectionRect, bool aHorizontal) { CheckInit(); // What kind of asset is this? if (aAssetPath != "" && aAssetPath != "Assets") { bool isFolder = Utils.IsDirectory(aAssetPath); AssetStatusCache.AssetStatus status; if (isFolder) status = AssetStatusCache.GetAssetStatus(Utils.MetaFromAsset(aAssetPath)); else status = AssetStatusCache.GetAssetStatus(aAssetPath); DrawItemIcons(status, isFolder, aSelectionRect, aHorizontal); } } public static void DrawItemIcons(AssetStatusCache.AssetStatus aStatus, bool aIsFolder, Rect aSelectionRect, bool aHorizontal) { //CheckInit(); Rect iconRect = GetIconRect(aSelectionRect, Corner.TopLeft, aHorizontal); switch (aStatus.LocalState) { case FileState.None: break; case FileState.InDepot: break; case FileState.MarkedForEdit: if (aIsFolder) GUI.DrawTexture(iconRect, IconFolderMetaModifiedLocal); else GUI.DrawTexture(iconRect, IconCheckedOutLocal); break; case FileState.MarkedForAdd: if (aIsFolder) GUI.DrawTexture(iconRect, IconFolderMetaModifiedLocal); else GUI.DrawTexture(iconRect, IconMarkedForAddLocal); break; case FileState.MarkedForDelete: if (aIsFolder) GUI.DrawTexture(iconRect, IconFolderMetaModifiedLocal); else GUI.DrawTexture(iconRect, IconMarkedForDeleteLocal); break; case FileState.MarkedForAddMove: if (aIsFolder) GUI.DrawTexture(iconRect, IconFolderMetaModifiedLocal); else GUI.DrawTexture(iconRect, IconMoveAdd); break; case FileState.MarkedForDeleteMove: if (aIsFolder) GUI.DrawTexture(iconRect, IconFolderMetaModifiedLocal); else GUI.DrawTexture(iconRect, IconMoveDelete); break; } iconRect = GetIconRect(aSelectionRect, Corner.TopRight, aHorizontal); switch (aStatus.OtherState) { case FileState.None: break; case FileState.InDepot: break; case FileState.MarkedForEdit: if (!aIsFolder) { GUI.DrawTexture(iconRect, IconCheckedOutOther); } break; case FileState.MarkedForAdd: if (!aIsFolder) { GUI.DrawTexture(iconRect, IconMarkedForAddOther); } break; case FileState.MarkedForDelete: if (!aIsFolder) { GUI.DrawTexture(iconRect, IconMarkedForDeleteOther); } break; case FileState.MarkedForAddMove: if (!aIsFolder) { GUI.DrawTexture(iconRect, IconMarkedForAddOther); } break; case FileState.MarkedForDeleteMove: if (!aIsFolder) { GUI.DrawTexture(iconRect, IconMarkedForDeleteOther); } break; } iconRect = GetIconRect(aSelectionRect, Corner.BottomRight, aHorizontal); switch (aStatus.LocalState) { case FileState.None: break; case FileState.InDepot: switch (aStatus.RevisionState) { case RevisionState.None: break; case RevisionState.HasLatest: if (!aIsFolder) { GUI.DrawTexture(iconRect, IconHasLatest); } break; case RevisionState.OutOfDate: if (aIsFolder) GUI.DrawTexture(iconRect, IconFolderMetaOutOfDate); else GUI.DrawTexture(iconRect, IconOutOfDate); break; } break; case FileState.MarkedForEdit: goto case FileState.InDepot; case FileState.MarkedForAdd: break; case FileState.MarkedForDelete: goto case FileState.InDepot; case FileState.MarkedForAddMove: if (!aIsFolder) { GUI.DrawTexture(iconRect, IconHasLatest); } break; case FileState.MarkedForDeleteMove: goto case FileState.InDepot; } switch (aStatus.ResolvedState) { case ResolvedState.None: break; case ResolvedState.NeedsResolve: if (aIsFolder) { iconRect = GetIconRect(aSelectionRect, Corner.BottomRight, aHorizontal); GUI.DrawTexture(iconRect, IconNeedsResolve); } else { iconRect = GetIconRect(aSelectionRect, Corner.Center, aHorizontal); GUI.DrawTexture(iconRect, IconNeedsResolve); } break; } switch (aStatus.LockState) { case LockState.None: break; case LockState.OurLock: iconRect = GetIconRect(aSelectionRect, Corner.BottomLeft, aHorizontal); GUI.DrawTexture(iconRect, IconLockedLocal); break; case LockState.TheirLock: iconRect = GetIconRect(aSelectionRect, Corner.BottomLeft, aHorizontal); GUI.DrawTexture(iconRect, IconLockedOther); break; } } public static Texture GetAssetIcon(string arPath) { CheckInit(); Texture asset = AssetDatabase.GetCachedIcon(arPath); // If we still don't have an icon, use a default one if (asset == null) { if (arPath.StartsWith("Assets/",!Utils.IsCaseSensitive(),null)) { asset = IconMissingFile; } else { asset = IconOtherFile; } } return asset; } public static Texture GetFileAndMetaTypeIcon(FileAndMetaType aType) { CheckInit(); Texture ret = null; switch (aType) { case FileAndMetaType.FileOnly: ret = IconPairFileOnly; break; case FileAndMetaType.MetaOnly: ret = IconPairMetaOnly; break; case FileAndMetaType.FileAndMeta: ret = IconPairFileAndMeta; break; } return ret; } static Rect GetIconRect(Rect aSelectionRect, Corner aCorner, bool aHorizontal) { Rect iconRect = aSelectionRect; float iconSize = 0.0f; if (aHorizontal) { // The view is in list mode iconSize = IconMinSize; switch (aCorner) { case Corner.BottomRight: iconRect.xMin += MinHeight - iconSize + MarginX; iconRect.yMin = iconRect.yMax - iconSize + MarginY; break; case Corner.BottomLeft: iconRect.xMin -= MarginX; iconRect.yMin = iconRect.yMax - iconSize + MarginY; break; case Corner.TopLeft: iconRect.xMin -= MarginX; iconRect.yMin -= MarginY; break; case Corner.TopRight: iconRect.xMin += MinHeight - iconSize + MarginX; iconRect.yMin -= MarginY; break; case Corner.Center: iconRect.xMin += 1.0f; iconRect.xMax = iconRect.xMin + MinHeight - 2.0f; iconRect.yMin += 1.0f; iconRect.yMax -= 1.0f; break; } } else { // The view is in grid mode float sizePercent = (aSelectionRect.width - MinWidth) / (MaxWidth - MinWidth); iconSize = Mathf.Round(Mathf.Lerp(IconMinSize, IconMaxSize, sizePercent)); switch (aCorner) { case Corner.BottomRight: iconRect.xMin = iconRect.xMax - iconSize + MarginX; iconRect.yMin = iconRect.yMax - MinHeight - iconSize + MarginY; break; case Corner.BottomLeft: iconRect.xMin -= MarginX; iconRect.yMin = iconRect.yMax - MinHeight - iconSize + MarginY; break; case Corner.TopLeft: iconRect.xMin -= MarginX; iconRect.yMin -= MarginY; break; case Corner.TopRight: iconRect.xMin = iconRect.xMax - iconSize + MarginX; iconRect.yMin -= MarginY; break; case Corner.Center: iconRect.yMax -= MinHeight + 1.0f; iconRect.yMin += 2.0f; iconRect.xMin += 2.0f; iconRect.xMax -= 2.0f; break; } } if (aCorner != Corner.Center) { iconRect.width = iconSize; iconRect.height = iconSize; } return iconRect; } public static Texture GetChangelistIcon(Changelist change) { CheckInit(); Texture asset; if (change.OwnerName == Config.Username && change.ClientId == Config.Workspace) { asset = change.Shelved ? IconChangeShelvedLocal : IconChangeLocal; } else { asset = change.Shelved ? IconChangeShelvedOther : IconChangeOther; } return asset; } static void CheckInit() { if (IconHasLatest == null) { Initialize(); } } } }