com.alicizax.unity.framework/Editor/Localization/LocalizationTableWindow/LocalizationTableWindow.cs

643 lines
23 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.IO;
using System.Collections.Generic;
using AlicizaX.Editor;
using UnityEditor.IMGUI.Controls;
using UnityEditor;
using UnityEngine;
namespace AlicizaX.Localization.Editor
{
public class LocalizationTableWindow : EditorWindow
{
private enum SearchTarget
{
Key,
Value
}
private static readonly string[] SearchTargetOptions = { "Key", "Value" };
private List<GameLocaizationTable> allTables = new List<GameLocaizationTable>(); // 存储所有找到的GameLocaizationTable
private string[] tableDisplayNames; // 用于下拉框显示的名称数组
private int selectedTableIndex = 0; // 当前选中的索引
private string selectString = string.Empty;
private GameLocaizationTable currentTable; // 当前选中的GameLocaizationTable
// 原本是 const现在改成可调的字段
[SerializeField] private float languagesWidth = 200f;
[SerializeField] private float tableSheetWidth = 300f;
private const float SplitterWidth = 3f; // 拖拽条的宽度
private bool isResizingLeft, isResizingMiddle;
private float Spacing => EditorGUIUtility.standardVerticalSpacing * 2;
private GUIStyle miniLabelButton => new GUIStyle(EditorStyles.miniButton)
{
font = EditorStyles.miniBoldLabel.font,
fontSize = EditorStyles.miniBoldLabel.fontSize
};
public class WindowSelection
{
public TreeViewItem TreeViewItem;
}
public sealed class LanguageSelect : WindowSelection
{
public TempLanguageData Language;
}
public sealed class SectionSelect : WindowSelection
{
public SheetSectionTreeView Section;
}
public sealed class ItemSelect : WindowSelection
{
public SheetItemTreeView Item;
}
private LocalizationWindowData windowData;
private SearchField searchField;
private string searchString;
[SerializeField] private SearchTarget searchTarget = SearchTarget.Key;
private Vector2 scrollPosition;
[SerializeField] private TreeViewState languagesTreeViewState;
private LanguagesTreeView languagesTreeView;
[SerializeField] private TreeViewState tableSheetTreeViewState;
private TableSheetTreeView tableSheetTreeView;
private WindowSelection selection = null;
private bool globalExpanded = false;
private void CreateGUI()
{
EnsureSearchField();
}
private void OnDestroy()
{
SaveSelection();
}
private void OnEnable()
{
EnsureSearchField();
RefreshTableList();
}
private void EnsureSearchField()
{
if (searchField == null)
{
searchField = new SearchField();
}
}
private void OnDisable()
{
SaveSelection();
}
private void RefreshTableList()
{
allTables.Clear();
string[] guids = AssetDatabase.FindAssets("t:GameLocaizationTable");
foreach (string guid in guids)
{
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
GameLocaizationTable table = AssetDatabase.LoadAssetAtPath<GameLocaizationTable>(assetPath);
if (table != null)
{
allTables.Add(table);
}
}
tableDisplayNames = new string[allTables.Count];
for (int i = 0; i < allTables.Count; i++)
{
tableDisplayNames[i] = allTables[i].name;
}
if (allTables.Count > 0 && selectedTableIndex >= allTables.Count)
{
selectedTableIndex = 0;
}
var selectIndex = selectedTableIndex;
var lastSelect = EditorPrefs.GetString("LastSelectedGameLocaizationTable", string.Empty);
if (!string.IsNullOrEmpty(lastSelect))
{
for (int i = 0; i < allTables.Count; i++)
{
var path = AssetDatabase.GetAssetPath(allTables[i]);
if (path.Equals(lastSelect))
{
selectIndex = i;
}
}
}
if (selectIndex <= tableDisplayNames.Length - 1 && selectIndex >= 0)
{
selectString = tableDisplayNames[selectIndex];
}
UpdateCurrentTable();
}
private void UpdateCurrentTable()
{
if (allTables.Count > 0 && selectedTableIndex >= 0 && selectedTableIndex < allTables.Count)
{
currentTable = allTables[selectedTableIndex];
SaveSelection();
}
else
{
currentTable = null;
}
InitializeTreeView();
}
private void SaveSelection()
{
if (currentTable != null)
{
string path = AssetDatabase.GetAssetPath(currentTable);
EditorPrefs.SetString("LastSelectedGameLocaizationTable", path);
}
}
private void InitializeTreeView()
{
if (!currentTable) return;
LocalizationWindowUtility.BuildWindowData(currentTable, out windowData);
foreach (var section in windowData.TableSheet)
{
section.IsExpanded = globalExpanded;
}
languagesTreeViewState = new TreeViewState();
languagesTreeView = new(languagesTreeViewState, windowData, currentTable)
{
OnLanguageSelect = (s) => selection = s
};
tableSheetTreeViewState = new TreeViewState();
tableSheetTreeView = new(tableSheetTreeViewState, windowData)
{
OnTableSheetSelect = (s) => selection = s
};
}
private void OnGUI()
{
EnsureSearchField();
// 顶部工具栏
Rect toolbarRect = new(0, 0, position.width, 20f);
GUI.Box(toolbarRect, GUIContent.none, EditorStyles.toolbar);
float buttonWidth = 100f;
float spacing = 5f;
float searchTypeWidth = 65f;
Rect leftTitle = new(toolbarRect.xMin, 0, 40, 20f);
Rect leftPop = new(leftTitle.xMin + 40, 0, 200, 20f);
Rect saveBtn = new(toolbarRect.xMax - buttonWidth - spacing, 0, buttonWidth, 20f);
Rect genBtn = new(saveBtn.xMin - buttonWidth - spacing, 0, buttonWidth, 20f);
Rect importBtn = new(genBtn.xMin - buttonWidth - spacing, 0, buttonWidth, 20f);
Rect exportBtn = new(importBtn.xMin - buttonWidth - spacing, 0, buttonWidth, 20f);
Rect searchTypePopup = new(leftPop.xMax + spacing, 0, searchTypeWidth, 20f);
Rect searchRect = new(searchTypePopup.xMax + spacing, 0, Mathf.Max(80f, exportBtn.xMin - spacing - (searchTypePopup.xMax + spacing)), 20f);
EditorGUI.LabelField(leftTitle, "Table", EditorStyles.boldLabel);
EditorDrawing.DrawStringSelectPopup(leftPop, tableDisplayNames, selectString, (e) =>
{
selectString = e;
selectedTableIndex = allTables.FindIndex(table => table.name == e);
UpdateCurrentTable();
});
using (new EditorGUI.DisabledGroupScope(currentTable == null || !(selection is LanguageSelect)))
{
searchTarget = (SearchTarget)EditorGUI.Popup(searchTypePopup, (int)searchTarget, SearchTargetOptions, EditorStyles.toolbarPopup);
searchString = searchField.OnToolbarGUI(searchRect, searchString);
}
if (currentTable == null) return;
if (GUI.Button(exportBtn, "Export CSV", EditorStyles.toolbarButton))
{
string path = EditorUtility.SaveFilePanel("Export CSV", "", "Localization", "csv");
LocalizationExporter.ExportLocalizationToCSV(windowData, path);
}
if (GUI.Button(importBtn, "Import CSV", EditorStyles.toolbarButton))
{
string path = EditorUtility.OpenFilePanel("Export CSV", "", "csv");
LocalizationExporter.ImportLocalizationFromCSV(windowData, path);
}
if (GUI.Button(genBtn, "Gen Code", EditorStyles.toolbarButton))
{
LocalizationWindowUtility.GenerateCode(currentTable);
}
if (GUI.Button(saveBtn, "Save Asset", EditorStyles.toolbarButton))
{
BuildLocalizationTable();
EditorUtility.SetDirty(currentTable);
AssetDatabase.SaveAssets();
}
// ---------------- 这里开始是 SplitView ----------------
Rect fullRect = new Rect(0, 20f, position.width, position.height - 20f);
// 左侧 Languages
Rect languagesRect = new Rect(fullRect.x, fullRect.y, languagesWidth, fullRect.height);
languagesTreeView?.OnGUI(languagesRect);
// 左侧拖拽条
Rect leftSplitter = new Rect(languagesRect.xMax, fullRect.y, SplitterWidth, fullRect.height);
EditorGUIUtility.AddCursorRect(leftSplitter, MouseCursor.ResizeHorizontal);
HandleResize(ref languagesWidth, leftSplitter, ref isResizingLeft);
// 中间 TableSheet
Rect tableSheetRect = new Rect(leftSplitter.xMax, fullRect.y, tableSheetWidth, fullRect.height);
tableSheetTreeView?.OnGUI(tableSheetRect);
// 中间拖拽条
Rect midSplitter = new Rect(tableSheetRect.xMax, fullRect.y, SplitterWidth, fullRect.height);
EditorGUIUtility.AddCursorRect(midSplitter, MouseCursor.ResizeHorizontal);
HandleResize(ref tableSheetWidth, midSplitter, ref isResizingMiddle);
// 右侧 Inspector剩余空间
float inspectorWidth = fullRect.width - (languagesWidth + tableSheetWidth + SplitterWidth * 2);
Rect inspectorRect = new Rect(midSplitter.xMax, fullRect.y, inspectorWidth, fullRect.height);
if (selection != null)
{
GUILayout.BeginArea(inspectorRect);
{
if (selection is LanguageSelect lang) OnDrawLanguageInspector(lang);
else if (selection is SectionSelect sec) OnDrawSectionInspector(sec);
else if (selection is ItemSelect item) OnDrawSectionItemInspector(item);
}
GUILayout.EndArea();
}
}
private void HandleResize(ref float targetWidth, Rect splitterRect, ref bool isResizing)
{
Event e = Event.current;
switch (e.type)
{
case EventType.MouseDown:
if (splitterRect.Contains(e.mousePosition))
{
isResizing = true;
e.Use();
}
break;
case EventType.MouseDrag:
if (isResizing)
{
targetWidth += e.delta.x;
targetWidth = Mathf.Max(100f, targetWidth); // 设置最小宽度
e.Use();
Repaint();
}
break;
case EventType.MouseUp:
if (isResizing)
{
isResizing = false;
e.Use();
}
break;
}
}
private void OnDrawSectionInspector(SectionSelect section)
{
// section name change
EditorGUI.BeginChangeCheck();
{
section.Section.Name = EditorGUILayout.TextField("Name", section.Section.Name);
}
if (EditorGUI.EndChangeCheck())
{
section.TreeViewItem.displayName = section.Section.Name;
}
using (new EditorGUI.DisabledGroupScope(true))
{
int childerCount = section.TreeViewItem.children?.Count ?? 0;
EditorGUILayout.IntField(new GUIContent("Keys"), childerCount);
}
EditorGUILayout.Space(2);
EditorDrawing.Separator();
EditorGUILayout.Space(1);
using (new EditorGUI.DisabledGroupScope(true))
{
EditorGUILayout.LabelField("Id: " + section.Section.Id, EditorStyles.miniBoldLabel);
}
}
private void OnDrawSectionItemInspector(ItemSelect itemSelect)
{
var item = itemSelect.Item;
EditorGUILayout.LabelField("Editing Key Across Languages", EditorStyles.boldLabel);
// 显示 Key 和 ID只读
using (new EditorGUI.DisabledGroupScope(true))
{
EditorGUILayout.TextField("Key", item.Key);
EditorGUILayout.LabelField("Id: " + item.Id);
}
EditorGUILayout.Space(4);
EditorDrawing.Separator();
EditorGUILayout.Space(4);
// 遍历所有语言
foreach (var lang in windowData.Languages)
{
if (lang.Entry.Asset == null)
continue;
var languageName = lang.Entry.LanguageName;
TempSheetItem targetItem = null;
// 在语言的 TableSheet 里找到对应的 Item
foreach (var section in lang.TableSheet)
{
targetItem = section.Items.Find(i => i.Id == item.Id);
if (targetItem != null)
break;
}
if (targetItem == null)
{
EditorGUILayout.HelpBox($"Language [{languageName}] does not contain this key.", MessageType.Info);
continue;
}
// 绘制语言名标题
EditorGUILayout.LabelField(languageName, EditorStyles.miniBoldLabel);
// 多行文本框
EditorGUI.BeginChangeCheck();
targetItem.Value = EditorGUILayout.TextArea(targetItem.Value, GUILayout.MinHeight(40));
if (EditorGUI.EndChangeCheck())
{
// 标记已修改
EditorUtility.SetDirty(lang.Entry.Asset);
}
EditorGUILayout.Space(6);
}
}
private void OnDrawLanguageInspector(LanguageSelect selection)
{
var language = selection.Language;
var entry = language.Entry;
using (new EditorGUI.DisabledGroupScope(entry.Asset == null))
{
EditorGUILayout.Space();
GUIContent expandText = new GUIContent("Expand");
float expandWidth = miniLabelButton.CalcSize(expandText).x;
using (new EditorGUILayout.HorizontalScope())
{
GUILayout.FlexibleSpace();
using (new EditorDrawing.BackgroundColorScope("#F7E987"))
{
if (GUILayout.Button(expandText, miniLabelButton, GUILayout.Width(expandWidth)))
{
globalExpanded = !globalExpanded;
foreach (var section in language.TableSheet)
{
section.Reference.IsExpanded = globalExpanded;
}
}
}
}
if (entry.Asset != null)
{
bool forceExpanded = !string.IsNullOrWhiteSpace(searchString);
// Draw localization data
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
{
foreach (var section in GetSearchResult(language, searchString, searchTarget))
{
DrawLocalizationKey(section, forceExpanded);
}
}
EditorGUILayout.EndScrollView();
}
else
{
EditorGUILayout.HelpBox("To begin editing localization data, you must first assign a localization asset.", MessageType.Warning);
}
}
}
private void DrawLocalizationKey(TempSheetSection section, bool forceExpanded = false)
{
if (section.Items == null || section.Items.Count == 0)
return;
using (new EditorDrawing.BorderBoxScope(false))
{
string sectionName = section.Name.Replace(" ", "");
bool isExpanded = forceExpanded || section.Reference.IsExpanded;
bool nextExpanded = EditorGUILayout.Foldout(isExpanded, new GUIContent(sectionName), true, EditorDrawing.Styles.miniBoldLabelFoldout);
if (!forceExpanded)
{
section.Reference.IsExpanded = nextExpanded;
}
// Show section keys when expanded
if (forceExpanded || nextExpanded)
{
foreach (var item in section.Items)
{
string key = GetItemDisplayKey(section, item);
if (IsMultiline(item.Value))
key += " (Multiline)";
using (new EditorGUILayout.VerticalScope(GUI.skin.box))
{
// Display the expandable toggle
using (new EditorGUILayout.HorizontalScope(GUI.skin.box))
{
item.IsExpanded = EditorGUILayout.Foldout(item.IsExpanded, new GUIContent(key), true, EditorDrawing.Styles.miniBoldLabelFoldout);
}
if (item.IsExpanded)
{
// Show TextArea when expanded
float height = (EditorGUIUtility.standardVerticalSpacing + EditorGUIUtility.singleLineHeight) * 3;
height += EditorGUIUtility.standardVerticalSpacing;
item.Scroll = EditorGUILayout.BeginScrollView(item.Scroll, GUILayout.Height(height));
item.Value = EditorGUILayout.TextArea(item.Value, GUILayout.ExpandHeight(true));
EditorGUILayout.EndScrollView();
}
else
{
// Show TextField when collapsed
item.Value = EditorGUILayout.TextField(item.Value);
}
}
}
}
}
EditorGUILayout.Space(1f);
}
private IEnumerable<TempSheetSection> GetSearchResult(TempLanguageData languageData, string search, SearchTarget target)
{
if (!string.IsNullOrWhiteSpace(search))
{
List<TempSheetSection> searchResult = new();
foreach (var section in languageData.TableSheet)
{
List<TempSheetItem> sectionItems = new();
foreach (var item in section.Items)
{
string source = target == SearchTarget.Value ? item.Value : GetItemDisplayKey(section, item);
if (ContainsSearch(source, search))
sectionItems.Add(item);
}
if (sectionItems.Count > 0)
{
searchResult.Add(new TempSheetSection()
{
Items = sectionItems,
Reference = section.Reference
});
}
}
return searchResult;
}
return languageData.TableSheet;
}
private bool IsMultiline(string text)
{
return !string.IsNullOrEmpty(text) && (text.Contains("\n") || text.Contains("\r"));
}
private string GetItemDisplayKey(TempSheetSection section, TempSheetItem item)
{
string sectionName = section.Name.Replace(" ", "");
string keyName = item.Key.Replace(" ", "");
return sectionName + "." + keyName;
}
private bool ContainsSearch(string source, string search)
{
return !string.IsNullOrEmpty(source) && source.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0;
}
private void BuildLocalizationTable()
{
// 1. build table sheet
currentTable.TableSheet = new();
foreach (var section in windowData.TableSheet)
{
GameLocaizationTable.TableData tableData = new GameLocaizationTable.TableData(section.Name, section.Id);
foreach (var item in section.Items)
{
GameLocaizationTable.SheetItem sheetItem = new GameLocaizationTable.SheetItem(item.Key, item.Id, item.isGen);
tableData.SectionSheet.Add(sheetItem);
}
currentTable.TableSheet.Add(tableData);
}
// 2. build table sheet for each language
IList<LocalizationLanguage> languages = new List<LocalizationLanguage>();
foreach (var language in windowData.Languages)
{
if (language.Entry.Asset == null)
continue;
LocalizationLanguage asset = language.Entry.Asset;
IList<LocalizationLanguage.LocalizationString> strings = new List<LocalizationLanguage.LocalizationString>();
foreach (var section in language.TableSheet)
{
string sectionKey = section.Name.Replace(" ", "");
foreach (var item in section.Items)
{
string itemKey = item.Key.Replace(" ", "");
string key = sectionKey + "." + itemKey;
strings.Add(new()
{
SectionId = section.Id,
EntryId = item.Id,
Key = key,
Value = item.Value
});
}
}
asset.LanguageName = language.Entry.LanguageName;
asset.Strings = new(strings);
languages.Add(asset);
EditorUtility.SetDirty(asset);
}
currentTable.Languages = new(languages);
}
}
}