using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using TyphoonGUIStyle; using TyphoonIniConfig; using UnityEditor; using UnityEngine; namespace TyphoonUI { /// /// UI资源包管理 /// public class UIPackageWindow : EditorWindow { private const string UI_PACKAGE_SELECT_HISTORY = "UI_PACKAGE_SELECT_HISTORY"; private const string LAST_UI_PACKAGE_CREATE_FOLDER = "LAST_UI_PACKAGE_CREATE_FOLDER"; private static GUIStyle _tipsStyle; private static GUIStyle TipsStyle { get { if (_tipsStyle == null) { _tipsStyle = new GUIStyle(Styles.HelpBox); _tipsStyle.richText = true; } return _tipsStyle; } } private static GUIStyle _styleLabelAssetInfo = null; private static GUIStyle StyleLabelAssetInfo { get { if (_styleLabelAssetInfo == null) { _styleLabelAssetInfo = new GUIStyle("label"); _styleLabelAssetInfo.fontSize = 11; _styleLabelAssetInfo.richText = true; _styleLabelAssetInfo.padding = new RectOffset(6, 0, 0, 0); _styleLabelAssetInfo.margin = new RectOffset(0, 0, 0, 0); _styleLabelAssetInfo.border = new RectOffset(0, 0, 0, 0); _styleLabelAssetInfo.overflow = new RectOffset(0, 0, 0, 0); _styleLabelAssetInfo.alignment = TextAnchor.MiddleLeft; // _styleLabelAssetInfo.normal.textColor = // EditorGUIUtility.isProSkin // ? new Color(0.43f, 0.43f, 0.43f, 1f) // : new Color(0.40F, 0.40F, 0.40F, 1f); } return _styleLabelAssetInfo; } } [MenuItem("TyphoonUI/管理 UIPackage", priority = 3000)] public static void Open() { var win = GetWindow(); win.titleContent = new GUIContent("管理 UIPackage"); win.minSize = new Vector2(1100, 600); win.Show(); } private const float MENU_WIDTH = 266; private const float MIDDLE_WIDTH = 460; private static List _packages = new List(); private static UIPackage _selectPackage = null; private static Vector2 _scroll; private static Vector2 _scrollMiddle; private static Vector2 _scrollRight; private static Rect _assetItemScrollRect; private static Dictionary _assetItemRectInfo = new Dictionary(); private static HashSet _selectItems = new HashSet(); private static int _lastSelectObjectIndex = -1; private static bool _triggerRightClick; private static int _triggerRightClickItemIndex; private static float _assetItemViewMaxWidth; private static string _intputSearch; private static INIConfig _config; private static INIConfig Config { get { if (_config == null) { _config = new INIConfig(".typhoon/typhoonui/config.ini"); } return _config; } } private Rect _dropArea; private Rect _assetItemViewCheckArea; private bool _isDragIn; private void OnEnable() { LoadPackages(); SelectHistory(); ClearDropInput(); ClearCacheSelectItems(); } private void OnFocus() { LoadPackages(); SelectHistory(); } private void OnGUI() { HandleEvent(); //左侧菜单 var area = new Rect(Vector2.zero, position.size); var leftLayoutArea = area; leftLayoutArea.width = MENU_WIDTH; DrawLeftLayoutGUI(leftLayoutArea); //分割线 var line = leftLayoutArea; line.width = 1; line.x = leftLayoutArea.xMax; var center = line.center; var draw = line; draw.height -= 8; draw.center = center; EditorGUI.DrawRect(draw, Color.black); var middleLayoutRect = line; middleLayoutRect.x = line.xMax; middleLayoutRect.width = MIDDLE_WIDTH; DrawMiddleLayoutGUI(middleLayoutRect); //分割线 line = middleLayoutRect; line.width = 1; line.x = middleLayoutRect.xMax; center = line.center; draw = line; draw.height -= 8; draw.center = center; EditorGUI.DrawRect(draw, Color.black); var rightLayoutArea = line; rightLayoutArea.x = line.xMax; rightLayoutArea.width = area.xMax - rightLayoutArea.x; DrawRightLayoutGUI(rightLayoutArea); DrawEventInputGUI(); Repaint(); } //左侧布局GUI private void DrawLeftLayoutGUI(Rect area) { var contentArea = CreateArea(area, new RectOffset(4, 4, 4, 4)); GUILayout.BeginArea(contentArea); GUILayout.Label("UIPackage", Styles.TitleBar, GUILayout.Height(28)); var rect = GUILayoutUtility.GetLastRect(); var btnUpdateRect = rect; btnUpdateRect.height -= 8; btnUpdateRect.width = 120; btnUpdateRect.center = rect.center; btnUpdateRect.x = rect.xMax - btnUpdateRect.width; btnUpdateRect.x -= 4; if (GUI.Button(btnUpdateRect, "一键更新资源", Styles.BtnBlue)) { TyphoonUIEditor.UpdatePackages(); } if (GUILayout.Button("+新建 UIPackage", Styles.BtnGreen, GUILayout.Height(32))) { var last = Config.GetString(LAST_UI_PACKAGE_CREATE_FOLDER); //弹出右键菜单,获取包名 var path = EditorUtility.SaveFilePanel("请选择创建位置", last, "untitle", "asset"); if (!string.IsNullOrEmpty(path)) { var file = Path.GetFullPath(path).Replace("/", "\\"); var projRoot = Path.GetFullPath(Application.dataPath).Replace("/", "\\"); if (!file.StartsWith(projRoot)) { TyphoonUIEditor.ShowMessageBox($"{path} 不是一个有效目录,只支持工程目录下创建"); } else if (File.Exists(path)) { TyphoonUIEditor.ShowMessageBox($"文件 {path} 已存在,无法创建"); } else { var savePath = TyphoonUIEditor.PathToUnityPath(file); var package = CreateInstance(); AssetDatabase.CreateAsset(package, savePath); AssetDatabase.Refresh(); TyphoonUIEditor.PingObject(package, false); Debug.Log($"创建成功:{savePath}"); //选中该包 LoadPackages(); ClearDropInput(); ClearCacheSelectItems(); SelectPackage(package); } } } _scroll = GUILayout.BeginScrollView(_scroll); foreach (var package in _packages) { var icon = AssetDatabase.GetCachedIcon(AssetDatabase.GetAssetPath(package)); var selected = _selectPackage == package; if (GUILayout.Button(new GUIContent(package.name, icon), (selected ? Styles.MenuButtonActive : Styles.MenuButton), GUILayout.Height(44))) { if (selected) { TyphoonUIEditor.PingObject(package); } SelectPackage(package); } if (!string.IsNullOrEmpty(package.ErrorString)) { var lastRect = GUILayoutUtility.GetLastRect(); var drawRect = lastRect; drawRect.width = 24; drawRect.height = 24; drawRect.center = lastRect.center; drawRect.x = lastRect.xMax - drawRect.width; drawRect.x -= 4; GUI.DrawTexture(drawRect, Styles.IconError); } } GUILayout.EndScrollView(); GUILayout.EndArea(); } private void DrawMiddleLayoutGUI(Rect area) { var contentArea = CreateArea(area, new RectOffset(4, 4, 4, 4)); if (_selectPackage == null) { GUILayout.BeginArea(contentArea); GUILayout.Label("未选中", Styles.TitleBar, GUILayout.Height(28)); //请选择 GUILayout.Label(new GUIContent("请 创建/选择 一个资源包", Styles.IconInfo), TipsStyle); GUILayout.EndArea(); } else { var topArea = contentArea; topArea.height = 72; var bottomArea = contentArea; bottomArea.y = topArea.yMax; bottomArea.height = contentArea.yMax - bottomArea.y; GUILayout.BeginArea(topArea); var icon = AssetDatabase.GetCachedIcon(AssetDatabase.GetAssetPath(_selectPackage)); //绘制资源包清单 GUILayout.Label(new GUIContent(_selectPackage.name, icon), Styles.TitleBar, GUILayout.Height(28)); var last = GUILayoutUtility.GetLastRect(); var btnUpdate = last; btnUpdate.width = 80; btnUpdate.height = 24; btnUpdate.center = last.center; btnUpdate.x = last.xMax - btnUpdate.width; btnUpdate.x -= 4; if (GUI.Button(btnUpdate, "更新资源", Styles.BtnGreen)) { UIPackageEditor.UpdatePackage(_selectPackage); } var btnTitle = last; btnTitle.width = btnUpdate.xMin - last.x; if (GUI.Button(btnTitle, "", GUIStyle.none)) { TyphoonUIEditor.PingObject(_selectPackage); } GUILayout.Label(new GUIContent("拖拽资源进行添加", Styles.IconInfo), Styles.HelpBox, GUILayout.Height(40)); GUILayout.EndArea(); _dropArea = bottomArea; _assetItemScrollRect = bottomArea; _assetItemViewCheckArea = _dropArea; _assetItemViewCheckArea.width -= 22; _assetItemViewCheckArea.height -= 22; var padding = new RectOffset(2, 20, 3, 20); var viewRect = new Rect(); viewRect.height = padding.vertical + _selectPackage.ObjectsOfPacking.Count * 18; viewRect.width = Mathf.Max(_assetItemViewMaxWidth, _assetItemScrollRect.width); _scrollMiddle = GUI.BeginScrollView(_assetItemScrollRect, _scrollMiddle, viewRect); // //绘制资源 var objects = _selectPackage.ObjectsOfPacking; var index = 0; foreach (var element in objects) { var itemRect = new Rect(); itemRect.position = Vector2.zero; itemRect.width = viewRect.width; itemRect.width -= padding.horizontal; itemRect.x = viewRect.x + padding.left; itemRect.height = 18; itemRect.y = index * 18 + padding.top; if (index % 2 == 0) { EditorGUI.DrawRect(itemRect, Color.black * 0.05f); } var selected = _selectItems.Contains(index); if (selected) { EditorGUI.DrawRect(itemRect, Styles.ColorSelected); } if (element == null) { GUI.Button(itemRect, new GUIContent("(missing)", Styles.IconWarning), StyleLabelAssetInfo); } else { var path = AssetDatabase.GetAssetPath(element); var elementIcon = AssetDatabase.GetCachedIcon(path); var guiContent = new GUIContent(path, elementIcon); var wid = StyleLabelAssetInfo.CalcSize(guiContent).x; _assetItemViewMaxWidth = Mathf.Max(_assetItemViewMaxWidth, wid); GUI.Button(itemRect, guiContent, StyleLabelAssetInfo); } if (itemRect.Contains(Event.current.mousePosition)) { var mark = itemRect; mark.width = 2; mark.x -= 2; EditorGUI.DrawRect(mark, Styles.ColorSelected); } if (Event.current.type == EventType.Repaint) { var drawRect = itemRect; drawRect.position += _assetItemScrollRect.position; drawRect.position -= _scrollMiddle; _assetItemRectInfo[index] = drawRect; } index += 1; } GUI.EndScrollView(); } } //右侧布局GUI private void DrawRightLayoutGUI(Rect area) { var contentArea = CreateArea(area, new RectOffset(4, 4, 4, 4)); GUILayout.BeginArea(contentArea); GUILayout.Label("资源预览", Styles.TitleBar, GUILayout.Height(28)); _intputSearch = EditorGUILayout.TextField(_intputSearch); var searchCheck = _intputSearch == null ? "" : _intputSearch.ToLower(); _scrollRight = GUILayout.BeginScrollView(_scrollRight); string previewPath = null; var names = new HashSet(); var errorCount = 0; //绘制导出项 if (_selectPackage != null) { var components = _selectPackage.Components; if (!string.IsNullOrEmpty(searchCheck)) { components = _selectPackage.Components.Where(e => e.name.ToLower().Contains(searchCheck)) .ToList(); } var index = 0; foreach (var item in components) { if (item == null) { continue; } var check = names.Add(item.name); var path = AssetDatabase.GetAssetPath(item); var icon = AssetDatabase.GetCachedIcon(path); GUILayout.BeginHorizontal(); var temEnable = GUI.enabled; GUI.enabled = false; GUILayout.Label("", GUILayout.Height(16)); var rect = GUILayoutUtility.GetLastRect(); if (index % 2 == 0) { EditorGUI.DrawRect(rect, Color.black * 0.05f); } var noArea = rect; noArea.width = 32; var itemArea = rect; itemArea.width = rect.width - noArea.width; itemArea.x = noArea.xMax; GUI.Label(noArea, (index + 1).ToString(), StyleLabelAssetInfo); GUI.Label(itemArea, new GUIContent(path, icon), StyleLabelAssetInfo); GUI.enabled = temEnable; if (GUI.Button(rect, "", GUIStyle.none)) { Selection.activeGameObject = item; TyphoonUIEditor.PingObject(item); } if (!check) { errorCount += 1; var error = rect; error.width = error.height; error.x = rect.xMax - error.width; error.x -= 22; GUI.Label(error, new GUIContent(Styles.IconError, "资源名重复")); } GUILayout.EndHorizontal(); index += 1; } GUILayout.Space(10); } GUILayout.EndScrollView(); GUILayout.Space(3); if (errorCount > 0) { GUILayout.Label(new GUIContent($"发现{errorCount}项同名资源,请处理", Styles.IconError), Styles.HelpBox); } GUILayout.Space(3); GUILayout.EndArea(); } private void DrawEventInputGUI() { if (_selectPackage != null && _isDragIn && _dropArea.Contains(Event.current.mousePosition)) { DragAndDrop.visualMode = DragAndDropVisualMode.Link; DrawDragInTips(_dropArea); } } private void DrawDragInTips(Rect rect) { var labelRect = rect; labelRect.height = 24; EditorGUI.DrawRect(labelRect, Styles.ColorSelected); EditorGUI.DrawRect(rect, Styles.ColorSelected * 0.5F); GUI.Label(rect, new GUIContent("+添加项"), Styles.BoldLabel); } //触发事件 private void HandleEvent() { TryPopUpAssetItemContextMenu(); if (Event.current.type == EventType.MouseDown) { if (_selectPackage != null && _assetItemViewCheckArea.Contains(Event.current.mousePosition)) { //左键按下 if (Event.current.button == 0) { if (TryGetClickPackageObjectItem(Event.current.mousePosition, out var index)) { var objectsOfPacking = _selectPackage.ObjectsOfPacking; switch (Event.current.modifiers) { case EventModifiers.Control: { if (!_selectItems.Add(index)) { _selectItems.Remove(index); } } break; case EventModifiers.Shift: { if (_lastSelectObjectIndex >= 0 && _lastSelectObjectIndex < objectsOfPacking.Count) { _selectItems = new HashSet(); var from = _lastSelectObjectIndex; var end = index; if (from > end) { from = index; end = _lastSelectObjectIndex; } for (int i = from; i <= end; i++) { _selectItems.Add(i); } } else { _selectItems = new HashSet() { index }; } } break; default: { _selectItems = new HashSet() { index }; } break; } var item = objectsOfPacking[index]; if (item != null && Event.current.clickCount >= 2) { TyphoonUIEditor.PingObject(item); } _lastSelectObjectIndex = index; } else { _selectItems.Clear(); } Event.current.Use(); } //右键按下 if (Event.current.button == 1) { if (TryGetClickPackageObjectItem(Event.current.mousePosition, out var index)) { if (_selectItems.Add(index)) { _selectItems = new HashSet { index }; _lastSelectObjectIndex = index; } _triggerRightClick = true; _triggerRightClickItemIndex = index; } Event.current.Use(); } } } if (Event.current.type == EventType.DragUpdated || Event.current.type == EventType.DragPerform) { _isDragIn = true; } if (Event.current.type == EventType.DragExited) { var mousePosition = Event.current.mousePosition; if (_selectPackage != null && _dropArea.Contains(mousePosition)) { //添加资源项 var refs = DragAndDrop.objectReferences; //TODO//筛选文件夹资源和预制物资源 foreach (var @ref in refs) { if (_selectPackage.ObjectsOfPacking.Contains(@ref)) { continue; } _selectPackage.ObjectsOfPacking.Add(@ref); } _selectPackage.SaveAssetWithDirty(); } _isDragIn = false; Event.current.Use(); } if (Event.current.type == EventType.KeyDown) { switch (Event.current.keyCode) { case KeyCode.Delete: if (_selectItems.Count > 0 && _selectPackage != null) { var sb = new StringBuilder(); var objectsOfPacking = _selectPackage.ObjectsOfPacking; var matches = objectsOfPacking.Where((e, idx) => _selectItems.Contains(idx)); sb.AppendLine($"移除选中项?无法撤销! 共:{matches.Count()}项 "); var count = 0; foreach (var match in matches) { sb.AppendLine(match == null ? "(missing)" : AssetDatabase.GetAssetPath(match)); if (count > 5) { sb.AppendLine("..."); break; } count += 1; } if (EditorUtility.DisplayDialog("提示", sb.ToString(), "是")) { _selectPackage.ObjectsOfPacking = objectsOfPacking .Where((e, idx) => !_selectItems.Contains(idx)).ToList(); ClearCacheSelectItems(); _selectPackage.SaveAssetWithDirty(); } } Event.current.Use(); break; } } } private void TryPopUpAssetItemContextMenu() { if (!_triggerRightClick) { return; } _triggerRightClick = false; var index = _triggerRightClickItemIndex; var objectsOfPacking = _selectPackage.ObjectsOfPacking; var item = objectsOfPacking[index]; var menu = new GenericMenu(); GenericMenu.MenuFunction copyPathFunc = null; GenericMenu.MenuFunction pingFunc = null; if (item != null) { copyPathFunc = () => { var path = AssetDatabase.GetAssetPath(item); EditorGUIUtility.systemCopyBuffer = path; Debug.Log($"复制:{path}"); }; pingFunc = () => { TyphoonUIEditor.PingObject(item); }; } menu.AddItem(new GUIContent("复制路径"), false, copyPathFunc); menu.AddItem(new GUIContent("Ping"), false, pingFunc); menu.AddItem(new GUIContent($"移除"), false, () => { var remove = _selectItems.ToList(); _selectPackage.ObjectsOfPacking = _selectPackage.ObjectsOfPacking .Where((e, idx) => !remove.Contains(idx)).ToList(); _selectItems.Clear(); _selectPackage.SaveAssetWithDirty(); }); menu.ShowAsContext(); } private bool TryGetClickPackageObjectItem(Vector2 mousePos, out int matchIndex) { var match = _assetItemRectInfo.Where(e => e.Value.Contains(mousePos) && e.Key >= 0 && e.Key < _selectPackage.ObjectsOfPacking.Count).ToList(); matchIndex = match.Count > 0 ? match.First().Key : -1; return matchIndex >= 0; } private void ClearDropInput() { _isDragIn = false; } private void ClearCacheSelectItems() { _selectItems.Clear(); _assetItemRectInfo.Clear(); _lastSelectObjectIndex = -1; } private void SelectPackage(UIPackage package) { Config.Set(UI_PACKAGE_SELECT_HISTORY, package.name); _selectPackage = package; ClearCacheSelectItems(); } private void LoadPackages() { _packages.Clear(); var guids = AssetDatabase.FindAssets($"t:{typeof(UIPackage).FullName}", new[] { "Assets" }); foreach (var guid in guids) { var path = AssetDatabase.GUIDToAssetPath(guid); var package = AssetDatabase.LoadAssetAtPath(path); if (package != null) { _packages.Add(package); } } } private void SelectHistory() { var history = Config.GetString(UI_PACKAGE_SELECT_HISTORY); var match = _packages.FirstOrDefault(e => e.name == history); if (match != null) { SelectPackage(match); } } private Rect CreateArea(Rect area, RectOffset padding) { var result = area; result.width -= padding.horizontal; result.height -= padding.vertical; result.x = area.x + padding.left; result.y = area.y + padding.top; return result; } private static void CreateUIPackage(string path) { var package = CreateInstance(); } } }