新Head
This commit is contained in:
@@ -1,8 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ea823e8c44a0ca842a3b0e59390994de
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,98 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Ichni.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// 数值或枚举控制参数标签,挂载在 Field 或 Property 上
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
||||
public class DynamicUIAttribute : Attribute
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 属于哪个 Container (比如 "InGame Settings" 或 "Path Node")
|
||||
/// </summary>
|
||||
public string Group { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 排版格子要求。一行最大宽度分为 3 份。
|
||||
/// 填 1 代表需占 1/3。填 3 代表全宽占满整行。
|
||||
/// 若填 0,由内置工厂根据数据类型自动识别(例如 Vector3 与 Color 自动为 3,bool为1 等)。
|
||||
/// </summary>
|
||||
public int Span { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 垂直占位高度。填 0 由系统智能推断(如基础件 100,ColorPicker 240),也可显式填写如 150f 覆盖。
|
||||
/// </summary>
|
||||
public float Height { get; set; }
|
||||
|
||||
public bool AutoUpdate { get; set; }
|
||||
public bool ReadOnly { get; set; }
|
||||
|
||||
public DynamicUIAttribute(string name = "", string group = "Default", int span = 0, float height = 0f, bool autoUpdate = false, bool readOnly = false)
|
||||
{
|
||||
Name = name;
|
||||
Group = group;
|
||||
Span = span;
|
||||
Height = height;
|
||||
AutoUpdate = autoUpdate;
|
||||
ReadOnly = readOnly;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 支持 HDR 以及独立 Emission 强度或开关控制通道的拾色器。
|
||||
/// 若无独立通道字段可置为 "NULL",将自动转为读取并写入至 Target Color 的 Alpha 通道中。
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
||||
public class DynamicUIEmissionColorAttribute : DynamicUIAttribute
|
||||
{
|
||||
public string EmissionEnabledName { get; set; }
|
||||
public string EmissionIntensityName { get; set; }
|
||||
|
||||
public DynamicUIEmissionColorAttribute(string name, string enabledName = "NULL", string intensityName = "NULL", string group = "Default", int span = 3, float height = 0f)
|
||||
: base(name, group, span, height)
|
||||
{
|
||||
EmissionEnabledName = enabledName;
|
||||
EmissionIntensityName = intensityName;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 滑块控制属性,适用于 float/int 类型字段,自动生成带范围限制的 Slider 替代 InputField。
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
||||
public class DynamicUISliderAttribute : DynamicUIAttribute
|
||||
{
|
||||
public float Min { get; set; }
|
||||
public float Max { get; set; }
|
||||
public bool WholeNumbers { get; set; }
|
||||
|
||||
public DynamicUISliderAttribute(string name = "", string group = "Default",
|
||||
float min = 0f, float max = 1f, bool wholeNumbers = false,
|
||||
int span = 0, float height = 0f, bool autoUpdate = false, bool readOnly = false)
|
||||
: base(name, group, span, height, autoUpdate, readOnly)
|
||||
{
|
||||
Min = min;
|
||||
Max = max;
|
||||
WholeNumbers = wholeNumbers;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 操作按钮卡片,挂载在没有任何参数的 Method() 方法上
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class DynamicUIButtonAttribute : Attribute
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Group { get; set; }
|
||||
|
||||
public DynamicUIButtonAttribute(string name = "", string group = "Default")
|
||||
{
|
||||
Name = name;
|
||||
Group = group;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8a540054c95f48544bd6efcea6b452cc
|
||||
@@ -1,228 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using Ichni.RhythmGame;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace Ichni.Editor
|
||||
{
|
||||
public static class DynamicUIAutoBuilder
|
||||
{
|
||||
// 反射信息快取
|
||||
private class FieldDef
|
||||
{
|
||||
public MemberInfo Member;
|
||||
public DynamicUIAttribute Attr;
|
||||
public Type ValueType;
|
||||
}
|
||||
|
||||
private class MethodDef
|
||||
{
|
||||
public MethodInfo Method;
|
||||
public DynamicUIButtonAttribute Attr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 接管任何 IBaseElement 的 UI 生成作业
|
||||
/// </summary>
|
||||
public static void AutoBuild(IBaseElement element, IHaveInspection inspector)
|
||||
{
|
||||
Type t = element.GetType();
|
||||
|
||||
// 扫描所有带 DynamicUI 标记的字段或属性
|
||||
var dynamicFields = new List<FieldDef>();
|
||||
foreach (var member in t.GetMembers(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
|
||||
{
|
||||
var uiAttr = member.GetCustomAttribute<DynamicUIAttribute>();
|
||||
if (uiAttr != null)
|
||||
{
|
||||
Type vType = member is FieldInfo f ? f.FieldType : (member as PropertyInfo)?.PropertyType;
|
||||
if (vType != null) dynamicFields.Add(new FieldDef { Member = member, Attr = uiAttr, ValueType = vType });
|
||||
}
|
||||
}
|
||||
|
||||
// 扫描所有带 DynamicUIButton 的方法
|
||||
var dynamicMethods = new List<MethodDef>();
|
||||
foreach (var method in t.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
|
||||
{
|
||||
var btnAttr = method.GetCustomAttribute<DynamicUIButtonAttribute>();
|
||||
if (btnAttr != null && method.GetParameters().Length == 0)
|
||||
{
|
||||
dynamicMethods.Add(new MethodDef { Method = method, Attr = btnAttr });
|
||||
}
|
||||
}
|
||||
|
||||
// 按照 Group 归类准备流式渲染
|
||||
var allGroups = dynamicFields.Select(f => f.Attr.Group)
|
||||
.Concat(dynamicMethods.Select(m => m.Attr.Group))
|
||||
.Distinct().ToList();
|
||||
|
||||
foreach (var groupName in allGroups)
|
||||
{
|
||||
var container = inspector.GenerateContainer(groupName);
|
||||
|
||||
var fieldsInGroup = dynamicFields.Where(f => f.Attr.Group == groupName).ToList();
|
||||
var methodsInGroup = dynamicMethods.Where(m => m.Attr.Group == groupName).ToList();
|
||||
|
||||
// ======== 智能栅格打包推演 ========
|
||||
List<FieldDef> currentLineBuffer = new List<FieldDef>();
|
||||
int currentSpanCount = 0; // 一行容量最高是 3
|
||||
|
||||
// 先排变量
|
||||
foreach (var field in fieldsInGroup)
|
||||
{
|
||||
int span = GetEffectiveSpan(field);
|
||||
span = Mathf.Clamp(span, 1, 3);
|
||||
|
||||
// 如果该行的槽被塞满了或者装不下了
|
||||
if (currentSpanCount > 0 && currentSpanCount + span > 3)
|
||||
{
|
||||
FlushLine(element, inspector, container, currentLineBuffer, 3);
|
||||
currentLineBuffer.Clear();
|
||||
currentSpanCount = 0;
|
||||
}
|
||||
|
||||
// 特殊兼容:由于 GridLayoutGroup 的强制统一 cellSize, 我们如果在同一行混搭不同 Span 宽度的,反而会让它变形。
|
||||
// 因此若当前这行的空间要求和前一个需求跨度不一致,也必须强行断点分层!
|
||||
if (currentLineBuffer.Count > 0)
|
||||
{
|
||||
var lastSpan = GetEffectiveSpan(currentLineBuffer[0]);
|
||||
if (span != lastSpan)
|
||||
{
|
||||
FlushLine(element, inspector, container, currentLineBuffer, GetHighestSpanRequirement(currentLineBuffer));
|
||||
currentLineBuffer.Clear();
|
||||
currentSpanCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
currentLineBuffer.Add(field);
|
||||
currentSpanCount += span;
|
||||
}
|
||||
if (currentLineBuffer.Count > 0) FlushLine(element, inspector, container, currentLineBuffer, GetHighestSpanRequirement(currentLineBuffer));
|
||||
|
||||
// ======== 最后把按钮并排排好 ========
|
||||
if (methodsInGroup.Count > 0)
|
||||
{
|
||||
var btnSubcontainer = container.GenerateSubcontainer(3); // 按钮统统放在 1/3 的槽里(你可改规则)
|
||||
foreach (var method in methodsInGroup)
|
||||
{
|
||||
MethodInfo mInfo = method.Method;
|
||||
// 通过强行抓取生成 UnityAction 给工厂
|
||||
UnityAction action = () => { mInfo.Invoke(element, null); element.Refresh(); };
|
||||
string n = string.IsNullOrEmpty(method.Attr.Name) ? mInfo.Name : method.Attr.Name;
|
||||
inspector.GenerateButton(element, btnSubcontainer, n, action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 核心工厂发射器
|
||||
private static void FlushLine(IBaseElement element, IHaveInspection inspector, DynamicUIContainer container, List<FieldDef> fields, int lineSpanCapacity)
|
||||
{
|
||||
// 如果一行要放 3 份(Span 都是 1 拼成的),那格子数量也是 3(600/3=200一个)
|
||||
// 但是如果是独占的(Span 都是 3),格子数量反而应该是 1(600/1=600一个!)
|
||||
// 为了防止排版错开变形,我们通过 3 / span 来决定 Subcontainer 内部允许的等分元素数量。
|
||||
int subcontainerDivision = Mathf.Max(1, Mathf.RoundToInt(3f / lineSpanCapacity));
|
||||
|
||||
float rowHeight = GetHighestHeightRequirement(fields);
|
||||
var sub = container.GenerateSubcontainer(subcontainerDivision, rowHeight);
|
||||
|
||||
foreach (var fd in fields)
|
||||
{
|
||||
string title = string.IsNullOrEmpty(fd.Attr.Name) ? fd.Member.Name : fd.Attr.Name;
|
||||
string pName = fd.Member.Name;
|
||||
|
||||
if (fd.Attr.ReadOnly)
|
||||
{
|
||||
var val = ReflectionHelper.GetDeepValue(element, pName);
|
||||
inspector.GenerateHintText(element, sub, $"{title}: {val}");
|
||||
continue;
|
||||
}
|
||||
|
||||
// 智能类型分流
|
||||
if (fd.Attr is DynamicUIEmissionColorAttribute emissionAttr)
|
||||
{
|
||||
// 使用之前为了消除扩展方法调不出来的尴尬而专门暴露的强转/普通调用,我们直接这里也安全使用
|
||||
IHaveInspection rawInspector = inspector as IHaveInspection;
|
||||
if(rawInspector != null) rawInspector.GenerateEmissionColorPicker(element, sub, title, emissionAttr.EmissionEnabledName, pName, emissionAttr.EmissionIntensityName);
|
||||
}
|
||||
else if (fd.ValueType == typeof(bool))
|
||||
{
|
||||
inspector.GenerateToggle(element, sub, title, pName);
|
||||
}
|
||||
else if (fd.ValueType == typeof(Vector2))
|
||||
{
|
||||
inspector.GenerateVector2InputField(element, sub, title, pName, fd.Attr.AutoUpdate);
|
||||
}
|
||||
else if (fd.ValueType == typeof(Vector3))
|
||||
{
|
||||
inspector.GenerateVector3InputField(element, sub, title, pName, fd.Attr.AutoUpdate);
|
||||
}
|
||||
else if (fd.ValueType == typeof(Color))
|
||||
{
|
||||
inspector.GenerateBaseColorPicker(element, sub, title, pName);
|
||||
}
|
||||
else if (fd.ValueType.IsEnum)
|
||||
{
|
||||
inspector.GenerateDropdown(element, sub, title, fd.ValueType, pName);
|
||||
}
|
||||
else if (fd.Attr is DynamicUISliderAttribute sliderAttr)
|
||||
{
|
||||
inspector.GenerateSlider(element, sub, title, pName, sliderAttr.Min, sliderAttr.Max, sliderAttr.WholeNumbers);
|
||||
}
|
||||
else // 容错给普通的 InputField
|
||||
{
|
||||
inspector.GenerateInputField(element, sub, title, pName, fd.Attr.AutoUpdate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int SmartInferSpan(Type t)
|
||||
{
|
||||
if (t == typeof(bool)) return 1;
|
||||
if (t == typeof(float) || t == typeof(int) || t == typeof(string)) return 1;
|
||||
if (t.IsEnum) return 1;
|
||||
if (t == typeof(Vector2)) return 2;
|
||||
if (t == typeof(Vector3) || t == typeof(Color)) return 3; // 巨型组件通篇独占一行
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int GetHighestSpanRequirement(List<FieldDef> fields)
|
||||
{
|
||||
int highest = 1;
|
||||
foreach (var f in fields)
|
||||
{
|
||||
int span = GetEffectiveSpan(f);
|
||||
if (span > highest) highest = span;
|
||||
}
|
||||
return highest;
|
||||
}
|
||||
|
||||
private static int GetEffectiveSpan(FieldDef fd)
|
||||
{
|
||||
return fd.Attr.Span > 0 ? fd.Attr.Span : SmartInferSpan(fd.ValueType);
|
||||
}
|
||||
|
||||
private static float SmartInferHeight(FieldDef fd)
|
||||
{
|
||||
// EmissionColorPicker 有 Toggle/RGB滑块/Intensity输入框,高度明显大于 BaseColorPicker
|
||||
if (fd.Attr is DynamicUIEmissionColorAttribute) return 320f;
|
||||
if (fd.ValueType == typeof(Color)) return 280f;
|
||||
// 多数基础组件默认为 100
|
||||
return 100f;
|
||||
}
|
||||
|
||||
private static float GetHighestHeightRequirement(List<FieldDef> fields)
|
||||
{
|
||||
float highest = 100f;
|
||||
foreach (var f in fields)
|
||||
{
|
||||
float height = f.Attr.Height > 0 ? f.Attr.Height : SmartInferHeight(f);
|
||||
if (height > highest) highest = height;
|
||||
}
|
||||
return highest;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f0bc252acb576104aaff0846e4c1e136
|
||||
@@ -13,6 +13,11 @@ using Object = UnityEngine.Object;
|
||||
|
||||
namespace Ichni.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Inspector 工厂接口 —— 提供底层 UI 控件的实例化方法。
|
||||
/// 外部代码应通过 <see cref="InspectorBuilder"/> 构建 Inspector,
|
||||
/// 而非直接调用本接口的 Generate 方法。
|
||||
/// </summary>
|
||||
public interface IHaveInspection
|
||||
{
|
||||
public RectTransform WindowRect { get; set; }
|
||||
@@ -226,6 +231,7 @@ namespace Ichni.Editor
|
||||
return hintText;
|
||||
}
|
||||
|
||||
[Obsolete("GenerateParameterText 已无外部调用者,请使用 InspectorBuilder.HintText() 替代。")]
|
||||
public DynamicUIParameterText GenerateParameterText(IBaseElement baseElement, DynamicUISubcontainer subcontainer,
|
||||
string title, string parameterName, bool isAutoUpdate = false)
|
||||
{
|
||||
|
||||
@@ -22,6 +22,9 @@ namespace Ichni.Editor
|
||||
public Dictionary<string, DynamicUISubcontainer> MarkedSubcontainers { get; set; }
|
||||
public Dictionary<string, DynamicUIElement> MarkedElements { get; set; }
|
||||
|
||||
/// <summary> Drawer 注册表单例的便捷访问 </summary>
|
||||
public DrawerRegistry DrawerRegistry => DrawerRegistry.Instance;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
WindowRect = inspectorRect;
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Ichni.RhythmGame;
|
||||
using UnityEngine;
|
||||
namespace Ichni.Editor
|
||||
{
|
||||
public static class StandardInspectionElement
|
||||
{
|
||||
private static IHaveInspection inspector => EditorManager.instance.uiManager.inspector;
|
||||
private static Inspector inspectorUI => EditorManager.instance.uiManager.inspector;
|
||||
public static void GenerateForTransform(GameElement gameElement, DynamicUIContainer generateContainer = null)//关于有Transform
|
||||
{
|
||||
if (generateContainer is null)
|
||||
{
|
||||
generateContainer = inspector.GenerateContainer("Generate Elements");
|
||||
}
|
||||
var animationSubcontainer = generateContainer.GenerateSubcontainer(3);
|
||||
var displacementButton = inspector.GenerateButton(gameElement, animationSubcontainer, "Displacement", () =>
|
||||
{
|
||||
Displacement.GenerateElement("New Displacement", Guid.NewGuid(), new List<string>(), true, gameElement,
|
||||
new FlexibleFloat(true), new FlexibleFloat(true), new FlexibleFloat(true));
|
||||
}); //位移
|
||||
|
||||
var swirlButton = inspector.GenerateButton(gameElement, animationSubcontainer, "Swirl", () =>
|
||||
{
|
||||
Swirl.GenerateElement("New Swirl", Guid.NewGuid(), new List<string>(), true, gameElement,
|
||||
new FlexibleFloat(true), new FlexibleFloat(true), new FlexibleFloat(true));
|
||||
}); //旋转
|
||||
var scaleButton = inspector.GenerateButton(gameElement, animationSubcontainer, "Scale", () =>
|
||||
{
|
||||
Scale.GenerateElement("New Scale", Guid.NewGuid(), new List<string>(), true, gameElement,
|
||||
new FlexibleFloat(true), new FlexibleFloat(true), new FlexibleFloat(true));
|
||||
}); //缩放
|
||||
var LookAtButton = inspector.GenerateButton(gameElement, animationSubcontainer, "Look At",
|
||||
() => LookAt.GenerateElement("New Look At", Guid.NewGuid(),
|
||||
new List<string>(), true, gameElement, null, new FlexibleBool()));
|
||||
var displacementTrackerButton = inspector.GenerateButton(gameElement, animationSubcontainer, "Displacement Tracker", () =>
|
||||
{
|
||||
DisplacementTracker.GenerateElement("New Displacement Tracker", Guid.NewGuid(), new List<string>(), true, gameElement,
|
||||
null, 0f);
|
||||
});
|
||||
var swirlTrackerButton = inspector.GenerateButton(gameElement, animationSubcontainer, "Swirl Tracker", () =>
|
||||
{
|
||||
SwirlTracker.GenerateElement("New Swirl Tracker", Guid.NewGuid(), new List<string>(), true, gameElement,
|
||||
null, 0f);
|
||||
}); var ScaleTrackerButton = inspector.GenerateButton(gameElement, animationSubcontainer, "Scale Tracker", () =>
|
||||
{
|
||||
ScaleTracker.GenerateElement("New Scale Tracker", Guid.NewGuid(), new List<string>(), true, gameElement,
|
||||
null, 0f);
|
||||
});
|
||||
}
|
||||
public static void GenerateForLoading()
|
||||
{
|
||||
inspectorUI.ClearInspector();
|
||||
var container = inspector.GenerateContainer("Loading");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 97779e95563721b4e8d4a28bb0d46cb9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user