diff --git a/Assets/Editor/DeveloperMode/DeveloperModeTestSavePanelBuilder.cs b/Assets/Editor/DeveloperMode/DeveloperModeTestSavePanelBuilder.cs new file mode 100644 index 000000000..8db0a8f31 --- /dev/null +++ b/Assets/Editor/DeveloperMode/DeveloperModeTestSavePanelBuilder.cs @@ -0,0 +1,460 @@ +#if UNITY_EDITOR +using System; +using System.Linq; +using AibisDream.UI; +using TMPro; +using UnityEditor; +using UnityEngine; +using UnityEngine.UI; + +namespace AibisDream.DeveloperMode.Editor +{ + /// 只重建 DeveloperModePanel 的测试存档页,避免覆盖其他页的人工调整。 + public static class DeveloperModeTestSavePanelBuilder + { + private const string PrefabPath = + "Assets/GameContent/Feature_MainUI/Prefabs/DeveloperModePanel.prefab"; + private const string FontAssetPath = + "Assets/Font/Assets/WenQuanYi Bitmap Song 14px SDF.asset"; + + private static readonly Color Surface = new(0.055f, 0.085f, 0.11f, 0.96f); + private static readonly Color Accent = new(0.16f, 0.75f, 0.88f, 1f); + private static readonly Color TextColor = new(0.88f, 0.96f, 1f, 1f); + + [MenuItem("Tools/AIBIS/Developer Mode/Rebuild Test Save Page")] + public static void BuildFromMenu() + { + Build(); + Selection.activeObject = AssetDatabase.LoadAssetAtPath(PrefabPath); + } + + public static void BuildFromCommandLine() + { + try + { + Build(); + Debug.Log("[DeveloperModeTestSavePanelBuilder] Build completed."); + } + catch (Exception ex) + { + Debug.LogException(ex); + EditorApplication.Exit(1); + } + } + + private static void Build() + { + var root = PrefabUtility.LoadPrefabContents(PrefabPath); + try + { + var panel = root.GetComponent() + ?? throw new InvalidOperationException("DeveloperModePanel component not found."); + var serialized = new SerializedObject(panel); + var pages = serialized.FindProperty("tabPages") + ?? throw new MissingFieldException(nameof(DeveloperModePanel), "tabPages"); + if (pages.arraySize < 4) + { + throw new InvalidOperationException("DeveloperModePanel requires five tab pages."); + } + + var savePage = pages.GetArrayElementAtIndex(3).objectReferenceValue as GameObject + ?? throw new InvalidOperationException("Save page reference is missing."); + ClearChildren(savePage.transform); + ConfigurePageLayout(savePage); + BuildSavePage(savePage.transform, serialized); + RenameSaveTab(serialized); + + var font = AssetDatabase.LoadAssetAtPath(FontAssetPath) + ?? throw new InvalidOperationException($"Font not found: {FontAssetPath}"); + foreach (var text in root.GetComponentsInChildren(true)) + { + text.font = font; + } + + serialized.ApplyModifiedPropertiesWithoutUndo(); + PrefabUtility.SaveAsPrefabAsset(root, PrefabPath); + } + finally + { + PrefabUtility.UnloadPrefabContents(root); + } + + AssetDatabase.SaveAssets(); + } + + private static void BuildSavePage(Transform page, SerializedObject serialized) + { + var recording = CreateHorizontal("Recording Toolbar", page, 6f); + AddLayout(recording, 38f); + var recordingToggle = CreateToggle("Record Test Saves", recording.transform, "录制测试存档", 145f); + var recordingState = CreateText("Recording State", recording.transform, "○ OFF", 14f, FontStyles.Bold); + AddLayout(recordingState.gameObject, 34f); + recordingState.rectTransform.sizeDelta = new Vector2(105f, 0f); + var search = CreateInput("Test Save Search", recording.transform, "搜索章节、节点或场景...", 250f); + var reload = CreateButton("Reload Test Saves", recording.transform, "重新扫描", 92f, 34f); + var open = CreateButton("Open Test Saves", recording.transform, "打开目录", 92f, 34f); + + var columns = CreateHorizontal("Library Columns", page, 7f); + var columnsLayout = columns.AddComponent(); + columnsLayout.flexibleHeight = 1f; + columnsLayout.minHeight = 420f; + + var chapters = CreateVertical("Chapter Column", columns.transform, 4f); + var chapterLayout = chapters.AddComponent(); + chapterLayout.preferredWidth = 245f; + chapterLayout.flexibleWidth = 0f; + chapterLayout.flexibleHeight = 1f; + var chapterTitle = CreateText("Title", chapters.transform, "章节 / 覆盖率", 16f, FontStyles.Bold); + AddLayout(chapterTitle.gameObject, 28f); + CreateList( + "Chapter List", + chapters.transform, + out var chapterContent, + out var chapterTemplate); + + var entries = CreateVertical("Entry Column", columns.transform, 4f); + var entryLayout = entries.AddComponent(); + entryLayout.flexibleWidth = 1f; + entryLayout.flexibleHeight = 1f; + var entryTitle = CreateText( + "Title", + entries.transform, + "存档点(● 已录制 / ○ 缺失 / ✕ 无效)", + 16f, + FontStyles.Bold); + AddLayout(entryTitle.gameObject, 28f); + CreateList( + "Entry List", + entries.transform, + out var entryContent, + out var entryTemplate); + + var actions = CreateHorizontal("Actions", page, 6f); + AddLayout(actions, 36f); + var jump = CreateButton("Jump", actions.transform, "跳转到选中", 118f, 32f); + var deleteSelected = CreateButton("Delete Selected", actions.transform, "删除选中", 105f, 32f); + var deleteInvalid = CreateButton("Delete Invalid", actions.transform, "清理无效", 105f, 32f); + var clear = CreateButton("Clear Library", actions.transform, "清空存档库", 118f, 32f); + + var status = CreateText( + "Save Status", + page, + "测试存档录制默认关闭;开启后仅复制正式节点自动档。", + 14f); + AddLayout(status.gameObject, 46f); + status.color = new Color(0.45f, 1f, 0.65f); + status.alignment = TextAlignmentOptions.TopLeft; + + Set(serialized, "testSaveRecordingToggle", recordingToggle); + Set(serialized, "testSaveRecordingStateText", recordingState); + Set(serialized, "testSaveSearchInput", search); + Set(serialized, "reloadSavesButton", reload.GetComponent