Files
aibis-dream/Assets/Editor/DeveloperMode/DeveloperModePanelBuilder.cs
T
2026-07-20 14:29:47 +08:00

632 lines
33 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.
#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 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 search = CreateInput("Save Search", toolbar.transform, "搜索开发检查点...", 350f);
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 columns = CreateHorizontal("Save Columns", page.transform, 8f, new RectOffset(0, 0, 0, 0));
var columnsLayout = columns.AddComponent<LayoutElement>();
columnsLayout.flexibleHeight = 1f;
var slotColumn = CreateVertical("Official Slots", columns.transform, 4f, new RectOffset(0, 0, 0, 0));
var slotLayout = slotColumn.AddComponent<LayoutElement>();
slotLayout.preferredWidth = 330f;
slotLayout.flexibleHeight = 1f;
var slotTitle = CreateText("Title", slotColumn.transform, "正式槽位(点击读档)", 16f, FontStyles.Bold);
AddLayout(slotTitle.gameObject, 28f);
CreateList("Slot List", slotColumn.transform, 430f, out var slotContent, out var slotTemplate);
var devColumn = CreateVertical("Dev Saves", columns.transform, 4f, new RectOffset(0, 0, 0, 0));
var devLayout = devColumn.AddComponent<LayoutElement>();
devLayout.flexibleWidth = 1f;
devLayout.flexibleHeight = 1f;
var devTitle = CreateText("Title", devColumn.transform, "开发检查点(严格恢复)", 16f, FontStyles.Bold);
AddLayout(devTitle.gameObject, 28f);
CreateList("Dev Save List", devColumn.transform, 430f, out var devContent, out var devTemplate);
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 paths2 = CreateHorizontal("Paths 2", page.transform, 5f, new RectOffset(0, 0, 0, 0));
AddLayout(paths2, 34f);
var openDev = CreateButton("Open Dev Saves", paths2.transform, "打开检查点目录", 140f, 30f);
var copyDev = CreateButton("Copy Dev Saves", paths2.transform, "复制检查点路径", 140f, 30f);
var openCurrent = CreateButton("Open Current Log", paths2.transform, "定位当前日志", 130f, 30f);
var copyCurrent = CreateButton("Copy Current Log", paths2.transform, "复制当前日志", 130f, 30f);
refs.Add("saveSearchInput", search);
refs.Add("reloadSavesButton", reload.GetComponent<Button>());
refs.Add("saveStatusText", status);
refs.Add("slotContent", slotContent);
refs.Add("slotRowTemplate", slotTemplate);
refs.Add("devSaveContent", devContent);
refs.Add("devSaveRowTemplate", devTemplate);
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("openDevSaveFolderButton", openDev.GetComponent<Button>());
refs.Add("copyDevSaveFolderButton", copyDev.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 = TMP_Settings.defaultFontAsset;
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