添加一个表格组件,原理上贴近现代表格系统,理论上可以应对超大型表格以及离散数据集合。
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
using UnityEditor;
|
||||
using UnityEditor.UI;
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
using XericUI.OverrideUI;
|
||||
|
||||
namespace XericUIEditor.OverrideUI
|
||||
{
|
||||
/// <summary>
|
||||
/// XericStyleButton 的 Inspector 编辑器。
|
||||
/// 完全替换 Selectable 的 Transition/ColorBlock/SpriteState 区域为 StyleSheet 状态块,
|
||||
/// 保留 Interactable、Navigation、onClick 等基础功能。
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(XericStyleButton), true)]
|
||||
[CanEditMultipleObjects]
|
||||
internal class XericStyleButtonEditor : SelectableEditor
|
||||
{
|
||||
private SerializedProperty m_InteractableProp;
|
||||
private SerializedProperty m_TargetGraphicProp;
|
||||
private SerializedProperty m_NavigationProp;
|
||||
|
||||
private SerializedProperty m_ColorStateProp;
|
||||
private SerializedProperty m_TextureStateProp;
|
||||
private SerializedProperty m_EnableStyleTransitionProp;
|
||||
private SerializedProperty m_OnClickProp;
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
|
||||
m_InteractableProp = serializedObject.FindProperty("m_Interactable");
|
||||
m_TargetGraphicProp = serializedObject.FindProperty("m_TargetGraphic");
|
||||
m_NavigationProp = serializedObject.FindProperty("m_Navigation");
|
||||
|
||||
m_ColorStateProp = serializedObject.FindProperty("m_ColorState");
|
||||
m_TextureStateProp = serializedObject.FindProperty("m_TextureState");
|
||||
m_EnableStyleTransitionProp = serializedObject.FindProperty("m_EnableStyleTransition");
|
||||
m_OnClickProp = serializedObject.FindProperty("m_OnClick");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
// === 1. Interactable ===
|
||||
EditorGUILayout.PropertyField(m_InteractableProp);
|
||||
|
||||
// === 2. 样式表状态块(替代 Transition + ColorBlock + SpriteState) ===
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("样式表配置(替代 Transition)", EditorStyles.boldLabel);
|
||||
|
||||
EditorGUILayout.PropertyField(m_ColorStateProp, new GUIContent("颜色状态块"), true);
|
||||
EditorGUILayout.Space(4);
|
||||
EditorGUILayout.PropertyField(m_TextureStateProp, new GUIContent("纹理状态块"), true);
|
||||
|
||||
if (m_ColorStateProp.isExpanded || m_TextureStateProp.isExpanded)
|
||||
{
|
||||
EditorGUILayout.Space(2);
|
||||
EditorGUILayout.PropertyField(m_EnableStyleTransitionProp);
|
||||
}
|
||||
|
||||
// === 3. Target Graphic ===
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.PropertyField(m_TargetGraphicProp);
|
||||
|
||||
Graphic graphic = m_TargetGraphicProp.objectReferenceValue as Graphic;
|
||||
if (graphic == null)
|
||||
graphic = (target as Selectable)?.GetComponent<Graphic>();
|
||||
if (graphic == null)
|
||||
EditorGUILayout.HelpBox("你需要指定一个 Graphic 目标才能使用样式表颜色过渡。", MessageType.Warning);
|
||||
|
||||
// === 4. Navigation ===
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.PropertyField(m_NavigationProp);
|
||||
|
||||
// === 5. onClick ===
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.PropertyField(m_OnClickProp);
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 93a0ee7d0089fb141a33b3db631b645a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,47 @@
|
||||
using UnityEditor;
|
||||
using UnityEditor.UI;
|
||||
|
||||
using XericUI.OverrideUI;
|
||||
|
||||
namespace XericUIEditor.OverrideUI
|
||||
{
|
||||
/// <summary>
|
||||
/// XericStyleImage 的 Inspector 编辑器——继承 GraphicEditor,
|
||||
/// 额外展示颜色、纹理、材质三个 StyleField 和自动刷新开关。
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(XericStyleImage), true)]
|
||||
[CanEditMultipleObjects]
|
||||
internal class XericStyleImageEditor : GraphicEditor
|
||||
{
|
||||
private SerializedProperty m_ColorStyleProp;
|
||||
private SerializedProperty m_TextureStyleProp;
|
||||
private SerializedProperty m_MaterialStyleProp;
|
||||
private SerializedProperty m_AutoRefreshProp;
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
|
||||
m_ColorStyleProp = serializedObject.FindProperty("m_ColorStyle");
|
||||
m_TextureStyleProp = serializedObject.FindProperty("m_TextureStyle");
|
||||
m_MaterialStyleProp = serializedObject.FindProperty("m_MaterialStyle");
|
||||
m_AutoRefreshProp = serializedObject.FindProperty("m_AutoRefresh");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
base.OnInspectorGUI();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
serializedObject.Update();
|
||||
|
||||
EditorGUILayout.LabelField("样式表配置", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(m_ColorStyleProp);
|
||||
EditorGUILayout.PropertyField(m_TextureStyleProp);
|
||||
EditorGUILayout.PropertyField(m_MaterialStyleProp);
|
||||
EditorGUILayout.PropertyField(m_AutoRefreshProp);
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8f9db8103c790b3449487b618b5a5db7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,135 @@
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEditor.UI;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
using UnityEngine.UI;
|
||||
|
||||
using XericUI.OverrideUI;
|
||||
|
||||
namespace XericUIEditor.OverrideUI
|
||||
{
|
||||
/// <summary>
|
||||
/// XericStyleToggle 的 Inspector 编辑器。
|
||||
/// 完全替换 Selectable 的 Transition/ColorBlock/SpriteState 区域为 StyleSheet 状态块,
|
||||
/// 保留 Interactable、Navigation、isOn、graphic、group、onValueChanged 等基础功能。
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(XericStyleToggle), true)]
|
||||
[CanEditMultipleObjects]
|
||||
internal class XericStyleToggleEditor : SelectableEditor
|
||||
{
|
||||
// Selectable 基础
|
||||
private SerializedProperty m_InteractableProp;
|
||||
private SerializedProperty m_NavigationProp;
|
||||
|
||||
// Toggle 基础
|
||||
private SerializedProperty m_IsOnProp;
|
||||
private SerializedProperty m_TransitionProp;
|
||||
private SerializedProperty m_GraphicProp;
|
||||
private SerializedProperty m_GroupProp;
|
||||
private SerializedProperty m_OnValueChangedProp;
|
||||
|
||||
// StyleSheet 专用
|
||||
private SerializedProperty m_ColorStateProp;
|
||||
private SerializedProperty m_TextureStateProp;
|
||||
private SerializedProperty m_OnOverrideColorProp;
|
||||
private SerializedProperty m_OnOverrideTextureProp;
|
||||
private SerializedProperty m_CheckmarkStyleProp;
|
||||
private SerializedProperty m_EnableStyleTransitionProp;
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
|
||||
m_InteractableProp = serializedObject.FindProperty("m_Interactable");
|
||||
m_NavigationProp = serializedObject.FindProperty("m_Navigation");
|
||||
|
||||
m_IsOnProp = serializedObject.FindProperty("m_IsOn");
|
||||
m_TransitionProp = serializedObject.FindProperty("toggleTransition");
|
||||
m_GraphicProp = serializedObject.FindProperty("graphic");
|
||||
m_GroupProp = serializedObject.FindProperty("m_Group");
|
||||
m_OnValueChangedProp = serializedObject.FindProperty("onValueChanged");
|
||||
|
||||
m_ColorStateProp = serializedObject.FindProperty("m_ColorState");
|
||||
m_TextureStateProp = serializedObject.FindProperty("m_TextureState");
|
||||
m_OnOverrideColorProp = serializedObject.FindProperty("m_OnOverrideColor");
|
||||
m_OnOverrideTextureProp = serializedObject.FindProperty("m_OnOverrideTexture");
|
||||
m_CheckmarkStyleProp = serializedObject.FindProperty("m_CheckmarkStyle");
|
||||
m_EnableStyleTransitionProp = serializedObject.FindProperty("m_EnableStyleTransition");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
XericStyleToggle toggle = target as XericStyleToggle;
|
||||
|
||||
// === 1. Interactable ===
|
||||
EditorGUILayout.PropertyField(m_InteractableProp);
|
||||
|
||||
// === 2. 样式表状态块(替代 Transition + ColorBlock + SpriteState) ===
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("样式表配置(替代 Transition)", EditorStyles.boldLabel);
|
||||
|
||||
EditorGUILayout.PropertyField(m_ColorStateProp, new GUIContent("颜色状态块"), true);
|
||||
EditorGUILayout.Space(4);
|
||||
EditorGUILayout.PropertyField(m_TextureStateProp, new GUIContent("纹理状态块"), true);
|
||||
|
||||
bool anyExpanded = m_ColorStateProp.isExpanded || m_TextureStateProp.isExpanded;
|
||||
if (anyExpanded)
|
||||
{
|
||||
EditorGUILayout.Space(2);
|
||||
EditorGUILayout.PropertyField(m_OnOverrideColorProp);
|
||||
EditorGUILayout.PropertyField(m_OnOverrideTextureProp);
|
||||
EditorGUILayout.PropertyField(m_CheckmarkStyleProp);
|
||||
EditorGUILayout.PropertyField(m_EnableStyleTransitionProp);
|
||||
}
|
||||
|
||||
// === 3. isOn ===
|
||||
EditorGUILayout.Space();
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(m_IsOnProp);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
if (!Application.isPlaying)
|
||||
EditorSceneManager.MarkSceneDirty(toggle!.gameObject.scene);
|
||||
|
||||
ToggleGroup group = m_GroupProp.objectReferenceValue as ToggleGroup;
|
||||
toggle!.isOn = m_IsOnProp.boolValue;
|
||||
|
||||
if (group != null && group.isActiveAndEnabled && toggle!.IsActive())
|
||||
{
|
||||
if (toggle!.isOn || (!group.AnyTogglesOn() && !group.allowSwitchOff))
|
||||
{
|
||||
toggle!.isOn = true;
|
||||
group.NotifyToggleOn(toggle!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === 4. Toggle 切换效果 & Graphic ===
|
||||
EditorGUILayout.PropertyField(m_TransitionProp);
|
||||
EditorGUILayout.PropertyField(m_GraphicProp);
|
||||
|
||||
// === 5. Group ===
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(m_GroupProp);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
if (!Application.isPlaying)
|
||||
EditorSceneManager.MarkSceneDirty(toggle!.gameObject.scene);
|
||||
toggle!.group = m_GroupProp.objectReferenceValue as ToggleGroup;
|
||||
}
|
||||
|
||||
// === 6. Navigation ===
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.PropertyField(m_NavigationProp);
|
||||
|
||||
// === 7. onValueChanged ===
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.PropertyField(m_OnValueChangedProp);
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 333577a929d25434e87d0a53360fce29
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 44c3980d2307e514a869459f8153cda2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,235 @@
|
||||
using UnityEditor;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
using XericLibrary.Runtime.SuperStyleSheet;
|
||||
|
||||
namespace XericUIEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// StyleField 的自定义 Inspector 属性绘制器。
|
||||
///
|
||||
/// 默认折叠状态:仅显示样式路径下拉框。
|
||||
/// 展开状态:显示命名空间、路径、资源值类型(有值时)、注解描述(有注解时)。
|
||||
/// 无值/无注解的行不显示。
|
||||
/// </summary>
|
||||
[CustomPropertyDrawer(typeof(StyleField))]
|
||||
public class StyleFieldDrawer : PropertyDrawer
|
||||
{
|
||||
private const float LINE_HEIGHT = 18f;
|
||||
private const float PADDING = 2f;
|
||||
private const float FOLD_BUTTON_WIDTH = 14f;
|
||||
|
||||
/// <summary>折叠状态(按 property path 缓存)</summary>
|
||||
private static readonly System.Collections.Generic.Dictionary<string, bool> s_FoldoutStates
|
||||
= new System.Collections.Generic.Dictionary<string, bool>();
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
string key = GetFoldKey(property);
|
||||
bool expanded = s_FoldoutStates.TryGetValue(key, out bool f) && f;
|
||||
|
||||
if (!expanded)
|
||||
return LINE_HEIGHT + PADDING * 2;
|
||||
|
||||
// 基础:命名空间 + 路径 = 2 行
|
||||
int rowCount = 2;
|
||||
|
||||
// 有值才显示值类型行
|
||||
string ns = GetNamespace(property);
|
||||
string path = property.FindPropertyRelative("StylePath").stringValue;
|
||||
StyleValue val = StyleManager.GetValue(ns, path);
|
||||
if (val != null && val.ValueType != null)
|
||||
rowCount++;
|
||||
|
||||
// 有注解才显示注解行
|
||||
var annotations = StyleManager.GetAnnotations(ns, path);
|
||||
if (annotations != null && annotations.Count > 0)
|
||||
rowCount++;
|
||||
|
||||
return (LINE_HEIGHT + PADDING) * rowCount + PADDING;
|
||||
}
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
string key = GetFoldKey(property);
|
||||
bool expanded = s_FoldoutStates.TryGetValue(key, out bool fold) && fold;
|
||||
|
||||
// 获取子属性
|
||||
SerializedProperty nsProp = property.FindPropertyRelative("Namespace");
|
||||
SerializedProperty pathProp = property.FindPropertyRelative("StylePath");
|
||||
|
||||
// === 标题行:折叠箭头 + 标签 + 路径下拉 ===
|
||||
Rect headerRect = new Rect(position.x, position.y + PADDING, position.width, LINE_HEIGHT);
|
||||
|
||||
// 折叠按钮
|
||||
Rect foldRect = new Rect(headerRect.x, headerRect.y, FOLD_BUTTON_WIDTH, LINE_HEIGHT);
|
||||
bool newFold = EditorGUI.Foldout(foldRect, expanded, GUIContent.none, true);
|
||||
if (newFold != expanded)
|
||||
{
|
||||
s_FoldoutStates[key] = newFold;
|
||||
expanded = newFold;
|
||||
}
|
||||
|
||||
// 标签
|
||||
Rect labelRect = new Rect(headerRect.x + FOLD_BUTTON_WIDTH, headerRect.y,
|
||||
EditorGUIUtility.labelWidth - FOLD_BUTTON_WIDTH, LINE_HEIGHT);
|
||||
EditorGUI.LabelField(labelRect, label);
|
||||
|
||||
// 路径下拉框(始终可见)
|
||||
Rect pathRect = new Rect(headerRect.x + EditorGUIUtility.labelWidth + 4f, headerRect.y,
|
||||
headerRect.width - EditorGUIUtility.labelWidth - 8f, LINE_HEIGHT);
|
||||
|
||||
string currentNs = GetNamespace(property);
|
||||
|
||||
DrawPathDropdown(pathRect, pathProp, currentNs);
|
||||
|
||||
if (!expanded)
|
||||
return;
|
||||
|
||||
// === 展开区域 ===
|
||||
float y = headerRect.y + LINE_HEIGHT + PADDING;
|
||||
float indent = 16f;
|
||||
float fieldWidth = position.width - indent;
|
||||
|
||||
// --- 命名空间 ---
|
||||
Rect nsLabelRect = new Rect(position.x + indent, y, 60f, LINE_HEIGHT);
|
||||
Rect nsFieldRect = new Rect(position.x + indent + 64f, y, fieldWidth - 64f, LINE_HEIGHT);
|
||||
EditorGUI.LabelField(nsLabelRect, "命名空间");
|
||||
DrawNamespaceDropdown(nsFieldRect, nsProp);
|
||||
y += LINE_HEIGHT + PADDING;
|
||||
|
||||
// --- 路径 ---
|
||||
Rect pathLabelRect = new Rect(position.x + indent, y, 60f, LINE_HEIGHT);
|
||||
Rect pathFieldRect = new Rect(position.x + indent + 64f, y, fieldWidth - 64f, LINE_HEIGHT);
|
||||
EditorGUI.LabelField(pathLabelRect, "路径");
|
||||
DrawPathDropdown(pathFieldRect, pathProp, currentNs);
|
||||
y += LINE_HEIGHT + PADDING;
|
||||
|
||||
// --- 资源值类型(仅在有值时显示) ---
|
||||
StyleValue currentValue = StyleManager.GetValue(currentNs, pathProp.stringValue);
|
||||
if (currentValue != null && currentValue.ValueType != null)
|
||||
{
|
||||
Rect valLabelRect = new Rect(position.x + indent, y, 80f, LINE_HEIGHT);
|
||||
Rect valFieldRect = new Rect(position.x + indent + 84f, y, fieldWidth - 84f, LINE_HEIGHT);
|
||||
|
||||
EditorGUI.LabelField(valLabelRect, "值类型");
|
||||
|
||||
string typeDisplay = GetShortTypeName(currentValue.ValueType);
|
||||
string valuePreview = currentValue.RawValue != null
|
||||
? currentValue.RawValue.ToString()
|
||||
: "";
|
||||
|
||||
EditorGUI.LabelField(valFieldRect, $"{typeDisplay} = {valuePreview}");
|
||||
y += LINE_HEIGHT + PADDING;
|
||||
}
|
||||
|
||||
// --- 注解描述(仅在有注解时显示) ---
|
||||
var annotations = StyleManager.GetAnnotations(currentNs, pathProp.stringValue);
|
||||
if (annotations != null && annotations.Count > 0)
|
||||
{
|
||||
Rect annoLabelRect = new Rect(position.x + indent, y, 80f, LINE_HEIGHT);
|
||||
Rect annoFieldRect = new Rect(position.x + indent + 84f, y, fieldWidth - 84f, LINE_HEIGHT);
|
||||
|
||||
EditorGUI.LabelField(annoLabelRect, "注解");
|
||||
|
||||
// 优先显示 @Desc
|
||||
annotations.TryGetValue("Desc", out string desc);
|
||||
if (!string.IsNullOrEmpty(desc))
|
||||
EditorGUI.LabelField(annoFieldRect, desc);
|
||||
else
|
||||
{
|
||||
foreach (var a in annotations)
|
||||
{
|
||||
EditorGUI.LabelField(annoFieldRect, $"@{a.Key}: {a.Value}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 变更检测
|
||||
if (GUI.changed)
|
||||
{
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
|
||||
#region 下拉框绘制
|
||||
|
||||
private void DrawNamespaceDropdown(Rect rect, SerializedProperty nsProp)
|
||||
{
|
||||
var namespaces = StyleManager.GetAvailableNamespaces();
|
||||
if (namespaces.Count == 0)
|
||||
namespaces.Add("default");
|
||||
|
||||
int currentIndex = namespaces.IndexOf(nsProp.stringValue);
|
||||
if (currentIndex < 0) currentIndex = 0;
|
||||
|
||||
int newIndex = EditorGUI.Popup(rect, currentIndex, namespaces.ToArray());
|
||||
if (newIndex >= 0 && newIndex < namespaces.Count)
|
||||
{
|
||||
nsProp.stringValue = namespaces[newIndex];
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawPathDropdown(Rect rect, SerializedProperty pathProp, string namespace_)
|
||||
{
|
||||
var paths = StyleManager.GetPathsForNamespace(namespace_);
|
||||
|
||||
var displayOptions = new System.Collections.Generic.List<string>();
|
||||
if (paths.Count == 0)
|
||||
{
|
||||
displayOptions.Add("(无可用路径)");
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var p in paths)
|
||||
{
|
||||
StyleValue val = StyleManager.GetValue(namespace_, p);
|
||||
string typeStr = val?.ValueType != null ? GetShortTypeName(val.ValueType) : "?";
|
||||
string valStr = val?.RawValue != null ? val.RawValue.ToString() : "";
|
||||
if (valStr.Length > 20) valStr = valStr.Substring(0, 17) + "...";
|
||||
displayOptions.Add($"{p} [{typeStr}] {valStr}");
|
||||
}
|
||||
}
|
||||
|
||||
int currentIndex = System.Math.Max(0, paths.IndexOf(pathProp.stringValue));
|
||||
|
||||
int newIndex = EditorGUI.Popup(rect, currentIndex, displayOptions.ToArray());
|
||||
if (newIndex >= 0 && newIndex < paths.Count)
|
||||
{
|
||||
pathProp.stringValue = paths[newIndex];
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 工具方法
|
||||
|
||||
private static string GetFoldKey(SerializedProperty property)
|
||||
{
|
||||
return $"{property.serializedObject.targetObject.GetInstanceID()}_{property.propertyPath}";
|
||||
}
|
||||
|
||||
private static string GetNamespace(SerializedProperty property)
|
||||
{
|
||||
string ns = property.FindPropertyRelative("Namespace").stringValue;
|
||||
return string.IsNullOrEmpty(ns) ? "default" : ns;
|
||||
}
|
||||
|
||||
private static string GetShortTypeName(System.Type type)
|
||||
{
|
||||
if (type == null) return "?";
|
||||
switch (type.Name)
|
||||
{
|
||||
case "Single": return "float";
|
||||
case "Int32": return "int";
|
||||
case "String": return "string";
|
||||
case "Boolean": return "bool";
|
||||
default: return type.Name;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ee1f2c601403504faea388e1f91f07b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4110db94896e7434a8eb8182809da2ad
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,172 @@
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using UnityEditor;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
using XericUI.XTable.Rendering.Component;
|
||||
|
||||
namespace XericUIEditor.XTable
|
||||
{
|
||||
/// <summary>
|
||||
/// 场景视图表格编辑覆盖工具——选中表格组件后,在世界编辑器视口中显示网格线和编辑辅助 UI。
|
||||
/// 包含合并工具、拖拽引用工具、单元格坐标提示等。
|
||||
/// </summary>
|
||||
public static class XTableEditorOverlay
|
||||
{
|
||||
/// <summary>是否启用覆盖层(可通过菜单开关)</summary>
|
||||
private static bool s_Enabled = true;
|
||||
|
||||
private const string MENU_PATH = "Xeric UI/表格编辑器覆盖层";
|
||||
|
||||
[MenuItem(MENU_PATH, false, 200)]
|
||||
private static void ToggleOverlay()
|
||||
{
|
||||
s_Enabled = !s_Enabled;
|
||||
Menu.SetChecked(MENU_PATH, s_Enabled);
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
[MenuItem(MENU_PATH, true)]
|
||||
private static bool ToggleOverlayValidate()
|
||||
{
|
||||
Menu.SetChecked(MENU_PATH, s_Enabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在 Scene View 中绘制表格编辑辅助线。
|
||||
/// 调用时机:挂到 SceneView.duringSceneGui 事件上。
|
||||
/// 示例:SceneView.duringSceneGui += OnSceneGUI;
|
||||
/// </summary>
|
||||
public static void OnSceneGUI(SceneView sceneView)
|
||||
{
|
||||
if (!s_Enabled) return;
|
||||
|
||||
// 获取当前选中的表格组件
|
||||
GameObject selected = Selection.activeGameObject;
|
||||
if (selected == null) return;
|
||||
|
||||
XericUIActionTable table = selected.GetComponent<XericUIActionTable>();
|
||||
if (table == null) return;
|
||||
|
||||
RectTransform rect = table.GetComponent<RectTransform>();
|
||||
if (rect == null) return;
|
||||
|
||||
DrawGridOverlay(table, rect);
|
||||
DrawCellInfo(table, rect, sceneView);
|
||||
}
|
||||
|
||||
/// <summary>绘制网格线</summary>
|
||||
private static void DrawGridOverlay(XericUIActionTable table, RectTransform rect)
|
||||
{
|
||||
float[] rowHeights = table.RowHeights;
|
||||
float[] colWidths = table.ColWidths;
|
||||
if (rowHeights == null || colWidths == null) return;
|
||||
|
||||
Vector3[] corners = new Vector3[4];
|
||||
rect.GetWorldCorners(corners);
|
||||
|
||||
Vector3 topLeft = corners[1];
|
||||
float totalWidth = rect.rect.width;
|
||||
float totalHeight = rect.rect.height;
|
||||
|
||||
Handles.color = new Color(0.5f, 0.5f, 0.5f, 0.3f);
|
||||
|
||||
// 绘制行分割线
|
||||
float accumulatedY = 0f;
|
||||
for (int r = 0; r < rowHeights.Length && accumulatedY < totalHeight; r++)
|
||||
{
|
||||
float y = topLeft.y - accumulatedY - rowHeights[r];
|
||||
Vector3 start = new Vector3(topLeft.x, y, topLeft.z);
|
||||
Vector3 end = new Vector3(topLeft.x + Mathf.Min(totalWidth, 500f), y, topLeft.z);
|
||||
Handles.DrawLine(start, end);
|
||||
accumulatedY += rowHeights[r];
|
||||
}
|
||||
|
||||
// 绘制列分割线
|
||||
float accumulatedX = 0f;
|
||||
for (int c = 0; c < colWidths.Length && accumulatedX < totalWidth; c++)
|
||||
{
|
||||
float x = topLeft.x + accumulatedX + colWidths[c];
|
||||
Vector3 start = new Vector3(x, topLeft.y, topLeft.z);
|
||||
Vector3 end = new Vector3(x, topLeft.y - Mathf.Min(totalHeight, 500f), topLeft.z);
|
||||
Handles.DrawLine(start, end);
|
||||
accumulatedX += colWidths[c];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>绘制单元格坐标提示</summary>
|
||||
private static void DrawCellInfo(XericUIActionTable table, RectTransform rect, SceneView sceneView)
|
||||
{
|
||||
int selRow = table.SelectedRow;
|
||||
int selCol = table.SelectedCol;
|
||||
if (selRow < 0 || selCol < 0) return;
|
||||
|
||||
float[] rowHeights = table.RowHeights;
|
||||
float[] colWidths = table.ColWidths;
|
||||
if (rowHeights == null || colWidths == null) return;
|
||||
|
||||
// 计算选中单元格的世界位置
|
||||
float cellX = 0f;
|
||||
for (int c = 0; c < selCol && c < colWidths.Length; c++)
|
||||
cellX += colWidths[c];
|
||||
|
||||
float cellY = 0f;
|
||||
for (int r = 0; r < selRow && r < rowHeights.Length; r++)
|
||||
cellY += rowHeights[r];
|
||||
|
||||
Vector3[] corners = new Vector3[4];
|
||||
rect.GetWorldCorners(corners);
|
||||
Vector3 topLeft = corners[1];
|
||||
|
||||
Vector3 cellCenter = new Vector3(
|
||||
topLeft.x + cellX + (selCol < colWidths.Length ? colWidths[selCol] * 0.5f : 0),
|
||||
topLeft.y - cellY - (selRow < rowHeights.Length ? rowHeights[selRow] * 0.5f : 0),
|
||||
topLeft.z);
|
||||
|
||||
// 绘制选中高亮框
|
||||
Handles.color = new Color(0.2f, 0.6f, 1f, 0.5f);
|
||||
float cellW = selCol < colWidths.Length ? colWidths[selCol] : 50f;
|
||||
float cellH = selRow < rowHeights.Length ? rowHeights[selRow] : 30f;
|
||||
Vector3 cellTL = new Vector3(cellCenter.x - cellW * 0.5f, cellCenter.y + cellH * 0.5f, cellCenter.z);
|
||||
Vector3 cellTR = new Vector3(cellCenter.x + cellW * 0.5f, cellCenter.y + cellH * 0.5f, cellCenter.z);
|
||||
Vector3 cellBR = new Vector3(cellCenter.x + cellW * 0.5f, cellCenter.y - cellH * 0.5f, cellCenter.z);
|
||||
Vector3 cellBL = new Vector3(cellCenter.x - cellW * 0.5f, cellCenter.y - cellH * 0.5f, cellCenter.z);
|
||||
Handles.DrawSolidRectangleWithOutline(
|
||||
new Vector3[] { cellTL, cellTR, cellBR, cellBL },
|
||||
new Color(0.2f, 0.6f, 1f, 0.2f),
|
||||
new Color(0.2f, 0.6f, 1f, 0.8f));
|
||||
|
||||
// 显示单元格坐标标签
|
||||
Handles.Label(cellCenter + Vector3.up * 10f,
|
||||
$"({selRow}, {selCol})",
|
||||
new GUIStyle(EditorStyles.label)
|
||||
{
|
||||
normal = { textColor = Color.white },
|
||||
fontStyle = FontStyle.Bold
|
||||
});
|
||||
|
||||
// 工具栏按钮(在 Scene View 顶部绘制)
|
||||
Handles.BeginGUI();
|
||||
GUILayout.BeginArea(new Rect(10, 10, 200, 80));
|
||||
GUILayout.BeginVertical("表格编辑工具", GUI.skin.window);
|
||||
|
||||
if (GUILayout.Button("合并选区"))
|
||||
{
|
||||
Debug.Log($"[XTable] 合并起始于 ({selRow}, {selCol})");
|
||||
}
|
||||
|
||||
if (GUILayout.Button("取消合并"))
|
||||
{
|
||||
Debug.Log($"[XTable] 取消合并 ({selRow}, {selCol})");
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
GUILayout.EndArea();
|
||||
Handles.EndGUI();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 220060be4d5df6a40afa23e4a7af1d64
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,334 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
using XericUI.XTable.Rendering.Component;
|
||||
|
||||
namespace XericUIEditor.XTable
|
||||
{
|
||||
/// <summary>
|
||||
/// XericUIActionTable 的 Inspector 编辑器——
|
||||
/// 折叠分组:[样式设定] [尺寸设定] [编辑测试] [数据浏览]。
|
||||
/// 使用 EditorGUILayout.Foldout 避免与数组 PropertyField 内部的折叠冲突。
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(XericUIActionTable))]
|
||||
public class XericUIActionTableEditor : Editor
|
||||
{
|
||||
private XericUIActionTable m_Table;
|
||||
|
||||
private SerializedProperty m_RowHeightsProp;
|
||||
private SerializedProperty m_ColWidthsProp;
|
||||
private SerializedProperty m_DefaultStyleNsProp;
|
||||
private SerializedProperty m_ScrollRectProp;
|
||||
|
||||
// 折叠状态
|
||||
private static bool s_StyleFoldout = true;
|
||||
private static bool s_SizeFoldout = true;
|
||||
private static bool s_EditTestFoldout;
|
||||
private static bool s_DataBrowseFoldout;
|
||||
|
||||
// 编辑测试参数
|
||||
private int m_BatchRowCount = 10;
|
||||
private int m_BatchColCount = 10;
|
||||
private float m_BatchCellHeight = 40f;
|
||||
private float m_BatchCellWidth = 100f;
|
||||
|
||||
private int m_MergeStartRow;
|
||||
private int m_MergeStartCol;
|
||||
private int m_MergeRowSpan = 2;
|
||||
private int m_MergeColSpan = 2;
|
||||
|
||||
private int m_EditRow;
|
||||
private int m_EditCol;
|
||||
private string m_EditText = "";
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
m_Table = (XericUIActionTable)target;
|
||||
|
||||
m_RowHeightsProp = serializedObject.FindProperty("m_RowHeights");
|
||||
m_ColWidthsProp = serializedObject.FindProperty("m_ColWidths");
|
||||
m_DefaultStyleNsProp = serializedObject.FindProperty("m_DefaultStyleNamespace");
|
||||
m_ScrollRectProp = serializedObject.FindProperty("m_ScrollRect");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
// ===== 样式设定 =====
|
||||
s_StyleFoldout = EditorGUILayout.Foldout(s_StyleFoldout, "样式设定", true);
|
||||
if (s_StyleFoldout)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
if (m_DefaultStyleNsProp != null)
|
||||
EditorGUILayout.PropertyField(m_DefaultStyleNsProp, new GUIContent("默认命名空间"));
|
||||
if (m_ScrollRectProp != null)
|
||||
EditorGUILayout.PropertyField(m_ScrollRectProp, new GUIContent("ScrollRect"));
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
// ===== 尺寸设定 =====
|
||||
EditorGUILayout.Space(4);
|
||||
s_SizeFoldout = EditorGUILayout.Foldout(s_SizeFoldout, "尺寸设定", true);
|
||||
if (s_SizeFoldout)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
if (m_RowHeightsProp != null)
|
||||
EditorGUILayout.PropertyField(m_RowHeightsProp, new GUIContent("行高"), true);
|
||||
if (m_ColWidthsProp != null)
|
||||
EditorGUILayout.PropertyField(m_ColWidthsProp, new GUIContent("列宽"), true);
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
// ===== 编辑测试 =====
|
||||
EditorGUILayout.Space(4);
|
||||
s_EditTestFoldout = EditorGUILayout.Foldout(s_EditTestFoldout, "编辑测试", true);
|
||||
if (s_EditTestFoldout)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
EditorGUILayout.LabelField("批量生成尺寸", EditorStyles.miniBoldLabel);
|
||||
m_BatchRowCount = EditorGUILayout.IntField("行数", m_BatchRowCount);
|
||||
m_BatchColCount = EditorGUILayout.IntField("列数", m_BatchColCount);
|
||||
m_BatchCellHeight = EditorGUILayout.FloatField("单元格高度", m_BatchCellHeight);
|
||||
m_BatchCellWidth = EditorGUILayout.FloatField("单元格宽度", m_BatchCellWidth);
|
||||
if (GUILayout.Button("应用批量尺寸", GUILayout.Height(22)))
|
||||
ApplyBatchRowColSizes();
|
||||
|
||||
EditorGUILayout.Space(6);
|
||||
|
||||
EditorGUILayout.LabelField("合并单元格", EditorStyles.miniBoldLabel);
|
||||
m_MergeStartRow = EditorGUILayout.IntField("起始行", m_MergeStartRow);
|
||||
m_MergeStartCol = EditorGUILayout.IntField("起始列", m_MergeStartCol);
|
||||
m_MergeRowSpan = EditorGUILayout.IntField("行跨度", m_MergeRowSpan);
|
||||
m_MergeColSpan = EditorGUILayout.IntField("列跨度", m_MergeColSpan);
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("合并", GUILayout.Height(22)))
|
||||
EditorApplication.delayCall += () => m_Table.MergeCells(m_MergeStartRow, m_MergeStartCol, m_MergeRowSpan, m_MergeColSpan);
|
||||
if (GUILayout.Button("取消合并", GUILayout.Height(22)))
|
||||
EditorApplication.delayCall += () => m_Table.UnmergeCells(m_MergeStartRow, m_MergeStartCol);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.Space(6);
|
||||
|
||||
EditorGUILayout.LabelField("单元格编辑", EditorStyles.miniBoldLabel);
|
||||
m_EditRow = EditorGUILayout.IntField("行", m_EditRow);
|
||||
m_EditCol = EditorGUILayout.IntField("列", m_EditCol);
|
||||
|
||||
var existing = m_Table.GetCellData(m_EditRow, m_EditCol);
|
||||
if (existing != null && !string.IsNullOrEmpty(existing.Text) && string.IsNullOrEmpty(m_EditText))
|
||||
m_EditText = existing.Text;
|
||||
|
||||
m_EditText = EditorGUILayout.TextField("文本内容", m_EditText);
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("应用文本", GUILayout.Height(22)))
|
||||
EditorApplication.delayCall += () => m_Table.SetCellText(m_EditRow, m_EditCol, m_EditText);
|
||||
if (GUILayout.Button("选中此格", GUILayout.Height(22)))
|
||||
EditorApplication.delayCall += () => m_Table.SelectCell(m_EditRow, m_EditCol);
|
||||
if (GUILayout.Button("清除选中", GUILayout.Height(22)))
|
||||
EditorApplication.delayCall += () => m_Table.ClearSelection();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
// ===== 数据浏览 =====
|
||||
EditorGUILayout.Space(4);
|
||||
s_DataBrowseFoldout = EditorGUILayout.Foldout(s_DataBrowseFoldout, "数据浏览", true);
|
||||
if (s_DataBrowseFoldout)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
if (m_Table.TableData != null)
|
||||
{
|
||||
EditorGUI.BeginDisabledGroup(true);
|
||||
EditorGUILayout.IntField("块数量", m_Table.TableData.BlockCount);
|
||||
EditorGUILayout.TextField("块尺寸",
|
||||
$"{m_Table.TableData.BlockSizeX} × {m_Table.TableData.BlockSizeY}");
|
||||
EditorGUILayout.TextField("选中单元格",
|
||||
m_Table.SelectedRow >= 0 ? $"({m_Table.SelectedRow}, {m_Table.SelectedCol})" : "(未选中)");
|
||||
EditorGUI.EndDisabledGroup();
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.HelpBox("TableData 为空——Awake() 执行后会自动创建默认实例。", MessageType.Info);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(4);
|
||||
if (GUILayout.Button("手动刷新视图", GUILayout.Height(22)))
|
||||
EditorApplication.delayCall += () => m_Table.RefreshView();
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
private void ApplyBatchRowColSizes()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
if (m_RowHeightsProp != null)
|
||||
{
|
||||
m_RowHeightsProp.arraySize = m_BatchRowCount;
|
||||
for (int i = 0; i < m_BatchRowCount; i++)
|
||||
m_RowHeightsProp.GetArrayElementAtIndex(i).floatValue = m_BatchCellHeight;
|
||||
}
|
||||
|
||||
if (m_ColWidthsProp != null)
|
||||
{
|
||||
m_ColWidthsProp.arraySize = m_BatchColCount;
|
||||
for (int i = 0; i < m_BatchColCount; i++)
|
||||
m_ColWidthsProp.GetArrayElementAtIndex(i).floatValue = m_BatchCellWidth;
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
#region Hierarchy 创建菜单
|
||||
|
||||
[MenuItem("GameObject/Xeric UI/Table/Xeric UI Action Table", false, 60)]
|
||||
private static void CreateTable(MenuCommand menuCommand)
|
||||
{
|
||||
// 1. 创建表格内容节点
|
||||
GameObject contentGo = new GameObject("Content",
|
||||
typeof(RectTransform), typeof(CanvasRenderer), typeof(XericUIActionTable));
|
||||
RectTransform contentRt = contentGo.GetComponent<RectTransform>();
|
||||
contentRt.anchorMin = new Vector2(0, 1);
|
||||
contentRt.anchorMax = new Vector2(0, 1);
|
||||
contentRt.pivot = new Vector2(0, 1);
|
||||
contentRt.anchoredPosition = Vector2.zero;
|
||||
contentRt.sizeDelta = new Vector2(800, 600);
|
||||
XericUIActionTable table = contentGo.GetComponent<XericUIActionTable>();
|
||||
|
||||
// 2. 创建 Viewport(Mask 裁剪区域)
|
||||
GameObject viewportGo = new GameObject("Viewport",
|
||||
typeof(RectTransform), typeof(Image), typeof(Mask));
|
||||
viewportGo.GetComponent<Image>().color = new Color(1, 1, 1, 0.01f);
|
||||
RectTransform vpRt = viewportGo.GetComponent<RectTransform>();
|
||||
vpRt.anchorMin = Vector2.zero;
|
||||
vpRt.anchorMax = Vector2.one;
|
||||
vpRt.sizeDelta = Vector2.zero;
|
||||
contentRt.SetParent(vpRt, false);
|
||||
|
||||
// 3. 创建水平滚动条
|
||||
GameObject hScrollbarGo = new GameObject("Scrollbar Horizontal",
|
||||
typeof(RectTransform), typeof(Scrollbar), typeof(Image));
|
||||
RectTransform hBarRt = hScrollbarGo.GetComponent<RectTransform>();
|
||||
hBarRt.anchorMin = new Vector2(0, 0);
|
||||
hBarRt.anchorMax = new Vector2(1, 0);
|
||||
hBarRt.pivot = new Vector2(0.5f, 0);
|
||||
hBarRt.anchoredPosition = new Vector2(0, 4);
|
||||
hBarRt.sizeDelta = new Vector2(-16, 20);
|
||||
Image hBarImg = hScrollbarGo.GetComponent<Image>();
|
||||
hBarImg.color = new Color(0.15f, 0.15f, 0.15f, 0.5f);
|
||||
Scrollbar hBar = hScrollbarGo.GetComponent<Scrollbar>();
|
||||
hBar.direction = Scrollbar.Direction.LeftToRight;
|
||||
|
||||
GameObject hSlidingGo = new GameObject("Sliding Area",
|
||||
typeof(RectTransform));
|
||||
hSlidingGo.transform.SetParent(hScrollbarGo.transform, false);
|
||||
RectTransform hSlidingRt = hSlidingGo.GetComponent<RectTransform>();
|
||||
hSlidingRt.anchorMin = Vector2.zero;
|
||||
hSlidingRt.anchorMax = Vector2.one;
|
||||
hSlidingRt.sizeDelta = new Vector2(-20, -20);
|
||||
|
||||
GameObject hHandleGo = new GameObject("Handle",
|
||||
typeof(RectTransform), typeof(Image));
|
||||
hHandleGo.transform.SetParent(hSlidingGo.transform, false);
|
||||
RectTransform hHandleRt = hHandleGo.GetComponent<RectTransform>();
|
||||
hHandleRt.anchorMin = Vector2.zero;
|
||||
hHandleRt.anchorMax = Vector2.one;
|
||||
hHandleRt.sizeDelta = new Vector2(-20, -20);
|
||||
hHandleGo.GetComponent<Image>().color = new Color(0.4f, 0.4f, 0.4f, 0.8f);
|
||||
hBar.handleRect = hHandleRt;
|
||||
|
||||
// 4. 创建垂直滚动条
|
||||
GameObject vScrollbarGo = new GameObject("Scrollbar Vertical",
|
||||
typeof(RectTransform), typeof(Scrollbar), typeof(Image));
|
||||
RectTransform vBarRt = vScrollbarGo.GetComponent<RectTransform>();
|
||||
vBarRt.anchorMin = new Vector2(1, 0);
|
||||
vBarRt.anchorMax = new Vector2(1, 1);
|
||||
vBarRt.pivot = new Vector2(1, 0.5f);
|
||||
vBarRt.anchoredPosition = new Vector2(-4, 0);
|
||||
vBarRt.sizeDelta = new Vector2(20, -16);
|
||||
Image vBarImg = vScrollbarGo.GetComponent<Image>();
|
||||
vBarImg.color = new Color(0.15f, 0.15f, 0.15f, 0.5f);
|
||||
Scrollbar vBar = vScrollbarGo.GetComponent<Scrollbar>();
|
||||
vBar.direction = Scrollbar.Direction.BottomToTop;
|
||||
|
||||
GameObject vSlidingGo = new GameObject("Sliding Area",
|
||||
typeof(RectTransform));
|
||||
vSlidingGo.transform.SetParent(vScrollbarGo.transform, false);
|
||||
RectTransform vSlidingRt = vSlidingGo.GetComponent<RectTransform>();
|
||||
vSlidingRt.anchorMin = Vector2.zero;
|
||||
vSlidingRt.anchorMax = Vector2.one;
|
||||
vSlidingRt.sizeDelta = new Vector2(-20, -20);
|
||||
|
||||
GameObject vHandleGo = new GameObject("Handle",
|
||||
typeof(RectTransform), typeof(Image));
|
||||
vHandleGo.transform.SetParent(vSlidingGo.transform, false);
|
||||
RectTransform vHandleRt = vHandleGo.GetComponent<RectTransform>();
|
||||
vHandleRt.anchorMin = Vector2.zero;
|
||||
vHandleRt.anchorMax = Vector2.one;
|
||||
vHandleRt.sizeDelta = new Vector2(-20, -20);
|
||||
vHandleGo.GetComponent<Image>().color = new Color(0.4f, 0.4f, 0.4f, 0.8f);
|
||||
vBar.handleRect = vHandleRt;
|
||||
|
||||
// 5. 创建 ScrollView 根节点
|
||||
GameObject scrollGo = new GameObject("Xeric UI Action Table",
|
||||
typeof(RectTransform), typeof(ScrollRect), typeof(Image));
|
||||
RectTransform scrollRt = scrollGo.GetComponent<RectTransform>();
|
||||
scrollRt.sizeDelta = new Vector2(800, 600);
|
||||
scrollGo.GetComponent<Image>().color = new Color(0.1f, 0.1f, 0.1f, 0.3f);
|
||||
|
||||
ScrollRect scrollRect = scrollGo.GetComponent<ScrollRect>();
|
||||
scrollRect.content = contentRt;
|
||||
scrollRect.viewport = vpRt;
|
||||
scrollRect.horizontalScrollbar = hBar;
|
||||
scrollRect.verticalScrollbar = vBar;
|
||||
scrollRect.horizontalScrollbarVisibility = ScrollRect.ScrollbarVisibility.AutoHide;
|
||||
scrollRect.verticalScrollbarVisibility = ScrollRect.ScrollbarVisibility.AutoHide;
|
||||
scrollRect.movementType = ScrollRect.MovementType.Clamped;
|
||||
|
||||
// 组装层级
|
||||
vpRt.SetParent(scrollRt, false);
|
||||
hScrollbarGo.transform.SetParent(scrollRt, false);
|
||||
vScrollbarGo.transform.SetParent(scrollRt, false);
|
||||
|
||||
// 绑定 ScrollRect 到表格(通过 SerializedObject 正确回写)
|
||||
using (SerializedObject so = new SerializedObject(table))
|
||||
{
|
||||
SerializedProperty prop = so.FindProperty("m_ScrollRect");
|
||||
if (prop != null)
|
||||
{
|
||||
prop.objectReferenceValue = scrollRect;
|
||||
so.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
|
||||
// 挂载到 Canvas
|
||||
GameObject parent = GetOrCreateCanvas();
|
||||
scrollRt.SetParent(parent.transform, false);
|
||||
|
||||
Undo.RegisterCreatedObjectUndo(scrollGo, "Create Xeric UI Action Table");
|
||||
Selection.activeGameObject = scrollGo;
|
||||
}
|
||||
|
||||
private static GameObject GetOrCreateCanvas()
|
||||
{
|
||||
Canvas canvas = Object.FindObjectOfType<Canvas>();
|
||||
if (canvas != null && canvas.isRootCanvas)
|
||||
return canvas.gameObject;
|
||||
|
||||
GameObject canvasGo = new GameObject("Canvas",
|
||||
typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster));
|
||||
canvasGo.GetComponent<Canvas>().renderMode = RenderMode.ScreenSpaceOverlay;
|
||||
return canvasGo;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9a0aa38413f5264a97d77cb6bdfde8d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,371 @@
|
||||
using UnityEditor;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
using XericUI.XTable.Rendering.Component;
|
||||
|
||||
namespace XericUIEditor.XTable
|
||||
{
|
||||
/// <summary>
|
||||
/// Scene View 表格编辑覆盖层——
|
||||
/// 左侧行索引、顶部列索引、底部输入框。
|
||||
/// 点击行列交叉区域选中单元格,底部输入框编辑文本。
|
||||
/// 不创建任何场景对象,通过 Handles.BeginGUI/EndGUI 绘制。
|
||||
/// </summary>
|
||||
[InitializeOnLoad]
|
||||
public static class XericUIActionTableSceneEditor
|
||||
{
|
||||
private static bool s_Enabled;
|
||||
private const string ENABLED_KEY = "XericUI_TableSceneEditor_Enabled";
|
||||
|
||||
private static string s_EditText = "";
|
||||
|
||||
private static readonly Color s_LabelBg = new Color(0.12f, 0.12f, 0.18f, 0.9f);
|
||||
private static readonly Color s_SelectedLabel = new Color(0.2f, 0.5f, 0.9f, 0.6f);
|
||||
private static readonly Color s_LabelText = new Color(0.75f, 0.75f, 0.8f);
|
||||
private static readonly Color s_InputBg = new Color(0.08f, 0.08f, 0.14f, 0.92f);
|
||||
|
||||
private const float ROW_H = 22f;
|
||||
private const float COL_W = 36f;
|
||||
private const float MARGIN = 3f;
|
||||
|
||||
static XericUIActionTableSceneEditor()
|
||||
{
|
||||
s_Enabled = EditorPrefs.GetBool(ENABLED_KEY, true);
|
||||
SceneView.duringSceneGui += OnSceneGUI;
|
||||
}
|
||||
|
||||
[MenuItem("Xeric UI/表格编辑器覆盖层", false, 200)]
|
||||
private static void ToggleEnabled()
|
||||
{
|
||||
s_Enabled = !s_Enabled;
|
||||
EditorPrefs.SetBool(ENABLED_KEY, s_Enabled);
|
||||
Menu.SetChecked("Xeric UI/表格编辑器覆盖层", s_Enabled);
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
[MenuItem("Xeric UI/表格编辑器覆盖层", true)]
|
||||
private static bool ToggleEnabledValidate()
|
||||
{
|
||||
Menu.SetChecked("Xeric UI/表格编辑器覆盖层", s_Enabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void OnSceneGUI(SceneView sceneView)
|
||||
{
|
||||
if (!s_Enabled) return;
|
||||
|
||||
GameObject sel = Selection.activeGameObject;
|
||||
if (sel == null) return;
|
||||
|
||||
// 不管选了表格祖上还是子孙,都能找到组件
|
||||
XericUIActionTable table = sel.GetComponentInChildren<XericUIActionTable>();
|
||||
if (table == null)
|
||||
table = sel.GetComponentInParent<XericUIActionTable>();
|
||||
if (table == null || !table.isActiveAndEnabled) return;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
Canvas.ForceUpdateCanvases();
|
||||
#endif
|
||||
|
||||
RectTransform tableRt = table.GetComponent<RectTransform>();
|
||||
float[] rh = table.RowHeights;
|
||||
float[] cw = table.ColWidths;
|
||||
if (rh == null || cw == null || rh.Length == 0 || cw.Length == 0) return;
|
||||
|
||||
// GetWorldCorners 返回世界坐标,topleft = corners[1]
|
||||
Vector3[] corners = new Vector3[4];
|
||||
tableRt.GetWorldCorners(corners);
|
||||
|
||||
Camera svCam = sceneView.camera;
|
||||
Rect camRect = svCam.pixelRect;
|
||||
|
||||
// 对 ScreenSpaceOverlay Canvas,世界坐标即屏幕像素坐标
|
||||
// 需要映射到 SceneView 本地 GUI 坐标
|
||||
Vector2 guiTL = ScreenToSceneViewGUI(corners[1], camRect);
|
||||
Vector2 guiBR = ScreenToSceneViewGUI(corners[3], camRect);
|
||||
|
||||
float tableW = guiBR.x - guiTL.x;
|
||||
float tableH = guiBR.y - guiTL.y;
|
||||
if (tableW <= 0 || tableH <= 0) return;
|
||||
|
||||
float totalW = 0, totalH = 0;
|
||||
foreach (float w in cw) totalW += w;
|
||||
foreach (float h in rh) totalH += h;
|
||||
if (totalW <= 0 || totalH <= 0) return;
|
||||
|
||||
float xScale = tableW / totalW;
|
||||
float yScale = tableH / totalH;
|
||||
|
||||
// 表格区域(GUI 坐标,Y 向下)
|
||||
Rect tableRect = new Rect(guiTL.x, guiTL.y, tableW, tableH);
|
||||
|
||||
Handles.BeginGUI();
|
||||
|
||||
DrawRowLabels(rh, tableRect, xScale, yScale, table);
|
||||
DrawColumnLabels(cw, tableRect, xScale, yScale, table);
|
||||
DrawGridLines(rh, cw, tableRect, xScale, yScale);
|
||||
DrawSelectionHighlight(rh, cw, tableRect, xScale, yScale, table);
|
||||
DrawInputBar(table, tableRect.x, tableRect.y + tableRect.height + MARGIN);
|
||||
HandleInput(table, rh, cw, tableRect, xScale, yScale);
|
||||
|
||||
Handles.EndGUI();
|
||||
}
|
||||
|
||||
/// <summary>屏幕坐标转 SceneView 本地 GUI 坐标</summary>
|
||||
private static Vector2 ScreenToSceneViewGUI(Vector2 screenPos, Rect camPixelRect)
|
||||
{
|
||||
return new Vector2(
|
||||
screenPos.x - camPixelRect.x,
|
||||
camPixelRect.yMax - screenPos.y
|
||||
);
|
||||
}
|
||||
|
||||
#region 行标签
|
||||
|
||||
private static void DrawRowLabels(float[] rh, Rect tableRect,
|
||||
float xScale, float yScale, XericUIActionTable table)
|
||||
{
|
||||
var style = new GUIStyle(GUI.skin.label)
|
||||
{
|
||||
fontSize = 10,
|
||||
normal = { textColor = s_LabelText },
|
||||
alignment = TextAnchor.MiddleCenter
|
||||
};
|
||||
|
||||
float y = tableRect.y;
|
||||
for (int r = 0; r < rh.Length; r++)
|
||||
{
|
||||
float cellH = rh[r] * yScale;
|
||||
if (cellH < 2) { y += cellH; continue; }
|
||||
|
||||
Rect labelRect = new Rect(
|
||||
tableRect.x - COL_W - MARGIN,
|
||||
y,
|
||||
COL_W,
|
||||
cellH);
|
||||
|
||||
bool isSelected = (table.SelectedRow == r);
|
||||
EditorGUI.DrawRect(labelRect, isSelected ? s_SelectedLabel : s_LabelBg);
|
||||
GUI.Label(labelRect, $"{r}", style);
|
||||
|
||||
if (Event.current.type == EventType.MouseDown &&
|
||||
Event.current.button == 0 &&
|
||||
labelRect.Contains(Event.current.mousePosition))
|
||||
{
|
||||
int col = table.SelectedCol;
|
||||
if (col < 0) col = 0;
|
||||
table.SelectCell(r, col);
|
||||
table.RefreshView();
|
||||
UpdateEditText(table, r, col);
|
||||
Event.current.Use();
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
y += cellH;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 列标签
|
||||
|
||||
private static void DrawColumnLabels(float[] cw, Rect tableRect,
|
||||
float xScale, float yScale, XericUIActionTable table)
|
||||
{
|
||||
var style = new GUIStyle(GUI.skin.label)
|
||||
{
|
||||
fontSize = 10,
|
||||
normal = { textColor = s_LabelText },
|
||||
alignment = TextAnchor.MiddleCenter
|
||||
};
|
||||
|
||||
float x = tableRect.x;
|
||||
for (int c = 0; c < cw.Length; c++)
|
||||
{
|
||||
float cellW = cw[c] * xScale;
|
||||
if (cellW < 2) { x += cellW; continue; }
|
||||
|
||||
Rect labelRect = new Rect(
|
||||
x,
|
||||
tableRect.y - ROW_H - MARGIN,
|
||||
cellW,
|
||||
ROW_H);
|
||||
|
||||
bool isSelected = (table.SelectedCol == c);
|
||||
EditorGUI.DrawRect(labelRect, isSelected ? s_SelectedLabel : s_LabelBg);
|
||||
string text = cellW > 40 ? $"列{c}" : (cellW > 18 ? $"{c}" : "");
|
||||
GUI.Label(labelRect, text, style);
|
||||
|
||||
if (Event.current.type == EventType.MouseDown &&
|
||||
Event.current.button == 0 &&
|
||||
labelRect.Contains(Event.current.mousePosition))
|
||||
{
|
||||
int row = table.SelectedRow;
|
||||
if (row < 0) row = 0;
|
||||
table.SelectCell(row, c);
|
||||
table.RefreshView();
|
||||
UpdateEditText(table, row, c);
|
||||
Event.current.Use();
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
x += cellW;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 网格线
|
||||
|
||||
private static void DrawGridLines(float[] rh, float[] cw, Rect tr,
|
||||
float xScale, float yScale)
|
||||
{
|
||||
Color lineColor = new Color(0.5f, 0.5f, 0.55f, 0.18f);
|
||||
|
||||
float y = tr.y;
|
||||
for (int r = 0; r < rh.Length; r++)
|
||||
{
|
||||
y += rh[r] * yScale;
|
||||
EditorGUI.DrawRect(new Rect(tr.x, y, tr.width, 1), lineColor);
|
||||
}
|
||||
|
||||
float x = tr.x;
|
||||
for (int c = 0; c < cw.Length; c++)
|
||||
{
|
||||
x += cw[c] * xScale;
|
||||
EditorGUI.DrawRect(new Rect(x, tr.y, 1, tr.height), lineColor);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 选中高亮
|
||||
|
||||
private static void DrawSelectionHighlight(float[] rh, float[] cw, Rect tr,
|
||||
float xScale, float yScale, XericUIActionTable table)
|
||||
{
|
||||
int sr = table.SelectedRow;
|
||||
int sc = table.SelectedCol;
|
||||
if (sr < 0 || sc < 0 || sr >= rh.Length || sc >= cw.Length) return;
|
||||
|
||||
float sx = tr.x;
|
||||
for (int c = 0; c < sc; c++) sx += cw[c] * xScale;
|
||||
|
||||
float sy = tr.y;
|
||||
for (int r = 0; r < sr; r++) sy += rh[r] * yScale;
|
||||
|
||||
float sw = cw[sc] * xScale;
|
||||
float sh = rh[sr] * yScale;
|
||||
|
||||
EditorGUI.DrawRect(new Rect(sx + 1, sy + 1, sw - 2, sh - 2),
|
||||
new Color(0.18f, 0.45f, 0.85f, 0.25f));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 底部输入框
|
||||
|
||||
private static void DrawInputBar(XericUIActionTable table, float x, float y)
|
||||
{
|
||||
Rect barRect = new Rect(x, y, 400, 24);
|
||||
EditorGUI.DrawRect(barRect, s_InputBg);
|
||||
|
||||
if (table.SelectedRow >= 0 && table.SelectedCol >= 0)
|
||||
{
|
||||
var labelStyle = new GUIStyle(GUI.skin.label)
|
||||
{
|
||||
fontSize = 11,
|
||||
normal = { textColor = s_LabelText },
|
||||
alignment = TextAnchor.MiddleCenter
|
||||
};
|
||||
GUI.Label(new Rect(barRect.x + 4, barRect.y, 50, barRect.height),
|
||||
$"({table.SelectedRow},{table.SelectedCol})", labelStyle);
|
||||
|
||||
var inputStyle = new GUIStyle(GUI.skin.textField)
|
||||
{
|
||||
fontSize = 12,
|
||||
margin = new RectOffset(2, 2, 2, 2)
|
||||
};
|
||||
Rect inputRect = new Rect(barRect.x + 56, barRect.y + 2, 180, barRect.height - 4);
|
||||
s_EditText = GUI.TextField(inputRect, s_EditText, inputStyle);
|
||||
|
||||
var btnStyle = new GUIStyle(GUI.skin.button)
|
||||
{
|
||||
fontSize = 11,
|
||||
margin = new RectOffset(2, 2, 2, 2),
|
||||
padding = new RectOffset(4, 4, 2, 2)
|
||||
};
|
||||
if (GUI.Button(new Rect(barRect.x + 242, barRect.y + 2, 50, barRect.height - 4),
|
||||
"应用", btnStyle))
|
||||
{
|
||||
ApplyEdit(table);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var hintStyle = new GUIStyle(GUI.skin.label)
|
||||
{
|
||||
fontSize = 10,
|
||||
normal = { textColor = new Color(0.5f, 0.5f, 0.5f) },
|
||||
alignment = TextAnchor.MiddleCenter
|
||||
};
|
||||
GUI.Label(barRect, "点击左侧行号或上方列号选中单元格", hintStyle);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyEdit(XericUIActionTable table)
|
||||
{
|
||||
if (table.SelectedRow < 0 || table.SelectedCol < 0) return;
|
||||
table.SetCellText(table.SelectedRow, table.SelectedCol, s_EditText);
|
||||
table.RefreshView();
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
private static void UpdateEditText(XericUIActionTable table, int row, int col)
|
||||
{
|
||||
var data = table.GetCellData(row, col);
|
||||
s_EditText = data != null ? data.Text ?? "" : "";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 表格区域点击
|
||||
|
||||
private static void HandleInput(XericUIActionTable table,
|
||||
float[] rh, float[] cw, Rect tr,
|
||||
float xScale, float yScale)
|
||||
{
|
||||
Event e = Event.current;
|
||||
if (e.type != EventType.MouseDown || e.button != 0) return;
|
||||
if (!tr.Contains(e.mousePosition)) return;
|
||||
|
||||
float rx = (e.mousePosition.x - tr.x) / xScale;
|
||||
float ry = (e.mousePosition.y - tr.y) / yScale;
|
||||
|
||||
int col = IndexFromCoord(rx, cw);
|
||||
int row = IndexFromCoord(ry, rh);
|
||||
if (row < 0 || col < 0) return;
|
||||
|
||||
table.SelectCell(row, col);
|
||||
table.RefreshView();
|
||||
UpdateEditText(table, row, col);
|
||||
|
||||
e.Use();
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private static int IndexFromCoord(float coord, float[] sizes)
|
||||
{
|
||||
float acc = 0;
|
||||
for (int i = 0; i < sizes.Length; i++)
|
||||
{
|
||||
if (coord >= acc && coord < acc + sizes[i]) return i;
|
||||
acc += sizes[i];
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e04322ddc12583c4187e283c0752bcf6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user