升级6.4.升级水,升级天气
This commit is contained in:
@@ -30,7 +30,7 @@ namespace WaveHarmonic.Crest.Attributes
|
||||
/// <summary>
|
||||
/// Override this method to customise the label.
|
||||
/// </summary>
|
||||
internal virtual GUIContent BuildLabel(GUIContent label) => label;
|
||||
internal virtual GUIContent BuildLabel(SerializedProperty property, GUIContent label, DecoratedDrawer drawer) => label;
|
||||
|
||||
/// <summary>
|
||||
/// Override this method to make your own IMGUI based GUI for the property.
|
||||
@@ -55,7 +55,7 @@ namespace WaveHarmonic.Crest.Attributes
|
||||
/// <summary>
|
||||
/// Override this method to customise the label.
|
||||
/// </summary>
|
||||
internal virtual GUIContent BuildLabel(GUIContent label) => label;
|
||||
internal virtual GUIContent BuildLabel(SerializedProperty property, GUIContent label, DecoratedDrawer drawer) => label;
|
||||
|
||||
/// <summary>
|
||||
/// Override this method to additively change the appearance of a decorated field.
|
||||
@@ -83,6 +83,37 @@ namespace WaveHarmonic.Crest.Attributes
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
sealed class CustomField : DecoratedProperty
|
||||
{
|
||||
internal override void OnGUI(Rect position, SerializedProperty property, GUIContent label, DecoratedDrawer drawer)
|
||||
{
|
||||
if (drawer._Inspector == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
drawer._Inspector.OnCustomField(position, property, label, drawer);
|
||||
}
|
||||
}
|
||||
|
||||
sealed class CustomLabel : Decorator
|
||||
{
|
||||
public override bool AlwaysVisible => false;
|
||||
|
||||
internal override GUIContent BuildLabel(SerializedProperty property, GUIContent label, DecoratedDrawer drawer)
|
||||
{
|
||||
label = base.BuildLabel(property, label, drawer);
|
||||
if (drawer._Inspector == null) return label;
|
||||
label = drawer._Inspector.OnCustomLabel(property, label, drawer);
|
||||
return label;
|
||||
}
|
||||
|
||||
internal override void Decorate(Rect position, SerializedProperty property, GUIContent label, DecoratedDrawer drawer)
|
||||
{
|
||||
// Empty
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders the property using EditorGUI.PropertyField.
|
||||
/// </summary>
|
||||
@@ -142,11 +173,48 @@ namespace WaveHarmonic.Crest
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sealed class Order : Decorator
|
||||
{
|
||||
public override bool AlwaysVisible => true;
|
||||
|
||||
public readonly string _Target;
|
||||
public readonly Placement _Placement;
|
||||
|
||||
public enum Placement
|
||||
{
|
||||
Heading,
|
||||
Below,
|
||||
Above,
|
||||
}
|
||||
|
||||
public Order(string target, Placement placement = Placement.Heading)
|
||||
{
|
||||
_Target = target;
|
||||
_Placement = placement;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders foldout without the foldout.
|
||||
/// </summary>
|
||||
sealed class Stripped : DecoratedProperty
|
||||
{
|
||||
public enum Style
|
||||
{
|
||||
None,
|
||||
PlatformTab,
|
||||
}
|
||||
|
||||
readonly bool _KeepIndent;
|
||||
readonly Style _Style;
|
||||
|
||||
public Stripped(Style style = Style.None, bool indent = false)
|
||||
{
|
||||
_KeepIndent = indent;
|
||||
_Style = style;
|
||||
}
|
||||
|
||||
internal override bool NeedsControlRectangle(SerializedProperty property) => false;
|
||||
|
||||
internal override void OnGUI(Rect position, SerializedProperty property, GUIContent label, DecoratedDrawer drawer)
|
||||
@@ -155,11 +223,33 @@ namespace WaveHarmonic.Crest
|
||||
DecoratedDrawer.s_TemporaryColor = true;
|
||||
DecoratedDrawer.s_PreviousColor = GUI.color;
|
||||
|
||||
if (_Style == Style.PlatformTab)
|
||||
{
|
||||
EditorGUI.indentLevel += 1;
|
||||
EditorGUILayout.LabelField(label);
|
||||
EditorGUI.indentLevel -= 1;
|
||||
}
|
||||
|
||||
GUI.color = new(0, 0, 0, 0);
|
||||
|
||||
EditorGUI.indentLevel -= 1;
|
||||
if (!_KeepIndent) EditorGUI.indentLevel -= 1;
|
||||
EditorGUI.PropertyField(position, property, label, true);
|
||||
EditorGUI.indentLevel += 1;
|
||||
if (!_KeepIndent) EditorGUI.indentLevel += 1;
|
||||
|
||||
if (_Style == Style.PlatformTab)
|
||||
{
|
||||
EditorGUILayout.Space(4);
|
||||
EditorGUILayout.EndBuildTargetSelectionGrouping();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class PlatformTabs : DecoratedProperty
|
||||
{
|
||||
static readonly GUIContent s_DefaultTab = new(EditorGUIUtility.IconContent("d_Settings").image, "Default");
|
||||
internal override void OnGUI(Rect position, SerializedProperty property, GUIContent label, DecoratedDrawer drawer)
|
||||
{
|
||||
property.intValue = Editor.Reflected.EditorGUILayout.BeginBuildTargetSelectionGrouping(s_DefaultTab);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -370,7 +460,7 @@ namespace WaveHarmonic.Crest
|
||||
property.floatValue = Mathf.Max(_Minimum, property.floatValue);
|
||||
break;
|
||||
case SerializedPropertyType.Integer:
|
||||
property.floatValue = Mathf.Max((int)_Minimum, property.intValue);
|
||||
property.intValue = Mathf.Max((int)_Minimum, property.intValue);
|
||||
break;
|
||||
case SerializedPropertyType.Vector2:
|
||||
var vector2Value = property.vector2Value;
|
||||
@@ -621,29 +711,15 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
sealed class InlineToggle : DecoratedProperty
|
||||
{
|
||||
// Add extra y offset. Needed for foldouts in foldouts so far.
|
||||
readonly bool _Fix;
|
||||
|
||||
public InlineToggle(bool fix = false)
|
||||
{
|
||||
_Fix = fix;
|
||||
}
|
||||
|
||||
internal override bool NeedsControlRectangle(SerializedProperty property)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
internal override void OnGUI(Rect position, SerializedProperty property, GUIContent label, DecoratedDrawer drawer)
|
||||
{
|
||||
var r = position;
|
||||
r.x -= 16f;
|
||||
// Align with Space offset.
|
||||
if (drawer.Space > 0) r.y += drawer.Space + 2f;
|
||||
if (_Fix) r.y += EditorGUIUtility.singleLineHeight + 2f;
|
||||
// Seems to be needed.
|
||||
|
||||
// Prevent click events blocking next property.
|
||||
r.width = 16f * (1f + EditorGUI.indentLevel);
|
||||
r.height = EditorGUIUtility.singleLineHeight;
|
||||
|
||||
// Hide text.
|
||||
label.text = "";
|
||||
|
||||
using (new EditorGUI.PropertyScope(r, label, property))
|
||||
@@ -654,6 +730,9 @@ namespace WaveHarmonic.Crest
|
||||
property.boolValue = EditorGUI.Toggle(r, property.boolValue);
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
// Pull up next property. Extra space might be margin/padding.
|
||||
GUILayout.Space(-(EditorGUIUtility.singleLineHeight + 2f));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -859,9 +938,9 @@ namespace WaveHarmonic.Crest
|
||||
_Label = label;
|
||||
}
|
||||
|
||||
internal override GUIContent BuildLabel(GUIContent label)
|
||||
internal override GUIContent BuildLabel(SerializedProperty property, GUIContent label, DecoratedDrawer drawer)
|
||||
{
|
||||
label = base.BuildLabel(label);
|
||||
label = base.BuildLabel(property, label, drawer);
|
||||
label.text = _Label;
|
||||
return label;
|
||||
}
|
||||
@@ -877,7 +956,7 @@ namespace WaveHarmonic.Crest
|
||||
/// </summary>
|
||||
sealed class Heading : Decorator
|
||||
{
|
||||
readonly GUIContent _Text;
|
||||
public readonly GUIContent _Text;
|
||||
readonly string _HelpLink;
|
||||
readonly Style _Style;
|
||||
readonly bool _AlwaysVisible;
|
||||
|
||||
@@ -23,6 +23,12 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
internal override void OnGUI(Rect position, SerializedProperty property, GUIContent label, DecoratedDrawer drawer)
|
||||
{
|
||||
if (drawer._Inspector != null && !drawer._Inspector._EmbeddedEditors.Contains(_Editor))
|
||||
{
|
||||
drawer._Inspector._EmbeddedEditors.Add(_Editor);
|
||||
Inspector.s_EmbeddedEditors.Add(_Editor);
|
||||
}
|
||||
|
||||
_Editor.DrawEditorCombo(this, label, drawer, property, "asset");
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ using WaveHarmonic.Crest.Editor;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
sealed class Predicated : Decorator
|
||||
abstract class Predicated : Decorator
|
||||
{
|
||||
enum Mode
|
||||
{
|
||||
@@ -24,6 +24,7 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
readonly bool _Inverted;
|
||||
readonly bool _Hide;
|
||||
readonly bool _Implicit;
|
||||
|
||||
readonly string _PropertyName;
|
||||
readonly object _DisableValue;
|
||||
@@ -38,16 +39,16 @@ namespace WaveHarmonic.Crest
|
||||
/// </summary>
|
||||
/// <param name="type">The type to call the method on. Must be either a static type or the type the field is defined on.</param>
|
||||
/// <param name="member">Member name. Method must match signature: bool MethodName(Component component). Can be any visibility and static or instance.</param>
|
||||
/// <param name="disableValue"></param>
|
||||
/// <param name="value">Disable/Hide if matches this value.</param>
|
||||
/// <param name="inverted">Flip behaviour - for example disable if a bool field is set to true (instead of false).</param>
|
||||
/// <param name="hide">Hide this component in the inspector.</param>
|
||||
public Predicated(Type type, string member, object disableValue, bool inverted = false, bool hide = false)
|
||||
public Predicated(Type type, string member, object value, bool inverted = false, bool hide = false)
|
||||
{
|
||||
_Mode = Mode.Member;
|
||||
_Inverted = inverted;
|
||||
_Hide = hide;
|
||||
_Type = type;
|
||||
_DisableValue = disableValue;
|
||||
_DisableValue = value;
|
||||
_Member = _Type.GetMember(member, Helpers.s_AnyMethod)[0];
|
||||
}
|
||||
|
||||
@@ -71,21 +72,30 @@ namespace WaveHarmonic.Crest
|
||||
_Type = type;
|
||||
}
|
||||
|
||||
public Predicated(string property, bool inverted = false, bool hide = false)
|
||||
{
|
||||
_Mode = Mode.Property;
|
||||
_Inverted = inverted;
|
||||
_Hide = hide;
|
||||
_PropertyName = property;
|
||||
_Implicit = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The field with this attribute will be drawn enabled/disabled based on another field. For example can be used
|
||||
/// to disable a field if a toggle is false.
|
||||
/// </summary>
|
||||
/// <param name="property">The name of the other property whose value dictates whether this field is enabled or not.</param>
|
||||
/// <param name="inverted">Flip behaviour - for example disable if a bool field is set to true (instead of false).</param>
|
||||
/// <param name="disableValue">If the field has this value, disable the GUI (or enable if inverted is true).</param>
|
||||
/// <param name="value">If the field has this value, disable the GUI (or enable if inverted is true).</param>
|
||||
/// <param name="hide">Hide this component in the inspector.</param>
|
||||
public Predicated(string property, bool inverted = false, object disableValue = null, bool hide = false)
|
||||
public Predicated(string property, object value, bool inverted = false, bool hide = false)
|
||||
{
|
||||
_Mode = Mode.Property;
|
||||
_Inverted = inverted;
|
||||
_Hide = hide;
|
||||
_PropertyName = property;
|
||||
_DisableValue = disableValue;
|
||||
_DisableValue = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -106,18 +116,24 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
public bool GUIEnabled(SerializedProperty property)
|
||||
{
|
||||
if (_Implicit && property.propertyType is not SerializedPropertyType.Boolean and not SerializedPropertyType.ObjectReference and not SerializedPropertyType.ManagedReference)
|
||||
{
|
||||
Debug.Log($"Crest.Predicated: Implicit predicated on {property.name} cannot be used for {property.propertyType}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return _Inverted != property.propertyType switch
|
||||
{
|
||||
// Enable GUI if int value of field is not equal to 0, or whatever the disable-value is set to
|
||||
SerializedPropertyType.Integer => property.intValue != ((int?)_DisableValue ?? 0),
|
||||
// Enable GUI if disable-value is 0 and field is true, or disable-value is not 0 and field is false
|
||||
SerializedPropertyType.Boolean => property.boolValue ^ (((int?)_DisableValue ?? 0) != 0),
|
||||
SerializedPropertyType.Boolean => _Implicit ? !property.boolValue : (bool)_DisableValue == property.boolValue,
|
||||
SerializedPropertyType.Float => property.floatValue != ((float?)_DisableValue ?? 0),
|
||||
SerializedPropertyType.String => property.stringValue != ((string)_DisableValue ?? ""),
|
||||
// Must pass nameof enum entry as we are comparing names because index != value.
|
||||
SerializedPropertyType.Enum => property.enumNames[property.enumValueIndex] != ((string)_DisableValue ?? ""),
|
||||
SerializedPropertyType.ObjectReference => property.objectReferenceValue != null,
|
||||
SerializedPropertyType.ManagedReference => property.managedReferenceValue != null,
|
||||
SerializedPropertyType.ObjectReference => _Implicit ? property.objectReferenceValue == null : property.objectReferenceValue != null,
|
||||
SerializedPropertyType.ManagedReference => _Implicit ? property.managedReferenceValue == null : property.managedReferenceValue != null,
|
||||
_ => throw new ArgumentException($"Crest.Predicated: property type on <i>{property.serializedObject.targetObject}</i> not implemented yet: {property.propertyType}."),
|
||||
};
|
||||
}
|
||||
@@ -201,12 +217,12 @@ namespace WaveHarmonic.Crest
|
||||
}
|
||||
|
||||
var enabledByTypeCheck = _Type.IsAssignableFrom(type);
|
||||
if (_Inverted) enabledByTypeCheck = !enabledByTypeCheck;
|
||||
if (!_Inverted) enabledByTypeCheck = !enabledByTypeCheck;
|
||||
enabled = enabledByTypeCheck && enabled;
|
||||
}
|
||||
else if (_Mode == Mode.RenderPipeline)
|
||||
{
|
||||
enabled = RenderPipelineHelper.RenderPipeline == _RenderPipeline != _Inverted;
|
||||
enabled = RenderPipelineHelper.RenderPipeline == _RenderPipeline == _Inverted;
|
||||
}
|
||||
|
||||
// Keep current disabled state.
|
||||
@@ -216,4 +232,228 @@ namespace WaveHarmonic.Crest
|
||||
DecoratedDrawer.s_HideInInspector = DecoratedDrawer.s_HideInInspector || (_Hide && !enabled);
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Show : Predicated
|
||||
{
|
||||
const bool k_Hide = true;
|
||||
const bool k_Inverted = true;
|
||||
|
||||
/// <summary>
|
||||
/// Show this field if member's (method) returned value matches provided.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="Predicated(Type, string, object, bool, bool)"/>
|
||||
public Show(Type type, string member, object value) : base(type, member, value, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show this field if member (method) returns true.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="Predicated(Type, string, bool, bool)"/>
|
||||
public Show(Type type, string member) : this(type, member, value: true)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show field if owning class is of type.
|
||||
/// </summary>
|
||||
public Show(Type type) : base(type, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show if field is null or false if reference or boolean respectively.
|
||||
/// </summary>
|
||||
public Show(string property) : base(property, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show if field matches value.
|
||||
/// </summary>
|
||||
public Show(string property, object value) : base(property, value, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show if current render pipeline matches.
|
||||
/// </summary>
|
||||
public Show(RenderPipeline rp) : base(rp, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Hide : Predicated
|
||||
{
|
||||
const bool k_Hide = true;
|
||||
const bool k_Inverted = false;
|
||||
|
||||
/// <summary>
|
||||
/// Hide this field if member's (method) returned value matches provided.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="Predicated(Type, string, object, bool, bool)"/>
|
||||
public Hide(Type type, string member, object value) : base(type, member, value, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hide this field if member (method) returns true.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="Predicated(Type, string, bool, bool)"/>
|
||||
public Hide(Type type, string member) : this(type, member, value: true)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hide field if owning class is of type.
|
||||
/// </summary>
|
||||
public Hide(Type type) : base(type, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hide if field is null or false if reference or boolean respectively.
|
||||
/// </summary>
|
||||
public Hide(string property) : base(property, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hide if field matches value.
|
||||
/// </summary>
|
||||
public Hide(string property, object value) : base(property, value, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hide if current render pipeline matches.
|
||||
/// </summary>
|
||||
public Hide(RenderPipeline rp) : base(rp, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Enable : Predicated
|
||||
{
|
||||
const bool k_Hide = false;
|
||||
const bool k_Inverted = true;
|
||||
|
||||
/// <summary>
|
||||
/// Enable this field if member's (method) returned value matches provided.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="Predicated(Type, string, object, bool, bool)"/>
|
||||
public Enable(Type type, string member, object value) : base(type, member, value, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable this field if member (method) returns true.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="Predicated(Type, string, bool, bool)"/>
|
||||
public Enable(Type type, string member) : this(type, member, value: true)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable field if owning class is of type.
|
||||
/// </summary>
|
||||
public Enable(Type type) : base(type, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable if field is null or false if reference or boolean respectively.
|
||||
/// </summary>
|
||||
public Enable(string property) : base(property, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable if field matches value.
|
||||
/// </summary>
|
||||
public Enable(string property, object value) : base(property, value, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable if current render pipeline matches.
|
||||
/// </summary>
|
||||
public Enable(RenderPipeline rp) : base(rp, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Disable : Predicated
|
||||
{
|
||||
const bool k_Hide = false;
|
||||
const bool k_Inverted = false;
|
||||
|
||||
/// <summary>
|
||||
/// Disable this field if member's (method) returned value matches provided.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="Predicated(Type, string, object, bool, bool)"/>
|
||||
public Disable(Type type, string member, object value) : base(type, member, value, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable this field if member (method) returns true.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="Predicated(Type, string, bool, bool)"/>
|
||||
public Disable(Type type, string member) : this(type, member, value: true)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable field if owning class is of type.
|
||||
/// </summary>
|
||||
public Disable(Type type) : base(type, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable if field is null or false if reference or boolean respectively.
|
||||
/// </summary>
|
||||
public Disable(string property) : base(property, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable if field matches value.
|
||||
/// </summary>
|
||||
public Disable(string property, object value) : base(property, value, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable if current render pipeline matches.
|
||||
/// </summary>
|
||||
public Disable(RenderPipeline rp) : base(rp, k_Inverted, k_Hide)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using WaveHarmonic.Crest.Attributes;
|
||||
@@ -23,19 +24,29 @@ namespace WaveHarmonic.Crest.Editor
|
||||
internal static bool s_HideInInspector = false;
|
||||
public static bool s_IsFoldout = false;
|
||||
public static bool s_IsFoldoutOpen = false;
|
||||
public static bool s_IsList = false;
|
||||
|
||||
public static bool s_TemporaryColor;
|
||||
public static Color s_PreviousColor;
|
||||
|
||||
// If instantiating ourselves. Avoids reflection.
|
||||
public PropertyAttribute _Attribute;
|
||||
public FieldInfo _Field;
|
||||
PropertyAttribute Attribute => _Attribute ?? attribute;
|
||||
FieldInfo Field => _Field ?? fieldInfo;
|
||||
|
||||
public float Space { get; private set; }
|
||||
|
||||
internal UnityEditor.Editor _Editor;
|
||||
internal Inspector _Inspector;
|
||||
|
||||
List<object> _Decorators = null;
|
||||
List<object> Decorators
|
||||
{
|
||||
get
|
||||
{
|
||||
// Populate list with decorators.
|
||||
_Decorators ??= fieldInfo
|
||||
_Decorators ??= Field
|
||||
.GetCustomAttributes(typeof(Decorator), false)
|
||||
.ToList();
|
||||
|
||||
@@ -44,7 +55,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
}
|
||||
|
||||
List<Attributes.Validator> _Validators = null;
|
||||
List<Attributes.Validator> Validators => _Validators ??= fieldInfo
|
||||
List<Attributes.Validator> Validators => _Validators ??= Field
|
||||
.GetCustomAttributes(typeof(Attributes.Validator), false)
|
||||
.Cast<Attributes.Validator>()
|
||||
.ToList();
|
||||
@@ -63,14 +74,55 @@ namespace WaveHarmonic.Crest.Editor
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
var height = base.GetPropertyHeight(property, label);
|
||||
|
||||
if (property.isArray)
|
||||
{
|
||||
// Constructor caches value and this call retrieves it.
|
||||
var list = ReorderableList.GetReorderableListFromSerializedProperty(property);
|
||||
list ??= new ReorderableList(property.serializedObject, property);
|
||||
// GetHeight does not include bottom buttons height.
|
||||
height = property.isExpanded ? list.GetHeight() + list.footerHeight : height;
|
||||
}
|
||||
|
||||
// Make original control rectangle be invisible because we always create our own. Zero still adds a little
|
||||
// height which becomes noticeable once multiple properties are hidden. This could be some GUI style
|
||||
// property but could not find which one.
|
||||
return -2f;
|
||||
return s_IsList ? height : -2f;
|
||||
}
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
// Get the owning editor.
|
||||
if (_Editor == null)
|
||||
{
|
||||
foreach (var editor in ActiveEditorTracker.sharedTracker?.activeEditors)
|
||||
{
|
||||
if (editor.serializedObject == property.serializedObject)
|
||||
{
|
||||
_Editor = editor;
|
||||
_Inspector = editor as Inspector;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Also check all embedded editors.
|
||||
foreach (var editor in Inspector.s_EmbeddedEditors)
|
||||
{
|
||||
if (editor._Editor == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (editor._Editor.serializedObject == property.serializedObject)
|
||||
{
|
||||
_Editor = editor._Editor;
|
||||
_Inspector = editor._Editor as Inspector;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store the original GUI state so it can be reset later.
|
||||
var originalColor = GUI.color;
|
||||
var originalEnabled = GUI.enabled;
|
||||
@@ -93,7 +145,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
var attribute = (Decorator)Decorators[index];
|
||||
if (attribute is Space space) Space = space._Height;
|
||||
label = attribute.BuildLabel(label);
|
||||
label = attribute.BuildLabel(property, label, this);
|
||||
}
|
||||
|
||||
if (!s_HideInInspector && (!s_IsFoldout || s_IsFoldoutOpen))
|
||||
@@ -106,8 +158,8 @@ namespace WaveHarmonic.Crest.Editor
|
||||
attribute.Decorate(position, property, label, this);
|
||||
}
|
||||
|
||||
var a = (DecoratedProperty)attribute;
|
||||
position = a.NeedsControlRectangle(property)
|
||||
var a = (DecoratedProperty)Attribute;
|
||||
position = a.NeedsControlRectangle(property) && (!s_IsList || property.isArray)
|
||||
? EditorGUILayout.GetControlRect(true, EditorGUI.GetPropertyHeight(property, label, true))
|
||||
: position;
|
||||
|
||||
@@ -117,14 +169,18 @@ namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
var attribute = (Decorator)Decorators[index];
|
||||
if (attribute.AlwaysVisible) continue;
|
||||
label = attribute.BuildLabel(label);
|
||||
label = attribute.BuildLabel(property, label, this);
|
||||
}
|
||||
|
||||
var skipChange = fieldInfo.FieldType == typeof(UnityEvent);
|
||||
var skipChange = Field.FieldType == typeof(UnityEvent) || property.isArray;
|
||||
|
||||
var isExpanded = property.isExpanded;
|
||||
var isUndoRedo = false;
|
||||
|
||||
if (!skipChange)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
isUndoRedo = _Inspector != null && _Inspector._UndoRedo;
|
||||
}
|
||||
|
||||
var oldValue = skipChange ? null : property.boxedValue;
|
||||
@@ -135,13 +191,19 @@ namespace WaveHarmonic.Crest.Editor
|
||||
Validators[index].Validate(position, property, label, this, oldValue);
|
||||
}
|
||||
|
||||
// Guard against foldouts triggering change check due to changing isExpanded.
|
||||
if (!skipChange && EditorGUI.EndChangeCheck() && oldValue != property.boxedValue)
|
||||
if (!skipChange && (EditorGUI.EndChangeCheck() || isUndoRedo))
|
||||
{
|
||||
// Apply any changes.
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
if (!isUndoRedo)
|
||||
{
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
OnChange(property, oldValue);
|
||||
// Guard against foldouts triggering change check due to changing isExpanded.
|
||||
if (property.isExpanded == isExpanded)
|
||||
{
|
||||
OnChange(property, oldValue);
|
||||
}
|
||||
}
|
||||
|
||||
for (var index = 0; index < Decorators.Count; index++)
|
||||
@@ -206,6 +268,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
var relativePath = string.Join(".", chunks[(i + 1)..]);
|
||||
|
||||
var nestedTarget = nestedProperty.managedReferenceValue;
|
||||
if (nestedTarget == null) continue;
|
||||
var nestedTargetType = nestedTarget.GetType();
|
||||
|
||||
foreach (var (method, attribute) in OnChangeHandlers)
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
/// </summary>
|
||||
interface IEmbeddableEditor
|
||||
{
|
||||
void SetHostComponent(object host);
|
||||
void SetTypeOfHostComponent(System.Type hostType);
|
||||
}
|
||||
|
||||
@@ -54,7 +55,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
public void OnDisable()
|
||||
{
|
||||
DestroyEditor();
|
||||
Helpers.Destroy(_DefaultTarget);
|
||||
Helpers.Destroy(_CreatedInstance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -62,11 +63,12 @@ namespace WaveHarmonic.Crest.Editor
|
||||
/// </summary>
|
||||
public GUIContent _CreateButtonGUIContent;
|
||||
|
||||
UnityEditor.Editor _Editor = null;
|
||||
internal UnityEditor.Editor _Editor = null;
|
||||
|
||||
System.Type _Type;
|
||||
|
||||
Object _DefaultTarget;
|
||||
Object _CreatedInstance;
|
||||
FieldInfo _DefaultTargetField;
|
||||
|
||||
const int k_IndentOffset = 3;
|
||||
@@ -214,7 +216,8 @@ namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
if (_DefaultTarget == null)
|
||||
{
|
||||
_DefaultTarget = ScriptableObject.CreateInstance(_Type);
|
||||
_CreatedInstance = ScriptableObject.CreateInstance(_Type);
|
||||
_DefaultTarget = _CreatedInstance;
|
||||
_DefaultTarget.hideFlags = HideFlags.DontSave | HideFlags.NotEditable;
|
||||
}
|
||||
}
|
||||
@@ -253,9 +256,10 @@ namespace WaveHarmonic.Crest.Editor
|
||||
_Editor = UnityEditor.Editor.CreateEditor(target);
|
||||
|
||||
// Pass through argument for editors that receive it
|
||||
if (property.serializedObject.targetObject != null)
|
||||
if (property.serializedObject.targetObject != null && _Editor is IEmbeddableEditor editor)
|
||||
{
|
||||
(_Editor as IEmbeddableEditor)?.SetTypeOfHostComponent(property.serializedObject.targetObject.GetType());
|
||||
editor.SetHostComponent(property.serializedObject.targetObject);
|
||||
editor.SetTypeOfHostComponent(property.serializedObject.targetObject.GetType());
|
||||
}
|
||||
|
||||
_OnCreateEditor?.Invoke(_Editor);
|
||||
|
||||
@@ -294,7 +294,13 @@ namespace WaveHarmonic.Crest.Editor
|
||||
width = Mathf.Max(width, minimumWidth);
|
||||
// TODO: Add option to disable this (consistent width).
|
||||
if (!hasDropDown && minimumWidth > 0) width += k_ButtonDropDownWidth;
|
||||
if (centerLabel && hasDropDown) style.padding.left += k_ButtonDropDownWidth;
|
||||
|
||||
// TODO: eyeballed based on Fix button but likely specific to it.
|
||||
if (centerLabel && hasDropDown)
|
||||
{
|
||||
style.padding.left += k_ButtonDropDownWidth / 2;
|
||||
width += k_ButtonDropDownWidth / 3;
|
||||
}
|
||||
|
||||
if (GUILayout.Button(label, style, expandWidth ? GUILayout.ExpandWidth(true) : GUILayout.Width(width)))
|
||||
{
|
||||
|
||||
@@ -18,13 +18,19 @@ namespace WaveHarmonic.Crest.Editor
|
||||
[CustomEditor(typeof(EditorBehaviour), editorForChildClasses: true)]
|
||||
partial class Inspector : UnityEditor.Editor
|
||||
{
|
||||
public const int k_FieldGroupOrder = 1000;
|
||||
public const int k_FieldGroupOrder = k_FieldHeadingOrder * 100;
|
||||
public const int k_FieldHeadingOrder = k_FieldOrder * 100;
|
||||
public const int k_FieldOrder = 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();
|
||||
readonly Dictionary<string, DecoratedDrawer> _Lists = new();
|
||||
|
||||
internal readonly List<EmbeddedAssetEditor> _EmbeddedEditors = new();
|
||||
internal static readonly List<EmbeddedAssetEditor> s_EmbeddedEditors = new();
|
||||
|
||||
public override bool RequiresConstantRepaint() => TexturePreview.s_ActiveInstance?.Open == true;
|
||||
|
||||
@@ -32,12 +38,16 @@ namespace WaveHarmonic.Crest.Editor
|
||||
.GetFieldsWithAttribute<AttachMaterialEditor>()
|
||||
.OrderBy(x => x.GetCustomAttribute<AttachMaterialEditor>().Order);
|
||||
|
||||
readonly List<string> _Headings = new();
|
||||
readonly Utility.SortedList<int, SerializedProperty> _Properties = new(Helpers.DuplicateComparison);
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
_MaterialOwners.Clear();
|
||||
|
||||
Undo.undoRedoPerformed -= OnUndoRedo;
|
||||
Undo.undoRedoPerformed += OnUndoRedo;
|
||||
|
||||
foreach (var field in s_AttachMaterialEditors)
|
||||
{
|
||||
var target = (object)this.target;
|
||||
@@ -60,7 +70,20 @@ namespace WaveHarmonic.Crest.Editor
|
||||
GUI.enabled = (target.hideFlags & HideFlags.NotEditable) == 0;
|
||||
|
||||
RenderBeforeInspectorGUI();
|
||||
RenderInspectorGUI();
|
||||
|
||||
try
|
||||
{
|
||||
RenderInspectorGUI();
|
||||
}
|
||||
catch (InvalidOperationException exception)
|
||||
{
|
||||
// Happens when using New with embedded editor and not change the filename.
|
||||
if (exception.Message != "Stack empty.")
|
||||
{
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
RenderValidationMessages();
|
||||
|
||||
EditorGUI.BeginDisabledGroup(target is Behaviour component && !component.isActiveAndEnabled);
|
||||
@@ -74,10 +97,23 @@ namespace WaveHarmonic.Crest.Editor
|
||||
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
Undo.undoRedoPerformed -= OnUndoRedo;
|
||||
|
||||
foreach (var (_, editor) in _MaterialEditors)
|
||||
{
|
||||
Helpers.Destroy(editor);
|
||||
}
|
||||
|
||||
foreach (var embedded in _EmbeddedEditors)
|
||||
{
|
||||
embedded?.OnDisable();
|
||||
s_EmbeddedEditors.Remove(embedded);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnChange()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected virtual void RenderBeforeInspectorGUI()
|
||||
@@ -95,10 +131,13 @@ namespace WaveHarmonic.Crest.Editor
|
||||
var previous = Current;
|
||||
Current = this;
|
||||
|
||||
_Headings.Clear();
|
||||
_Properties.Clear();
|
||||
|
||||
serializedObject.Update();
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
using var iterator = serializedObject.GetIterator();
|
||||
if (iterator.NextVisible(true))
|
||||
{
|
||||
@@ -112,7 +151,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
if (iterator.name == "m_Script")
|
||||
{
|
||||
#if CREST_DEBUG
|
||||
_Properties.Add(index++, property);
|
||||
_Properties.Add(index++ * k_FieldOrder, property);
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
@@ -129,7 +168,44 @@ namespace WaveHarmonic.Crest.Editor
|
||||
// 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);
|
||||
|
||||
var headingAttribute = field.GetCustomAttribute<Heading>();
|
||||
if (headingAttribute != null)
|
||||
{
|
||||
_Headings.Add(headingAttribute._Text.text);
|
||||
}
|
||||
|
||||
var heading = Mathf.Max(0, _Headings.Count - 1);
|
||||
|
||||
var orderAttribute = field.GetCustomAttribute<Order>();
|
||||
|
||||
if (orderAttribute != null)
|
||||
{
|
||||
var target = orderAttribute._Target;
|
||||
|
||||
if (orderAttribute._Placement == Order.Placement.Heading)
|
||||
{
|
||||
heading = _Headings.FindIndex(x => x == target);
|
||||
}
|
||||
}
|
||||
|
||||
var key = order + group + heading * k_FieldHeadingOrder + index * k_FieldOrder;
|
||||
|
||||
// Override.
|
||||
if (orderAttribute != null)
|
||||
{
|
||||
var target = orderAttribute._Target;
|
||||
|
||||
switch (orderAttribute._Placement)
|
||||
{
|
||||
case Order.Placement.Below: key = _Properties.First(x => x.Value.name == target).Key + index; break;
|
||||
case Order.Placement.Above: key = _Properties.First(x => x.Value.name == target).Key - index; break;
|
||||
}
|
||||
}
|
||||
|
||||
_Properties.Add(key, property);
|
||||
|
||||
index += 1;
|
||||
}
|
||||
while (iterator.NextVisible(false));
|
||||
}
|
||||
@@ -140,6 +216,33 @@ namespace WaveHarmonic.Crest.Editor
|
||||
using (new EditorGUI.DisabledGroupScope(property.name == "m_Script"))
|
||||
#endif
|
||||
{
|
||||
// Handle lists as PropertyDrawer is not called on the list itself.
|
||||
if (property.isArray)
|
||||
{
|
||||
var field = property.GetFieldInfo(out var _);
|
||||
var attribute = field?.GetCustomAttribute<Attributes.DecoratedProperty>();
|
||||
|
||||
if (field != null && attribute != null)
|
||||
{
|
||||
var id = GetPropertyIdentifier(property);
|
||||
|
||||
if (!_Lists.ContainsKey(id))
|
||||
{
|
||||
_Lists[id] = new DecoratedDrawer()
|
||||
{
|
||||
_Attribute = attribute,
|
||||
_Field = field,
|
||||
};
|
||||
}
|
||||
|
||||
DecoratedDrawer.s_IsList = true;
|
||||
var label = new GUIContent(property.displayName);
|
||||
_Lists[id].OnGUI(Rect.zero, property, label);
|
||||
DecoratedDrawer.s_IsList = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Only support top level ordering for now.
|
||||
EditorGUILayout.PropertyField(property, includeChildren: true);
|
||||
}
|
||||
@@ -148,11 +251,18 @@ namespace WaveHarmonic.Crest.Editor
|
||||
// Need to call just in case there is no decorated property.
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
OnChange();
|
||||
}
|
||||
|
||||
// Restore previous in case this is a nested editor.
|
||||
Current = previous;
|
||||
|
||||
// Fixes indented validation etc.
|
||||
EditorGUI.indentLevel = 0;
|
||||
|
||||
_UndoRedo = false;
|
||||
}
|
||||
|
||||
protected virtual void RenderBottomButtons()
|
||||
@@ -212,6 +322,55 @@ namespace WaveHarmonic.Crest.Editor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal virtual void OnCustomField(Rect position, SerializedProperty property, GUIContent label, DecoratedDrawer drawer)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
internal virtual GUIContent OnCustomLabel(SerializedProperty property, GUIContent label, DecoratedDrawer drawer)
|
||||
{
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
||||
// Reflection
|
||||
partial class Inspector
|
||||
{
|
||||
static readonly PropertyInfo s_GUIViewCurrent = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.GUIView").GetProperty("current", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
static readonly PropertyInfo s_GUIViewNativeHandle = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.GUIView").GetProperty("nativeHandle", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
|
||||
// Adapted from:
|
||||
// https://github.com/Unity-Technologies/UnityCsReference/blob/59b03b8a0f179c0b7e038178c90b6c80b340aa9f/Editor/Mono/Inspector/ReorderableListWrapper.cs#L77-L88
|
||||
static string GetPropertyIdentifier(SerializedProperty serializedProperty)
|
||||
{
|
||||
// Property may be disposed.
|
||||
try
|
||||
{
|
||||
var handle = -1;
|
||||
var current = s_GUIViewCurrent.GetValue(null);
|
||||
|
||||
if (current != null)
|
||||
{
|
||||
handle = ((IntPtr)s_GUIViewNativeHandle.GetValue(current)).ToInt32();
|
||||
}
|
||||
|
||||
return serializedProperty?.propertyPath + serializedProperty.serializedObject.targetObject.GetEntityId() + handle;
|
||||
}
|
||||
catch (NullReferenceException)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial class Inspector
|
||||
{
|
||||
internal bool _UndoRedo;
|
||||
void OnUndoRedo()
|
||||
{
|
||||
_UndoRedo = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Adapted from:
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor.Build;
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor.Reflected
|
||||
{
|
||||
static class BuildTargetGroup
|
||||
{
|
||||
public const int k_Server = -2;
|
||||
}
|
||||
|
||||
static class BuildPlatform
|
||||
{
|
||||
internal static readonly Type s_BuildPlatformType = Type.GetType("UnityEditor.Build.BuildPlatform,UnityEditor.CoreModule");
|
||||
internal static readonly Type s_BuildPlatformArrayType = s_BuildPlatformType.MakeArrayType();
|
||||
|
||||
static readonly FieldInfo s_NamedBuildTargetField = s_BuildPlatformType.GetField
|
||||
(
|
||||
"namedBuildTarget",
|
||||
BindingFlags.Instance | BindingFlags.Public
|
||||
);
|
||||
|
||||
public static NamedBuildTarget GetNamedBuildTarget(object platform)
|
||||
{
|
||||
return (NamedBuildTarget)s_NamedBuildTargetField.GetValue(platform);
|
||||
}
|
||||
}
|
||||
|
||||
static class BuildPlatforms
|
||||
{
|
||||
static readonly Type s_BuildPlatformsType = Type.GetType("UnityEditor.Build.BuildPlatforms,UnityEditor.CoreModule");
|
||||
static readonly PropertyInfo s_BuildPlatformsInstanceProperty = s_BuildPlatformsType.GetProperty("instance", BindingFlags.Static | BindingFlags.Public);
|
||||
static readonly MethodInfo s_GetValidPlatformsMethod = s_BuildPlatformsType.GetMethod("GetValidPlatforms", new Type[] { });
|
||||
static Array s_Platforms; // Should be safe to cache.
|
||||
|
||||
public static Array GetValidPlatforms()
|
||||
{
|
||||
if (s_Platforms == null)
|
||||
{
|
||||
var instance = s_BuildPlatformsInstanceProperty.GetValue(null);
|
||||
|
||||
// We cannot just cast to the type we want it seems.
|
||||
var enumerable = ((IEnumerable<object>)s_GetValidPlatformsMethod.Invoke(instance, null)).ToList();
|
||||
|
||||
s_Platforms = Array.CreateInstance(BuildPlatform.s_BuildPlatformType, enumerable.Count);
|
||||
|
||||
for (var i = 0; i < enumerable.Count; i++)
|
||||
{
|
||||
s_Platforms.SetValue(enumerable[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
return s_Platforms;
|
||||
}
|
||||
}
|
||||
|
||||
static class EditorGUILayout
|
||||
{
|
||||
static readonly MethodInfo s_BeginPlatformGroupingMethod = typeof(UnityEditor.EditorGUILayout).GetMethod
|
||||
(
|
||||
"BeginPlatformGrouping",
|
||||
BindingFlags.Static | BindingFlags.NonPublic,
|
||||
null,
|
||||
new Type[]
|
||||
{
|
||||
BuildPlatform.s_BuildPlatformArrayType,
|
||||
typeof(GUIContent),
|
||||
},
|
||||
null
|
||||
);
|
||||
|
||||
static readonly object[] s_Parameters = new object[2];
|
||||
|
||||
public static int BeginBuildTargetSelectionGrouping(GUIContent defaultTab)
|
||||
{
|
||||
var platforms = BuildPlatforms.GetValidPlatforms();
|
||||
|
||||
s_Parameters[0] = platforms;
|
||||
s_Parameters[1] = defaultTab;
|
||||
|
||||
var index = (int)s_BeginPlatformGroupingMethod.Invoke(null, s_Parameters);
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
// Default
|
||||
return (int)UnityEditor.BuildTargetGroup.Unknown;
|
||||
}
|
||||
|
||||
var target = BuildPlatform.GetNamedBuildTarget(platforms.GetValue(index));
|
||||
|
||||
if (target == NamedBuildTarget.Server)
|
||||
{
|
||||
// Server
|
||||
return BuildTargetGroup.k_Server;
|
||||
}
|
||||
|
||||
return (int)target.ToBuildTargetGroup();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cd362a01f52cc4002857e9f549c641be
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -10,10 +10,19 @@ namespace WaveHarmonic.Crest.Editor.Settings
|
||||
{
|
||||
static class ScriptingSymbols
|
||||
{
|
||||
static NamedBuildTarget CurrentNamedBuildTarget
|
||||
// Too lazy to add separate calls just to provide a NamedBuildTarget.
|
||||
internal static bool s_OverrideCurrentNamedBuildTarget;
|
||||
internal static NamedBuildTarget s_CurrentNamedBuildTargetOverride;
|
||||
|
||||
internal static NamedBuildTarget CurrentNamedBuildTarget
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_OverrideCurrentNamedBuildTarget)
|
||||
{
|
||||
return s_CurrentNamedBuildTargetOverride;
|
||||
}
|
||||
|
||||
#if UNITY_SERVER
|
||||
return NamedBuildTarget.Server;
|
||||
#else
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
static Dictionary<string, string> s_Tooltips;
|
||||
static readonly GUIContent s_Label = new();
|
||||
|
||||
public static void DrawShaderGraphGUI(MaterialEditor materialEditor, IEnumerable<MaterialProperty> properties, Dictionary<string, string> tooltips)
|
||||
public static void DrawShaderGraphGUI(MaterialEditor materialEditor, IEnumerable<MaterialProperty> properties, Dictionary<string, string> tooltips, List<string> hiddenCategories)
|
||||
{
|
||||
s_Tooltips = tooltips;
|
||||
Material m = materialEditor.target as Material;
|
||||
@@ -37,7 +37,7 @@ namespace WaveHarmonic.Crest.Editor
|
||||
}
|
||||
|
||||
if (metadata != null)
|
||||
DrawShaderGraphGUI(materialEditor, properties, metadata.categoryDatas);
|
||||
DrawShaderGraphGUI(materialEditor, properties, metadata.categoryDatas, hiddenCategories);
|
||||
else
|
||||
PropertiesDefaultGUI(materialEditor, properties);
|
||||
}
|
||||
@@ -76,10 +76,15 @@ namespace WaveHarmonic.Crest.Editor
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void DrawShaderGraphGUI(MaterialEditor materialEditor, IEnumerable<MaterialProperty> properties, IEnumerable<MinimalCategoryData> categoryDatas)
|
||||
public static void DrawShaderGraphGUI(MaterialEditor materialEditor, IEnumerable<MaterialProperty> properties, IEnumerable<MinimalCategoryData> categoryDatas, List<string> hiddenCategories)
|
||||
{
|
||||
foreach (MinimalCategoryData mcd in categoryDatas)
|
||||
{
|
||||
if (hiddenCategories.Count > 0 && hiddenCategories.Contains(mcd.categoryName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
DrawCategory(materialEditor, properties, mcd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,10 @@ namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
abstract class TexturePreview : ObjectPreview
|
||||
{
|
||||
static readonly System.Reflection.MethodInfo s_DrawPreview = typeof(ObjectPreview).GetMethod("DrawPreview", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
|
||||
static readonly object[] s_DrawPreviewArguments = new object[3];
|
||||
static readonly Object[] s_DrawPreviewTargets = new Object[1];
|
||||
|
||||
public static TexturePreview s_ActiveInstance;
|
||||
public bool Open { get; private set; }
|
||||
|
||||
@@ -101,6 +105,9 @@ namespace WaveHarmonic.Crest.Editor
|
||||
Helpers.SafeCreateRenderTexture(ref _RenderTexture, descriptor);
|
||||
_RenderTexture.Create();
|
||||
Object.DestroyImmediate(_Editor);
|
||||
// Raises both, but no way to avoid it:
|
||||
// | The targets array should not be used inside OnSceneGUI or OnPreviewGUI. Use the single target property instead.
|
||||
// | The serializedObject should not be used inside OnSceneGUI or OnPreviewGUI. Use the target property directly instead.
|
||||
_Editor = UnityEditor.Editor.CreateEditor(_RenderTexture);
|
||||
// Reset for incompatible copy.
|
||||
descriptor = _OriginalDescriptor;
|
||||
@@ -121,7 +128,13 @@ namespace WaveHarmonic.Crest.Editor
|
||||
Graphics.CopyTexture(Texture, _RenderTexture);
|
||||
}
|
||||
|
||||
_Editor.DrawPreview(rect);
|
||||
s_DrawPreviewTargets[0] = _Editor.target;
|
||||
s_DrawPreviewArguments[0] = _Editor;
|
||||
s_DrawPreviewArguments[1] = rect;
|
||||
s_DrawPreviewArguments[2] = s_DrawPreviewTargets;
|
||||
|
||||
// Use to be _Editor.DrawPreview(rect) but spammed errors with multiple selected.
|
||||
s_DrawPreview?.Invoke(null, s_DrawPreviewArguments);
|
||||
}
|
||||
|
||||
#if CREST_DEBUG
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using WaveHarmonic.Crest.Editor;
|
||||
using WaveHarmonic.Crest.Internal;
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor
|
||||
@@ -54,9 +55,9 @@ namespace WaveHarmonic.Crest.Editor
|
||||
new(),
|
||||
};
|
||||
|
||||
public delegate void ShowMessage(string message, string fixDescription, MessageType type, Object @object = null, FixValidation action = null, string property = null);
|
||||
public delegate void ShowMessage(string message, string fixDescription, MessageType type, Object @object = null, FixValidation action = null, string property = null, Object caller = null);
|
||||
|
||||
public static void DebugLog(string message, string fixDescription, MessageType type, Object @object = null, FixValidation action = null, string property = null)
|
||||
public static void DebugLog(string message, string fixDescription, MessageType type, Object @object = null, FixValidation action = null, string property = null, Object caller = null)
|
||||
{
|
||||
// Never log info validation to console.
|
||||
if (type == MessageType.Info)
|
||||
@@ -64,22 +65,27 @@ namespace WaveHarmonic.Crest.Editor
|
||||
return;
|
||||
}
|
||||
|
||||
message = $"Crest Validation: {message} {fixDescription} Click this message to highlight the problem object.";
|
||||
// Always link back to the caller so developers know the origin. They can always
|
||||
// use the help box "Inspect" once there to get to the object to fix. Even better,
|
||||
// they can use any available fix buttons too.
|
||||
var context = caller != null ? caller : @object;
|
||||
|
||||
message = $"<b>Crest Validation:</b> {message} {fixDescription} Click this message to highlight the problem object.";
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case MessageType.Error: Debug.LogError(message, @object); break;
|
||||
case MessageType.Warning: Debug.LogWarning(message, @object); break;
|
||||
default: Debug.Log(message, @object); break;
|
||||
case MessageType.Error: Debug.LogError(message, context); break;
|
||||
case MessageType.Warning: Debug.LogWarning(message, context); break;
|
||||
default: Debug.Log(message, context); break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void HelpBox(string message, string fixDescription, MessageType type, Object @object = null, FixValidation action = null, string property = null)
|
||||
public static void HelpBox(string message, string fixDescription, MessageType type, Object @object = null, FixValidation action = null, string property = null, Object caller = null)
|
||||
{
|
||||
s_Messages[(int)type].Add(new() { _Message = message, _FixDescription = fixDescription, _Object = @object, _Action = action, _PropertyPath = property });
|
||||
}
|
||||
|
||||
public static void Suppressed(string _0, string _1, MessageType _2, Object _3 = null, FixValidation _4 = null, string _5 = null)
|
||||
public static void Suppressed(string _0, string _1, MessageType _2, Object _3 = null, FixValidation _4 = null, string _5 = null, Object _6 = null)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -291,6 +297,24 @@ namespace WaveHarmonic.Crest.Editor
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Nested components do not descend from Object, but they could and this
|
||||
// would work for them.
|
||||
if (target is Object @object)
|
||||
{
|
||||
foreach (var field in TypeCache.GetFieldsWithAttribute<Validated>())
|
||||
{
|
||||
if (field.DeclaringType != type)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var attribute in field.GetCustomAttributes<Validated>())
|
||||
{
|
||||
isValid &= attribute.Validate(@object, field, messenger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
@@ -299,4 +323,89 @@ namespace WaveHarmonic.Crest.Editor
|
||||
return ExecuteValidators(target, DebugLog);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Validated : System.Attribute
|
||||
{
|
||||
public abstract bool Validate(Object target, FieldInfo property, ValidatedHelper.ShowMessage messenger);
|
||||
}
|
||||
}
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Validates that field is not null.
|
||||
/// </summary>
|
||||
[System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = false)]
|
||||
sealed class Required : Validated
|
||||
{
|
||||
public override bool Validate(Object target, FieldInfo field, ValidatedHelper.ShowMessage messenger)
|
||||
{
|
||||
var isValid = true;
|
||||
|
||||
if ((Object)field.GetValue(target) == null)
|
||||
{
|
||||
var typeName = EditorHelpers.Pretty(target.GetType().Name);
|
||||
var fieldName = EditorHelpers.Pretty(field.Name);
|
||||
|
||||
messenger
|
||||
(
|
||||
$"<i>{fieldName}</i> is required for the <i>{typeName}</i> component to function.",
|
||||
$"Please set <i>{fieldName}</i>.",
|
||||
ValidatedHelper.MessageType.Error,
|
||||
target
|
||||
);
|
||||
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows a info message if field is null.
|
||||
/// </summary>
|
||||
[System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = false)]
|
||||
sealed class Optional : Validated
|
||||
{
|
||||
readonly string _Message;
|
||||
|
||||
public Optional(string message)
|
||||
{
|
||||
_Message = message;
|
||||
}
|
||||
|
||||
public override bool Validate(Object target, FieldInfo field, ValidatedHelper.ShowMessage messenger)
|
||||
{
|
||||
var value = field.GetValue(target);
|
||||
|
||||
if (value is ICollection<Object> list)
|
||||
{
|
||||
if (list != null && list.Count > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (value is Object @object && @object != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
var typeName = EditorHelpers.Pretty(target.GetType().Name);
|
||||
var fieldName = EditorHelpers.Pretty(field.Name);
|
||||
|
||||
messenger
|
||||
(
|
||||
$"<i>{fieldName}</i> is not set for the <i>{typeName}</i> component. " + _Message,
|
||||
string.Empty,
|
||||
ValidatedHelper.MessageType.Info,
|
||||
target
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user