DragonECS-Unity/src/Internal/Editor/EcsGUI.cs
2024-05-16 23:27:45 +08:00

598 lines
25 KiB
C#

#if UNITY_EDITOR
using DCFApixels.DragonECS.Unity.Internal;
using System;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using UnityObject = UnityEngine.Object;
using UnityComponent = UnityEngine.Component;
namespace DCFApixels.DragonECS.Unity.Editors
{
internal static class EcsGUI
{
#region Scores
public struct LabelWidthScore : IDisposable
{
private readonly float _value;
public LabelWidthScore(float value)
{
_value = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = value;
}
public void Dispose()
{
EditorGUIUtility.labelWidth = _value;
}
}
public struct ColorScope : IDisposable
{
private readonly Color _value;
public ColorScope(float r, float g, float b, float a = 1f) : this(new Color(r, g, b, a)) { }
public ColorScope(Color value)
{
_value = GUI.color;
GUI.color = value;
}
public void Dispose()
{
GUI.color = _value;
}
}
public struct ContentColorScope : IDisposable
{
private readonly Color _value;
public ContentColorScope(float r, float g, float b, float a = 1f) : this(new Color(r, g, b, a)) { }
public ContentColorScope(Color value)
{
_value = GUI.contentColor;
GUI.contentColor = value;
}
public void Dispose()
{
GUI.contentColor = _value;
}
}
private static ContentColorScope SetContentColor(Color value) => new ContentColorScope(value);
private static ContentColorScope SetContentColor(float r, float g, float b, float a = 1f) => new ContentColorScope(r, g, b, a);
private static ColorScope SetColor(Color value) => new ColorScope(value);
private static ColorScope SetColor(float r, float g, float b, float a = 1f) => new ColorScope(r, g, b, a);
private static EditorGUI.DisabledScope Enable => new EditorGUI.DisabledScope(false);
private static EditorGUI.DisabledScope Disable => new EditorGUI.DisabledScope(true);
private static EditorGUI.DisabledScope SetEnable(bool value) => new EditorGUI.DisabledScope(!value);
#endregion
private static readonly BindingFlags fieldFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
internal readonly static Color GrayColor = new Color32(100, 100, 100, 255);
internal readonly static Color GreenColor = new Color32(75, 255, 0, 255);
internal readonly static Color RedColor = new Color32(255, 0, 75, 255);
private static readonly Rect RemoveButtonRect = new Rect(0f, 0f, 19f, 19f);
private static readonly Rect TooltipIconRect = new Rect(0f, 0f, 19f, 19f);
public static float EntityBarHeight => EditorGUIUtility.singleLineHeight + 3f;
#region Properties
private static ComponentColorMode AutoColorMode
{
get { return SettingsPrefs.instance.ComponentColorMode; }
set { SettingsPrefs.instance.ComponentColorMode = value; }
}
private static bool IsShowHidden
{
get { return SettingsPrefs.instance.IsShowHidden; }
set { SettingsPrefs.instance.IsShowHidden = value; }
}
private static bool IsShowRuntimeComponents
{
get { return SettingsPrefs.instance.IsShowRuntimeComponents; }
set { SettingsPrefs.instance.IsShowRuntimeComponents = value; }
}
#endregion
#region enums
public enum AddClearComponentButton : byte
{
None = 0,
AddComponent,
Clear,
}
[Flags]
public enum EntityStatus
{
NotAlive = 0,
Alive = 1 << 0,
Undefined = 1 << 1,
}
#endregion
#region HitTest
internal static bool HitTest(Rect rect)
{
return HitTest(rect, Event.current.mousePosition);
}
internal static bool HitTest(Rect rect, Event evt)
{
return HitTest(rect, evt.mousePosition);
}
internal static bool HitTest(Rect rect, Vector2 point)
{
int offset = 0;
return HitTest(rect, point, offset);
}
internal static bool HitTest(Rect rect, Vector2 point, int offset)
{
return point.x >= rect.xMin - (float)offset && point.x < rect.xMax + (float)offset && point.y >= rect.yMin - (float)offset && point.y < rect.yMax + (float)offset;
}
#endregion
#region small elems
public static void DrawIcon(Rect position, Texture icon, float iconPadding, string description)
{
using (SetColor(GUI.enabled ? GUI.color : GUI.color * new Color(1f, 1f, 1f, 0.4f)))
{
GUI.Label(position, UnityEditorUtility.GetLabel(string.Empty, description));
GUI.DrawTexture(RectUtility.AddPadding(position, iconPadding), icon);
}
}
public static (bool, bool) IconButtonGeneric(Rect position)
{
Color dc = GUI.color;
GUI.color = Color.clear; //Хак чтобы сделать реакцию от курсора мыши без лага
bool result = GUI.Button(position, "", EditorStyles.miniButtonMid);
GUI.color = dc;
var current = Event.current;
return (GUI.enabled && HitTest(position, current), result);
}
public static bool IconButton(Rect position, Texture icon, float iconPadding, string description)
{
bool result = GUI.Button(position, UnityEditorUtility.GetLabel(string.Empty));
DrawIcon(position, icon, iconPadding, description);
return result;
}
public static void DescriptionIcon(Rect position, string description)
{
using (new ColorScope(new Color(1f, 1f, 1f, 0.8f)))
{
DrawIcon(position, Icons.Instance.HelpIcon, 0, description);
}
}
public static bool CloseButton(Rect position)
{
using (new ColorScope(new Color(1f, 1f, 1f, 0.8f)))
{
var (hover, click) = IconButtonGeneric(position);
if (hover)
{
DrawIcon(position, Icons.Instance.CloseIconOn, -4f, null);
}
else
{
DrawIcon(position, Icons.Instance.CloseIcon, 0, null);
}
return click;
}
}
public static bool AutosetCascadeButton(Rect position)
{
return IconButton(position, Icons.Instance.AutosetCascadeIcon, 0f, "Autoset Cascade");
}
public static bool AutosetButton(Rect position)
{
return IconButton(position, Icons.Instance.AuotsetIcon, 1f, "Autoset");
}
public static bool UnlinkButton(Rect position)
{
return IconButton(position, Icons.Instance.UnlinkIcon, 1f, "Unlink Entity");
}
public static bool DelEntityButton(Rect position)
{
return IconButton(position, Icons.Instance.CloseIcon, 0f, "Delete Entity");
}
#endregion
#region entity bar
public static void EntityBarForAlive(Rect position, EntityStatus status, int id, short gen, short world)
{
EntityBar(position, status != EntityStatus.Alive, status, id, gen, world);
}
public static void EntityBar(Rect position, int id, short gen, short world)
{
EntityBar_Internal(position, false, id, gen, world);
}
public static void EntityBar(Rect position)
{
EntityBar_Internal(position, true);
}
public static void EntityBar(Rect position, bool isPlaceholder, EntityStatus status, int id = 0, short gen = 0, short world = 0)
{
using (new LabelWidthScore(0f))
{
var (entityInfoRect, statusRect) = RectUtility.VerticalSliceBottom(position, 3f);
Color statusColor;
switch (status)
{
case EntityStatus.NotAlive:
statusColor = EcsGUI.RedColor;
break;
case EntityStatus.Alive:
statusColor = EcsGUI.GreenColor;
break;
default:
statusColor = new Color32(200, 200, 200, 255);
break;
}
statusColor.a = 0.6f;
EditorGUI.DrawRect(statusRect, statusColor);
EntityBar_Internal(entityInfoRect, isPlaceholder, id, gen, world);
}
}
private static void EntityBar_Internal(Rect position, bool isPlaceHolder, int id = 0, short gen = 0, short world = 0)
{
using (new LabelWidthScore(0f))
{
Color w = Color.gray;
w.a = 0.6f;
Color b = Color.black;
b.a = 0.55f;
EditorGUI.DrawRect(position, w);
var (idRect, genWorldRect) = RectUtility.HorizontalSliceLerp(position, 0.4f);
var (genRect, worldRect) = RectUtility.HorizontalSliceLerp(genWorldRect, 0.5f);
idRect = RectUtility.AddPadding(idRect, 2, 1, 0, 0);
genRect = RectUtility.AddPadding(genRect, 1, 1, 0, 0);
worldRect = RectUtility.AddPadding(worldRect, 1, 2, 0, 0);
EditorGUI.DrawRect(idRect, b);
EditorGUI.DrawRect(genRect, b);
EditorGUI.DrawRect(worldRect, b);
GUIStyle style = UnityEditorUtility.GetInputFieldCenterAnhor();
if (isPlaceHolder)
{
using (new EditorGUI.DisabledScope(true))
{
GUI.Label(idRect, "Entity ID", style);
GUI.Label(genRect, "Generation", style);
GUI.Label(worldRect, "World ID", style);
}
}
else
{
EditorGUI.IntField(idRect, id, style);
EditorGUI.IntField(genRect, gen, style);
EditorGUI.IntField(worldRect, world, style);
}
}
}
#endregion
internal static int GetChildPropertiesCount(SerializedProperty property, Type type, out bool isEmpty)
{
int result = GetChildPropertiesCount(property);
isEmpty = result <= 0 && type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Length <= 0;
return result;
}
internal static int GetChildPropertiesCount(SerializedProperty property)
{
var propsCounter = property.Copy();
int lastDepth = propsCounter.depth;
bool next = propsCounter.Next(true) && lastDepth < propsCounter.depth;
int result = next ? -1 : 0;
while (next)
{
result++;
next = propsCounter.Next(false);
}
return result;
}
public static Color SelectPanelColor(ITypeMeta meta, int index, int total)
{
var trueMeta = meta.Type.ToMeta();
if (trueMeta.IsCustomColor || meta.Color != trueMeta.Color)
{
return meta.Color.ToUnityColor();
}
else
{
switch (AutoColorMode)
{
case ComponentColorMode.Auto:
return meta.Color.ToUnityColor().Desaturate(0.48f) / 1.18f; //.Desaturate(0.48f) / 1.18f;
case ComponentColorMode.Rainbow:
int localTotal = Mathf.Max(total, EscEditorConsts.AUTO_COLOR_RAINBOW_MIN_RANGE);
Color hsv = Color.HSVToRGB(1f / localTotal * (index % localTotal), 1, 1);
return hsv.Desaturate(0.48f) / 1.18f;
default:
return index % 2 == 0 ? new Color(0.40f, 0.40f, 0.40f) : new Color(0.54f, 0.54f, 0.54f);
}
}
}
public static bool AddComponentButtons(Rect position)
{
position = RectUtility.AddPadding(position, 20f, 20f, 12f, 2f);
return GUI.Button(position, "Add Component");
}
public static AddClearComponentButton AddClearComponentButtons(Rect position)
{
position = RectUtility.AddPadding(position, 20f, 20f, 12f, 2f);
var (left, right) = RectUtility.HorizontalSliceLerp(position, 0.75f);
if (GUI.Button(left, "Add Component"))
{
return AddClearComponentButton.AddComponent;
}
if (GUI.Button(right, "Clear"))
{
return AddClearComponentButton.Clear;
}
return AddClearComponentButton.None;
}
public static void DrawEmptyComponentProperty(Rect position, SerializedProperty property, string name, bool isDisplayEmpty)
{
DrawEmptyComponentProperty(position, property, UnityEditorUtility.GetLabel(name), isDisplayEmpty);
}
public static void DrawEmptyComponentProperty(Rect position, SerializedProperty property, GUIContent label, bool isDisplayEmpty)
{
EditorGUI.LabelField(position, label);
if (isDisplayEmpty)
{
using (SetContentColor(1f, 1f, 1f, 0.4f))
{
GUI.Label(position.AddPadding(EditorGUIUtility.labelWidth, 0, 0, 0), "empty");
}
}
EditorGUI.BeginProperty(position, label, property);
EditorGUI.EndProperty();
}
public static class Layout
{
public static void DrawEmptyComponentProperty(SerializedProperty property, string name, bool isDisplayEmpty)
{
EcsGUI.DrawEmptyComponentProperty(GUILayoutUtility.GetRect(UnityEditorUtility.GetLabel(name), EditorStyles.label), property, name, isDisplayEmpty);
}
public static void DrawEmptyComponentProperty(SerializedProperty property, GUIContent label, bool isDisplayEmpty)
{
EcsGUI.DrawEmptyComponentProperty(GUILayoutUtility.GetRect(label, EditorStyles.label), property, label, isDisplayEmpty);
}
public static void DrawWorldBaseInfo(EcsWorld world)
{
bool isNull = world == null || world.IsDestroyed || world.id == 0;
int entitesCount = isNull ? 0 : world.Count;
int capacity = isNull ? 0 : world.Capacity;
int leakedEntitesCount = isNull ? 0 : world.CountLeakedEntitesDebug();
EditorGUILayout.IntField("Entities", entitesCount, EditorStyles.boldLabel);
EditorGUILayout.IntField("Capacity", capacity, EditorStyles.boldLabel);
Color color = leakedEntitesCount > 0 ? Color.yellow : GUI.contentColor;
using (new ContentColorScope(color))
{
EditorGUILayout.IntField("Leaked Entites", leakedEntitesCount, EditorStyles.boldLabel);
}
}
#region entity bar
public static void EntityBarForAlive(EntityStatus status, int id, short gen, short world)
{
float width = EditorGUIUtility.currentViewWidth;
float height = EntityBarHeight;
EcsGUI.EntityBarForAlive(GUILayoutUtility.GetRect(width, height), status, id, gen, world);
}
public static void EntityBar(EntityStatus status, bool isPlaceholder, int id, short gen, short world)
{
float width = EditorGUIUtility.currentViewWidth;
float height = EntityBarHeight;
EcsGUI.EntityBar(GUILayoutUtility.GetRect(width, height), isPlaceholder, status, id, gen, world);
}
public static void EntityBar(int id, short gen, short world)
{
float width = EditorGUIUtility.currentViewWidth;
float height = EntityBarHeight;
EcsGUI.EntityBar(GUILayoutUtility.GetRect(width, height), id, gen, world);
}
public static void EntityBar()
{
float width = EditorGUIUtility.currentViewWidth;
float height = EntityBarHeight;
EcsGUI.EntityBar(GUILayoutUtility.GetRect(width, height));
}
#endregion
public static bool AddComponentButtons()
{
return EcsGUI.AddComponentButtons(GUILayoutUtility.GetRect(EditorGUIUtility.currentViewWidth, 36f));
}
public static AddClearComponentButton AddClearComponentButtons()
{
return EcsGUI.AddClearComponentButtons(GUILayoutUtility.GetRect(EditorGUIUtility.currentViewWidth, 36f));
}
public static void DrawRuntimeComponents(entlong entity, bool isWithFoldout = true)
{
if (entity.TryUnpackForUnityEditor(out int entityID, out _, out _, out EcsWorld world))
{
DrawRuntimeComponents(entityID, world, isWithFoldout);
}
}
public static void DrawRuntimeComponents(int entityID, EcsWorld world, bool isWithFoldout = true)
{
var componentTypeIDs = world.GetComponentTypeIDsFor(entityID);
GUILayout.BeginVertical(UnityEditorUtility.GetStyle(Color.black, 0.2f));
if (isWithFoldout)
{
IsShowRuntimeComponents = EditorGUILayout.BeginFoldoutHeaderGroup(IsShowRuntimeComponents, "RUNTIME COMPONENTS", EditorStyles.foldout);
EditorGUILayout.EndFoldoutHeaderGroup();
}
if (isWithFoldout == false || IsShowRuntimeComponents)
{
if (AddComponentButtons())
{
GenericMenu genericMenu = RuntimeComponentsUtility.GetAddComponentGenericMenu(world);
RuntimeComponentsUtility.CurrentEntityID = entityID;
genericMenu.ShowAsContext();
}
GUILayout.Box("", UnityEditorUtility.GetStyle(GUI.color, 0.16f), GUILayout.ExpandWidth(true));
IsShowHidden = EditorGUI.Toggle(GUILayoutUtility.GetLastRect(), "Show Hidden", IsShowHidden);
int i = 0;
foreach (var componentTypeID in componentTypeIDs)
{
var pool = world.GetPoolInstance(componentTypeID);
{
DrawRuntimeComponent(componentTypeIDs.Length, i++, entityID, pool);
}
}
}
GUILayout.EndVertical();
}
private static void DrawRuntimeComponent(int total, int index, int entityID, IEcsPool pool)
{
var meta = pool.ComponentType.ToMeta();
if (meta.IsHidden == false || IsShowHidden)
{
object data = pool.GetRaw(entityID);
Color panelColor = SelectPanelColor(meta, index, total).Desaturate(EscEditorConsts.COMPONENT_DRAWER_DESATURATE);
float padding = EditorGUIUtility.standardVerticalSpacing;
Rect removeButtonRect = GUILayoutUtility.GetLastRect();
GUILayout.BeginVertical(UnityEditorUtility.GetStyle(panelColor, EscEditorConsts.COMPONENT_DRAWER_ALPHA));
EditorGUI.BeginChangeCheck();
bool isRemoveComponent = false;
removeButtonRect.yMin = removeButtonRect.yMax;
removeButtonRect.yMax += RemoveButtonRect.height;
removeButtonRect.xMin = removeButtonRect.xMax - RemoveButtonRect.width;
removeButtonRect.center += Vector2.up * padding * 2f;
if (CloseButton(removeButtonRect))
{
isRemoveComponent = true;
}
Type componentType = pool.ComponentType;
ExpandMatrix expandMatrix = ExpandMatrix.Take(componentType);
bool changed = DrawRuntimeData(componentType, UnityEditorUtility.GetLabel(meta.Name), expandMatrix, data, out object resultData);
if (changed || isRemoveComponent)
{
if (isRemoveComponent)
{
pool.Del(entityID);
}
else
{
pool.SetRaw(entityID, resultData);
}
}
if (string.IsNullOrEmpty(meta.Description.Text) == false)
{
Rect tooltipIconRect = TooltipIconRect;
tooltipIconRect.center = removeButtonRect.center;
tooltipIconRect.center -= Vector2.right * tooltipIconRect.width;
DescriptionIcon(tooltipIconRect, meta.Description.Text);
}
GUILayout.EndVertical();
}
}
private static bool DrawRuntimeData(Type fieldType, GUIContent label, ExpandMatrix expandMatrix, object data, out object outData)
{
outData = data;
Type type = data == null ? typeof(void) : data.GetType();
bool isUnityObject = typeof(UnityObject).IsAssignableFrom(fieldType);
if (isUnityObject == false && data == null)
{
EditorGUILayout.TextField(label, "Null");
return false;
}
ref bool isExpanded = ref expandMatrix.Down();
bool changed = false;
outData = data;
if (isUnityObject == false && (type.IsGenericType || !type.IsSerializable))
{
isExpanded = EditorGUILayout.BeginFoldoutHeaderGroup(isExpanded, label, EditorStyles.foldout);
EditorGUILayout.EndFoldoutHeaderGroup();
if (isExpanded)
{
EditorGUI.indentLevel++;
foreach (var field in type.GetFields(fieldFlags))
{
GUIContent subLabel = UnityEditorUtility.GetLabel(UnityEditorUtility.TransformFieldName(field.Name));
if (DrawRuntimeData(field.FieldType, subLabel, expandMatrix, field.GetValue(data), out object fieldData))
{
field.SetValue(data, fieldData);
outData = data;
changed = true;
}
}
EditorGUI.indentLevel--;
}
}
else
{
if (isUnityObject)
{
EditorGUI.BeginChangeCheck();
var uobj = (UnityObject)data;
bool isComponent = typeof(UnityComponent).IsAssignableFrom(fieldType);
if (isComponent)
{
uobj = EditorGUILayout.ObjectField(label, uobj, typeof(UnityObject), true);
}
else
{
uobj = EditorGUILayout.ObjectField(label, uobj, fieldType, true);
}
if (isComponent && uobj is GameObject go)
{
uobj = go.GetComponent(fieldType);
}
if (EditorGUI.EndChangeCheck())
{
outData = uobj;
changed = true;
}
}
else
{
EditorGUI.BeginChangeCheck();
WrapperBase w = RefEditorWrapper.Take(data);
w.IsExpanded = isExpanded;
EditorGUILayout.PropertyField(w.Property, label, true);
isExpanded = w.IsExpanded;
if (EditorGUI.EndChangeCheck())
{
w.SO.ApplyModifiedProperties();
outData = w.Data;
changed = true;
}
w.Release();
}
}
expandMatrix.Up();
return changed;
}
}
}
}
#endif