using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Thrift;
using Thrift.Protocol;
using Thrift.Transport;
using Thrift.Transport.Client;
using UnityEditor;
using UnityEngine;
namespace BytesViewerTool
{
///
/// Bytes文件查看器 - 将Thrift bytes数据以表格形式展示
///
public class BytesViewerWindow : EditorWindow
{
private TextAsset selectedBytesFile;
private string currentFilePath = "";
private object parsedData;
private Vector2 scrollPosition;
private bool dataLoaded = false;
private string errorMessage = "";
// 表格显示相关
private List columnNames = new List();
private List rowDataList = new List();
private List filteredRowDataList = new List();
private Vector2 tableScrollPosition;
// 配置选择
private string[] availableConfigs = new string[0];
private string[] filteredConfigs = new string[0];
private int selectedConfigIndex = 0;
private string currentConfigName = "";
private string configSearchText = "";
private Vector2 configListScrollPosition;
private bool isConfigListExpanded = true;
// 分页相关
private int currentPage = 0;
private int pageSize = 50;
private int[] pageSizeOptions = new int[] { 20, 50, 100, 200, 500 };
private string pageInputText = "1";
// 筛选相关
private string filterText = "";
private string lastFilterText = "";
private int filterColumnIndex = 0;
private string[] filterColumnOptions = new string[0];
// 全局搜索
private string globalSearchText = "";
private List globalSearchResults = new List();
private Vector2 globalSearchScrollPosition;
// 单元格详情查看
private int selectedRowIndex = -1;
private string selectedColumnName = "";
private string selectedCellFullData = "";
private Vector2 detailScrollPosition;
private class GlobalSearchResult
{
public string ConfigName;
public int RowIndex;
public string ColumnName;
public object RowData;
public string MatchedValue;
}
[MenuItem("配置表pipeline/Bytes查看器")]
public static void ShowWindow()
{
BytesViewerWindow window = GetWindow("Bytes查看器");
window.minSize = new Vector2(1000, 600);
window.Show();
}
private void OnEnable()
{
// 窗口启动时自动查找并加载AllConfigs.bytes
AutoLoadAllConfigs();
}
private void OnGUI()
{
EditorGUILayout.Space(10);
EditorGUILayout.LabelField("Bytes文件查看器", EditorStyles.boldLabel, GUILayout.Height(24));
EditorGUILayout.Space(5);
// 文件选择区域
EditorGUILayout.BeginVertical("box");
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("选择Bytes文件:", GUILayout.Width(100));
TextAsset newFile = EditorGUILayout.ObjectField(selectedBytesFile, typeof(TextAsset), false) as TextAsset;
if (newFile != selectedBytesFile)
{
selectedBytesFile = newFile;
dataLoaded = false;
errorMessage = "";
availableConfigs = new string[0];
selectedConfigIndex = 0;
}
if (GUILayout.Button("加载并解析", GUILayout.Width(100)))
{
LoadAndParseBytes();
}
EditorGUILayout.EndHorizontal();
if (!string.IsNullOrEmpty(currentFilePath))
{
EditorGUILayout.LabelField("当前文件:", currentFilePath, EditorStyles.miniLabel);
}
EditorGUILayout.EndVertical();
EditorGUILayout.Space(5);
// 全局搜索功能
if (availableConfigs.Length > 0)
{
EditorGUILayout.BeginVertical("box");
EditorGUILayout.LabelField("🔍 全局搜索 (所有配置表)", EditorStyles.boldLabel);
EditorGUILayout.Space(3);
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("关键词:", GUILayout.Width(60));
string newGlobalSearch = EditorGUILayout.TextField(globalSearchText, GUILayout.MinWidth(300));
if (newGlobalSearch != globalSearchText)
{
globalSearchText = newGlobalSearch;
}
if (GUILayout.Button("搜索", GUILayout.Width(80)))
{
PerformGlobalSearch();
}
if (GUILayout.Button("清空", GUILayout.Width(60)))
{
globalSearchText = "";
globalSearchResults.Clear();
}
EditorGUILayout.EndHorizontal();
// 显示搜索结果
if (globalSearchResults.Count > 0)
{
EditorGUILayout.Space(3);
EditorGUILayout.LabelField($"找到 {globalSearchResults.Count} 条结果:", EditorStyles.boldLabel);
globalSearchScrollPosition = EditorGUILayout.BeginScrollView(
globalSearchScrollPosition,
GUILayout.Height(Mathf.Min(globalSearchResults.Count * 22 + 10, 150)));
for (int i = 0; i < globalSearchResults.Count; i++)
{
var result = globalSearchResults[i];
string displayText = $"{result.ConfigName} - 行{result.RowIndex} - {result.ColumnName}: {result.MatchedValue}";
if (GUILayout.Button(displayText, EditorStyles.label))
{
// 点击结果跳转到对应配置
JumpToSearchResult(result);
}
}
EditorGUILayout.EndScrollView();
}
else if (!string.IsNullOrEmpty(globalSearchText) && globalSearchResults.Count == 0)
{
EditorGUILayout.HelpBox("未找到匹配的结果", MessageType.Info);
}
EditorGUILayout.EndVertical();
EditorGUILayout.Space(5);
}
// 配置类型选择
if (availableConfigs.Length > 0)
{
EditorGUILayout.BeginVertical("box");
EditorGUILayout.BeginHorizontal();
// 折叠按钮
isConfigListExpanded = EditorGUILayout.Foldout(isConfigListExpanded, "配置类型", true, EditorStyles.foldoutHeader);
GUILayout.FlexibleSpace();
EditorGUILayout.LabelField("配置类型:", GUILayout.Width(70));
// 搜索框
string newSearchText = EditorGUILayout.TextField(configSearchText, EditorStyles.toolbarSearchField, GUILayout.Width(200));
if (newSearchText != configSearchText)
{
configSearchText = newSearchText;
FilterConfigList();
}
if (GUILayout.Button("X", GUILayout.Width(25)))
{
configSearchText = "";
FilterConfigList();
}
// 显示当前选中的配置
if (!string.IsNullOrEmpty(currentConfigName))
{
GUILayout.FlexibleSpace();
EditorGUILayout.LabelField($"当前: {currentConfigName}", EditorStyles.boldLabel, GUILayout.Width(250));
}
EditorGUILayout.LabelField($"{rowDataList.Count} 条", EditorStyles.miniLabel, GUILayout.Width(60));
EditorGUILayout.EndHorizontal();
// 配置列表(可折叠)
if (isConfigListExpanded && filteredConfigs.Length > 0)
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.Space(10);
configListScrollPosition = EditorGUILayout.BeginScrollView(
configListScrollPosition,
GUILayout.Height(Mathf.Min(filteredConfigs.Length * 22 + 10, 120)));
for (int i = 0; i < filteredConfigs.Length; i++)
{
string configName = filteredConfigs[i];
int actualIndex = Array.IndexOf(availableConfigs, configName);
bool isSelected = actualIndex == selectedConfigIndex;
GUIStyle buttonStyle = isSelected ?
new GUIStyle(EditorStyles.toolbarButton) { fontStyle = FontStyle.Bold } :
EditorStyles.toolbarButton;
if (GUILayout.Button(configName, buttonStyle, GUILayout.Height(20)))
{
if (actualIndex != selectedConfigIndex)
{
selectedConfigIndex = actualIndex;
PrepareTableDataForSelectedConfig();
Repaint();
}
}
}
EditorGUILayout.EndScrollView();
EditorGUILayout.EndHorizontal();
}
else if (!string.IsNullOrEmpty(configSearchText))
{
EditorGUILayout.HelpBox("没有匹配的配置类型", MessageType.Info);
}
EditorGUILayout.EndVertical();
EditorGUILayout.Space(5);
}
// 筛选和分页控制
if (rowDataList.Count > 0)
{
EditorGUILayout.BeginVertical("box");
EditorGUILayout.LabelField("📋 表格筛选 (仅过滤显示)", EditorStyles.boldLabel);
EditorGUILayout.Space(3);
// 筛选区域
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("选择字段:", GUILayout.Width(70));
if (filterColumnOptions.Length > 0)
{
filterColumnIndex = EditorGUILayout.Popup(filterColumnIndex, filterColumnOptions, GUILayout.Width(150));
}
EditorGUILayout.LabelField("条件:", GUILayout.Width(40));
string newFilterText = EditorGUILayout.TextField(filterText, GUILayout.MinWidth(200));
if (newFilterText != filterText)
{
filterText = newFilterText;
}
if (GUILayout.Button("筛选", GUILayout.Width(60)))
{
ApplyFilter();
}
if (GUILayout.Button("重置", GUILayout.Width(60)))
{
filterText = "";
ApplyFilter();
}
EditorGUILayout.EndHorizontal();
// 分页区域
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("每页:", GUILayout.Width(50));
int selectedPageSizeIndex = Array.IndexOf(pageSizeOptions, pageSize);
if (selectedPageSizeIndex < 0) selectedPageSizeIndex = 1;
int newPageSizeIndex = EditorGUILayout.Popup(selectedPageSizeIndex,
pageSizeOptions.Select(p => p.ToString()).ToArray(), GUILayout.Width(80));
if (newPageSizeIndex != selectedPageSizeIndex)
{
pageSize = pageSizeOptions[newPageSizeIndex];
currentPage = 0;
}
int totalPages = Mathf.CeilToInt((float)filteredRowDataList.Count / pageSize);
EditorGUILayout.LabelField($"显示 {filteredRowDataList.Count} 条 (共 {rowDataList.Count} 条)", EditorStyles.miniLabel, GUILayout.Width(150));
GUILayout.FlexibleSpace();
GUI.enabled = currentPage > 0;
if (GUILayout.Button("<<", GUILayout.Width(40)))
{
currentPage = 0;
pageInputText = "1";
}
if (GUILayout.Button("<", GUILayout.Width(40)))
{
currentPage--;
pageInputText = (currentPage + 1).ToString();
}
GUI.enabled = true;
// 页码输入框
EditorGUILayout.LabelField("第", GUILayout.Width(20));
string newPageInput = EditorGUILayout.TextField(pageInputText, GUILayout.Width(50));
if (newPageInput != pageInputText)
{
pageInputText = newPageInput;
// 尝试解析输入
if (int.TryParse(pageInputText, out int inputPage))
{
inputPage = Mathf.Clamp(inputPage, 1, Mathf.Max(1, totalPages));
currentPage = inputPage - 1;
}
}
if (GUILayout.Button("跳转", GUILayout.Width(50)))
{
if (int.TryParse(pageInputText, out int inputPage))
{
inputPage = Mathf.Clamp(inputPage, 1, Mathf.Max(1, totalPages));
currentPage = inputPage - 1;
pageInputText = (currentPage + 1).ToString();
}
}
EditorGUILayout.LabelField($"/ {Mathf.Max(1, totalPages)} 页", GUILayout.Width(60));
GUI.enabled = currentPage < totalPages - 1;
if (GUILayout.Button(">", GUILayout.Width(40)))
{
currentPage++;
pageInputText = (currentPage + 1).ToString();
}
if (GUILayout.Button(">>", GUILayout.Width(40)))
{
currentPage = Mathf.Max(0, totalPages - 1);
pageInputText = (currentPage + 1).ToString();
}
GUI.enabled = true;
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
EditorGUILayout.Space(5);
}
// 错误信息
if (!string.IsNullOrEmpty(errorMessage))
{
EditorGUILayout.HelpBox(errorMessage, MessageType.Error);
}
// 数据表格
if (dataLoaded && parsedData != null && filteredRowDataList.Count > 0)
{
DrawDataTable();
// 单元格详细信息显示
if (selectedRowIndex >= 0 && !string.IsNullOrEmpty(selectedColumnName))
{
DrawCellDetailPanel();
}
}
else if (dataLoaded && rowDataList.Count > 0 && filteredRowDataList.Count == 0)
{
EditorGUILayout.HelpBox("没有匹配筛选条件的数据", MessageType.Info);
}
else if (dataLoaded && rowDataList.Count == 0 && availableConfigs.Length == 0)
{
EditorGUILayout.HelpBox("未找到可用的配置数据", MessageType.Warning);
}
else if (!dataLoaded && selectedBytesFile == null)
{
EditorGUILayout.HelpBox("请选择一个.bytes文件进行查看\n\n推荐路径: Assets/Resources/ConfigData/AllConfigs.bytes", MessageType.Info);
}
}
private void LoadAndParseBytes()
{
if (selectedBytesFile == null)
{
errorMessage = "请先选择一个bytes文件!";
return;
}
try
{
currentFilePath = AssetDatabase.GetAssetPath(selectedBytesFile);
byte[] bytes = selectedBytesFile.bytes;
if (bytes == null || bytes.Length == 0)
{
errorMessage = "文件为空或无法读取!";
return;
}
// 解析Thrift数据
parsedData = DeserializeThriftData(bytes);
if (parsedData != null)
{
PrepareTableData();
dataLoaded = true;
errorMessage = "";
}
else
{
errorMessage = "无法解析文件数据,请确认这是一个有效的Thrift配置文件。";
}
}
catch (Exception ex)
{
errorMessage = $"解析失败: {ex.Message}\n{ex.StackTrace}";
dataLoaded = false;
}
}
private void AutoLoadAllConfigs()
{
// 如果已经加载了文件,不再自动加载
if (selectedBytesFile != null)
return;
// 在整个Assets目录下查找AllConfigs.bytes
string[] guids = AssetDatabase.FindAssets("AllConfigs t:TextAsset");
foreach (string guid in guids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
// 检查是否为.bytes文件
if (path.EndsWith(".bytes", StringComparison.OrdinalIgnoreCase))
{
TextAsset asset = AssetDatabase.LoadAssetAtPath(path);
if (asset != null)
{
selectedBytesFile = asset;
Debug.Log($"[Bytes查看器] 自动找到并加载: {path}");
LoadAndParseBytes();
return;
}
}
}
// 如果没找到AllConfigs.bytes,尝试查找任何Config相关的bytes文件
if (selectedBytesFile == null)
{
guids = AssetDatabase.FindAssets("Config t:TextAsset");
foreach (string guid in guids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
if (path.EndsWith(".bytes", StringComparison.OrdinalIgnoreCase) &&
path.Contains("Config"))
{
TextAsset asset = AssetDatabase.LoadAssetAtPath(path);
if (asset != null)
{
selectedBytesFile = asset;
Debug.Log($"[Bytes查看器] 自动找到并加载: {path}");
LoadAndParseBytes();
return;
}
}
}
}
}
private object DeserializeThriftData(byte[] bytes)
{
try
{
// 创建Thrift transport和protocol
using (var transport = new TMemoryBufferTransport(bytes, new TConfiguration()))
using (var protocol = new TCompactProtocol(transport))
{
// 尝试读取为AllConfigs
var allConfigs = new Byway.Thrift.Data.AllConfigs();
allConfigs.ReadAsync(protocol, System.Threading.CancellationToken.None).Wait();
return allConfigs;
}
}
catch (Exception ex)
{
Debug.LogError($"Thrift反序列化失败: {ex.Message}");
return null;
}
}
private void PrepareTableData()
{
columnNames.Clear();
rowDataList.Clear();
List configNames = new List();
if (parsedData == null)
return;
Type dataType = parsedData.GetType();
// 获取所有public属性
PropertyInfo[] properties = dataType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var prop in properties)
{
// 跳过__isset等内部属性
if (prop.Name.StartsWith("__"))
continue;
object propValue = prop.GetValue(parsedData);
if (propValue == null)
continue;
// 检查配置对象内部是否有字典
Type propType = propValue.GetType();
PropertyInfo[] innerProps = propType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var innerProp in innerProps)
{
if (innerProp.Name.StartsWith("__"))
continue;
object innerValue = innerProp.GetValue(propValue);
if (innerValue != null && innerValue is IDictionary dict && dict.Count > 0)
{
// 找到了包含数据的字典,记录这个配置
configNames.Add(prop.Name);
break; // 找到一个字典就够了
}
}
}
availableConfigs = configNames.ToArray();
if (availableConfigs.Length > 0)
{
selectedConfigIndex = 0;
FilterConfigList();
PrepareTableDataForSelectedConfig();
}
}
private void FilterConfigList()
{
if (string.IsNullOrEmpty(configSearchText))
{
filteredConfigs = availableConfigs;
}
else
{
string searchLower = configSearchText.ToLower();
filteredConfigs = availableConfigs
.Where(c => c.ToLower().Contains(searchLower))
.ToArray();
}
}
private void PerformGlobalSearch()
{
globalSearchResults.Clear();
if (string.IsNullOrEmpty(globalSearchText) || parsedData == null)
return;
string searchLower = globalSearchText.ToLower();
Type dataType = parsedData.GetType();
PropertyInfo[] properties = dataType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var prop in properties)
{
if (prop.Name.StartsWith("__"))
continue;
object propValue = prop.GetValue(parsedData);
if (propValue == null)
continue;
// 检查配置对象内部的字典
Type propType = propValue.GetType();
PropertyInfo[] innerProps = propType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var innerProp in innerProps)
{
if (innerProp.Name.StartsWith("__"))
continue;
object innerValue = innerProp.GetValue(propValue);
if (innerValue != null && innerValue is IDictionary dict && dict.Count > 0)
{
// 在这个配置的数据中搜索
SearchInConfig(prop.Name, dict, searchLower);
break;
}
}
}
}
private void SearchInConfig(string configName, IDictionary dataDict, string searchLower)
{
int rowIndex = 0;
foreach (DictionaryEntry entry in dataDict)
{
object rowData = entry.Value;
if (rowData == null)
{
rowIndex++;
continue;
}
Type rowType = rowData.GetType();
PropertyInfo[] props = rowType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => !p.Name.StartsWith("__"))
.ToArray();
foreach (var prop in props)
{
object value = prop.GetValue(rowData);
string valueStr = FormatValue(value).ToLower();
if (valueStr.Contains(searchLower))
{
globalSearchResults.Add(new GlobalSearchResult
{
ConfigName = configName,
RowIndex = rowIndex,
ColumnName = prop.Name,
RowData = rowData,
MatchedValue = FormatValue(value)
});
// 每个配置最多显示100个结果
if (globalSearchResults.Count >= 100)
return;
}
}
rowIndex++;
}
}
private void JumpToSearchResult(GlobalSearchResult result)
{
// 切换到对应的配置
int configIndex = Array.IndexOf(availableConfigs, result.ConfigName);
if (configIndex >= 0 && configIndex != selectedConfigIndex)
{
selectedConfigIndex = configIndex;
PrepareTableDataForSelectedConfig();
}
// 找到对应的行在filteredRowDataList中的位置
int rowIndexInFiltered = filteredRowDataList.IndexOf(result.RowData);
if (rowIndexInFiltered >= 0)
{
// 计算应该跳转到哪一页
currentPage = rowIndexInFiltered / pageSize;
pageInputText = (currentPage + 1).ToString();
// 高亮选中这一行的单元格
selectedRowIndex = rowIndexInFiltered;
selectedColumnName = result.ColumnName;
selectedCellFullData = GetFullValueText(result.RowData.GetType().GetProperty(result.ColumnName).GetValue(result.RowData));
}
Repaint();
}
private void PrepareTableDataForSelectedConfig()
{
columnNames.Clear();
rowDataList.Clear();
if (parsedData == null || availableConfigs.Length == 0)
return;
string configName = availableConfigs[selectedConfigIndex];
currentConfigName = configName;
Type dataType = parsedData.GetType();
PropertyInfo configProp = dataType.GetProperty(configName);
if (configProp == null)
return;
object configValue = configProp.GetValue(parsedData);
if (configValue == null)
return;
// 在配置对象中查找字典属性
Type configType = configValue.GetType();
PropertyInfo[] innerProps = configType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var innerProp in innerProps)
{
if (innerProp.Name.StartsWith("__"))
continue;
object innerValue = innerProp.GetValue(configValue);
if (innerValue != null && innerValue is IDictionary dict && dict.Count > 0)
{
PrepareTableForConfig(configName, dict);
return;
}
}
}
private void PrepareTableForConfig(string configName, IDictionary dataDict)
{
if (dataDict == null || dataDict.Count == 0)
return;
// 获取第一个元素来确定列结构
object firstValue = null;
foreach (DictionaryEntry entry in dataDict)
{
firstValue = entry.Value;
break;
}
if (firstValue == null)
return;
Type itemType = firstValue.GetType();
PropertyInfo[] itemProps = itemType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => !p.Name.StartsWith("__"))
.OrderBy(p => p.Name == "Id" || p.Name == "ID" ? 0 : 1) // ID放在最前面
.ToArray();
// 构建列名
foreach (var prop in itemProps)
{
columnNames.Add(prop.Name);
}
// 构建行数据
foreach (DictionaryEntry entry in dataDict)
{
rowDataList.Add(entry.Value);
}
// 初始化筛选列选项
filterColumnOptions = new string[columnNames.Count];
for (int i = 0; i < columnNames.Count; i++)
{
filterColumnOptions[i] = columnNames[i];
}
// 重置筛选和分页
filterText = "";
lastFilterText = "";
currentPage = 0;
filterColumnIndex = 0;
ApplyFilter();
}
private void ApplyFilter()
{
filteredRowDataList.Clear();
if (string.IsNullOrEmpty(filterText) || filterColumnIndex < 0 || filterColumnIndex >= columnNames.Count)
{
filteredRowDataList.AddRange(rowDataList);
lastFilterText = filterText;
currentPage = 0;
return;
}
string filterColumn = columnNames[filterColumnIndex];
string filterLower = filterText.ToLower();
foreach (var rowData in rowDataList)
{
Type rowType = rowData.GetType();
PropertyInfo prop = rowType.GetProperty(filterColumn);
if (prop != null)
{
object value = prop.GetValue(rowData);
string valueStr = FormatValue(value).ToLower();
if (valueStr.Contains(filterLower))
{
filteredRowDataList.Add(rowData);
}
}
}
lastFilterText = filterText;
currentPage = 0;
}
private void DrawDataTable()
{
EditorGUILayout.BeginVertical("box");
tableScrollPosition = EditorGUILayout.BeginScrollView(tableScrollPosition);
// 绘制表头
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
foreach (string columnName in columnNames)
{
EditorGUILayout.LabelField(columnName, EditorStyles.toolbarButton, GUILayout.MinWidth(80), GUILayout.MaxWidth(250));
}
EditorGUILayout.EndHorizontal();
// 计算当前页的数据范围
int startIndex = currentPage * pageSize;
int endIndex = Mathf.Min(startIndex + pageSize, filteredRowDataList.Count);
// 绘制当前页的数据行
for (int i = startIndex; i < endIndex; i++)
{
Color backgroundColor = (i - startIndex) % 2 == 0 ? new Color(0.85f, 0.85f, 0.85f, 0.3f) : Color.clear;
EditorGUILayout.BeginHorizontal();
Rect rowRect = EditorGUILayout.BeginVertical();
if (backgroundColor != Color.clear)
{
EditorGUI.DrawRect(rowRect, backgroundColor);
}
DrawRowData(filteredRowDataList[i], i);
EditorGUILayout.EndVertical();
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndScrollView();
EditorGUILayout.EndVertical();
}
private void DrawRowData(object rowData, int rowIndex)
{
if (rowData == null)
return;
EditorGUILayout.BeginHorizontal();
Type rowType = rowData.GetType();
PropertyInfo[] props = rowType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => !p.Name.StartsWith("__"))
.OrderBy(p => p.Name == "Id" || p.Name == "ID" ? 0 : 1)
.ToArray();
foreach (var prop in props)
{
object value = prop.GetValue(rowData);
string displayValue = FormatValue(value);
GUIStyle style = new GUIStyle(EditorStyles.label);
style.wordWrap = false;
style.clipping = TextClipping.Clip;
// 高亮选中的单元格
if (selectedRowIndex == rowIndex && selectedColumnName == prop.Name)
{
style.normal.textColor = new Color(0.2f, 0.5f, 1f);
style.fontStyle = FontStyle.Bold;
}
Rect cellRect = GUILayoutUtility.GetRect(new GUIContent(displayValue), style,
GUILayout.MinWidth(80), GUILayout.MaxWidth(250));
// 检测点击
if (Event.current.type == EventType.MouseDown && cellRect.Contains(Event.current.mousePosition))
{
selectedRowIndex = rowIndex;
selectedColumnName = prop.Name;
selectedCellFullData = GetFullValueText(value);
Event.current.Use();
Repaint();
}
GUI.Label(cellRect, displayValue, style);
}
EditorGUILayout.EndHorizontal();
}
private void DrawCellDetailPanel()
{
EditorGUILayout.Space(5);
EditorGUILayout.BeginVertical("box");
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("单元格详情", EditorStyles.boldLabel);
if (GUILayout.Button("关闭", GUILayout.Width(60)))
{
selectedRowIndex = -1;
selectedColumnName = "";
selectedCellFullData = "";
}
if (GUILayout.Button("复制", GUILayout.Width(60)))
{
EditorGUIUtility.systemCopyBuffer = selectedCellFullData;
Debug.Log($"已复制到剪贴板: {selectedCellFullData}");
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.LabelField($"列: {selectedColumnName}", EditorStyles.miniLabel);
EditorGUILayout.LabelField($"行: {selectedRowIndex}", EditorStyles.miniLabel);
EditorGUILayout.Space(3);
// 显示完整内容
detailScrollPosition = EditorGUILayout.BeginScrollView(detailScrollPosition, GUILayout.Height(100));
EditorGUILayout.SelectableLabel(selectedCellFullData, EditorStyles.textArea, GUILayout.ExpandHeight(true));
EditorGUILayout.EndScrollView();
EditorGUILayout.EndVertical();
}
private string GetFullValueText(object value)
{
if (value == null)
return "null";
// 处理列表
if (value is IList list)
{
var items = new System.Text.StringBuilder();
items.AppendLine($"List ({list.Count} items):");
int index = 0;
foreach (var item in list)
{
items.AppendLine($" [{index}]: {FormatValue(item)}");
index++;
if (index > 100) // 限制显示数量
{
items.AppendLine($" ... and {list.Count - 100} more items");
break;
}
}
return items.ToString();
}
// 处理字典
if (value is IDictionary dict)
{
var items = new System.Text.StringBuilder();
items.AppendLine($"Dictionary ({dict.Count} pairs):");
int index = 0;
foreach (DictionaryEntry entry in dict)
{
items.AppendLine($" [{FormatValue(entry.Key)}]: {FormatValue(entry.Value)}");
index++;
if (index > 100)
{
items.AppendLine($" ... and {dict.Count - 100} more pairs");
break;
}
}
return items.ToString();
}
// 处理基本类型
Type valueType = value.GetType();
if (valueType.IsPrimitive || valueType == typeof(string) || valueType == typeof(decimal))
{
return value.ToString();
}
// 复杂对象 - 显示所有属性
var props = valueType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => !p.Name.StartsWith("__"));
var sb = new System.Text.StringBuilder();
sb.AppendLine($"{valueType.Name}:");
foreach (var prop in props)
{
try
{
object propValue = prop.GetValue(value);
sb.AppendLine($" {prop.Name}: {FormatValue(propValue)}");
}
catch
{
sb.AppendLine($" {prop.Name}: [Error reading value]");
}
}
return sb.ToString();
}
private string FormatValue(object value)
{
if (value == null)
return "null";
// 处理列表/数组
if (value is IList list)
{
return $"[{list.Count} items]";
}
// 处理字典
if (value is IDictionary dict)
{
return $"[{dict.Count} pairs]";
}
// 处理基本类型
Type valueType = value.GetType();
if (valueType.IsPrimitive || valueType == typeof(string) || valueType == typeof(decimal))
{
return value.ToString();
}
// 复杂对象显示类型名
return $"[{valueType.Name}]";
}
}
}