Files
DuckovMods/SceneView/ControlUtilities.cs

799 lines
34 KiB
C#
Raw Permalink 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 UnityEngine.Events;
using UnityEngine.UI;
using UnityEngine;
using System;
using System.Collections.Generic;
using TMPro;
namespace SceneView
{
[Serializable]
public struct RectTransformConfig
{
public Vector2 AnchorMin;
public Vector2 AnchorMax;
public Vector2 AnchoredPosition;
public Vector2 SizeDelta;
public Vector2 OffsetMin;
public Vector2 OffsetMax;
public Vector2 Pivot;
// 默认配置
public static readonly RectTransformConfig Default = new RectTransformConfig(
Vector2.up, // 锚点最小:左上角 (0, 1)
Vector2.up, // 锚点最大:左上角 (0, 1)
Vector2.zero,
Vector2.zero,
Vector2.zero,
Vector2.zero,
new Vector2(0.5f, 0.5f) // 默认居中轴心点
);
// 填充父级的配置
public static readonly RectTransformConfig FillParent = new RectTransformConfig(
Vector2.zero, // 锚点最小:左下角 (0, 0)
Vector2.one, // 锚点最大:右上角 (1, 1)
Vector2.zero,
Vector2.zero, // sizeDelta为零表示通过offsetMin/Max来控制尺寸
Vector2.zero,
Vector2.zero,
new Vector2(0.5f, 0.5f)
);
public RectTransformConfig(
Vector2 anchorMin,
Vector2 anchorMax,
Vector2 anchoredPosition,
Vector2 sizeDelta,
Vector2 offsetMin,
Vector2 offsetMax,
Vector2 pivot)
{
AnchorMin = anchorMin;
AnchorMax = anchorMax;
AnchoredPosition = anchoredPosition;
SizeDelta = sizeDelta;
OffsetMin = offsetMin;
OffsetMax = offsetMax;
Pivot = pivot;
}
}
[Serializable]
public struct ButtonConfig
{
public RectTransformConfig RectConfig;
public Color BackgroundColor;
public string Text;
public int FontSize;
public Color TextColor;
public bool RaycastTarget;
public static readonly ButtonConfig Default = new ButtonConfig(
RectTransformConfig.Default,
new Color(0.2f, 0.2f, 0.2f, 1f),
"Button",
18,
Color.white,
true
);
public ButtonConfig(
RectTransformConfig rectConfig,
Color backgroundColor,
string text,
int fontSize,
Color textColor,
bool raycastTarget = true)
{
RectConfig = rectConfig;
BackgroundColor = backgroundColor;
Text = text;
FontSize = fontSize;
TextColor = textColor;
RaycastTarget = raycastTarget;
}
}
[Serializable]
public struct TextConfig
{
public RectTransformConfig RectConfig;
public string Text;
public int FontSize;
public Color TextColor;
public TextAlignmentOptions Alignment;
public bool RaycastTarget;
public static readonly TextConfig Default = new TextConfig(
rectConfig: RectTransformConfig.Default,
text: "New Text",
fontSize: 18,
textColor: Color.white,
alignment: TextAlignmentOptions.Center,
raycastTarget: false
);
public TextConfig(
string text,
int fontSize,
Color textColor,
TextAlignmentOptions alignment,
bool raycastTarget,
RectTransformConfig rectConfig)
{
RectConfig = rectConfig;
Text = text;
FontSize = fontSize;
TextColor = textColor;
Alignment = alignment;
RaycastTarget = raycastTarget;
}
}
[Serializable]
public struct ScrollViewConfig
{
public RectTransformConfig RectConfig; // 新增ScrollView自身的RectTransform配置
public bool Vertical;
public bool Horizontal;
public Color BackgroundColor;
public Vector2 ContentPadding; // 内容区域的内边距(上下左右)
public static readonly ScrollViewConfig Default = new ScrollViewConfig
{
RectConfig = new RectTransformConfig(
new Vector2(0.5f, 0.5f), // 锚点最小:中心 (0.5, 0.5)
new Vector2(0.5f, 0.5f), // 锚点最大:中心 (0.5, 0.5)
Vector2.zero,
new Vector2(400, 300), // 默认尺寸
Vector2.zero,
Vector2.zero,
new Vector2(0.5f, 0.5f)
),
Vertical = true,
Horizontal = false,
BackgroundColor = new Color(0.1f, 0.1f, 0.1f, 0.8f),
ContentPadding = new Vector2(10, 10) // (horizontal, vertical)
};
}
[Serializable]
public struct InputFieldConfig
{
public RectTransformConfig RectConfig;
public Color BackgroundColor;
public string PlaceholderText;
public int PlaceholderFontSize;
public Color PlaceholderTextColor;
public Color TextColor;
public int FontSize;
public TextAlignmentOptions TextAlignment;
public TMP_InputField.CharacterValidation CharacterValidation;
public int CharacterLimit;
public static readonly InputFieldConfig Default = new InputFieldConfig(
RectTransformConfig.Default,
new Color(0.2f, 0.2f, 0.2f, 1f),
"Enter text here",
14,
new Color(0.7f, 0.7f, 0.7f, 1f),
Color.white,
18,
TextAlignmentOptions.Left,
TMP_InputField.CharacterValidation.None,
0
);
public InputFieldConfig(
RectTransformConfig rectConfig,
Color backgroundColor,
string placeholderText,
int placeholderFontSize,
Color placeholderTextColor,
Color textColor,
int fontSize,
TextAlignmentOptions textAlignment,
TMP_InputField.CharacterValidation characterValidation = TMP_InputField.CharacterValidation.None,
int characterLimit = 0)
{
RectConfig = rectConfig;
BackgroundColor = backgroundColor;
PlaceholderText = placeholderText;
PlaceholderFontSize = placeholderFontSize;
PlaceholderTextColor = placeholderTextColor;
TextColor = textColor;
FontSize = fontSize;
TextAlignment = textAlignment;
CharacterValidation = characterValidation;
CharacterLimit = characterLimit;
}
}
[Serializable]
public struct LabeledInputFieldConfig
{
public RectTransformConfig RectConfig; // 整个控件(标签+输入框)的配置
public string LabelText;
public int LabelFontSize;
public Color LabelTextColor;
public float LabelWidth; // 标签部分的固定宽度
public float Spacing; // 标签和输入框之间的间距
public InputFieldConfig InputFieldConfig; // 复用已有的输入框配置
public static readonly LabeledInputFieldConfig Default = new LabeledInputFieldConfig
{
RectConfig = new RectTransformConfig(
Vector2.up, // 锚点最小:左上角 (0, 1)
Vector2.one, // 锚点最大:右上角 (1, 1),默认水平拉伸
Vector2.zero,
new Vector2(0, 30), // 默认高度30
Vector2.zero,
Vector2.zero,
new Vector2(0.5f, 1) // 轴心点:顶部居中
),
LabelText = "Label",
LabelFontSize = 18,
LabelTextColor = Color.white,
LabelWidth = 100f,
Spacing = 10f,
InputFieldConfig = InputFieldConfig.Default
};
}
[Serializable]
public struct DropdownConfig
{
public RectTransformConfig RectConfig;
public List<string> Options;
public Color BackgroundColor;
public int CaptionFontSize; // 下拉框当前选中项的文本大小
public Color CaptionTextColor;
public int ItemFontSize; // 下拉列表每一项的文本大小
public Color ItemTextColor;
public static readonly DropdownConfig Default = new DropdownConfig(
new RectTransformConfig(
new Vector2(0.5f, 0.5f), // 锚点最小:中心 (0.5, 0.5)
new Vector2(0.5f, 0.5f), // 锚点最大:中心 (0.5, 0.5)
Vector2.zero,
new Vector2(160, 30),
Vector2.zero,
Vector2.zero,
new Vector2(0.5f, 0.5f)
),
new List<string> { "Option A", "Option B", "Option C" },
new Color(0.2f, 0.2f, 0.2f, 1f),
18,
Color.white,
16,
Color.white
);
public DropdownConfig(RectTransformConfig rectConfig, List<string> options, Color backgroundColor,
int captionFontSize, Color captionTextColor, int itemFontSize, Color itemTextColor)
{
RectConfig = rectConfig;
Options = options;
BackgroundColor = backgroundColor;
CaptionFontSize = captionFontSize;
CaptionTextColor = captionTextColor;
ItemFontSize = itemFontSize;
ItemTextColor = itemTextColor;
}
}
[Serializable]
public struct LabeledDropdownConfig
{
public RectTransformConfig RectConfig;
public string LabelText;
public int LabelFontSize;
public Color LabelTextColor;
public float LabelWidth;
public float Spacing;
public DropdownConfig DropdownConfig;
public static readonly LabeledDropdownConfig Default = new LabeledDropdownConfig
{
RectConfig = new RectTransformConfig(
Vector2.up, // 锚点最小:左上角 (0, 1)
Vector2.one, // 锚点最大:右上角 (1, 1)
Vector2.zero,
new Vector2(0, 30),
Vector2.zero,
Vector2.zero,
new Vector2(0.5f, 1)
),
LabelText = "Label",
LabelFontSize = 18,
LabelTextColor = Color.white,
LabelWidth = 100f,
Spacing = 10f,
DropdownConfig = DropdownConfig.Default
};
}
[Serializable]
public struct ToggleConfig
{
public RectTransformConfig RectConfig;
public string LabelText;
public int LabelFontSize;
public Color LabelTextColor;
public Color BackgroundColor;
public Color CheckmarkColor;
public float Spacing; // 开关图形和标签之间的间距
public bool IsOnByDefault;
public static readonly ToggleConfig Default = new ToggleConfig
{
RectConfig = new RectTransformConfig(
Vector2.up, // 锚点最小:左上角 (0, 1)
Vector2.up, // 锚点最大:左上角 (0, 1)
Vector2.zero,
new Vector2(160, 20),
Vector2.zero,
Vector2.zero,
new Vector2(0.5f, 0.5f)
),
LabelText = "Toggle",
LabelFontSize = 18,
LabelTextColor = Color.white,
BackgroundColor = new Color(0.2f, 0.2f, 0.2f, 1f),
CheckmarkColor = new Color(0.1f, 0.6f, 1f, 1f),
Spacing = 10f,
IsOnByDefault = false
};
}
public static class ControlUtilities
{
// 通用方法:将 RectTransformConfig 应用于 RectTransform
private static void ApplyRectTransformConfig(RectTransform rectTransform, RectTransformConfig config)
{
rectTransform.anchorMin = config.AnchorMin;
rectTransform.anchorMax = config.AnchorMax;
rectTransform.pivot = config.Pivot;
// 当sizeDelta为Vector2.zero时我们假定用户希望通过offsetMin/Max来拉伸元素
// 否则使用anchoredPosition和sizeDelta来定位和设置尺寸
if (config.SizeDelta == Vector2.zero && (config.OffsetMin != Vector2.zero ||
config.OffsetMax != Vector2.zero ||
(config.AnchorMin == Vector2.zero &&
config.AnchorMax == Vector2.one)))
{
rectTransform.offsetMin = config.OffsetMin;
rectTransform.offsetMax = config.OffsetMax;
}
else
{
rectTransform.anchoredPosition = config.AnchoredPosition;
rectTransform.sizeDelta = config.SizeDelta;
}
}
// ========================
// 矩形对象创建
// ========================
/// <summary>
/// 创建一个带有 RectTransform 组件的空 GameObject。
/// </summary>
/// <param name="parent">父级 Transform。</param>
/// <param name="name">GameObject 的名称。</param>
/// <param name="config">RectTransform 的配置。</param>
/// <returns>创建的 RectTransform 组件。</returns>
public static RectTransform CreateRect(Transform parent, string name, RectTransformConfig config)
{
var obj = new GameObject(name);
var rect = obj.AddComponent<RectTransform>();
rect.SetParent(parent, false); // false表示不保留世界坐标而是以父级为基准
ApplyRectTransformConfig(rect, config);
return rect;
}
// ========================
// 按钮创建
// ========================
public static (Button? button, TextMeshProUGUI? text) CreateButton(RectTransform? parent, ButtonConfig config,
UnityAction? onClick)
{
// 使用 CreateRect 创建按钮根对象并应用配置
var btnRect = CreateRect(parent, config.Text + "Button", config.RectConfig);
var button = btnRect.gameObject.AddComponent<Button>();
var image = btnRect.gameObject.AddComponent<Image>();
image.color = config.BackgroundColor;
button.image = image;
// 创建文本子对象,并使其填充按钮
var txtRect = CreateRect(btnRect, "Text (TMP)", RectTransformConfig.FillParent);
var tmpText = txtRect.gameObject.AddComponent<TextMeshProUGUI>();
tmpText.text = config.Text;
tmpText.color = config.TextColor;
tmpText.alignment = TextAlignmentOptions.Center;
tmpText.fontSize = config.FontSize;
tmpText.raycastTarget = config.RaycastTarget; // 文本RaycastTarget通常为false除非文本本身需要互动
if (onClick != null)
button.onClick.AddListener(onClick);
return (button, tmpText);
}
// ========================
// 文本创建(重载)
// ========================
public static TextMeshProUGUI CreateText(Transform parent, string text)
{
var config = TextConfig.Default;
config.Text = text;
return CreateText(parent, config);
}
public static TextMeshProUGUI CreateText(Transform parent, TextConfig config)
{
// 使用 CreateRect 创建文本对象并应用配置
var textRect = CreateRect(parent, config.Text + "Text", config.RectConfig);
var tmpText = textRect.gameObject.AddComponent<TextMeshProUGUI>();
tmpText.text = config.Text;
tmpText.color = config.TextColor;
tmpText.alignment = config.Alignment;
tmpText.fontSize = config.FontSize;
tmpText.raycastTarget = config.RaycastTarget;
return tmpText;
}
// ========================
// 滚动视图创建
// ========================
public static (ScrollRect scrollRect, RectTransform content) CreateScrollView(
Transform parent,
ScrollViewConfig config)
{
// 1. ScrollView 根对象
var scrollViewRect = CreateRect(parent, "ScrollView", config.RectConfig);
var scrollView = scrollViewRect.gameObject.AddComponent<ScrollRect>();
var scrollViewImage = scrollViewRect.gameObject.AddComponent<Image>();
scrollViewImage.color = config.BackgroundColor;
scrollViewImage.raycastTarget = true;
// 2. Viewport 子对象 (始终填充 ScrollView)
var viewportRect = CreateRect(scrollViewRect, "Viewport", RectTransformConfig.FillParent);
viewportRect.gameObject.AddComponent<RectMask2D>(); // 或 Mask但 RectMask2D 性能更好
var viewportImage = viewportRect.gameObject.AddComponent<Image>();
viewportImage.color = Color.clear; // 透明背景
// 3. Content 子对象
var contentRect =
CreateRect(viewportRect, "Content", RectTransformConfig.Default); // 初始使用Default后续会根据滚动方向调整
// 设置 Content 的锚点:根据滚动方向决定
if (config.Vertical && !config.Horizontal)
{
// 垂直滚动:宽度拉满,高度自适应
contentRect.anchorMin = new Vector2(0, 1);
contentRect.anchorMax = new Vector2(1, 1);
contentRect.pivot = new Vector2(0.5f, 1);
contentRect.anchoredPosition = new Vector2(0, -config.ContentPadding.y);
contentRect.sizeDelta = new Vector2(-2 * config.ContentPadding.x, 0); // 左右留边距
// 添加 VerticalLayoutGroup 通常是需要的,但这里只创建基础结构
}
else if (config.Horizontal && !config.Vertical)
{
// 水平滚动:高度拉满,宽度自适应
contentRect.anchorMin = new Vector2(0, 0);
contentRect.anchorMax = new Vector2(0, 1);
contentRect.pivot = new Vector2(0, 0.5f);
contentRect.anchoredPosition = new Vector2(config.ContentPadding.x, 0);
contentRect.sizeDelta = new Vector2(0, -2 * config.ContentPadding.y);
// 添加 HorizontalLayoutGroup 通常是需要的
}
else
{
// 双向滚动:自由布局,通常锚点设为左上角
contentRect.anchorMin = new Vector2(0, 1);
contentRect.anchorMax = new Vector2(0, 1);
contentRect.pivot = new Vector2(0, 1);
contentRect.anchoredPosition = new Vector2(config.ContentPadding.x, -config.ContentPadding.y);
contentRect.sizeDelta = new Vector2(0, 0); // 自适应
}
// 4. 关联 ScrollRect
scrollView.viewport = viewportRect;
scrollView.content = contentRect;
scrollView.vertical = config.Vertical;
scrollView.horizontal = config.Horizontal;
scrollView.movementType = ScrollRect.MovementType.Elastic;
scrollView.inertia = true;
scrollView.decelerationRate = 0.135f; // 默认值
return (scrollView, contentRect);
}
// ========================
// 文本编辑器创建
// ========================
public static (TMP_InputField inputField, TextMeshProUGUI text) CreateInputField(Transform parent,
InputFieldConfig config)
{
// 使用 CreateRect 创建输入框根对象并应用配置
var inputFieldRect = CreateRect(parent, "InputField", config.RectConfig);
var inputField = inputFieldRect.gameObject.AddComponent<TMP_InputField>();
// 背景图像
var backgroundImage = inputFieldRect.gameObject.AddComponent<Image>();
// 注意Resources.Load 会在运行时加载,推荐通过 Inspector 赋值或使用 Addressables
// 为了保持原有功能暂时保留如果提示找不到请确保路径正确或导入默认UI资源
backgroundImage.sprite = Resources.Load<Sprite>("UI/Skins/Background");
backgroundImage.type = Image.Type.Sliced;
backgroundImage.color = config.BackgroundColor;
inputField.image = backgroundImage; // 设置TMP_InputField的图片引用
// 输入框文本组件 (始终填充输入框)
var textRect = CreateRect(inputFieldRect, "Text (TMP)", RectTransformConfig.FillParent);
var tmpText = textRect.gameObject.AddComponent<TextMeshProUGUI>();
tmpText.text = "";
tmpText.color = config.TextColor;
tmpText.alignment = config.TextAlignment;
tmpText.fontSize = config.FontSize;
tmpText.raycastTarget = false; // 输入框内的文本通常不需要射线检测
inputField.textComponent = tmpText; // 设置TMP_InputField的文字组件引用
// 占位符文本组件 (始终填充输入框)
var placeholderRect = CreateRect(inputFieldRect, "Placeholder", RectTransformConfig.FillParent);
var placeholderText = placeholderRect.gameObject.AddComponent<TextMeshProUGUI>();
placeholderText.text = config.PlaceholderText;
placeholderText.color = config.PlaceholderTextColor;
placeholderText.alignment = config.TextAlignment;
placeholderText.fontSize = config.PlaceholderFontSize;
placeholderText.raycastTarget = false; // 占位符通常不需要射线检测
inputField.placeholder = placeholderText; // 设置TMP_InputField的占位符引用
// 配置输入字段属性
inputField.characterValidation = config.CharacterValidation;
inputField.characterLimit = config.CharacterLimit;
return (inputField, tmpText);
}
/// <summary>
/// 创建一个网格布局容器,用于自动排列子对象。
/// </summary>
/// <param name="parent">父级 Transform。</param>
/// <param name="name">GameObject 名称。</param>
/// <param name="rectConfig">容器的 RectTransform 配置。</param>
/// <param name="constraintCount">每行或每列的元素数量。</param>
/// <param name="cellSize">每个单元格的大小。</param>
/// <param name="spacing">单元格之间的间距。</param>
/// <param name="constraint">约束类型(按行数或列数)。</param>
/// <returns>创建的网格容器的 RectTransform。</returns>
public static RectTransform CreateGridLayoutContainer(
Transform parent,
string name,
RectTransformConfig rectConfig,
int constraintCount,
Vector2 cellSize,
Vector2 spacing,
GridLayoutGroup.Constraint constraint = GridLayoutGroup.Constraint.FixedColumnCount)
{
var containerRect = CreateRect(parent, name, rectConfig);
var gridLayout = containerRect.gameObject.AddComponent<GridLayoutGroup>();
gridLayout.constraint = constraint;
if (constraint == GridLayoutGroup.Constraint.FixedColumnCount)
gridLayout.constraintCount = constraintCount;
else if (constraint == GridLayoutGroup.Constraint.FixedRowCount)
gridLayout.constraintCount = constraintCount;
gridLayout.cellSize = cellSize;
gridLayout.spacing = spacing;
return containerRect;
}
/// <summary>
/// 创建一个带标签的输入框(左边文字,右边输入)。
/// </summary>
/// <param name="parent">父级 Transform。</param>
/// <param name="config">控件配置。</param>
/// <returns>包含根对象、标签和输入框的元组。</returns>
public static (RectTransform root, TextMeshProUGUI label, TMP_InputField inputField) CreateLabeledInputField(
Transform parent, LabeledInputFieldConfig config)
{
// 1. 创建根容器并添加水平布局
var rootRect = CreateRect(parent, config.LabelText + " LabeledInput", config.RectConfig);
var layoutGroup = rootRect.gameObject.AddComponent<HorizontalLayoutGroup>();
layoutGroup.childControlWidth = true;
layoutGroup.childControlHeight = true;
layoutGroup.childForceExpandWidth = false;
layoutGroup.spacing = config.Spacing;
// 2. 创建标签
var labelConfig = new TextConfig(
config.LabelText,
config.LabelFontSize,
config.LabelTextColor,
TextAlignmentOptions.Left,
false,
RectTransformConfig.Default // RectTransform由LayoutGroup控制
);
var label = CreateText(rootRect, labelConfig);
var labelLayout = label.gameObject.AddComponent<LayoutElement>();
labelLayout.preferredWidth = config.LabelWidth;
labelLayout.flexibleWidth = 0; // 不参与弹性宽度分配
// 3. 创建输入框
var (inputField, _) = CreateInputField(rootRect, config.InputFieldConfig);
var inputLayout = inputField.gameObject.AddComponent<LayoutElement>();
inputLayout.flexibleWidth = 1; // 占据剩余所有宽度
return (rootRect, label, inputField);
}
/// <summary>
/// 创建一个下拉框控件。
/// </summary>
/// <param name="parent">父级 Transform。</param>
/// <param name="config">控件配置。</param>
/// <returns>包含下拉框组件和其主标签的元组。</returns>
public static (TMP_Dropdown dropdown, TextMeshProUGUI captionText) CreateDropdown(Transform parent,
DropdownConfig config)
{
// Unity 从代码创建 Dropdown 比较繁琐,因为它依赖于一个预设的模板结构。
// 以下代码将手动构建这个结构。
// 1. Root Dropdown Object
var dropdownRect = CreateRect(parent, "Dropdown", config.RectConfig);
var dropdownImage = dropdownRect.gameObject.AddComponent<Image>();
dropdownImage.color = config.BackgroundColor;
var dropdown = dropdownRect.gameObject.AddComponent<TMP_Dropdown>();
// 2. Label (Caption Text)
var labelRect = CreateRect(dropdownRect, "Label", RectTransformConfig.FillParent);
labelRect.offsetMin = new Vector2(10, 0);
labelRect.offsetMax = new Vector2(-25, 0);
var labelText = labelRect.gameObject.AddComponent<TextMeshProUGUI>();
labelText.alignment = TextAlignmentOptions.Left;
labelText.color = config.CaptionTextColor;
labelText.fontSize = config.CaptionFontSize;
dropdown.captionText = labelText;
// 3. Arrow
var arrowRect = CreateRect(dropdownRect, "Arrow", new RectTransformConfig(
new Vector2(1, 0.5f), new Vector2(1, 0.5f), new Vector2(-15, 0), new Vector2(20, 20), Vector2.zero,
Vector2.zero, new Vector2(0.5f, 0.5f)
));
var arrowImage = arrowRect.gameObject.AddComponent<Image>();
arrowImage.color = new Color(0.8f, 0.8f, 0.8f); // 默认箭头颜色
// 4. Template (Scroll View, the dropdown list) - 必须默认禁用
var templateRect = CreateRect(dropdownRect, "Template", new RectTransformConfig(
new Vector2(0, 0), new Vector2(1, 0), new Vector2(0, 2), new Vector2(0, 150), Vector2.zero,
Vector2.zero, new Vector2(0.5f, 1)
));
var templateImage = templateRect.gameObject.AddComponent<Image>();
templateImage.color = config.BackgroundColor;
var scrollRect = templateRect.gameObject.AddComponent<ScrollRect>();
scrollRect.movementType = ScrollRect.MovementType.Clamped;
templateRect.gameObject.SetActive(false);
dropdown.template = templateRect;
// 5. Viewport
var viewportRect = CreateRect(templateRect, "Viewport", RectTransformConfig.FillParent);
viewportRect.gameObject.AddComponent<RectMask2D>();
scrollRect.viewport = viewportRect;
// 6. Content
var contentRect = CreateRect(viewportRect, "Content", new RectTransformConfig(
new Vector2(0, 1), new Vector2(1, 1), Vector2.zero, new Vector2(0, 28), Vector2.zero, Vector2.zero,
new Vector2(0.5f, 1)
));
scrollRect.content = contentRect;
// 7. Item (The template for each option)
var itemRect = CreateRect(contentRect, "Item", new RectTransformConfig(
new Vector2(0, 1), new Vector2(1, 1), Vector2.zero, new Vector2(0, 20), Vector2.zero, Vector2.zero,
new Vector2(0.5f, 1)
));
var itemToggle = itemRect.gameObject.AddComponent<Toggle>();
itemToggle.targetGraphic = itemRect.gameObject.AddComponent<Image>(); // Add an image to be the background
var itemLabelRect = CreateRect(itemRect, "Item Label", RectTransformConfig.FillParent);
itemLabelRect.offsetMin = new Vector2(10, 0);
itemLabelRect.offsetMax = new Vector2(-10, 0);
var itemLabel = itemLabelRect.gameObject.AddComponent<TextMeshProUGUI>();
itemLabel.color = config.ItemTextColor;
itemLabel.fontSize = config.ItemFontSize;
dropdown.itemText = itemLabel;
// 8. Populate options
dropdown.ClearOptions();
dropdown.AddOptions(config.Options);
return (dropdown, labelText);
}
/// <summary>
/// 创建一个带标签的下拉框(左边文字,右边下拉)。
/// </summary>
/// <param name="parent">父级 Transform。</param>
/// <param name="config">控件配置。</param>
/// <returns>包含根对象、标签和下拉框的元组。</returns>
public static (RectTransform root, TextMeshProUGUI label, TMP_Dropdown dropdown) CreateLabeledDropdown(
Transform parent, LabeledDropdownConfig config)
{
// 1. 创建根容器并添加水平布局
var rootRect = CreateRect(parent, config.LabelText + " LabeledDropdown", config.RectConfig);
var layoutGroup = rootRect.gameObject.AddComponent<HorizontalLayoutGroup>();
layoutGroup.childControlWidth = true;
layoutGroup.childControlHeight = true;
layoutGroup.childForceExpandWidth = true;
layoutGroup.spacing = config.Spacing;
// 2. 创建标签
var labelConfig = new TextConfig(
config.LabelText,
config.LabelFontSize,
config.LabelTextColor,
TextAlignmentOptions.Left,
false,
RectTransformConfig.Default
);
var label = CreateText(rootRect, labelConfig);
var labelLayout = label.gameObject.AddComponent<LayoutElement>();
labelLayout.preferredWidth = config.LabelWidth;
labelLayout.flexibleWidth = 0;
// 3. 创建下拉框
var (dropdown, _) = CreateDropdown(rootRect, config.DropdownConfig);
var dropdownLayout = dropdown.gameObject.AddComponent<LayoutElement>();
dropdownLayout.flexibleWidth = 1;
return (rootRect, label, dropdown);
}
/// <summary>
/// 创建一个切换开关控件。
/// </summary>
/// <param name="parent">父级 Transform。</param>
/// <param name="config">控件配置。</param>
/// <returns>包含Toggle组件、标签和勾选标记图像的元组。</returns>
public static (Toggle toggle, TextMeshProUGUI label, Image checkmark) CreateToggle(Transform parent,
ToggleConfig config)
{
// 1. Root Toggle Object
var toggleRect = CreateRect(parent, "Toggle", config.RectConfig);
var toggle = toggleRect.gameObject.AddComponent<Toggle>();
toggle.isOn = config.IsOnByDefault;
// 2. Background
var backgroundRect = CreateRect(toggleRect, "Background", new RectTransformConfig(
new Vector2(0, 0.5f), new Vector2(0, 0.5f), Vector2.zero, new Vector2(20, 20), Vector2.zero,
Vector2.zero, new Vector2(0, 0.5f)
));
var backgroundImage = backgroundRect.gameObject.AddComponent<Image>();
backgroundImage.color = config.BackgroundColor;
toggle.targetGraphic = backgroundImage;
// 3. Checkmark
var checkmarkRect = CreateRect(backgroundRect, "Checkmark", RectTransformConfig.FillParent);
checkmarkRect.offsetMin = new Vector2(3, 3);
checkmarkRect.offsetMax = new Vector2(-3, -3);
var checkmarkImage = checkmarkRect.gameObject.AddComponent<Image>();
checkmarkImage.color = config.CheckmarkColor;
toggle.graphic = checkmarkImage;
// 4. Label
var labelRect = CreateRect(toggleRect, "Label", new RectTransformConfig(
new Vector2(0, 0), new Vector2(1, 1), Vector2.zero, new Vector2(-config.Spacing - 20, 0),
new Vector2(config.Spacing + 20, 0), Vector2.zero, new Vector2(0, 0.5f)
));
var label = labelRect.gameObject.AddComponent<TextMeshProUGUI>();
label.text = config.LabelText;
label.color = config.LabelTextColor;
label.fontSize = config.LabelFontSize;
label.alignment = TextAlignmentOptions.Left;
return (toggle, label, checkmarkImage);
}
}
}