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();
}
}
}