610 lines
32 KiB
C#
610 lines
32 KiB
C#
#if UNITY_EDITOR
|
||
using System;
|
||
using System.Linq;
|
||
using AibisDream.UI;
|
||
using TMPro;
|
||
using UnityEditor;
|
||
using UnityEditor.SceneManagement;
|
||
using UnityEngine;
|
||
using UnityEngine.SceneManagement;
|
||
using UnityEngine.UI;
|
||
|
||
namespace AibisDream.DeveloperMode.Editor
|
||
{
|
||
public static class DeveloperModePanelBuilder
|
||
{
|
||
private const string PrefabPath = "Assets/GameContent/Feature_MainUI/Prefabs/DeveloperModePanel.prefab";
|
||
private const string ScenePath = "Assets/Scenes/Persistence.unity";
|
||
private const string FontAssetPath = "Assets/Font/Assets/WenQuanYi Bitmap Song 14px SDF.asset";
|
||
private static readonly Color Background = new(0.025f, 0.045f, 0.065f, 0.97f);
|
||
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 Runtime Panel")]
|
||
public static void BuildFromMenu()
|
||
{
|
||
Build();
|
||
Selection.activeObject = AssetDatabase.LoadAssetAtPath<GameObject>(PrefabPath);
|
||
}
|
||
|
||
public static void BuildFromCommandLine()
|
||
{
|
||
try
|
||
{
|
||
Build();
|
||
Debug.Log("[DeveloperModePanelBuilder] Build completed.");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.LogException(ex);
|
||
EditorApplication.Exit(1);
|
||
}
|
||
}
|
||
|
||
private static void Build()
|
||
{
|
||
var root = BuildPrefabContents();
|
||
root.SetActive(false);
|
||
PrefabUtility.SaveAsPrefabAsset(root, PrefabPath);
|
||
UnityEngine.Object.DestroyImmediate(root);
|
||
AssetDatabase.SaveAssets();
|
||
ReplaceSceneInstance();
|
||
AssetDatabase.SaveAssets();
|
||
}
|
||
|
||
private static GameObject BuildPrefabContents()
|
||
{
|
||
var root = CreateUiObject("Developer Mode Panel", typeof(CanvasRenderer), typeof(Image), typeof(DeveloperModePanel));
|
||
var rootRect = root.GetComponent<RectTransform>();
|
||
rootRect.anchorMin = new Vector2(1f, 0.5f);
|
||
rootRect.anchorMax = new Vector2(1f, 0.5f);
|
||
rootRect.pivot = new Vector2(1f, 0.5f);
|
||
rootRect.anchoredPosition = new Vector2(-16f, 0f);
|
||
rootRect.sizeDelta = new Vector2(840f, 760f);
|
||
root.GetComponent<Image>().color = Background;
|
||
|
||
var panel = root.GetComponent<DeveloperModePanel>();
|
||
var header = CreatePanel("Header", root.transform, Surface);
|
||
SetAnchors(header.GetComponent<RectTransform>(), new Vector2(0f, 1f), new Vector2(1f, 1f),
|
||
new Vector2(0f, -46f), Vector2.zero);
|
||
var title = CreateText("Title", header.transform, "AIBIS DREAM / DEVELOPMENT MODE", 22f, FontStyles.Bold);
|
||
SetAnchors(title.rectTransform, new Vector2(0f, 0f), new Vector2(1f, 1f),
|
||
new Vector2(18f, 0f), new Vector2(-64f, 0f));
|
||
title.alignment = TextAlignmentOptions.MidlineLeft;
|
||
var close = CreateButton("Close", header.transform, "×", 44f, 36f);
|
||
var closeRect = close.GetComponent<RectTransform>();
|
||
closeRect.anchorMin = new Vector2(1f, 0.5f);
|
||
closeRect.anchorMax = new Vector2(1f, 0.5f);
|
||
closeRect.pivot = new Vector2(1f, 0.5f);
|
||
closeRect.anchoredPosition = new Vector2(-5f, 0f);
|
||
closeRect.sizeDelta = new Vector2(44f, 36f);
|
||
|
||
var tabs = CreateHorizontal("Tabs", root.transform, 4f, new RectOffset(4, 4, 3, 3));
|
||
SetAnchors(tabs.GetComponent<RectTransform>(), new Vector2(0f, 1f), new Vector2(1f, 1f),
|
||
new Vector2(0f, -88f), new Vector2(0f, -46f));
|
||
var tabNames = new[] { "概览", "日志", "Yarn 变量", "存档槽位", "显示与控制" };
|
||
var tabButtons = tabNames.Select(name => CreateButton($"Tab {name}", tabs.transform, name, 0f, 34f)).ToArray();
|
||
foreach (var button in tabButtons)
|
||
{
|
||
var layout = button.GetComponent<LayoutElement>();
|
||
layout.flexibleWidth = 1f;
|
||
layout.preferredWidth = 0f;
|
||
}
|
||
|
||
var pageRoot = CreateUiObject("Pages", typeof(RectTransform));
|
||
pageRoot.transform.SetParent(root.transform, false);
|
||
SetAnchors(pageRoot.GetComponent<RectTransform>(), Vector2.zero, Vector2.one,
|
||
new Vector2(8f, 8f), new Vector2(-8f, -94f));
|
||
|
||
var overview = BuildOverviewPage(pageRoot.transform, out var overviewRefs);
|
||
var logs = BuildLogPage(pageRoot.transform, out var logRefs);
|
||
var variables = BuildVariablePage(pageRoot.transform, out var variableRefs);
|
||
var saves = BuildSavePage(pageRoot.transform, out var saveRefs);
|
||
var display = BuildDisplayPage(pageRoot.transform, out var displayRefs);
|
||
var pages = new[] { overview, logs, variables, saves, display };
|
||
foreach (var page in pages)
|
||
{
|
||
Stretch(page.GetComponent<RectTransform>());
|
||
page.SetActive(false);
|
||
}
|
||
|
||
var serialized = new SerializedObject(panel);
|
||
Set(serialized, "closeButton", close.GetComponent<Button>());
|
||
SetArray(serialized, "tabButtons", tabButtons.Select(item => (UnityEngine.Object)item.GetComponent<Button>()).ToArray());
|
||
SetArray(serialized, "tabPages", pages.Cast<UnityEngine.Object>().ToArray());
|
||
overviewRefs.Apply(serialized);
|
||
logRefs.Apply(serialized);
|
||
variableRefs.Apply(serialized);
|
||
saveRefs.Apply(serialized);
|
||
displayRefs.Apply(serialized);
|
||
serialized.ApplyModifiedPropertiesWithoutUndo();
|
||
return root;
|
||
}
|
||
|
||
private static GameObject BuildOverviewPage(Transform parent, out ReferenceSet refs)
|
||
{
|
||
refs = new ReferenceSet();
|
||
var page = CreateVertical("Overview Page", parent, 8f, new RectOffset(12, 12, 12, 12));
|
||
var header = CreateText("Section Title", page.transform, "运行状态", 20f, FontStyles.Bold);
|
||
AddLayout(header.gameObject, 32f);
|
||
var overview = CreateTextArea("Overview Text", page.transform, 520f, out _);
|
||
overview.fontSize = 17f;
|
||
overview.alignment = TextAlignmentOptions.TopLeft;
|
||
overview.enableWordWrapping = false;
|
||
var copy = CreateButton("Copy Diagnostics", page.transform, "复制诊断摘要", 180f, 38f);
|
||
var status = CreateText("Status", page.transform, string.Empty, 15f);
|
||
AddLayout(status.gameObject, 28f);
|
||
status.color = new Color(0.45f, 1f, 0.65f);
|
||
refs.Add("overviewText", overview);
|
||
refs.Add("overviewStatusText", status);
|
||
refs.Add("copyDiagnosticsButton", copy.GetComponent<Button>());
|
||
return page;
|
||
}
|
||
|
||
private static GameObject BuildLogPage(Transform parent, out ReferenceSet refs)
|
||
{
|
||
refs = new ReferenceSet();
|
||
var page = CreateVertical("Logs Page", parent, 6f, new RectOffset(8, 8, 8, 8));
|
||
var toolbar1 = CreateHorizontal("Filters", page.transform, 6f, new RectOffset(0, 0, 0, 0));
|
||
AddLayout(toolbar1, 38f);
|
||
var search = CreateInput("Log Search", toolbar1.transform, "搜索 message/context/stack...", 290f);
|
||
var level = CreateButton("Log Level", toolbar1.transform, "最低等级:全部", 150f, 34f);
|
||
var category = CreateButton("Log Category", toolbar1.transform, "类别:全部", 130f, 34f);
|
||
var pause = CreateToggle("Pause Log", toolbar1.transform, "暂停滚动", 110f, true);
|
||
pause.isOn = false;
|
||
|
||
var toolbar2 = CreateHorizontal("Actions", page.transform, 6f, new RectOffset(0, 0, 0, 0));
|
||
AddLayout(toolbar2, 36f);
|
||
var clear = CreateButton("Clear", toolbar2.transform, "清空内存", 100f, 32f);
|
||
var copyFiltered = CreateButton("Copy Filtered", toolbar2.transform, "复制过滤结果", 130f, 32f);
|
||
var copySelected = CreateButton("Copy Selected", toolbar2.transform, "复制选中/堆栈", 150f, 32f);
|
||
var count = CreateText("Count", toolbar2.transform, "缓冲 0", 14f);
|
||
AddFlexible(count.gameObject);
|
||
count.alignment = TextAlignmentOptions.MidlineRight;
|
||
|
||
var scroll = CreateList("Log List", page.transform, 390f, out var content, out var template);
|
||
var details = CreateTextArea("Log Details", page.transform, 150f, out _);
|
||
details.text = "未选择日志。";
|
||
details.fontSize = 14f;
|
||
|
||
refs.Add("logSearchInput", search);
|
||
refs.Add("logLevelButton", level.GetComponent<Button>());
|
||
refs.Add("logLevelLabel", level.GetComponentInChildren<TMP_Text>());
|
||
refs.Add("logCategoryButton", category.GetComponent<Button>());
|
||
refs.Add("logCategoryLabel", category.GetComponentInChildren<TMP_Text>());
|
||
refs.Add("pauseLogToggle", pause);
|
||
refs.Add("clearLogsButton", clear.GetComponent<Button>());
|
||
refs.Add("copyFilteredLogsButton", copyFiltered.GetComponent<Button>());
|
||
refs.Add("copySelectedLogButton", copySelected.GetComponent<Button>());
|
||
refs.Add("logCountText", count);
|
||
refs.Add("logDetailsText", details);
|
||
refs.Add("logScrollRect", scroll);
|
||
refs.Add("logContent", content);
|
||
refs.Add("logRowTemplate", template);
|
||
return page;
|
||
}
|
||
|
||
private static GameObject BuildVariablePage(Transform parent, out ReferenceSet refs)
|
||
{
|
||
refs = new ReferenceSet();
|
||
var page = CreateVertical("Variables Page", parent, 7f, new RectOffset(8, 8, 8, 8));
|
||
var toolbar = CreateHorizontal("Filters", page.transform, 6f, new RectOffset(0, 0, 0, 0));
|
||
AddLayout(toolbar, 40f);
|
||
var search = CreateInput("Variable Search", toolbar.transform, "搜索变量名或值...", 320f);
|
||
var scope = CreateButton("Variable Scope", toolbar.transform, "范围:全部", 130f, 34f);
|
||
var type = CreateButton("Variable Type", toolbar.transform, "类型:全部", 140f, 34f);
|
||
var count = CreateText("Count", toolbar.transform, "0 variables", 14f);
|
||
AddFlexible(count.gameObject);
|
||
count.alignment = TextAlignmentOptions.MidlineRight;
|
||
CreateList("Variable List", page.transform, 620f, out var content, out var template);
|
||
refs.Add("variableSearchInput", search);
|
||
refs.Add("variableScopeButton", scope.GetComponent<Button>());
|
||
refs.Add("variableScopeLabel", scope.GetComponentInChildren<TMP_Text>());
|
||
refs.Add("variableTypeButton", type.GetComponent<Button>());
|
||
refs.Add("variableTypeLabel", type.GetComponentInChildren<TMP_Text>());
|
||
refs.Add("variableCountText", count);
|
||
refs.Add("variableContent", content);
|
||
refs.Add("variableRowTemplate", template);
|
||
return page;
|
||
}
|
||
|
||
private static GameObject BuildSavePage(Transform parent, out ReferenceSet refs)
|
||
{
|
||
refs = new ReferenceSet();
|
||
var page = CreateVertical("Saves Page", parent, 6f, new RectOffset(8, 8, 8, 8));
|
||
var toolbar = CreateHorizontal("Toolbar", page.transform, 6f, new RectOffset(0, 0, 0, 0));
|
||
AddLayout(toolbar, 38f);
|
||
var reload = CreateButton("Reload Saves", toolbar.transform, "重新扫描", 110f, 34f);
|
||
var status = CreateText("Save Status", toolbar.transform, "Ready", 14f);
|
||
AddFlexible(status.gameObject);
|
||
status.alignment = TextAlignmentOptions.MidlineRight;
|
||
|
||
var slotColumn = CreateVertical("Official Slots", page.transform, 4f, new RectOffset(0, 0, 0, 0));
|
||
var slotLayout = slotColumn.AddComponent<LayoutElement>();
|
||
slotLayout.flexibleHeight = 1f;
|
||
var slotTitle = CreateText("Title", slotColumn.transform, "正式槽位(点击读档)", 16f, FontStyles.Bold);
|
||
AddLayout(slotTitle.gameObject, 28f);
|
||
CreateList("Slot List", slotColumn.transform, 500f, out var slotContent, out var slotTemplate);
|
||
|
||
var paths1 = CreateHorizontal("Paths 1", page.transform, 5f, new RectOffset(0, 0, 0, 0));
|
||
AddLayout(paths1, 34f);
|
||
var openSave = CreateButton("Open Saves", paths1.transform, "打开存档目录", 130f, 30f);
|
||
var copySave = CreateButton("Copy Saves", paths1.transform, "复制存档路径", 130f, 30f);
|
||
var openLog = CreateButton("Open Logs", paths1.transform, "打开日志目录", 130f, 30f);
|
||
var copyLog = CreateButton("Copy Logs", paths1.transform, "复制日志路径", 130f, 30f);
|
||
var openCurrent = CreateButton("Open Current Log", paths1.transform, "定位当前日志", 130f, 30f);
|
||
var copyCurrent = CreateButton("Copy Current Log", paths1.transform, "复制当前日志", 130f, 30f);
|
||
|
||
refs.Add("reloadSavesButton", reload.GetComponent<Button>());
|
||
refs.Add("saveStatusText", status);
|
||
refs.Add("slotContent", slotContent);
|
||
refs.Add("slotRowTemplate", slotTemplate);
|
||
refs.Add("openSaveFolderButton", openSave.GetComponent<Button>());
|
||
refs.Add("copySaveFolderButton", copySave.GetComponent<Button>());
|
||
refs.Add("openLogFolderButton", openLog.GetComponent<Button>());
|
||
refs.Add("copyLogFolderButton", copyLog.GetComponent<Button>());
|
||
refs.Add("openCurrentLogButton", openCurrent.GetComponent<Button>());
|
||
refs.Add("copyCurrentLogButton", copyCurrent.GetComponent<Button>());
|
||
return page;
|
||
}
|
||
|
||
private static GameObject BuildDisplayPage(Transform parent, out ReferenceSet refs)
|
||
{
|
||
refs = new ReferenceSet();
|
||
var page = CreateVertical("Display Page", parent, 10f, new RectOffset(18, 18, 18, 18));
|
||
var title = CreateText("Title", page.transform, "录制显示", 20f, FontStyles.Bold);
|
||
AddLayout(title.gameObject, 34f);
|
||
var cursor = CreateToggle("Show Cursor", page.transform, "显示游戏光标(面板打开时始终可见)", 600f, true);
|
||
var version = CreateToggle("Show Version", page.transform, "显示版本号", 600f, true);
|
||
var topBar = CreateToggle("Show Top Bar", page.transform, "显示 Top Bar", 600f, true);
|
||
var dialog = CreateToggle("Show Dialog", page.transform, "显示对话视觉(不停止 Yarn)", 600f, true);
|
||
var divider = CreateText("Control Title", page.transform, "会话与时间控制", 20f, FontStyles.Bold);
|
||
AddLayout(divider.gameObject, 44f);
|
||
var sessionButtons = CreateHorizontal("Session Buttons", page.transform, 8f, new RectOffset(0, 0, 0, 0));
|
||
AddLayout(sessionButtons, 42f);
|
||
var pause = CreateButton("Pause", sessionButtons.transform, "暂停", 120f, 36f);
|
||
var resume = CreateButton("Resume", sessionButtons.transform, "继续", 120f, 36f);
|
||
var speedButtons = CreateHorizontal("Speed Buttons", page.transform, 8f, new RectOffset(0, 0, 0, 0));
|
||
AddLayout(speedButtons, 42f);
|
||
var half = CreateButton("Half Speed", speedButtons.transform, "0.5×", 120f, 36f);
|
||
var normal = CreateButton("Normal Speed", speedButtons.transform, "1×", 120f, 36f);
|
||
var twice = CreateButton("Double Speed", speedButtons.transform, "2×", 120f, 36f);
|
||
var status = CreateText("Control Status", page.transform, "游戏保持运行;所有控制仍受 GameManager 状态约束。", 16f);
|
||
AddLayout(status.gameObject, 60f);
|
||
|
||
refs.Add("showCursorToggle", cursor);
|
||
refs.Add("showVersionToggle", version);
|
||
refs.Add("showTopBarToggle", topBar);
|
||
refs.Add("showDialogToggle", dialog);
|
||
refs.Add("pauseGameButton", pause.GetComponent<Button>());
|
||
refs.Add("resumeGameButton", resume.GetComponent<Button>());
|
||
refs.Add("halfSpeedButton", half.GetComponent<Button>());
|
||
refs.Add("normalSpeedButton", normal.GetComponent<Button>());
|
||
refs.Add("doubleSpeedButton", twice.GetComponent<Button>());
|
||
refs.Add("controlStatusText", status);
|
||
return page;
|
||
}
|
||
|
||
private static void ReplaceSceneInstance()
|
||
{
|
||
var scene = EditorSceneManager.OpenScene(ScenePath, OpenSceneMode.Single);
|
||
var infoPanel = scene.GetRootGameObjects()
|
||
.SelectMany(root => root.GetComponentsInChildren<Transform>(true))
|
||
.FirstOrDefault(item => item.name == "Info Panel");
|
||
if (infoPanel == null) throw new InvalidOperationException("Persistence/Info Panel not found.");
|
||
|
||
var existingPanels = scene.GetRootGameObjects()
|
||
.SelectMany(root => root.GetComponentsInChildren<DeveloperModePanel>(true))
|
||
.ToArray();
|
||
var existingInInfoPanel = existingPanels.FirstOrDefault(item => item.transform.parent == infoPanel);
|
||
var siblingIndex = existingInInfoPanel != null
|
||
? existingInInfoPanel.transform.GetSiblingIndex()
|
||
: infoPanel.childCount;
|
||
foreach (var existing in existingPanels)
|
||
{
|
||
UnityEngine.Object.DestroyImmediate(existing.gameObject);
|
||
}
|
||
|
||
var legacyPanels = scene.GetRootGameObjects()
|
||
.SelectMany(root => root.GetComponentsInChildren<Transform>(true))
|
||
.Where(item => item.name == "Recording Tool Panel")
|
||
.Select(item => item.gameObject)
|
||
.Distinct()
|
||
.ToArray();
|
||
foreach (var legacyPanel in legacyPanels)
|
||
{
|
||
UnityEngine.Object.DestroyImmediate(legacyPanel);
|
||
}
|
||
|
||
var variableBar = infoPanel.GetComponentsInChildren<Transform>(true)
|
||
.FirstOrDefault(item => item.name == "Varible Bar" || item.name == "Variable Bar");
|
||
if (variableBar != null) UnityEngine.Object.DestroyImmediate(variableBar.gameObject);
|
||
|
||
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PrefabPath);
|
||
if (prefab == null) throw new InvalidOperationException($"Prefab not found: {PrefabPath}");
|
||
var instance = (GameObject)PrefabUtility.InstantiatePrefab(prefab, scene);
|
||
instance.transform.SetParent(infoPanel, false);
|
||
instance.transform.SetSiblingIndex(Mathf.Clamp(siblingIndex, 0, infoPanel.childCount - 1));
|
||
instance.SetActive(false);
|
||
EditorSceneManager.MarkSceneDirty(scene);
|
||
EditorSceneManager.SaveScene(scene);
|
||
}
|
||
|
||
private static GameObject CreateUiObject(string name, params Type[] components)
|
||
{
|
||
var types = components.Contains(typeof(RectTransform))
|
||
? components
|
||
: new[] { typeof(RectTransform) }.Concat(components).ToArray();
|
||
var go = new GameObject(name, types);
|
||
go.layer = LayerMask.NameToLayer("UI");
|
||
return go;
|
||
}
|
||
|
||
private static GameObject CreatePanel(string name, Transform parent, Color color)
|
||
{
|
||
var go = CreateUiObject(name, typeof(CanvasRenderer), typeof(Image));
|
||
go.transform.SetParent(parent, false);
|
||
go.GetComponent<Image>().color = color;
|
||
return go;
|
||
}
|
||
|
||
private static GameObject CreateVertical(string name, Transform parent, float spacing, RectOffset padding)
|
||
{
|
||
var go = CreateUiObject(name, typeof(VerticalLayoutGroup));
|
||
go.transform.SetParent(parent, false);
|
||
var layout = go.GetComponent<VerticalLayoutGroup>();
|
||
layout.padding = padding;
|
||
layout.spacing = spacing;
|
||
layout.childAlignment = TextAnchor.UpperLeft;
|
||
layout.childControlWidth = true;
|
||
layout.childForceExpandWidth = true;
|
||
layout.childControlHeight = true;
|
||
layout.childForceExpandHeight = false;
|
||
return go;
|
||
}
|
||
|
||
private static GameObject CreateHorizontal(string name, Transform parent, float spacing, RectOffset padding)
|
||
{
|
||
var go = CreateUiObject(name, typeof(HorizontalLayoutGroup));
|
||
go.transform.SetParent(parent, false);
|
||
var layout = go.GetComponent<HorizontalLayoutGroup>();
|
||
layout.padding = padding;
|
||
layout.spacing = spacing;
|
||
layout.childAlignment = TextAnchor.MiddleLeft;
|
||
layout.childControlWidth = true;
|
||
layout.childControlHeight = true;
|
||
layout.childForceExpandWidth = false;
|
||
layout.childForceExpandHeight = true;
|
||
return go;
|
||
}
|
||
|
||
private static TMP_Text CreateText(string name, Transform parent, string value, float size,
|
||
FontStyles style = FontStyles.Normal)
|
||
{
|
||
var go = CreateUiObject(name, typeof(CanvasRenderer), typeof(TextMeshProUGUI));
|
||
go.transform.SetParent(parent, false);
|
||
var text = go.GetComponent<TextMeshProUGUI>();
|
||
text.font = AssetDatabase.LoadAssetAtPath<TMP_FontAsset>(FontAssetPath);
|
||
text.text = value;
|
||
text.fontSize = size;
|
||
text.fontStyle = style;
|
||
text.color = TextColor;
|
||
text.enableWordWrapping = true;
|
||
text.overflowMode = TextOverflowModes.Ellipsis;
|
||
text.raycastTarget = false;
|
||
return text;
|
||
}
|
||
|
||
private static TMP_Text CreateTextArea(string name, Transform parent, float height, out GameObject area)
|
||
{
|
||
area = CreatePanel(name, parent, Surface);
|
||
AddLayout(area, height);
|
||
area.AddComponent<RectMask2D>();
|
||
var text = CreateText("Text", area.transform, string.Empty, 15f);
|
||
SetAnchors(text.rectTransform, Vector2.zero, Vector2.one, new Vector2(9f, 7f), new Vector2(-9f, -7f));
|
||
text.alignment = TextAlignmentOptions.TopLeft;
|
||
text.overflowMode = TextOverflowModes.Truncate;
|
||
return text;
|
||
}
|
||
|
||
private static GameObject CreateButton(string name, Transform parent, string label, float width, float height)
|
||
{
|
||
var go = CreateUiObject(name, typeof(CanvasRenderer), typeof(Image), typeof(Button), typeof(LayoutElement));
|
||
go.transform.SetParent(parent, false);
|
||
var image = go.GetComponent<Image>();
|
||
image.color = new Color(0.09f, 0.18f, 0.23f, 1f);
|
||
var button = go.GetComponent<Button>();
|
||
button.targetGraphic = image;
|
||
var colors = button.colors;
|
||
colors.normalColor = Color.white;
|
||
colors.highlightedColor = new Color(0.75f, 1f, 1f, 1f);
|
||
colors.pressedColor = new Color(0.45f, 0.85f, 0.95f, 1f);
|
||
colors.disabledColor = new Color(0.35f, 0.4f, 0.42f, 0.65f);
|
||
button.colors = colors;
|
||
var layout = go.GetComponent<LayoutElement>();
|
||
if (width > 0f) layout.preferredWidth = width;
|
||
layout.preferredHeight = height;
|
||
var text = CreateText("Label", go.transform, label, 14f, FontStyles.Bold);
|
||
Stretch(text.rectTransform, new Vector2(5f, 2f), new Vector2(-5f, -2f));
|
||
text.alignment = TextAlignmentOptions.Center;
|
||
return go;
|
||
}
|
||
|
||
private static TMP_InputField CreateInput(string name, Transform parent, string placeholder, float width)
|
||
{
|
||
var root = CreateUiObject(name, typeof(CanvasRenderer), typeof(Image), typeof(TMP_InputField), typeof(LayoutElement));
|
||
root.transform.SetParent(parent, false);
|
||
root.GetComponent<Image>().color = new Color(0.035f, 0.065f, 0.085f, 1f);
|
||
var layout = root.GetComponent<LayoutElement>();
|
||
layout.preferredWidth = width;
|
||
layout.preferredHeight = 34f;
|
||
var viewport = CreateUiObject("Text Area", typeof(RectMask2D));
|
||
viewport.transform.SetParent(root.transform, false);
|
||
Stretch(viewport.GetComponent<RectTransform>(), new Vector2(8f, 3f), new Vector2(-8f, -3f));
|
||
var placeholderText = CreateText("Placeholder", viewport.transform, placeholder, 14f);
|
||
Stretch(placeholderText.rectTransform);
|
||
placeholderText.color = new Color(0.45f, 0.58f, 0.63f, 1f);
|
||
placeholderText.fontStyle = FontStyles.Italic;
|
||
placeholderText.alignment = TextAlignmentOptions.MidlineLeft;
|
||
var inputText = CreateText("Text", viewport.transform, string.Empty, 14f);
|
||
Stretch(inputText.rectTransform);
|
||
inputText.alignment = TextAlignmentOptions.MidlineLeft;
|
||
var input = root.GetComponent<TMP_InputField>();
|
||
input.textViewport = viewport.GetComponent<RectTransform>();
|
||
input.textComponent = inputText;
|
||
input.placeholder = placeholderText;
|
||
input.lineType = TMP_InputField.LineType.SingleLine;
|
||
return input;
|
||
}
|
||
|
||
private static Toggle CreateToggle(string name, Transform parent, string label, float width, bool initial)
|
||
{
|
||
var root = CreateUiObject(name, typeof(Toggle), typeof(LayoutElement));
|
||
root.transform.SetParent(parent, false);
|
||
var layout = root.GetComponent<LayoutElement>();
|
||
layout.preferredWidth = width;
|
||
layout.preferredHeight = 34f;
|
||
var box = CreatePanel("Background", root.transform, new Color(0.08f, 0.15f, 0.19f, 1f));
|
||
var boxRect = box.GetComponent<RectTransform>();
|
||
boxRect.anchorMin = boxRect.anchorMax = new Vector2(0f, 0.5f);
|
||
boxRect.pivot = new Vector2(0f, 0.5f);
|
||
boxRect.anchoredPosition = Vector2.zero;
|
||
boxRect.sizeDelta = new Vector2(24f, 24f);
|
||
var check = CreatePanel("Checkmark", box.transform, Accent);
|
||
Stretch(check.GetComponent<RectTransform>(), new Vector2(5f, 5f), new Vector2(-5f, -5f));
|
||
var text = CreateText("Label", root.transform, label, 15f);
|
||
SetAnchors(text.rectTransform, Vector2.zero, Vector2.one, new Vector2(34f, 0f), Vector2.zero);
|
||
text.alignment = TextAlignmentOptions.MidlineLeft;
|
||
var toggle = root.GetComponent<Toggle>();
|
||
toggle.targetGraphic = box.GetComponent<Image>();
|
||
toggle.graphic = check.GetComponent<Image>();
|
||
toggle.isOn = initial;
|
||
return toggle;
|
||
}
|
||
|
||
private static ScrollRect CreateList(string name, Transform parent, float height, out RectTransform content,
|
||
out DeveloperModeListRow template)
|
||
{
|
||
var root = CreatePanel(name, parent, new Color(0.02f, 0.035f, 0.05f, 1f));
|
||
var rootLayout = root.AddComponent<LayoutElement>();
|
||
rootLayout.preferredHeight = height;
|
||
rootLayout.flexibleHeight = 1f;
|
||
var scroll = root.AddComponent<ScrollRect>();
|
||
scroll.horizontal = false;
|
||
scroll.vertical = true;
|
||
scroll.movementType = ScrollRect.MovementType.Clamped;
|
||
scroll.scrollSensitivity = 24f;
|
||
|
||
var viewport = CreateUiObject("Viewport", typeof(RectMask2D));
|
||
viewport.transform.SetParent(root.transform, false);
|
||
Stretch(viewport.GetComponent<RectTransform>(), new Vector2(3f, 3f), new Vector2(-3f, -3f));
|
||
var contentObject = CreateUiObject("Content", typeof(VerticalLayoutGroup), typeof(ContentSizeFitter));
|
||
contentObject.transform.SetParent(viewport.transform, false);
|
||
content = contentObject.GetComponent<RectTransform>();
|
||
content.anchorMin = new Vector2(0f, 1f);
|
||
content.anchorMax = new Vector2(1f, 1f);
|
||
content.pivot = new Vector2(0.5f, 1f);
|
||
content.anchoredPosition = Vector2.zero;
|
||
content.sizeDelta = Vector2.zero;
|
||
var vertical = contentObject.GetComponent<VerticalLayoutGroup>();
|
||
vertical.spacing = 2f;
|
||
vertical.childControlWidth = true;
|
||
vertical.childForceExpandWidth = true;
|
||
vertical.childControlHeight = true;
|
||
vertical.childForceExpandHeight = false;
|
||
var fitter = contentObject.GetComponent<ContentSizeFitter>();
|
||
fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||
|
||
var row = CreateUiObject("Row Template", typeof(CanvasRenderer), typeof(Image), typeof(Button),
|
||
typeof(LayoutElement), typeof(DeveloperModeListRow));
|
||
row.transform.SetParent(content, false);
|
||
var rowImage = row.GetComponent<Image>();
|
||
rowImage.color = Surface;
|
||
var rowButton = row.GetComponent<Button>();
|
||
rowButton.targetGraphic = rowImage;
|
||
var rowLayout = row.GetComponent<LayoutElement>();
|
||
rowLayout.preferredHeight = 28f;
|
||
rowLayout.minHeight = 28f;
|
||
var rowText = CreateText("Label", row.transform, "Template", 13f);
|
||
Stretch(rowText.rectTransform, new Vector2(7f, 2f), new Vector2(-7f, -2f));
|
||
rowText.alignment = TextAlignmentOptions.MidlineLeft;
|
||
rowText.enableWordWrapping = false;
|
||
var rowSerialized = new SerializedObject(row.GetComponent<DeveloperModeListRow>());
|
||
Set(rowSerialized, "button", rowButton);
|
||
Set(rowSerialized, "label", rowText);
|
||
Set(rowSerialized, "background", rowImage);
|
||
rowSerialized.ApplyModifiedPropertiesWithoutUndo();
|
||
row.SetActive(false);
|
||
template = row.GetComponent<DeveloperModeListRow>();
|
||
scroll.viewport = viewport.GetComponent<RectTransform>();
|
||
scroll.content = content;
|
||
return scroll;
|
||
}
|
||
|
||
private static void SetAnchors(RectTransform rect, Vector2 min, Vector2 max, Vector2 offsetMin, Vector2 offsetMax)
|
||
{
|
||
rect.anchorMin = min;
|
||
rect.anchorMax = max;
|
||
rect.offsetMin = offsetMin;
|
||
rect.offsetMax = offsetMax;
|
||
}
|
||
|
||
private static void Stretch(RectTransform rect) => Stretch(rect, Vector2.zero, Vector2.zero);
|
||
|
||
private static void Stretch(RectTransform rect, Vector2 offsetMin, Vector2 offsetMax)
|
||
{
|
||
rect.anchorMin = Vector2.zero;
|
||
rect.anchorMax = Vector2.one;
|
||
rect.offsetMin = offsetMin;
|
||
rect.offsetMax = offsetMax;
|
||
}
|
||
|
||
private static void AddLayout(GameObject go, float height)
|
||
{
|
||
var layout = go.GetComponent<LayoutElement>() ?? go.AddComponent<LayoutElement>();
|
||
layout.preferredHeight = height;
|
||
}
|
||
|
||
private static void AddFlexible(GameObject go)
|
||
{
|
||
var layout = go.GetComponent<LayoutElement>() ?? go.AddComponent<LayoutElement>();
|
||
layout.flexibleWidth = 1f;
|
||
}
|
||
|
||
private static void Set(SerializedObject serialized, string name, UnityEngine.Object value)
|
||
{
|
||
var property = serialized.FindProperty(name)
|
||
?? throw new MissingFieldException(serialized.targetObject.GetType().Name, name);
|
||
property.objectReferenceValue = value;
|
||
}
|
||
|
||
private static void SetArray(SerializedObject serialized, string name, UnityEngine.Object[] values)
|
||
{
|
||
var property = serialized.FindProperty(name)
|
||
?? throw new MissingFieldException(serialized.targetObject.GetType().Name, name);
|
||
property.arraySize = values.Length;
|
||
for (var i = 0; i < values.Length; i++)
|
||
{
|
||
property.GetArrayElementAtIndex(i).objectReferenceValue = values[i];
|
||
}
|
||
}
|
||
|
||
private sealed class ReferenceSet
|
||
{
|
||
private readonly System.Collections.Generic.List<(string Name, UnityEngine.Object Value)> _values = new();
|
||
|
||
public void Add(string name, UnityEngine.Object value) => _values.Add((name, value));
|
||
|
||
public void Apply(SerializedObject serialized)
|
||
{
|
||
foreach (var value in _values)
|
||
{
|
||
Set(serialized, value.Name, value.Value);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
#endif
|