1048 lines
39 KiB
C#
1048 lines
39 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using AibisDream.Utility;
|
||
using Newtonsoft.Json.Linq;
|
||
using UnityEditor;
|
||
using UnityEngine;
|
||
|
||
namespace AibisDream.SystemEditor
|
||
{
|
||
public class JsonEditWindow : EditorWindow
|
||
{
|
||
// === 布局变量(可调整) ===
|
||
private float _fileListWidth = 220f;
|
||
private float _keyListWidth = 180f;
|
||
private bool _isDraggingFileSplitter = false;
|
||
private bool _isDraggingKeySplitter = false;
|
||
|
||
// === 核心数据 ===
|
||
private string _rootPath = "Assets/StreamingAssets";
|
||
private List<string> _jsonFileList = new List<string>();
|
||
private string _selectedFile;
|
||
private JObject _jsonData;
|
||
private JArray _jsonArray; // Array格式的JSON数据
|
||
private bool _isArrayFormat = false; // 标识当前是否为Array格式
|
||
private List<string> _firstLevelKeys = new List<string>();
|
||
private string _selectedKey;
|
||
private bool _editModeTree = true; // true=树形编辑, false=文本编辑
|
||
|
||
// === 状态 ===
|
||
private Vector2 _scrollPosFiles;
|
||
private Vector2 _scrollPosKeys;
|
||
private Vector2 _scrollPosEditor;
|
||
private string _searchString = "";
|
||
private string _newFileName = "";
|
||
private string _jsonTextContent = "";
|
||
private string _validationError = "";
|
||
private Dictionary<string, float> _keyPositions = new Dictionary<string, float>();
|
||
private Dictionary<string, bool> _expandedNodes = new Dictionary<string, bool>();
|
||
private bool _showDeleteButton = false; // 是否显示JSON编辑器中的删除按钮(默认隐藏)
|
||
|
||
[MenuItem("Tools/JSON编辑器 (Json Editor)")]
|
||
public static void ShowWindow()
|
||
{
|
||
GetWindow<JsonEditWindow>("JSON Editor");
|
||
}
|
||
|
||
private void OnEnable()
|
||
{
|
||
_rootPath = EditorPrefs.GetString("JsonEditWindow_RootPath", "Assets/StreamingAssets");
|
||
_selectedFile = EditorPrefs.GetString("JsonEditWindow_SelectedFile", "");
|
||
_showDeleteButton = EditorPrefs.GetBool("JsonEditWindow_ShowDeleteButton", false);
|
||
_fileListWidth = EditorPrefs.GetFloat("JsonEditWindow_FileListWidth", 220f);
|
||
_keyListWidth = EditorPrefs.GetFloat("JsonEditWindow_KeyListWidth", 180f);
|
||
RefreshFileList();
|
||
if (!string.IsNullOrEmpty(_selectedFile) && File.Exists(_selectedFile))
|
||
{
|
||
LoadJsonFile(_selectedFile);
|
||
}
|
||
}
|
||
|
||
private void OnDisable()
|
||
{
|
||
EditorPrefs.SetString("JsonEditWindow_RootPath", _rootPath);
|
||
EditorPrefs.SetString("JsonEditWindow_SelectedFile", _selectedFile ?? "");
|
||
EditorPrefs.SetBool("JsonEditWindow_ShowDeleteButton", _showDeleteButton);
|
||
EditorPrefs.SetFloat("JsonEditWindow_FileListWidth", _fileListWidth);
|
||
EditorPrefs.SetFloat("JsonEditWindow_KeyListWidth", _keyListWidth);
|
||
}
|
||
|
||
private void OnGUI()
|
||
{
|
||
DrawTopBar();
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
{
|
||
DrawFileList();
|
||
DrawSplitter(ref _fileListWidth, ref _isDraggingFileSplitter);
|
||
DrawKeyList();
|
||
DrawSplitter(ref _keyListWidth, ref _isDraggingKeySplitter);
|
||
DrawEditor();
|
||
}
|
||
EditorGUILayout.EndHorizontal();
|
||
}
|
||
|
||
// ------------------------------------------------------------------------
|
||
// UI 绘制函数
|
||
// ------------------------------------------------------------------------
|
||
|
||
private void DrawTopBar()
|
||
{
|
||
EditorGUILayout.BeginHorizontal("box", GUILayout.Height(30));
|
||
{
|
||
GUILayout.Label("资源路径:", GUILayout.Width(60));
|
||
_rootPath = EditorGUILayout.TextField(_rootPath, GUILayout.Width(200));
|
||
if (GUILayout.Button("...", EditorStyles.miniButton, GUILayout.Width(25)))
|
||
{
|
||
string path = EditorUtility.OpenFolderPanel("选择根目录", Application.dataPath, "");
|
||
if (!string.IsNullOrEmpty(path) && path.StartsWith(Application.dataPath))
|
||
{
|
||
_rootPath = "Assets" + path.Substring(Application.dataPath.Length);
|
||
RefreshFileList();
|
||
}
|
||
}
|
||
|
||
GUILayout.Space(20);
|
||
|
||
if (GUILayout.Button("刷新", EditorStyles.toolbarButton, GUILayout.Width(60)))
|
||
{
|
||
AssetDatabase.Refresh();
|
||
RefreshFileList();
|
||
}
|
||
|
||
if (GUILayout.Button("验证", EditorStyles.toolbarButton, GUILayout.Width(60)))
|
||
{
|
||
ValidateJson();
|
||
}
|
||
|
||
if (GUILayout.Button("格式化", EditorStyles.toolbarButton, GUILayout.Width(60)))
|
||
{
|
||
FormatJson();
|
||
}
|
||
|
||
if (!string.IsNullOrEmpty(_validationError))
|
||
{
|
||
GUI.color = Color.red;
|
||
GUILayout.Label(_validationError, EditorStyles.miniLabel);
|
||
GUI.color = Color.white;
|
||
}
|
||
|
||
GUILayout.FlexibleSpace();
|
||
}
|
||
EditorGUILayout.EndHorizontal();
|
||
}
|
||
|
||
private void DrawSplitter(ref float width, ref bool isDragging)
|
||
{
|
||
Rect rect = GUILayoutUtility.GetRect(5f, EditorGUIUtility.singleLineHeight, GUILayout.Width(5f), GUILayout.ExpandHeight(true));
|
||
|
||
// 绘制分割条
|
||
Color splitterColor = EditorGUIUtility.isProSkin ? new Color(0.3f, 0.3f, 0.3f) : new Color(0.6f, 0.6f, 0.6f);
|
||
EditorGUI.DrawRect(rect, splitterColor);
|
||
|
||
// 检测鼠标事件
|
||
Event e = Event.current;
|
||
int controlID = GUIUtility.GetControlID(FocusType.Passive);
|
||
|
||
switch (e.type)
|
||
{
|
||
case EventType.MouseDown:
|
||
if (rect.Contains(e.mousePosition))
|
||
{
|
||
isDragging = true;
|
||
GUIUtility.hotControl = controlID;
|
||
e.Use();
|
||
}
|
||
break;
|
||
|
||
case EventType.MouseDrag:
|
||
if (isDragging && GUIUtility.hotControl == controlID)
|
||
{
|
||
float delta = e.delta.x;
|
||
width = Mathf.Clamp(width + delta, 100f, 500f);
|
||
e.Use();
|
||
Repaint();
|
||
}
|
||
break;
|
||
|
||
case EventType.MouseUp:
|
||
if (isDragging)
|
||
{
|
||
isDragging = false;
|
||
GUIUtility.hotControl = 0;
|
||
e.Use();
|
||
}
|
||
break;
|
||
}
|
||
|
||
// 改变鼠标光标
|
||
if (rect.Contains(e.mousePosition) || isDragging)
|
||
{
|
||
EditorGUIUtility.AddCursorRect(rect, MouseCursor.ResizeHorizontal);
|
||
}
|
||
}
|
||
|
||
private void DrawFileList()
|
||
{
|
||
EditorGUILayout.BeginVertical("box", GUILayout.Width(_fileListWidth), GUILayout.ExpandHeight(true));
|
||
|
||
GUILayout.Label("文件列表", EditorStyles.boldLabel);
|
||
GUILayout.Space(5);
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
{
|
||
GUILayout.Label("搜索:", GUILayout.Width(40));
|
||
string newSearch = EditorGUILayout.TextField(_searchString, EditorStyles.toolbarSearchField);
|
||
if (newSearch != _searchString)
|
||
{
|
||
_searchString = newSearch;
|
||
RefreshFileList();
|
||
}
|
||
}
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
GUILayout.Space(5);
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
{
|
||
GUILayout.Label("新文件:", GUILayout.Width(50));
|
||
_newFileName = EditorGUILayout.TextField(_newFileName, GUILayout.Width(100));
|
||
if (GUILayout.Button("创建", EditorStyles.miniButton, GUILayout.Width(50)))
|
||
{
|
||
CreateJsonFile();
|
||
}
|
||
}
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
GUILayout.Space(5);
|
||
|
||
_scrollPosFiles = EditorGUILayout.BeginScrollView(_scrollPosFiles);
|
||
|
||
if (_jsonFileList != null && _jsonFileList.Count > 0)
|
||
{
|
||
foreach (var filePath in _jsonFileList)
|
||
{
|
||
if (filePath == null) continue;
|
||
string fileName = Path.GetFileName(filePath);
|
||
bool isSelected = _selectedFile == filePath;
|
||
|
||
EditorGUILayout.BeginHorizontal("box");
|
||
{
|
||
if (isSelected) GUI.backgroundColor = new Color(0.6f, 0.8f, 1f);
|
||
|
||
if (GUILayout.Button(fileName, EditorStyles.label, GUILayout.Height(22)))
|
||
{
|
||
SelectFile(filePath);
|
||
}
|
||
|
||
GUI.backgroundColor = Color.white;
|
||
|
||
// 定位按钮
|
||
if (GUILayout.Button(EditorGUIUtility.IconContent("d_ViewToolOrbit"), EditorStyles.iconButton, GUILayout.Width(22)))
|
||
{
|
||
string assetPath = filePath.Replace(Application.dataPath, "Assets").Replace("\\", "/");
|
||
var asset = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(assetPath);
|
||
if (asset != null)
|
||
EditorGUIUtility.PingObject(asset);
|
||
}
|
||
|
||
// 删除按钮
|
||
if (GUILayout.Button(EditorGUIUtility.IconContent("TreeEditor.Trash"), EditorStyles.iconButton, GUILayout.Width(22)))
|
||
{
|
||
if (EditorUtility.DisplayDialog("删除确认", $"确定删除 {fileName} 吗?", "删除", "取消"))
|
||
{
|
||
DeleteFile(filePath);
|
||
GUIUtility.ExitGUI();
|
||
}
|
||
}
|
||
}
|
||
EditorGUILayout.EndHorizontal();
|
||
}
|
||
}
|
||
else
|
||
{
|
||
GUILayout.Label("没有找到JSON文件", EditorStyles.centeredGreyMiniLabel);
|
||
}
|
||
|
||
EditorGUILayout.EndScrollView();
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
private void DrawKeyList()
|
||
{
|
||
EditorGUILayout.BeginVertical("box", GUILayout.Width(_keyListWidth), GUILayout.ExpandHeight(true));
|
||
|
||
GUILayout.Label("第一层Key", EditorStyles.boldLabel);
|
||
GUILayout.Space(5);
|
||
|
||
_scrollPosKeys = EditorGUILayout.BeginScrollView(_scrollPosKeys);
|
||
|
||
if (_isArrayFormat)
|
||
{
|
||
// Array格式,Key栏置空
|
||
GUILayout.FlexibleSpace();
|
||
GUILayout.Label("Array格式\n无Key列表", EditorStyles.centeredGreyMiniLabel);
|
||
GUILayout.FlexibleSpace();
|
||
}
|
||
else if (_jsonData != null && _firstLevelKeys != null && _firstLevelKeys.Count > 0)
|
||
{
|
||
foreach (var key in _firstLevelKeys)
|
||
{
|
||
bool isSelected = (_selectedKey == key);
|
||
if (isSelected) GUI.backgroundColor = new Color(0.6f, 0.8f, 1f);
|
||
|
||
string displayText = key;
|
||
if (_jsonData[key] != null)
|
||
{
|
||
JToken token = _jsonData[key];
|
||
if (token.Type == JTokenType.Object)
|
||
displayText = $"{{}} {key}";
|
||
else if (token.Type == JTokenType.Array)
|
||
displayText = $"[] {key}";
|
||
else
|
||
displayText = $"{GetValueTypeIcon(token)} {key}";
|
||
}
|
||
|
||
if (GUILayout.Button(displayText, EditorStyles.miniButton, GUILayout.Height(24)))
|
||
{
|
||
SelectKey(key);
|
||
}
|
||
|
||
GUI.backgroundColor = Color.white;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
GUILayout.Label("<< 请选择文件", EditorStyles.centeredGreyMiniLabel);
|
||
}
|
||
|
||
EditorGUILayout.EndScrollView();
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
private void DrawEditor()
|
||
{
|
||
EditorGUILayout.BeginVertical("box", GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true));
|
||
|
||
if (_jsonData != null || _jsonArray != null)
|
||
{
|
||
EditorGUILayout.BeginHorizontal();
|
||
{
|
||
GUILayout.Label("JSON编辑器", EditorStyles.boldLabel);
|
||
if (_isArrayFormat)
|
||
{
|
||
GUI.color = new Color(0.6f, 0.8f, 1f);
|
||
GUILayout.Label("Array格式", EditorStyles.miniLabel);
|
||
GUI.color = Color.white;
|
||
}
|
||
else if (!string.IsNullOrEmpty(_selectedKey))
|
||
{
|
||
GUI.color = new Color(0.6f, 0.8f, 1f);
|
||
GUILayout.Label($"当前Key: {_selectedKey}", EditorStyles.miniLabel);
|
||
GUI.color = Color.white;
|
||
if (GUILayout.Button("显示全部", EditorStyles.miniButton, GUILayout.Width(70)))
|
||
{
|
||
_selectedKey = null;
|
||
}
|
||
}
|
||
GUILayout.FlexibleSpace();
|
||
_showDeleteButton = GUILayout.Toggle(_showDeleteButton, "显示删除", EditorStyles.toolbarButton, GUILayout.Width(70));
|
||
_editModeTree = GUILayout.Toggle(_editModeTree, "树形编辑", EditorStyles.toolbarButton);
|
||
_editModeTree = !GUILayout.Toggle(!_editModeTree, "文本编辑", EditorStyles.toolbarButton);
|
||
}
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
GUILayout.Space(5);
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
{
|
||
EditorGUI.BeginDisabledGroup(string.IsNullOrEmpty(_selectedFile));
|
||
if (GUILayout.Button("保存", EditorStyles.miniButton, GUILayout.Width(60)))
|
||
{
|
||
SaveJsonFile();
|
||
}
|
||
EditorGUI.EndDisabledGroup();
|
||
|
||
EditorGUI.BeginDisabledGroup(string.IsNullOrEmpty(_selectedFile));
|
||
if (GUILayout.Button("重新加载", EditorStyles.miniButton, GUILayout.Width(70)))
|
||
{
|
||
if (!string.IsNullOrEmpty(_selectedFile))
|
||
LoadJsonFile(_selectedFile);
|
||
}
|
||
EditorGUI.EndDisabledGroup();
|
||
|
||
GUILayout.FlexibleSpace();
|
||
|
||
if (!string.IsNullOrEmpty(_validationError))
|
||
{
|
||
GUI.color = Color.red;
|
||
GUILayout.Label(_validationError, EditorStyles.miniLabel);
|
||
GUI.color = Color.white;
|
||
}
|
||
}
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
GUILayout.Space(5);
|
||
|
||
_scrollPosEditor = EditorGUILayout.BeginScrollView(_scrollPosEditor);
|
||
|
||
if (_editModeTree)
|
||
{
|
||
if (_isArrayFormat)
|
||
{
|
||
DrawArrayTreeEditor();
|
||
}
|
||
else
|
||
{
|
||
DrawTreeEditor();
|
||
}
|
||
}
|
||
else
|
||
{
|
||
DrawTextEditor();
|
||
}
|
||
|
||
EditorGUILayout.EndScrollView();
|
||
}
|
||
else
|
||
{
|
||
GUILayout.FlexibleSpace();
|
||
GUILayout.Label("<< 请选择JSON文件", EditorStyles.centeredGreyMiniLabel);
|
||
GUILayout.FlexibleSpace();
|
||
}
|
||
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
private void DrawTreeEditor()
|
||
{
|
||
if (_jsonData == null) return;
|
||
|
||
_keyPositions.Clear();
|
||
|
||
// 如果选中了Key,只显示该Key对应的部分
|
||
if (!string.IsNullOrEmpty(_selectedKey) && _jsonData[_selectedKey] != null)
|
||
{
|
||
JToken selectedToken = _jsonData[_selectedKey];
|
||
DrawJToken(selectedToken, _selectedKey, 0, _selectedKey);
|
||
}
|
||
else
|
||
{
|
||
// 显示完整的JSON
|
||
foreach (var prop in _jsonData.Properties())
|
||
{
|
||
DrawJToken(prop.Value, prop.Name, 0, prop.Name);
|
||
}
|
||
}
|
||
}
|
||
|
||
private void DrawArrayTreeEditor()
|
||
{
|
||
if (_jsonArray == null) return;
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
{
|
||
GUILayout.Space(15);
|
||
if (GUILayout.Button("+ 添加元素", EditorStyles.miniButton, GUILayout.Width(100)))
|
||
{
|
||
_jsonArray.Add("");
|
||
}
|
||
}
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
GUILayout.Space(5);
|
||
|
||
var indicesToRemove = new List<int>();
|
||
for (int i = 0; i < _jsonArray.Count; i++)
|
||
{
|
||
if (_showDeleteButton)
|
||
{
|
||
EditorGUILayout.BeginHorizontal();
|
||
GUILayout.Space(15);
|
||
if (GUILayout.Button("×", EditorStyles.miniButton, GUILayout.Width(20)))
|
||
{
|
||
indicesToRemove.Add(i);
|
||
}
|
||
EditorGUILayout.EndHorizontal();
|
||
}
|
||
|
||
DrawJToken(_jsonArray[i], $"[{i}]", 0, $"[{i}]");
|
||
}
|
||
|
||
// 删除元素(从后往前删除,避免索引问题)
|
||
for (int i = indicesToRemove.Count - 1; i >= 0; i--)
|
||
{
|
||
_jsonArray.RemoveAt(indicesToRemove[i]);
|
||
}
|
||
}
|
||
|
||
private void DrawJToken(JToken token, string key, int indent, string path = "")
|
||
{
|
||
if (token == null) return;
|
||
|
||
string nodePath = string.IsNullOrEmpty(path) ? key : $"{path}.{key}";
|
||
float startY = GUILayoutUtility.GetRect(0, 0).y;
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
GUILayout.Space(indent * 15);
|
||
|
||
if (token.Type == JTokenType.Object)
|
||
{
|
||
JObject obj = (JObject)token;
|
||
if (!_expandedNodes.ContainsKey(nodePath))
|
||
_expandedNodes[nodePath] = true;
|
||
|
||
bool expanded = _expandedNodes[nodePath];
|
||
expanded = EditorGUILayout.Foldout(expanded, $"{key}: {{}} ({obj.Count})", true);
|
||
_expandedNodes[nodePath] = expanded;
|
||
|
||
if (GUILayout.Button("+", EditorStyles.miniButtonLeft, GUILayout.Width(20)))
|
||
{
|
||
string newKey = "newKey";
|
||
obj[newKey] = "";
|
||
}
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
if (expanded)
|
||
{
|
||
var propsToRemove = new List<string>();
|
||
foreach (var prop in obj.Properties().ToList())
|
||
{
|
||
if (_showDeleteButton)
|
||
{
|
||
EditorGUILayout.BeginHorizontal();
|
||
GUILayout.Space(15);
|
||
if (GUILayout.Button("×", EditorStyles.miniButton, GUILayout.Width(20)))
|
||
{
|
||
propsToRemove.Add(prop.Name);
|
||
}
|
||
EditorGUILayout.EndHorizontal();
|
||
}
|
||
|
||
DrawJToken(prop.Value, prop.Name, indent + 1, nodePath);
|
||
}
|
||
|
||
foreach (var keyToRemove in propsToRemove)
|
||
{
|
||
obj.Remove(keyToRemove);
|
||
}
|
||
}
|
||
}
|
||
else if (token.Type == JTokenType.Array)
|
||
{
|
||
JArray arr = (JArray)token;
|
||
if (!_expandedNodes.ContainsKey(nodePath))
|
||
_expandedNodes[nodePath] = true;
|
||
|
||
bool expanded = _expandedNodes[nodePath];
|
||
expanded = EditorGUILayout.Foldout(expanded, $"{key}: [{arr.Count}]", true);
|
||
_expandedNodes[nodePath] = expanded;
|
||
|
||
if (GUILayout.Button("+", EditorStyles.miniButtonLeft, GUILayout.Width(20)))
|
||
{
|
||
arr.Add("");
|
||
}
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
if (expanded)
|
||
{
|
||
var indicesToRemove = new List<int>();
|
||
for (int i = 0; i < arr.Count; i++)
|
||
{
|
||
if (_showDeleteButton)
|
||
{
|
||
EditorGUILayout.BeginHorizontal();
|
||
GUILayout.Space(15);
|
||
if (GUILayout.Button("×", EditorStyles.miniButton, GUILayout.Width(20)))
|
||
{
|
||
indicesToRemove.Add(i);
|
||
}
|
||
EditorGUILayout.EndHorizontal();
|
||
}
|
||
|
||
DrawJToken(arr[i], $"[{i}]", indent + 1, nodePath);
|
||
}
|
||
|
||
for (int i = indicesToRemove.Count - 1; i >= 0; i--)
|
||
{
|
||
arr.RemoveAt(indicesToRemove[i]);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 值类型
|
||
string valueStr = token.ToString();
|
||
if (token.Type == JTokenType.String)
|
||
valueStr = $"\"{valueStr}\"";
|
||
|
||
EditorGUILayout.LabelField($"{key}:", GUILayout.Width(100));
|
||
string newValue = EditorGUILayout.TextField(valueStr);
|
||
if (newValue != valueStr)
|
||
{
|
||
try
|
||
{
|
||
if (token.Type == JTokenType.String)
|
||
{
|
||
string trimmed = newValue.Trim('"');
|
||
token.Replace(trimmed);
|
||
}
|
||
else if (token.Type == JTokenType.Integer)
|
||
{
|
||
if (long.TryParse(newValue, out long longVal))
|
||
token.Replace(longVal);
|
||
}
|
||
else if (token.Type == JTokenType.Float)
|
||
{
|
||
if (double.TryParse(newValue, out double doubleVal))
|
||
token.Replace(doubleVal);
|
||
}
|
||
else if (token.Type == JTokenType.Boolean)
|
||
{
|
||
if (bool.TryParse(newValue, out bool boolVal))
|
||
token.Replace(boolVal);
|
||
}
|
||
else
|
||
{
|
||
token.Replace(newValue);
|
||
}
|
||
}
|
||
catch { }
|
||
}
|
||
EditorGUILayout.EndHorizontal();
|
||
}
|
||
|
||
if (indent == 0 && !string.IsNullOrEmpty(key))
|
||
{
|
||
float endY = GUILayoutUtility.GetRect(0, 0).y;
|
||
_keyPositions[key] = startY;
|
||
}
|
||
}
|
||
|
||
private void DrawTextEditor()
|
||
{
|
||
string displayText = _jsonTextContent;
|
||
|
||
// 如果选中了Key,只显示该Key对应的部分
|
||
if (!string.IsNullOrEmpty(_selectedKey) && _jsonData != null && _jsonData[_selectedKey] != null)
|
||
{
|
||
JToken selectedToken = _jsonData[_selectedKey];
|
||
displayText = selectedToken.ToString(Newtonsoft.Json.Formatting.Indented);
|
||
}
|
||
|
||
EditorGUI.BeginChangeCheck();
|
||
displayText = EditorGUILayout.TextArea(displayText, GUILayout.ExpandHeight(true));
|
||
if (EditorGUI.EndChangeCheck())
|
||
{
|
||
try
|
||
{
|
||
// 如果只编辑选中的Key部分,需要更新整个JSON
|
||
if (!string.IsNullOrEmpty(_selectedKey) && _jsonData != null)
|
||
{
|
||
JToken newToken = JToken.Parse(displayText);
|
||
_jsonData[_selectedKey] = newToken;
|
||
_jsonTextContent = _jsonData.ToString(Newtonsoft.Json.Formatting.Indented);
|
||
}
|
||
else if (_isArrayFormat)
|
||
{
|
||
// Array格式
|
||
_jsonArray = JArray.Parse(displayText);
|
||
_jsonTextContent = displayText;
|
||
}
|
||
else
|
||
{
|
||
// Object格式
|
||
_jsonData = JObject.Parse(displayText);
|
||
_jsonTextContent = displayText;
|
||
_firstLevelKeys = _jsonData.Properties().Select(p => p.Name).ToList();
|
||
}
|
||
_validationError = "";
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
_validationError = $"JSON格式错误: {e.Message}";
|
||
}
|
||
}
|
||
}
|
||
|
||
// ------------------------------------------------------------------------
|
||
// 逻辑处理
|
||
// ------------------------------------------------------------------------
|
||
|
||
private void RefreshFileList()
|
||
{
|
||
_jsonFileList.Clear();
|
||
if (string.IsNullOrEmpty(_rootPath)) return;
|
||
|
||
string fullPath = _rootPath;
|
||
if (!Path.IsPathRooted(fullPath))
|
||
{
|
||
fullPath = Path.Combine(Application.dataPath.Replace("Assets", ""), _rootPath).Replace("\\", "/");
|
||
Debug.Log("fullPath: " + fullPath);
|
||
}
|
||
|
||
Debug.Log("fullPath: " + Directory.Exists(fullPath));
|
||
|
||
if (!Directory.Exists(fullPath)) return;
|
||
|
||
string[] files = Directory.GetFiles(fullPath, "*.json", SearchOption.AllDirectories);
|
||
foreach (var file in files)
|
||
{
|
||
string relativePath = file.Replace(Application.dataPath, "Assets");
|
||
if (string.IsNullOrEmpty(_searchString) ||
|
||
Path.GetFileName(file).ToLower().Contains(_searchString.ToLower()))
|
||
{
|
||
_jsonFileList.Add(file);
|
||
}
|
||
}
|
||
|
||
_jsonFileList.Sort();
|
||
}
|
||
|
||
private void SelectFile(string filePath)
|
||
{
|
||
_selectedFile = filePath;
|
||
_selectedKey = null; // 切换文件时清除选中的Key,显示完整JSON
|
||
_isArrayFormat = false; // 重置格式标识
|
||
LoadJsonFile(filePath);
|
||
GUI.FocusControl(null);
|
||
}
|
||
|
||
private void SelectKey(string key)
|
||
{
|
||
_selectedKey = key;
|
||
// 选中Key时,重置滚动位置到顶部(因为只显示该Key的内容)
|
||
_scrollPosEditor.y = 0;
|
||
Repaint();
|
||
}
|
||
|
||
private void LoadJsonFile(string filePath)
|
||
{
|
||
try
|
||
{
|
||
string jsonText = File.ReadAllText(filePath, System.Text.Encoding.UTF8);
|
||
JToken rootToken = JToken.Parse(jsonText);
|
||
|
||
// 判断是Object还是Array
|
||
if (rootToken.Type == JTokenType.Array)
|
||
{
|
||
_jsonArray = (JArray)rootToken;
|
||
_jsonData = null;
|
||
_isArrayFormat = true;
|
||
_firstLevelKeys.Clear();
|
||
_jsonTextContent = _jsonArray.ToString(Newtonsoft.Json.Formatting.Indented);
|
||
}
|
||
else if (rootToken.Type == JTokenType.Object)
|
||
{
|
||
_jsonData = (JObject)rootToken;
|
||
_jsonArray = null;
|
||
_isArrayFormat = false;
|
||
_firstLevelKeys = _jsonData.Properties().Select(p => p.Name).ToList();
|
||
_jsonTextContent = _jsonData.ToString(Newtonsoft.Json.Formatting.Indented);
|
||
}
|
||
else
|
||
{
|
||
throw new Exception("JSON格式必须是Object或Array");
|
||
}
|
||
|
||
_selectedKey = null;
|
||
_validationError = "";
|
||
_expandedNodes.Clear();
|
||
_keyPositions.Clear();
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
_validationError = $"加载失败: {e.Message}";
|
||
_jsonData = null;
|
||
_jsonArray = null;
|
||
_isArrayFormat = false;
|
||
_firstLevelKeys.Clear();
|
||
}
|
||
}
|
||
|
||
private void SaveJsonFile()
|
||
{
|
||
if (string.IsNullOrEmpty(_selectedFile)) return;
|
||
if (_jsonData == null && _jsonArray == null) return;
|
||
|
||
try
|
||
{
|
||
string jsonText;
|
||
if (_isArrayFormat && _jsonArray != null)
|
||
{
|
||
jsonText = _jsonArray.ToString(Newtonsoft.Json.Formatting.Indented);
|
||
}
|
||
else if (_jsonData != null)
|
||
{
|
||
jsonText = _jsonData.ToString(Newtonsoft.Json.Formatting.Indented);
|
||
}
|
||
else
|
||
{
|
||
return;
|
||
}
|
||
|
||
File.WriteAllText(_selectedFile, jsonText, System.Text.Encoding.UTF8);
|
||
_jsonTextContent = jsonText;
|
||
_validationError = "";
|
||
AssetDatabase.Refresh();
|
||
EditorUtility.DisplayDialog("保存成功", "JSON文件已保存", "确定");
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
_validationError = $"保存失败: {e.Message}";
|
||
EditorUtility.DisplayDialog("保存失败", e.Message, "确定");
|
||
}
|
||
}
|
||
|
||
private void CreateJsonFile()
|
||
{
|
||
if (string.IsNullOrEmpty(_newFileName)) return;
|
||
|
||
string fullPath = _rootPath;
|
||
if (!Path.IsPathRooted(fullPath))
|
||
{
|
||
fullPath = Path.Combine(Application.dataPath, _rootPath.Replace("Assets/", ""));
|
||
}
|
||
|
||
if (!Directory.Exists(fullPath))
|
||
{
|
||
Directory.CreateDirectory(fullPath);
|
||
}
|
||
|
||
string fileName = JsonUtil.FormatAsJsonPath(_newFileName);
|
||
string filePath = Path.Combine(fullPath, fileName);
|
||
filePath = GetUniqueFilePath(filePath);
|
||
|
||
JObject newJson = new JObject();
|
||
File.WriteAllText(filePath, newJson.ToString(Newtonsoft.Json.Formatting.Indented), System.Text.Encoding.UTF8);
|
||
|
||
AssetDatabase.Refresh();
|
||
RefreshFileList();
|
||
SelectFile(filePath);
|
||
_newFileName = "";
|
||
}
|
||
|
||
private void DeleteFile(string filePath)
|
||
{
|
||
try
|
||
{
|
||
File.Delete(filePath);
|
||
if (File.Exists(filePath + ".meta"))
|
||
File.Delete(filePath + ".meta");
|
||
|
||
AssetDatabase.Refresh();
|
||
if (_selectedFile == filePath)
|
||
{
|
||
_selectedFile = null;
|
||
_jsonData = null;
|
||
_jsonArray = null;
|
||
_isArrayFormat = false;
|
||
_firstLevelKeys.Clear();
|
||
}
|
||
RefreshFileList();
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
EditorUtility.DisplayDialog("删除失败", e.Message, "确定");
|
||
}
|
||
}
|
||
|
||
private void DuplicateFile(string filePath)
|
||
{
|
||
try
|
||
{
|
||
string dir = Path.GetDirectoryName(filePath);
|
||
string fileName = Path.GetFileNameWithoutExtension(filePath);
|
||
string ext = Path.GetExtension(filePath);
|
||
string newPath = Path.Combine(dir, fileName + "_copy" + ext);
|
||
newPath = GetUniqueFilePath(newPath);
|
||
|
||
File.Copy(filePath, newPath);
|
||
AssetDatabase.Refresh();
|
||
RefreshFileList();
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
EditorUtility.DisplayDialog("复制失败", e.Message, "确定");
|
||
}
|
||
}
|
||
|
||
private void ValidateJson()
|
||
{
|
||
if (string.IsNullOrEmpty(_selectedFile) && _jsonData == null && _jsonArray == null)
|
||
{
|
||
// 验证文本编辑器中的内容
|
||
if (!string.IsNullOrEmpty(_jsonTextContent))
|
||
{
|
||
try
|
||
{
|
||
JToken.Parse(_jsonTextContent);
|
||
_validationError = "";
|
||
EditorUtility.DisplayDialog("验证成功", "JSON格式正确", "确定");
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
_validationError = $"验证失败: {e.Message}";
|
||
EditorUtility.DisplayDialog("验证失败", e.Message, "确定");
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
string jsonText;
|
||
if (_jsonArray != null)
|
||
jsonText = _jsonArray.ToString();
|
||
else if (_jsonData != null)
|
||
jsonText = _jsonData.ToString();
|
||
else
|
||
jsonText = File.ReadAllText(_selectedFile, System.Text.Encoding.UTF8);
|
||
|
||
JToken.Parse(jsonText);
|
||
_validationError = "";
|
||
EditorUtility.DisplayDialog("验证成功", "JSON格式正确", "确定");
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
_validationError = $"验证失败: {e.Message}";
|
||
EditorUtility.DisplayDialog("验证失败", e.Message, "确定");
|
||
}
|
||
}
|
||
|
||
private void FormatJson()
|
||
{
|
||
if (_jsonData == null && _jsonArray == null)
|
||
{
|
||
// 如果当前是文本模式,尝试从文本格式化
|
||
if (!string.IsNullOrEmpty(_jsonTextContent))
|
||
{
|
||
try
|
||
{
|
||
JToken rootToken = JToken.Parse(_jsonTextContent);
|
||
if (rootToken.Type == JTokenType.Array)
|
||
{
|
||
_jsonArray = (JArray)rootToken;
|
||
_jsonData = null;
|
||
_isArrayFormat = true;
|
||
_firstLevelKeys.Clear();
|
||
}
|
||
else
|
||
{
|
||
_jsonData = (JObject)rootToken;
|
||
_jsonArray = null;
|
||
_isArrayFormat = false;
|
||
_firstLevelKeys = _jsonData.Properties().Select(p => p.Name).ToList();
|
||
}
|
||
string formatted = rootToken.ToString(Newtonsoft.Json.Formatting.Indented);
|
||
_jsonTextContent = formatted;
|
||
_validationError = "";
|
||
return;
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
_validationError = $"格式化失败: {e.Message}";
|
||
return;
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
string formatted;
|
||
if (_isArrayFormat && _jsonArray != null)
|
||
{
|
||
formatted = _jsonArray.ToString(Newtonsoft.Json.Formatting.Indented);
|
||
_jsonArray = JArray.Parse(formatted);
|
||
}
|
||
else if (_jsonData != null)
|
||
{
|
||
formatted = _jsonData.ToString(Newtonsoft.Json.Formatting.Indented);
|
||
_jsonData = JObject.Parse(formatted);
|
||
_firstLevelKeys = _jsonData.Properties().Select(p => p.Name).ToList();
|
||
}
|
||
else
|
||
{
|
||
return;
|
||
}
|
||
_jsonTextContent = formatted;
|
||
_validationError = "";
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
_validationError = $"格式化失败: {e.Message}";
|
||
}
|
||
}
|
||
|
||
private void RenameFile(string filePath, string newName)
|
||
{
|
||
try
|
||
{
|
||
string dir = Path.GetDirectoryName(filePath);
|
||
string ext = Path.GetExtension(filePath);
|
||
string newPath = Path.Combine(dir, newName + ext);
|
||
|
||
if (File.Exists(newPath))
|
||
{
|
||
EditorUtility.DisplayDialog("重命名失败", "文件已存在", "确定");
|
||
return;
|
||
}
|
||
|
||
File.Move(filePath, newPath);
|
||
if (File.Exists(filePath + ".meta"))
|
||
{
|
||
File.Move(filePath + ".meta", newPath + ".meta");
|
||
}
|
||
|
||
AssetDatabase.Refresh();
|
||
if (_selectedFile == filePath)
|
||
{
|
||
_selectedFile = newPath;
|
||
}
|
||
RefreshFileList();
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
EditorUtility.DisplayDialog("重命名失败", e.Message, "确定");
|
||
}
|
||
}
|
||
|
||
private string GetUniqueFilePath(string filePath)
|
||
{
|
||
if (!File.Exists(filePath)) return filePath;
|
||
|
||
string dir = Path.GetDirectoryName(filePath);
|
||
string fileName = Path.GetFileNameWithoutExtension(filePath);
|
||
string ext = Path.GetExtension(filePath);
|
||
int counter = 1;
|
||
|
||
string newPath;
|
||
do
|
||
{
|
||
newPath = Path.Combine(dir, $"{fileName}_{counter}{ext}");
|
||
counter++;
|
||
} while (File.Exists(newPath));
|
||
|
||
return newPath;
|
||
}
|
||
|
||
private string GetValueTypeIcon(JToken token)
|
||
{
|
||
switch (token.Type)
|
||
{
|
||
case JTokenType.String: return "\"\"";
|
||
case JTokenType.Integer: return "123";
|
||
case JTokenType.Float: return "1.0";
|
||
case JTokenType.Boolean: return token.Value<bool>() ? "true" : "false";
|
||
case JTokenType.Null: return "null";
|
||
default: return "?";
|
||
}
|
||
}
|
||
}
|
||
}
|