Files
Fishing2/Packages/com.waveharmonic.crest/Editor/Scripts/Utility/Shared/Inspector.cs
2026-03-05 00:14:42 +08:00

239 lines
8.1 KiB
C#

// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using WaveHarmonic.Crest.Editor.Internal;
using WaveHarmonic.Crest.Internal;
namespace WaveHarmonic.Crest.Editor
{
/// <summary>
/// Base editor. Needed as custom drawers require a custom editor to work.
/// </summary>
[CustomEditor(typeof(EditorBehaviour), editorForChildClasses: true)]
partial class Inspector : UnityEditor.Editor
{
public const int k_FieldGroupOrder = 1000;
const string k_NoPrefabModeSupportWarning = "Prefab mode is not supported. Changes made in prefab mode will not be reflected in the scene view. Save this prefab to see changes.";
internal static Inspector Current { get; private set; }
readonly Dictionary<FieldInfo, object> _MaterialOwners = new();
readonly Dictionary<Material, MaterialEditor> _MaterialEditors = new();
public override bool RequiresConstantRepaint() => TexturePreview.s_ActiveInstance?.Open == true;
static readonly IOrderedEnumerable<FieldInfo> s_AttachMaterialEditors = TypeCache
.GetFieldsWithAttribute<AttachMaterialEditor>()
.OrderBy(x => x.GetCustomAttribute<AttachMaterialEditor>().Order);
readonly Utility.SortedList<int, SerializedProperty> _Properties = new(Helpers.DuplicateComparison);
protected virtual void OnEnable()
{
_MaterialOwners.Clear();
foreach (var field in s_AttachMaterialEditors)
{
var target = (object)this.target;
if (field.FindOwner(ref target))
{
_MaterialOwners.Add(field, target);
}
}
}
public override void OnInspectorGUI()
{
// Reset foldout values.
DecoratedDrawer.s_IsFoldout = false;
DecoratedDrawer.s_IsFoldoutOpen = false;
// Make sure we adhere to flags.
var enabled = GUI.enabled;
GUI.enabled = (target.hideFlags & HideFlags.NotEditable) == 0;
RenderBeforeInspectorGUI();
RenderInspectorGUI();
RenderValidationMessages();
EditorGUI.BeginDisabledGroup(target is Behaviour component && !component.isActiveAndEnabled);
RenderBottomButtons();
EditorGUI.EndDisabledGroup();
RenderAfterInspectorGUI();
GUI.enabled = enabled;
}
protected virtual void OnDisable()
{
foreach (var (_, editor) in _MaterialEditors)
{
Helpers.Destroy(editor);
}
}
protected virtual void RenderBeforeInspectorGUI()
{
if (this.target is EditorBehaviour target && target._IsPrefabStageInstance)
{
EditorGUILayout.Space();
EditorGUILayout.HelpBox(k_NoPrefabModeSupportWarning, MessageType.Warning);
EditorGUILayout.Space();
}
}
protected virtual void RenderInspectorGUI()
{
var previous = Current;
Current = this;
_Properties.Clear();
serializedObject.Update();
using var iterator = serializedObject.GetIterator();
if (iterator.NextVisible(true))
{
var index = 0;
var group = 0;
Type type = null;
do
{
var property = serializedObject.FindProperty(iterator.name);
if (iterator.name == "m_Script")
{
#if CREST_DEBUG
_Properties.Add(index++, property);
#endif
continue;
}
var field = property.GetFieldInfo(out _);
// If field is on a new type (inheritance) then reset the group to prevent group leakage.
if (field.DeclaringType != type)
{
group = 0;
type = field.DeclaringType;
}
// Null checking but there should always be one DecoratedProperty.
var order = field.GetCustomAttribute<Attributes.DecoratedProperty>()?.order ?? 0;
group = field.GetCustomAttribute<Group>()?.order * k_FieldGroupOrder ?? group;
_Properties.Add(order + group + index++, property);
}
while (iterator.NextVisible(false));
}
foreach (var (_, property) in _Properties)
{
#if CREST_DEBUG
using (new EditorGUI.DisabledGroupScope(property.name == "m_Script"))
#endif
{
// Only support top level ordering for now.
EditorGUILayout.PropertyField(property, includeChildren: true);
}
}
// Need to call just in case there is no decorated property.
serializedObject.ApplyModifiedProperties();
// Restore previous in case this is a nested editor.
Current = previous;
// Fixes indented validation etc.
EditorGUI.indentLevel = 0;
}
protected virtual void RenderBottomButtons()
{
}
protected virtual void RenderAfterInspectorGUI()
{
foreach (var mapping in _MaterialOwners)
{
var material = (Material)mapping.Key.GetValue(mapping.Value);
if (material != null)
{
DrawMaterialEditor(material);
}
}
}
// Adapted from: http://answers.unity.com/answers/975894/view.html
void DrawMaterialEditor(Material material)
{
MaterialEditor editor;
if (!_MaterialEditors.ContainsKey(material))
{
editor = (MaterialEditor)CreateEditor(material);
_MaterialEditors[material] = editor;
}
else
{
editor = _MaterialEditors[material];
}
// Check material again as sometimes null.
if (editor != null && material != null)
{
EditorGUILayout.Space();
// Draw the material's foldout and the material shader field. Required to call OnInspectorGUI.
editor.DrawHeader();
var path = AssetDatabase.GetAssetPath(material);
// We need to prevent the user from editing Unity's default materials.
// Check Editor.IsEnabled in Editor.cs for further filtering.
var isEditable = (material.hideFlags & HideFlags.NotEditable) == 0;
#if !CREST_DEBUG
// Do not allow editing of our assets.
isEditable &= !path.StartsWithNoAlloc("Packages/com.waveharmonic");
#endif
using (new EditorGUI.DisabledGroupScope(!isEditable))
{
// Draw the material properties. Works only if the foldout of DrawHeader is open.
editor.OnInspectorGUI();
}
}
}
}
// Adapted from:
// https://gist.github.com/thebeardphantom/1ad9aee0ef8de6271fff39f1a6a3d66d
static partial class Extensions
{
static readonly MethodInfo s_GetFieldInfoFromProperty;
static Extensions()
{
var utility = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.ScriptAttributeUtility");
Debug.Assert(utility != null, "Crest: ScriptAttributeUtility not found!");
s_GetFieldInfoFromProperty = utility.GetMethod("GetFieldInfoFromProperty", BindingFlags.NonPublic | BindingFlags.Static);
Debug.Assert(s_GetFieldInfoFromProperty != null, "Crest: GetFieldInfoFromProperty not found!");
}
public static FieldInfo GetFieldInfo(this SerializedProperty property, out Type type)
{
type = null;
return (FieldInfo)s_GetFieldInfoFromProperty.Invoke(null, new object[] { property, type });
}
}
}