修改水
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1db68a6c1808a4d4d9099ac4c9f9812c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: abe8bdec24030408b8a545ae03e751f6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a8b444bc1fb43438aadcfff26a9342e3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,7 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Editor")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Shared.Editor")]
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ebc89e55d7e224f58b73430f7f836895
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"reference": "GUID:3eae0364be2026648bf74846acb8a731"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5373b95f6a6234bd9acf598c2856b1c9
|
||||
AssemblyDefinitionReferenceImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9efee046059b1440a932e76cb96a30d0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 645345341492f4e7daaf6a332bf6d944
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,6 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Editor")]
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 89ed50bd0e0cc4e0593b1eb86b22e426
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"reference": "GUID:c579267770062bf448e75eb160330b7f"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b4e50428db6234431b8ab6d9fe9af9ad
|
||||
AssemblyDefinitionReferenceImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ab8bbb10bd06a476aa3c33abddbdd42d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: abd60842ea94549cbbde56358927ae18
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,6 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Shared.Editor")]
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 88af6858f7fbe4851ae792f703289573
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"reference": "GUID:be0903cd8e1546f498710afdc59db5eb"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1d2e8dc2707d4ea38f4e65cafa7ab83
|
||||
AssemblyDefinitionReferenceImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c2df0bac542844fa0a8e4ad57f8e16a0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,24 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Editor")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Samples")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Samples.Editor")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Samples.Examples")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.CPUQueries")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.CPUQueries.Editor")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Paint")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Paint.Editor")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.ShallowWater")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.ShallowWater.Editor")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.ShiftingOrigin")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.ShiftingOrigin.Editor")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Splines")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Splines.Editor")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Watercraft")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Watercraft.Editor")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Whirlpool")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Whirlpool.Editor")]
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 959b22cf6debd4b5398062eb810543b2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 70a73c2fbd8a142b3956c49e19e17c31
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d4feed62e65f34662aea25208d1ce0bf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,29 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using WaveHarmonic.Crest.Attributes;
|
||||
using WaveHarmonic.Crest.Editor;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
sealed class Embedded : DecoratedProperty
|
||||
{
|
||||
internal EmbeddedAssetEditor _Editor;
|
||||
readonly int _BottomMargin;
|
||||
|
||||
public Embedded(int margin = 0)
|
||||
{
|
||||
_Editor = new();
|
||||
_BottomMargin = margin;
|
||||
}
|
||||
|
||||
internal override void OnGUI(Rect position, SerializedProperty property, GUIContent label, DecoratedDrawer drawer)
|
||||
{
|
||||
_Editor.DrawEditorCombo(label, drawer, property, "asset", _BottomMargin);
|
||||
}
|
||||
|
||||
internal override bool NeedsControlRectangle(SerializedProperty property) => false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c024c6def66044f1cb4783f23a050bec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,72 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using WaveHarmonic.Crest.Attributes;
|
||||
using WaveHarmonic.Crest.Editor;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
sealed class HelpBox : Decorator
|
||||
{
|
||||
// Define our own as Unity's won't be available in builds.
|
||||
public enum MessageType
|
||||
{
|
||||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
}
|
||||
|
||||
public string _Message;
|
||||
public MessageType _MessageType;
|
||||
public Visibility _Visibility;
|
||||
|
||||
public enum Visibility
|
||||
{
|
||||
Always,
|
||||
PropertyEnabled,
|
||||
PropertyDisabled,
|
||||
}
|
||||
|
||||
readonly GUIContent _GuiContent;
|
||||
|
||||
public override bool AlwaysVisible => false;
|
||||
|
||||
public HelpBox(string message, MessageType messageType = MessageType.Info, Visibility visibility = Visibility.Always)
|
||||
{
|
||||
_Message = message;
|
||||
_MessageType = messageType;
|
||||
_Visibility = visibility;
|
||||
_GuiContent = new(message);
|
||||
}
|
||||
|
||||
internal override void Decorate(Rect position, SerializedProperty property, GUIContent label, DecoratedDrawer drawer)
|
||||
{
|
||||
if (_Visibility == Visibility.PropertyEnabled && !GUI.enabled || _Visibility == Visibility.PropertyDisabled && GUI.enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Enable rich text in help boxes. Store original so we can revert since this might be a "hack".
|
||||
var style = GUI.skin.GetStyle("HelpBox");
|
||||
var styleRichText = style.richText;
|
||||
style.richText = true;
|
||||
|
||||
var height = style.CalcHeight(_GuiContent, EditorGUIUtility.currentViewWidth);
|
||||
if (height <= EditorGUIUtility.singleLineHeight)
|
||||
{
|
||||
// This gets internal layout of the help box right but breaks down if multiline.
|
||||
height += style.padding.horizontal + style.lineHeight;
|
||||
}
|
||||
|
||||
// Always get a new control rect so we don't have to deal with positions and offsets.
|
||||
position = EditorGUILayout.GetControlRect(true, height, style);
|
||||
// + 1 maps our MessageType to Unity's.
|
||||
EditorGUI.HelpBox(position, _Message, (UnityEditor.MessageType)_MessageType + 1);
|
||||
|
||||
// Revert skin since it persists.
|
||||
style.richText = styleRichText;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 49c68d73fb5ce4690a444ab84a0abc8e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,25 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a custom link to Crest's documentation for the help URL button.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum, AllowMultiple = false)]
|
||||
sealed class HelpURL : HelpURLAttribute
|
||||
{
|
||||
public HelpURL(string path = "") : base(GetPageLink(path))
|
||||
{
|
||||
// Blank.
|
||||
}
|
||||
|
||||
public static string GetPageLink(string path)
|
||||
{
|
||||
return "https://docs.crest.waveharmonic.com/" + path;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f113b211b4a014cf19cf7828018f49d0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using WaveHarmonic.Crest.Attributes;
|
||||
using WaveHarmonic.Crest.Editor;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
sealed class Layer : DecoratedProperty
|
||||
{
|
||||
internal override void OnGUI(Rect position, SerializedProperty property, GUIContent label, DecoratedDrawer drawer)
|
||||
{
|
||||
property.intValue = EditorGUI.LayerField(position, label, property.intValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a25b1272591fc4fd6a34265f20064746
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,33 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using System;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||
sealed class OnChange : Attribute
|
||||
{
|
||||
public Type Type { get; }
|
||||
public bool SkipIfInactive { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Register an instance method as an OnChange handler.
|
||||
/// </summary>
|
||||
public OnChange(bool skipIfInactive = true)
|
||||
{
|
||||
SkipIfInactive = skipIfInactive;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register a static method as an OnChange handler.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to target.</param>
|
||||
/// <param name="skipIfInactive">Skip this handler if component is inactive.</param>
|
||||
public OnChange(Type type, bool skipIfInactive = true)
|
||||
{
|
||||
Type = type;
|
||||
SkipIfInactive = skipIfInactive;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 75d2e7be9abf446048de686f69cb485c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,213 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using WaveHarmonic.Crest.Attributes;
|
||||
using WaveHarmonic.Crest.Editor;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
sealed class Predicated : Decorator
|
||||
{
|
||||
enum Mode
|
||||
{
|
||||
Property,
|
||||
Member,
|
||||
Type,
|
||||
RenderPipeline,
|
||||
}
|
||||
|
||||
readonly Mode _Mode;
|
||||
|
||||
readonly bool _Inverted;
|
||||
readonly bool _Hide;
|
||||
|
||||
readonly string _PropertyName;
|
||||
readonly object _DisableValue;
|
||||
|
||||
readonly Type _Type;
|
||||
readonly MemberInfo _Member;
|
||||
|
||||
readonly RenderPipeline _RenderPipeline;
|
||||
|
||||
/// <summary>
|
||||
/// The field with this attribute will be drawn enabled/disabled based on return of method.
|
||||
/// </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="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)
|
||||
{
|
||||
_Mode = Mode.Member;
|
||||
_Inverted = inverted;
|
||||
_Hide = hide;
|
||||
_Type = type;
|
||||
_DisableValue = disableValue;
|
||||
_Member = _Type.GetMember(member, Helpers.s_AnyMethod)[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable/Disable field depending on the current type of the component.
|
||||
/// </summary>
|
||||
/// <param name="type">If a component of this type is not attached to this GameObject, disable the GUI (or enable if inverted is true).</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, bool inverted = false, bool hide = false)
|
||||
{
|
||||
_Mode = Mode.Type;
|
||||
_Inverted = inverted;
|
||||
_Hide = hide;
|
||||
_Type = type;
|
||||
}
|
||||
|
||||
/// <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="hide">Hide this component in the inspector.</param>
|
||||
public Predicated(string property, bool inverted = false, object disableValue = null, bool hide = false)
|
||||
{
|
||||
_Mode = Mode.Property;
|
||||
_Inverted = inverted;
|
||||
_Hide = hide;
|
||||
_PropertyName = property;
|
||||
_DisableValue = disableValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Field is predicated (enabled/shown) on which render pipeline is active.
|
||||
/// </summary>
|
||||
/// <param name="rp">Enable if this render pipeline is active.</param>
|
||||
/// <param name="inverted">Invert behaviour.</param>
|
||||
/// <param name="hide">Hide instead of disable.</param>
|
||||
public Predicated(RenderPipeline rp, bool inverted = false, bool hide = false)
|
||||
{
|
||||
_Mode = Mode.RenderPipeline;
|
||||
_Inverted = inverted;
|
||||
_Hide = hide;
|
||||
_RenderPipeline = rp;
|
||||
}
|
||||
|
||||
public override bool AlwaysVisible => true;
|
||||
|
||||
public bool GUIEnabled(SerializedProperty property)
|
||||
{
|
||||
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.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,
|
||||
_ => throw new ArgumentException($"Crest.Predicated: property type on <i>{property.serializedObject.targetObject}</i> not implemented yet: {property.propertyType}."),
|
||||
};
|
||||
}
|
||||
|
||||
internal override void Decorate(Rect position, SerializedProperty property, GUIContent label, DecoratedDrawer drawer)
|
||||
{
|
||||
var enabled = true;
|
||||
|
||||
if (_Mode == Mode.Property)
|
||||
{
|
||||
var propertyPath = _PropertyName;
|
||||
|
||||
if (property.depth > 0)
|
||||
{
|
||||
// Get the property path so we can find it from the serialized object.
|
||||
propertyPath = $"{string.Join(".", property.propertyPath.Split(".", StringSplitOptions.None)[0..^1])}.{propertyPath}";
|
||||
}
|
||||
|
||||
// Get the other property to be the predicate for the enabled/disabled state of this property.
|
||||
// Do not use property.FindPropertyRelative as it does not work with nested properties.
|
||||
// Try and find the nested property first and then fallback to the root object.
|
||||
var otherProperty = property.serializedObject.FindProperty(propertyPath) ?? property.serializedObject.FindProperty(_PropertyName);
|
||||
Debug.AssertFormat(otherProperty != null, "Crest.Predicated: {0} or {1} property on {2} ({3}) could not be found!", propertyPath, _PropertyName, property.serializedObject.targetObject.GetType(), property.name);
|
||||
|
||||
if (otherProperty != null)
|
||||
{
|
||||
enabled = GUIEnabled(otherProperty);
|
||||
}
|
||||
}
|
||||
else if (_Mode == Mode.Member)
|
||||
{
|
||||
// Static is both abstract and sealed: https://stackoverflow.com/a/1175950
|
||||
object @object = _Type.IsAbstract && _Type.IsSealed ? null : property.serializedObject.targetObject;
|
||||
|
||||
// If this is a nested type, grab that type. This may not be suitable in all cases.
|
||||
if (property.depth > 0)
|
||||
{
|
||||
// Get the property path so we can find it from the serialized object.
|
||||
var propertyPath = string.Join(".", property.propertyPath.Split(".", StringSplitOptions.None)[0..^1]);
|
||||
|
||||
var otherProperty = property.serializedObject.FindProperty(propertyPath);
|
||||
|
||||
@object = otherProperty.propertyType switch
|
||||
{
|
||||
SerializedPropertyType.ManagedReference => otherProperty.managedReferenceValue,
|
||||
SerializedPropertyType.Generic => otherProperty.boxedValue,
|
||||
_ => @object,
|
||||
};
|
||||
}
|
||||
|
||||
if (_Member is PropertyInfo autoProperty)
|
||||
{
|
||||
// == operator does not work.
|
||||
enabled = autoProperty.GetValue(@object).Equals(_DisableValue);
|
||||
}
|
||||
else if (_Member is MethodInfo method)
|
||||
{
|
||||
enabled = method.Invoke(@object, new object[] { }).Equals(_DisableValue);
|
||||
}
|
||||
|
||||
if (_Inverted) enabled = !enabled;
|
||||
}
|
||||
else if (_Mode == Mode.Type)
|
||||
{
|
||||
var type = property.serializedObject.targetObject.GetType();
|
||||
|
||||
// If this is a nested type, grab that type. This may not be suitable in all cases.
|
||||
if (property.depth > 0)
|
||||
{
|
||||
// Get the property path so we can find it from the serialized object.
|
||||
var propertyPath = string.Join(".", property.propertyPath.Split(".", StringSplitOptions.None)[0..^1]);
|
||||
|
||||
var otherProperty = property.serializedObject.FindProperty(propertyPath);
|
||||
|
||||
type = otherProperty.propertyType switch
|
||||
{
|
||||
SerializedPropertyType.ManagedReference => otherProperty.managedReferenceValue.GetType(),
|
||||
SerializedPropertyType.Generic => otherProperty.boxedValue.GetType(),
|
||||
_ => type,
|
||||
};
|
||||
}
|
||||
|
||||
var enabledByTypeCheck = _Type.IsAssignableFrom(type);
|
||||
if (_Inverted) enabledByTypeCheck = !enabledByTypeCheck;
|
||||
enabled = enabledByTypeCheck && enabled;
|
||||
}
|
||||
else if (_Mode == Mode.RenderPipeline)
|
||||
{
|
||||
enabled = RenderPipelineHelper.RenderPipeline == _RenderPipeline != _Inverted;
|
||||
}
|
||||
|
||||
// Keep current disabled state.
|
||||
GUI.enabled &= enabled;
|
||||
|
||||
// Keep previous hidden state.
|
||||
DecoratedDrawer.s_HideInInspector = DecoratedDrawer.s_HideInInspector || (_Hide && !enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a36938141700f4f47b31f7565311ace0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,217 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
// Adapted from:
|
||||
// https://forum.unity.com/threads/drawing-a-field-using-multiple-property-drawers.479377/
|
||||
|
||||
// This class draws all the attributes which inherit from DecoratedProperty. This class may need to be
|
||||
// extended to handle reseting GUI states as we need them.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using WaveHarmonic.Crest.Attributes;
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(DecoratedProperty), true)]
|
||||
sealed class DecoratedDrawer : PropertyDrawer
|
||||
{
|
||||
internal static bool s_HideInInspector = false;
|
||||
public static bool s_IsFoldout = false;
|
||||
public static bool s_IsFoldoutOpen = false;
|
||||
|
||||
public static bool s_TemporaryColor;
|
||||
public static Color s_PreviousColor;
|
||||
|
||||
List<object> _Decorators = null;
|
||||
List<object> Decorators
|
||||
{
|
||||
get
|
||||
{
|
||||
// Populate list with decorators.
|
||||
_Decorators ??= fieldInfo
|
||||
.GetCustomAttributes(typeof(Decorator), false)
|
||||
.ToList();
|
||||
|
||||
return _Decorators;
|
||||
}
|
||||
}
|
||||
|
||||
List<Attributes.Validator> _Validators = null;
|
||||
List<Attributes.Validator> Validators => _Validators ??= fieldInfo
|
||||
.GetCustomAttributes(typeof(Attributes.Validator), false)
|
||||
.Cast<Attributes.Validator>()
|
||||
.ToList();
|
||||
|
||||
static List<(MethodInfo, OnChange)> s_OnChangeHandlers;
|
||||
static List<(MethodInfo, OnChange)> OnChangeHandlers => s_OnChangeHandlers ??= TypeCache
|
||||
.GetMethodsWithAttribute<OnChange>()
|
||||
.Select(x => (x, x.GetCustomAttribute<OnChange>()))
|
||||
.ToList();
|
||||
|
||||
[InitializeOnLoadMethod]
|
||||
static void OnDomainReload()
|
||||
{
|
||||
s_OnChangeHandlers = null;
|
||||
}
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
// Store the original GUI state so it can be reset later.
|
||||
var originalColor = GUI.color;
|
||||
var originalEnabled = GUI.enabled;
|
||||
|
||||
if (s_TemporaryColor) GUI.color = s_PreviousColor;
|
||||
|
||||
// Execute all non visual attributes like Predicated. If these change any IMGUI
|
||||
// properties they might be overriden with EditorGUI.GetPropertyHeight call.
|
||||
for (var index = 0; index < Decorators.Count; index++)
|
||||
{
|
||||
var attribute = (Decorator)Decorators[index];
|
||||
if (!attribute.AlwaysVisible) continue;
|
||||
attribute.Decorate(position, property, label, this);
|
||||
}
|
||||
|
||||
if (!s_HideInInspector && (!s_IsFoldout || s_IsFoldoutOpen))
|
||||
{
|
||||
// Execute all visual attributes.
|
||||
for (var index = 0; index < Decorators.Count; index++)
|
||||
{
|
||||
var attribute = (Decorator)Decorators[index];
|
||||
if (attribute.AlwaysVisible) continue;
|
||||
label = attribute.BuildLabel(label);
|
||||
attribute.Decorate(position, property, label, this);
|
||||
}
|
||||
|
||||
var a = (DecoratedProperty)attribute;
|
||||
position = a.NeedsControlRectangle(property)
|
||||
? EditorGUILayout.GetControlRect(true, EditorGUI.GetPropertyHeight(property, label, true))
|
||||
: position;
|
||||
|
||||
// Call for labels again as EditorGUI.GetPropertyHeight will revert them.
|
||||
for (var index = 0; index < Decorators.Count; index++)
|
||||
{
|
||||
var attribute = (Decorator)Decorators[index];
|
||||
if (attribute.AlwaysVisible) continue;
|
||||
label = attribute.BuildLabel(label);
|
||||
}
|
||||
|
||||
var skipChange = fieldInfo.FieldType == typeof(UnityEvent);
|
||||
|
||||
if (!skipChange)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
}
|
||||
|
||||
var oldValue = skipChange ? null : property.boxedValue;
|
||||
a.OnGUI(position, property, label, this);
|
||||
|
||||
for (var index = 0; index < Validators.Count; index++)
|
||||
{
|
||||
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)
|
||||
{
|
||||
// Apply any changes.
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
|
||||
OnChange(property, oldValue);
|
||||
}
|
||||
|
||||
for (var index = 0; index < Decorators.Count; index++)
|
||||
{
|
||||
var attribute = (Decorator)Decorators[index];
|
||||
if (attribute.AlwaysVisible) continue;
|
||||
attribute.DecorateAfter(position, property, label, this);
|
||||
}
|
||||
}
|
||||
|
||||
if (!s_IsFoldout || s_IsFoldoutOpen)
|
||||
{
|
||||
for (var index = 0; index < Decorators.Count; index++)
|
||||
{
|
||||
var attribute = (Decorator)Decorators[index];
|
||||
if (!attribute.AlwaysVisible) continue;
|
||||
attribute.DecorateAfter(position, property, label, this);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle resetting the GUI state.
|
||||
s_HideInInspector = false;
|
||||
GUI.color = originalColor;
|
||||
GUI.enabled = originalEnabled;
|
||||
}
|
||||
|
||||
public static void OnChange(SerializedProperty property, object oldValue)
|
||||
{
|
||||
var target = property.serializedObject.targetObject;
|
||||
// This is the type the field is declared on so it will work for nested objects.
|
||||
var targetType = target.GetType();
|
||||
|
||||
var isActive = true;
|
||||
if (property.serializedObject.targetObject is Internal.EditorBehaviour c && !c.isActiveAndEnabled)
|
||||
{
|
||||
isActive = false;
|
||||
}
|
||||
|
||||
// Send event to target.
|
||||
foreach (var (method, attribute) in OnChangeHandlers)
|
||||
{
|
||||
if (attribute.SkipIfInactive && !isActive) continue;
|
||||
var type = attribute.Type ?? method.DeclaringType;
|
||||
if (!type.IsAssignableFrom(targetType)) continue;
|
||||
|
||||
if (attribute.Type == null)
|
||||
{
|
||||
method.Invoke(target, new object[] { property.propertyPath, oldValue });
|
||||
}
|
||||
else
|
||||
{
|
||||
method.Invoke(null, new object[] { target, property.propertyPath, oldValue, property });
|
||||
}
|
||||
}
|
||||
|
||||
// Propagate event to nested classes.
|
||||
for (var i = 0; i < property.depth; i++)
|
||||
{
|
||||
var chunks = property.propertyPath.Split(".");
|
||||
var nestedProperty = property.serializedObject.FindProperty(string.Join(".", chunks[..(i + 1)]));
|
||||
if (nestedProperty.propertyType != SerializedPropertyType.ManagedReference) continue;
|
||||
var relativePath = string.Join(".", chunks[(i + 1)..]);
|
||||
|
||||
var nestedTarget = nestedProperty.managedReferenceValue;
|
||||
var nestedTargetType = nestedTarget.GetType();
|
||||
|
||||
foreach (var (method, attribute) in OnChangeHandlers)
|
||||
{
|
||||
if (attribute.SkipIfInactive && !isActive) continue;
|
||||
var type = attribute.Type ?? method.DeclaringType;
|
||||
if (!type.IsAssignableFrom(nestedTargetType)) continue;
|
||||
|
||||
if (attribute.Type == null)
|
||||
{
|
||||
method.Invoke(nestedTarget, new object[] { relativePath, oldValue });
|
||||
}
|
||||
else
|
||||
{
|
||||
method.Invoke(null, new object[] { nestedTarget, relativePath, oldValue, property });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bb1d9ebb9335a411586a683ac6f78acd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,246 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
// This file is subject to the Unity Companion License:
|
||||
// https://github.com/Unity-Technologies/com.unity.cinemachine/blob/593fa283bee378322337e5d9f5a7b91331a45799/LICENSE.md
|
||||
|
||||
// Lovingly adapted from Cinemachine:
|
||||
// https://github.com/Unity-Technologies/com.unity.cinemachine/blob/593fa283bee378322337e5d9f5a7b91331a45799/Editor/Utility/EmbeddedAssetHelpers.cs
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEditor.VersionControl;
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for editors that receive an argument
|
||||
/// </summary>
|
||||
interface IEmbeddableEditor
|
||||
{
|
||||
void SetTypeOfHostComponent(System.Type hostType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper for drawing embedded asset editors
|
||||
/// </summary>
|
||||
sealed class EmbeddedAssetEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Create in OnEnable()
|
||||
/// </summary>
|
||||
public EmbeddedAssetEditor()
|
||||
{
|
||||
_CreateButtonGUIContent = new("Create Asset", "Create a new shared settings asset");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after the asset editor is created, in case it needs
|
||||
/// to be customized
|
||||
/// </summary>
|
||||
public OnCreateEditorDelegate _OnCreateEditor;
|
||||
public delegate void OnCreateEditorDelegate(UnityEditor.Editor editor);
|
||||
|
||||
/// <summary>
|
||||
/// Called when the asset being edited was changed by the user.
|
||||
/// </summary>
|
||||
public OnChangedDelegate _OnChanged;
|
||||
public delegate void OnChangedDelegate(System.Type type, Object obj);
|
||||
|
||||
/// <summary>
|
||||
/// Free the resources in OnDisable()
|
||||
/// </summary>
|
||||
public void OnDisable()
|
||||
{
|
||||
DestroyEditor();
|
||||
Helpers.Destroy(_DefaultTarget);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Customize this after creation if you want
|
||||
/// </summary>
|
||||
public GUIContent _CreateButtonGUIContent;
|
||||
|
||||
UnityEditor.Editor _Editor = null;
|
||||
|
||||
System.Type _Type;
|
||||
|
||||
Object _DefaultTarget;
|
||||
|
||||
const int k_IndentOffset = 3;
|
||||
|
||||
public void DrawEditorCombo(GUIContent label, PropertyDrawer drawer, SerializedProperty property, string extension, int bottomMargin = 0)
|
||||
{
|
||||
_Type = drawer.fieldInfo.FieldType;
|
||||
|
||||
DrawEditorCombo
|
||||
(
|
||||
label,
|
||||
$"Create {property.displayName} Asset",
|
||||
$"{property.displayName.Replace(' ', '_')}",
|
||||
extension,
|
||||
string.Empty,
|
||||
false,
|
||||
property,
|
||||
bottomMargin
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call this from OnInspectorGUI. Will draw the asset reference field, and
|
||||
/// the embedded editor, or a Create Asset button, if no asset is set.
|
||||
/// </summary>
|
||||
public void DrawEditorCombo
|
||||
(
|
||||
GUIContent label,
|
||||
string title,
|
||||
string defaultName,
|
||||
string extension,
|
||||
string message,
|
||||
bool indent,
|
||||
SerializedProperty property,
|
||||
int bottomMargin
|
||||
)
|
||||
{
|
||||
UpdateEditor(property);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var rect = AssetField(label, property, title, defaultName, extension, message);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
UpdateEditor(property);
|
||||
}
|
||||
|
||||
// Display embedded editor.
|
||||
if (_Editor != null)
|
||||
{
|
||||
var foldoutRect = new Rect(rect.x - k_IndentOffset, rect.y, rect.width + k_IndentOffset, EditorGUIUtility.singleLineHeight);
|
||||
property.isExpanded = EditorGUI.Foldout(foldoutRect, property.isExpanded, GUIContent.none, true);
|
||||
|
||||
var canEditAsset = AssetDatabase.IsOpenForEdit(_Editor.target, StatusQueryOptions.UseCachedIfPossible);
|
||||
|
||||
// We take the current GUI state into account to support attribute stacking.
|
||||
var guiEnabled = GUI.enabled;
|
||||
GUI.enabled = guiEnabled && canEditAsset;
|
||||
|
||||
if (property.isExpanded)
|
||||
{
|
||||
var level = EditorGUI.indentLevel;
|
||||
EditorGUI.indentLevel = 0;
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
// NOTE: Tweaked for current usage but probably will not work everywhere.
|
||||
if (level > 0) GUILayout.Space(8 * (level + 2));
|
||||
EditorGUILayout.BeginVertical(GUI.skin.box);
|
||||
|
||||
if ((_Editor.target.hideFlags & HideFlags.NotEditable) == 0)
|
||||
{
|
||||
EditorGUILayout.HelpBox("This is a shared asset. Changes made here will apply to all users of this asset.", MessageType.Info);
|
||||
}
|
||||
EditorGUI.BeginChangeCheck();
|
||||
_Editor.OnInspectorGUI();
|
||||
if (EditorGUI.EndChangeCheck() && (_OnChanged != null))
|
||||
_OnChanged(_Type, property.objectReferenceValue);
|
||||
EditorGUILayout.EndVertical();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
if (bottomMargin > 0)
|
||||
{
|
||||
EditorGUILayout.Space(bottomMargin);
|
||||
}
|
||||
}
|
||||
|
||||
// Enable GUI so the checkout button works.
|
||||
GUI.enabled = true;
|
||||
|
||||
if (_Editor.target != null)
|
||||
{
|
||||
if (!canEditAsset && GUILayout.Button("Check out"))
|
||||
{
|
||||
var task = Provider.Checkout(AssetDatabase.GetAssetPath(_Editor.target), CheckoutMode.Asset);
|
||||
task.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
// Restore stacked GUI enabled state.
|
||||
GUI.enabled = guiEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
Rect AssetField
|
||||
(
|
||||
GUIContent label,
|
||||
SerializedProperty property,
|
||||
string title,
|
||||
string defaultName,
|
||||
string extension,
|
||||
string message
|
||||
)
|
||||
{
|
||||
return EditorHelpers.AssetField
|
||||
(
|
||||
_Type,
|
||||
label,
|
||||
property,
|
||||
EditorGUILayout.GetControlRect(true),
|
||||
title,
|
||||
defaultName,
|
||||
extension,
|
||||
message,
|
||||
x => ScriptableObject.CreateInstance(_Type)
|
||||
);
|
||||
}
|
||||
|
||||
public void DestroyEditor()
|
||||
{
|
||||
if (_Editor != null)
|
||||
{
|
||||
Object.DestroyImmediate(_Editor);
|
||||
_Editor = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateEditor(SerializedProperty property)
|
||||
{
|
||||
var target = property.objectReferenceValue;
|
||||
|
||||
if (target == null && _DefaultTarget == null)
|
||||
{
|
||||
_DefaultTarget = ScriptableObject.CreateInstance(_Type);
|
||||
_DefaultTarget.hideFlags = HideFlags.DontSave | HideFlags.NotEditable;
|
||||
}
|
||||
|
||||
if (target == null)
|
||||
{
|
||||
target = _DefaultTarget;
|
||||
}
|
||||
|
||||
// Destroy the editor if target has changed.
|
||||
if (_Editor != null && _Editor.target != target)
|
||||
{
|
||||
DestroyEditor();
|
||||
}
|
||||
|
||||
if (_Editor != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// NOTE: This is triggered twice on asset switch for some reason.
|
||||
// Create editor if need one.
|
||||
if (target != null)
|
||||
{
|
||||
_Editor = UnityEditor.Editor.CreateEditor(target);
|
||||
|
||||
// Pass through argument for editors that receive it
|
||||
if (property.serializedObject.targetObject != null)
|
||||
{
|
||||
(_Editor as IEmbeddableEditor)?.SetTypeOfHostComponent(property.serializedObject.targetObject.GetType());
|
||||
}
|
||||
|
||||
_OnCreateEditor?.Invoke(_Editor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 13b989c062ddf461a9afd71ab6da8921
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,272 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Rendering;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides general helper functions for the editor.
|
||||
/// </summary>
|
||||
static class EditorHelpers
|
||||
{
|
||||
internal static ComputeShader s_VisualizeNegativeValuesShader;
|
||||
internal static ComputeShader VisualizeNegativeValuesShader
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_VisualizeNegativeValuesShader == null)
|
||||
{
|
||||
s_VisualizeNegativeValuesShader = AssetDatabase.LoadAssetAtPath<ComputeShader>("Packages/com.waveharmonic.crest/Editor/Shaders/VisualizeNegativeValues.compute");
|
||||
}
|
||||
|
||||
return s_VisualizeNegativeValuesShader;
|
||||
}
|
||||
}
|
||||
|
||||
public static LayerMask LayerMaskField(string label, LayerMask layerMask)
|
||||
{
|
||||
// Adapted from: http://answers.unity.com/answers/1387522/view.html
|
||||
var temporary = EditorGUILayout.MaskField(
|
||||
label,
|
||||
UnityEditorInternal.InternalEditorUtility.LayerMaskToConcatenatedLayersMask(layerMask),
|
||||
UnityEditorInternal.InternalEditorUtility.layers);
|
||||
return UnityEditorInternal.InternalEditorUtility.ConcatenatedLayersMaskToLayerMask(temporary);
|
||||
}
|
||||
|
||||
/// <summary>Attempts to get the scene view this camera is rendering.</summary>
|
||||
/// <returns>The scene view or null if not found.</returns>
|
||||
public static SceneView GetSceneViewFromSceneCamera(Camera camera)
|
||||
{
|
||||
foreach (SceneView sceneView in SceneView.sceneViews)
|
||||
{
|
||||
if (sceneView.camera == camera)
|
||||
{
|
||||
return sceneView;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>Get time passed to animated materials.</summary>
|
||||
public static float GetShaderTime()
|
||||
{
|
||||
// When "Always Refresh" is disabled, Unity passes zero. Also uses realtimeSinceStartup:
|
||||
// https://github.com/Unity-Technologies/Graphics/blob/5743e39cdf0795cf7cbeb7ba8ffbbcc7ca200709/Packages/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariablesGlobal.cs#L116
|
||||
return !Application.isPlaying && SceneView.lastActiveSceneView != null &&
|
||||
!SceneView.lastActiveSceneView.sceneViewState.alwaysRefresh ? 0f : Time.realtimeSinceStartup;
|
||||
}
|
||||
|
||||
public static GameObject GetGameObject(SerializedObject serializedObject)
|
||||
{
|
||||
// We will either get the component or the GameObject it is attached to.
|
||||
return serializedObject.targetObject is GameObject
|
||||
? serializedObject.targetObject as GameObject
|
||||
: (serializedObject.targetObject as Component).gameObject;
|
||||
}
|
||||
|
||||
public static Material CreateSerializedMaterial(string shaderPath, string message)
|
||||
{
|
||||
var shader = Shader.Find(shaderPath);
|
||||
Debug.Assert(shader != null, "Crest: Cannot create required material because shader is null");
|
||||
|
||||
var material = new Material(shader);
|
||||
|
||||
// Record the material and any subsequent changes.
|
||||
Undo.RegisterCreatedObjectUndo(material, message);
|
||||
Undo.RegisterCompleteObjectUndo(material, message);
|
||||
|
||||
return material;
|
||||
}
|
||||
|
||||
public static Material CreateSerializedMaterial(string shaderPath)
|
||||
{
|
||||
return CreateSerializedMaterial(shaderPath, Undo.GetCurrentGroupName());
|
||||
}
|
||||
|
||||
public static Object GetDefaultReference(this SerializedObject self, string property)
|
||||
{
|
||||
var path = AssetDatabase.GetAssetPath(MonoScript.FromMonoBehaviour(self.targetObject as MonoBehaviour));
|
||||
var importer = AssetImporter.GetAtPath(path) as MonoImporter;
|
||||
return importer.GetDefaultReference(property);
|
||||
}
|
||||
|
||||
public static object GetDefiningBoxedObject(this SerializedProperty property)
|
||||
{
|
||||
object target = property.serializedObject.targetObject;
|
||||
|
||||
if (property.depth > 0)
|
||||
{
|
||||
// Get the property path so we can find it from the serialized object.
|
||||
var path = string.Join(".", property.propertyPath.Split(".", System.StringSplitOptions.None)[0..^1]);
|
||||
var other = property.serializedObject.FindProperty(path);
|
||||
// Boxed value can handle both managed and generic with caveats:
|
||||
// https://docs.unity3d.com/ScriptReference/SerializedProperty-boxedValue.html
|
||||
// Not sure if it will be a new or same instance as in the scene.
|
||||
target = other.boxedValue;
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
internal delegate Object CreateInstance(SerializedProperty property);
|
||||
|
||||
internal static Rect AssetField
|
||||
(
|
||||
System.Type type,
|
||||
GUIContent label,
|
||||
SerializedProperty property,
|
||||
Rect rect,
|
||||
string title,
|
||||
string defaultName,
|
||||
string extension,
|
||||
string message,
|
||||
CreateInstance create
|
||||
)
|
||||
{
|
||||
var hSpace = 5;
|
||||
var buttonWidth = 45;
|
||||
var buttonCount = 2;
|
||||
|
||||
rect.width -= buttonWidth * buttonCount + hSpace;
|
||||
EditorGUI.PropertyField(rect, property, label);
|
||||
|
||||
var r = new Rect(rect);
|
||||
|
||||
r.x += r.width + hSpace;
|
||||
r.width = buttonWidth;
|
||||
if (GUI.Button(r, "New", EditorStyles.miniButtonLeft))
|
||||
{
|
||||
var path = EditorUtility.SaveFilePanelInProject(title, defaultName, extension, message);
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
var asset = create(property);
|
||||
if (asset != null)
|
||||
{
|
||||
if (extension == "prefab")
|
||||
{
|
||||
PrefabUtility.SaveAsPrefabAsset(asset as GameObject, path);
|
||||
}
|
||||
else
|
||||
{
|
||||
AssetDatabase.CreateAsset(asset, path);
|
||||
}
|
||||
|
||||
property.objectReferenceValue = AssetDatabase.LoadAssetAtPath<Object>(path);
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"Crest: Could not create file");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only allow cloning if extensions match. Guards against cloning Shader Graph if
|
||||
// using its embedded material.
|
||||
var cloneable = property.objectReferenceValue != null;
|
||||
cloneable = cloneable && Path.GetExtension(AssetDatabase.GetAssetPath(property.objectReferenceValue)) == $".{extension}";
|
||||
|
||||
EditorGUI.BeginDisabledGroup(!cloneable);
|
||||
r.x += r.width;
|
||||
if (GUI.Button(r, "Clone", EditorStyles.miniButtonRight))
|
||||
{
|
||||
var oldPath = AssetDatabase.GetAssetPath(property.objectReferenceValue);
|
||||
var newPath = oldPath;
|
||||
if (!newPath.StartsWithNoAlloc("Assets")) newPath = Path.Join("Assets", Path.GetFileName(newPath));
|
||||
newPath = AssetDatabase.GenerateUniqueAssetPath(newPath);
|
||||
AssetDatabase.CopyAsset(oldPath, newPath);
|
||||
property.objectReferenceValue = AssetDatabase.LoadAssetAtPath<Object>(newPath);
|
||||
}
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
internal static void RichTextHelpBox(string message, MessageType type)
|
||||
{
|
||||
var styleRichText = GUI.skin.GetStyle("HelpBox").richText;
|
||||
GUI.skin.GetStyle("HelpBox").richText = true;
|
||||
|
||||
EditorGUILayout.HelpBox(message, type);
|
||||
|
||||
// Revert skin since it persists.
|
||||
GUI.skin.GetStyle("HelpBox").richText = styleRichText;
|
||||
}
|
||||
|
||||
// Prettify nameof.
|
||||
internal static string Pretty(this string text)
|
||||
{
|
||||
// Regular expression to split on transitions from lower to upper case and keep acronyms together
|
||||
return Regex.Replace(text, @"([a-z])([A-Z])|([A-Z])([A-Z][a-z])", "$1$3 $2$4").Replace("_", "");
|
||||
}
|
||||
|
||||
internal static string Italic(this string text)
|
||||
{
|
||||
return $"<i>{text}</i>";
|
||||
}
|
||||
|
||||
public static void MarkCurrentStageAsDirty()
|
||||
{
|
||||
var stage = UnityEditor.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage();
|
||||
|
||||
if (stage != null)
|
||||
{
|
||||
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(stage.scene);
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(UnityEngine.SceneManagement.SceneManager.GetActiveScene());
|
||||
}
|
||||
}
|
||||
|
||||
static readonly MethodInfo s_ButtonWithDropdownList = typeof(EditorGUI).GetMethod
|
||||
(
|
||||
"ButtonWithDropdownList",
|
||||
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public,
|
||||
null,
|
||||
new System.Type[] { typeof(GUIContent), typeof(string[]), typeof(GenericMenu.MenuFunction2), typeof(GUILayoutOption[]) },
|
||||
null
|
||||
);
|
||||
|
||||
static readonly GUILayoutOption[] s_GUILayoutOptionsZero;
|
||||
|
||||
public static bool ButtonWithDropdownList(GUIContent name, string[] names, GenericMenu.MenuFunction2 callback)
|
||||
{
|
||||
if (names == null)
|
||||
{
|
||||
return GUILayout.Button(name);
|
||||
}
|
||||
|
||||
return (bool)s_ButtonWithDropdownList.Invoke(null, new object[] { name, names, callback, s_GUILayoutOptionsZero });
|
||||
}
|
||||
}
|
||||
|
||||
static partial class Extensions
|
||||
{
|
||||
internal static string GetSubShaderTag([DisallowNull] this Shader shader, ShaderSnippetData snippet, ShaderTagId id)
|
||||
{
|
||||
var data = ShaderUtil.GetShaderData(shader);
|
||||
if (data == null) return null;
|
||||
|
||||
var index = (int)snippet.pass.SubshaderIndex;
|
||||
if (index < 0 || index >= shader.subshaderCount) return null;
|
||||
|
||||
var subShader = data.GetSerializedSubshader(index);
|
||||
if (subShader == null) return null;
|
||||
|
||||
var tag = subShader.FindTagValue(id);
|
||||
if (string.IsNullOrEmpty(tag.name)) return null;
|
||||
|
||||
return tag.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 19e5156bd1ea44af484895930b902df7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,158 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using System.Text.RegularExpressions;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
partial class Inspector
|
||||
{
|
||||
static readonly bool s_GroupMessages = false;
|
||||
static GUIContent s_JumpButtonContent = null;
|
||||
static GUIContent s_FixButtonContent = null;
|
||||
|
||||
protected virtual void RenderValidationMessages()
|
||||
{
|
||||
// Enable rich text in help boxes. Store original so we can revert since this might be a "hack".
|
||||
var styleRichText = GUI.skin.GetStyle("HelpBox").richText;
|
||||
GUI.skin.GetStyle("HelpBox").richText = true;
|
||||
|
||||
// This is a static list so we need to clear it before use. Not sure if this will ever be a threaded
|
||||
// operation which would be an issue.
|
||||
foreach (var messages in ValidatedHelper.s_Messages)
|
||||
{
|
||||
messages.Clear();
|
||||
}
|
||||
|
||||
ValidatedHelper.ExecuteValidators(target, ValidatedHelper.HelpBox);
|
||||
|
||||
// We only want space before and after the list of help boxes. We don't want space between.
|
||||
var needsSpaceAbove = true;
|
||||
var needsSpaceBelow = false;
|
||||
|
||||
// We loop through in reverse order so errors appears at the top.
|
||||
for (var messageTypeIndex = 0; messageTypeIndex < ValidatedHelper.s_Messages.Length; messageTypeIndex++)
|
||||
{
|
||||
var messages = ValidatedHelper.s_Messages[messageTypeIndex];
|
||||
|
||||
if (messages.Count > 0)
|
||||
{
|
||||
if (needsSpaceAbove)
|
||||
{
|
||||
// Double space looks good at top.
|
||||
EditorGUILayout.Space();
|
||||
// EditorGUILayout.Space();
|
||||
needsSpaceAbove = false;
|
||||
}
|
||||
|
||||
needsSpaceBelow = true;
|
||||
|
||||
// Map Validated.MessageType to HelpBox.MessageType.
|
||||
var messageType = (MessageType)ValidatedHelper.s_Messages.Length - messageTypeIndex;
|
||||
|
||||
if (s_GroupMessages)
|
||||
{
|
||||
// We join the messages together to reduce vertical space since HelpBox has padding, borders etc.
|
||||
var joinedMessage = messages[0]._Message;
|
||||
// Format as list if we have more than one message.
|
||||
if (messages.Count > 1) joinedMessage = $"- {joinedMessage}";
|
||||
|
||||
for (var messageIndex = 1; messageIndex < messages.Count; messageIndex++)
|
||||
{
|
||||
joinedMessage += $"\n- {messages[messageIndex]}";
|
||||
}
|
||||
|
||||
EditorGUILayout.HelpBox(joinedMessage, messageType);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var message in messages)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
var fixDescription = message._FixDescription;
|
||||
var originalGUIEnabled = GUI.enabled;
|
||||
|
||||
if (message._Action != null)
|
||||
{
|
||||
fixDescription += " Click the fix/repair button on the right to fix.";
|
||||
|
||||
if ((message._Action == ValidatedHelper.FixAddMissingMathPackage || message._Action == ValidatedHelper.FixAddMissingBurstPackage) && PackageManagerHelpers.IsBusy)
|
||||
{
|
||||
GUI.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.HelpBox($"{message._Message} {fixDescription}", messageType);
|
||||
|
||||
// Jump to object button.
|
||||
if (message._Object != null)
|
||||
{
|
||||
// Selection.activeObject can be message._object.gameObject instead of the component
|
||||
// itself. We soft cast to MonoBehaviour to get the gameObject for comparison.
|
||||
// Alternatively, we could always pass gameObject instead of "this".
|
||||
var casted = message._Object as MonoBehaviour;
|
||||
|
||||
if (Selection.activeObject != message._Object && (casted == null || casted.gameObject != Selection.activeObject))
|
||||
{
|
||||
s_JumpButtonContent ??= new(EditorGUIUtility.FindTexture("scenepicking_pickable_hover@2x"), "Jump to object to resolve issue");
|
||||
|
||||
if (GUILayout.Button(s_JumpButtonContent, GUILayout.ExpandWidth(false), GUILayout.ExpandHeight(true)))
|
||||
{
|
||||
Selection.activeObject = message._Object;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fix the issue button.
|
||||
if (message._Action != null)
|
||||
{
|
||||
s_FixButtonContent ??= new(EditorGUIUtility.FindTexture("SceneViewTools"));
|
||||
|
||||
if (message._FixDescription != null)
|
||||
{
|
||||
var sanitisedFixDescr = Regex.Replace(message._FixDescription, @"<[^<>]*>", "'");
|
||||
s_FixButtonContent.tooltip = $"Apply fix: {sanitisedFixDescr}";
|
||||
}
|
||||
else
|
||||
{
|
||||
s_FixButtonContent.tooltip = "Fix issue";
|
||||
}
|
||||
|
||||
if (GUILayout.Button(s_FixButtonContent, GUILayout.ExpandWidth(false), GUILayout.ExpandHeight(true)))
|
||||
{
|
||||
// Run fix function
|
||||
var serializedObject = new SerializedObject(message._Object);
|
||||
// Property is optional.
|
||||
var property = message._PropertyPath != null ? serializedObject?.FindProperty(message._PropertyPath) : null;
|
||||
var oldValue = property?.boxedValue;
|
||||
message._Action.Invoke(serializedObject, property);
|
||||
if (serializedObject.ApplyModifiedProperties())
|
||||
{
|
||||
// SerializedObject does this for us, but gives the history item a nicer label.
|
||||
Undo.RecordObject(message._Object, s_FixButtonContent.tooltip);
|
||||
DecoratedDrawer.OnChange(property, oldValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GUI.enabled = originalGUIEnabled;
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (needsSpaceBelow)
|
||||
{
|
||||
// EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
// Revert skin since it persists.
|
||||
GUI.skin.GetStyle("HelpBox").richText = styleRichText;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a2125841df4154bd89cf5af6847a0d40
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,218 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
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<Material, MaterialEditor> _MaterialEditors = new();
|
||||
|
||||
public override bool RequiresConstantRepaint() => TexturePreview.AnyActivePreviews;
|
||||
|
||||
// Set this from a decorator to enable the material editor.
|
||||
internal List<Material> _Materials = new();
|
||||
|
||||
readonly Utility.SortedList<int, SerializedProperty> _Properties = new(Helpers.DuplicateComparison);
|
||||
|
||||
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 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 material in _Materials)
|
||||
{
|
||||
if (material == null) continue;
|
||||
DrawMaterialEditor(material);
|
||||
}
|
||||
|
||||
_Materials.Clear();
|
||||
}
|
||||
|
||||
// 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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: db8aed30f95c447c3b9491865a600372
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,54 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a deprecated message to shaders.
|
||||
///
|
||||
/// USAGE
|
||||
/// Add to bottom of Shader block:
|
||||
/// CustomEditor "Crest.ObsoleteShaderGUI"
|
||||
/// Optionally add to Properties block:
|
||||
/// [HideInInspector] _ObsoleteMessage("The additional message.", Float) = 0
|
||||
/// </summary>
|
||||
sealed class ObsoleteShaderGUI : ShaderGUI
|
||||
{
|
||||
public override void OnGUI(MaterialEditor editor, MaterialProperty[] properties)
|
||||
{
|
||||
// Enable rich text in help boxes. Store original so we can revert since this might be a "hack".
|
||||
var styleRichText = GUI.skin.GetStyle("HelpBox").richText;
|
||||
GUI.skin.GetStyle("HelpBox").richText = true;
|
||||
|
||||
var message = "";
|
||||
|
||||
{
|
||||
var property = FindProperty("_Message", properties, propertyIsMandatory: false);
|
||||
if (property != null)
|
||||
{
|
||||
message += property.displayName;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var property = FindProperty("_ObsoleteMessage", properties, propertyIsMandatory: false);
|
||||
if (property != null)
|
||||
{
|
||||
message += "This shader is deprecated and will be removed in a future version. " + property.displayName;
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.HelpBox(message, MessageType.Warning);
|
||||
EditorGUILayout.Space(3f);
|
||||
|
||||
// Revert skin since it persists.
|
||||
GUI.skin.GetStyle("HelpBox").richText = styleRichText;
|
||||
|
||||
// Render the default GUI.
|
||||
base.OnGUI(editor, properties);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3a7b7bf49879b47f5b5e4fe887760848
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,41 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
// Helpers for using the Unity Package Manager.
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEditor.PackageManager;
|
||||
using UnityEditor.PackageManager.Requests;
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
static class PackageManagerHelpers
|
||||
{
|
||||
static AddRequest s_Request;
|
||||
public static bool IsBusy => s_Request?.IsCompleted == false;
|
||||
|
||||
public static void AddMissingPackage(string packageName)
|
||||
{
|
||||
s_Request = Client.Add(packageName);
|
||||
EditorApplication.update += AddMissingPackageProgress;
|
||||
}
|
||||
|
||||
static void AddMissingPackageProgress()
|
||||
{
|
||||
if (s_Request.IsCompleted)
|
||||
{
|
||||
if (s_Request.Status == StatusCode.Success)
|
||||
{
|
||||
Debug.Log("Installed: " + s_Request.Result.packageId);
|
||||
}
|
||||
else if (s_Request.Status >= StatusCode.Failure)
|
||||
{
|
||||
Debug.Log(s_Request.Error.message);
|
||||
}
|
||||
|
||||
EditorApplication.update -= AddMissingPackageProgress;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f5138ba66b6604da09f58ac99ee035f8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,79 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
// Exposes a customized version of Unity's shader generator task to only generate
|
||||
// our source files.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Rendering;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
static class ShaderGeneratorUtility
|
||||
{
|
||||
static MethodInfo s_GenerateAsync;
|
||||
static MethodInfo GenerateAsync => s_GenerateAsync ??= typeof(CSharpToHLSL)
|
||||
.GetMethod("GenerateAsync", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
|
||||
static async Task InvokeAsync(this MethodInfo methodInfo, object obj, params object[] parameters)
|
||||
{
|
||||
dynamic awaitable = methodInfo.Invoke(obj, parameters);
|
||||
await awaitable;
|
||||
}
|
||||
|
||||
// Adapted from:
|
||||
// https://github.com/Unity-Technologies/Graphics/blob/96ba978a240e96adcb2abceb21e90b24caa484a3/Packages/com.unity.render-pipelines.core/Editor/ShaderGenerator/CSharpToHLSL.cs#L18L53
|
||||
internal static async Task GenerateAll()
|
||||
{
|
||||
Dictionary<string, List<ShaderTypeGenerator>> sourceGenerators = null;
|
||||
try
|
||||
{
|
||||
// Store per source file path the generator definitions
|
||||
sourceGenerators = DictionaryPool<string, List<ShaderTypeGenerator>>.Get();
|
||||
|
||||
// Extract all types with the GenerateHLSL tag
|
||||
foreach (var type in TypeCache.GetTypesWithAttribute<GenerateHLSL>())
|
||||
{
|
||||
// Only generate our sources as Unity's will trigger a package refresh.
|
||||
if (!type.FullName.StartsWith("WaveHarmonic.Crest")) continue;
|
||||
|
||||
var attr = type.GetCustomAttributes(typeof(GenerateHLSL), false).First() as GenerateHLSL;
|
||||
if (!sourceGenerators.TryGetValue(attr.sourcePath, out var generators))
|
||||
{
|
||||
generators = ListPool<ShaderTypeGenerator>.Get();
|
||||
sourceGenerators.Add(attr.sourcePath, generators);
|
||||
}
|
||||
|
||||
generators.Add(new(type, attr));
|
||||
}
|
||||
|
||||
// We need to force the culture to invariant, otherwise generated code can replace characters.
|
||||
// For example, Turkish will replace "I" with "İ". This is a bug with GenerateAsync.
|
||||
var culture = System.Globalization.CultureInfo.CurrentCulture;
|
||||
System.Globalization.CultureInfo.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture;
|
||||
|
||||
// Generate all files
|
||||
await Task.WhenAll(sourceGenerators.Select(async it => await GenerateAsync
|
||||
.InvokeAsync(null, new object[] { $"{it.Key}.hlsl", $"{Path.ChangeExtension(it.Key, "custom")}.hlsl", it.Value })));
|
||||
|
||||
System.Globalization.CultureInfo.CurrentCulture = culture;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Make sure we always release pooled resources
|
||||
if (sourceGenerators != null)
|
||||
{
|
||||
foreach (var pair in sourceGenerators)
|
||||
ListPool<ShaderTypeGenerator>.Release(pair.Value);
|
||||
DictionaryPool<string, List<ShaderTypeGenerator>>.Release(sourceGenerators);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2bf974bd66ba04a1aa65908c4308b1b3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,199 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
#pragma warning disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEditor;
|
||||
using UnityEditor.ShaderGraph;
|
||||
using UnityEditor.ShaderGraph.Internal;
|
||||
using UnityEngine;
|
||||
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Editor")]
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
internal static class ShaderGraphPropertyDrawers
|
||||
{
|
||||
static Dictionary<GraphInputData, bool> s_CompoundPropertyFoldoutStates = new();
|
||||
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)
|
||||
{
|
||||
s_Tooltips = tooltips;
|
||||
Material m = materialEditor.target as Material;
|
||||
Shader s = m.shader;
|
||||
string path = AssetDatabase.GetAssetPath(s);
|
||||
ShaderGraphMetadata metadata = null;
|
||||
foreach (var obj in AssetDatabase.LoadAllAssetsAtPath(path))
|
||||
{
|
||||
if (obj is ShaderGraphMetadata meta)
|
||||
{
|
||||
metadata = meta;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (metadata != null)
|
||||
DrawShaderGraphGUI(materialEditor, properties, metadata.categoryDatas);
|
||||
else
|
||||
PropertiesDefaultGUI(materialEditor, properties);
|
||||
}
|
||||
|
||||
static void PropertiesDefaultGUI(MaterialEditor materialEditor, IEnumerable<MaterialProperty> properties)
|
||||
{
|
||||
foreach (var property in properties)
|
||||
{
|
||||
if ((property.flags & (MaterialProperty.PropFlags.HideInInspector | MaterialProperty.PropFlags.PerRendererData)) != 0)
|
||||
continue;
|
||||
|
||||
float h = materialEditor.GetPropertyHeight(property, property.displayName);
|
||||
Rect r = EditorGUILayout.GetControlRect(true, h, EditorStyles.layerMaskField);
|
||||
|
||||
s_Label.text = property.displayName;
|
||||
s_Label.tooltip = s_Tooltips.GetValueOrDefault(property.name, null);
|
||||
materialEditor.ShaderProperty(r, property, s_Label);
|
||||
}
|
||||
}
|
||||
|
||||
static Rect GetRect(MaterialProperty prop)
|
||||
{
|
||||
return EditorGUILayout.GetControlRect(true, MaterialEditor.GetDefaultPropertyHeight(prop));
|
||||
}
|
||||
|
||||
static MaterialProperty FindProperty(string propertyName, IEnumerable<MaterialProperty> properties)
|
||||
{
|
||||
foreach (var prop in properties)
|
||||
{
|
||||
if (prop.name == propertyName)
|
||||
{
|
||||
return prop;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void DrawShaderGraphGUI(MaterialEditor materialEditor, IEnumerable<MaterialProperty> properties, IEnumerable<MinimalCategoryData> categoryDatas)
|
||||
{
|
||||
foreach (MinimalCategoryData mcd in categoryDatas)
|
||||
{
|
||||
DrawCategory(materialEditor, properties, mcd);
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawCategory(MaterialEditor materialEditor, IEnumerable<MaterialProperty> properties, MinimalCategoryData minimalCategoryData)
|
||||
{
|
||||
if (minimalCategoryData.categoryName.Length > 0)
|
||||
{
|
||||
minimalCategoryData.expanded = EditorGUILayout.BeginFoldoutHeaderGroup(minimalCategoryData.expanded, minimalCategoryData.categoryName);
|
||||
}
|
||||
else
|
||||
{
|
||||
// force draw if no category name to do foldout on
|
||||
minimalCategoryData.expanded = true;
|
||||
}
|
||||
|
||||
if (minimalCategoryData.expanded)
|
||||
{
|
||||
foreach (var propData in minimalCategoryData.propertyDatas)
|
||||
{
|
||||
if (propData.isCompoundProperty == false)
|
||||
{
|
||||
MaterialProperty prop = FindProperty(propData.referenceName, properties);
|
||||
if (prop == null) continue;
|
||||
DrawMaterialProperty(materialEditor, prop, propData.propertyType, propData.isKeyword, propData.keywordType);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawCompoundProperty(materialEditor, properties, propData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.EndFoldoutHeaderGroup();
|
||||
}
|
||||
|
||||
static void DrawCompoundProperty(MaterialEditor materialEditor, IEnumerable<MaterialProperty> properties, GraphInputData compoundPropertyData)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
bool foldoutState = true;
|
||||
var exists = s_CompoundPropertyFoldoutStates.ContainsKey(compoundPropertyData);
|
||||
if (!exists)
|
||||
s_CompoundPropertyFoldoutStates.Add(compoundPropertyData, true);
|
||||
else
|
||||
foldoutState = s_CompoundPropertyFoldoutStates[compoundPropertyData];
|
||||
|
||||
foldoutState = EditorGUILayout.Foldout(foldoutState, compoundPropertyData.referenceName);
|
||||
if (foldoutState)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
foreach (var subProperty in compoundPropertyData.subProperties)
|
||||
{
|
||||
var property = FindProperty(subProperty.referenceName, properties);
|
||||
if (property == null) continue;
|
||||
DrawMaterialProperty(materialEditor, property, subProperty.propertyType);
|
||||
}
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
if (exists)
|
||||
s_CompoundPropertyFoldoutStates[compoundPropertyData] = foldoutState;
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
static void DrawMaterialProperty(MaterialEditor materialEditor, MaterialProperty property, PropertyType propertyType, bool isKeyword = false, KeywordType keywordType = KeywordType.Boolean)
|
||||
{
|
||||
if (!isKeyword)
|
||||
{
|
||||
switch (propertyType)
|
||||
{
|
||||
case PropertyType.SamplerState:
|
||||
case PropertyType.Matrix4:
|
||||
case PropertyType.Matrix3:
|
||||
case PropertyType.Matrix2:
|
||||
case PropertyType.VirtualTexture:
|
||||
case PropertyType.Gradient:
|
||||
return;
|
||||
case PropertyType.Vector3:
|
||||
DrawVector3Property(materialEditor, property);
|
||||
return;
|
||||
case PropertyType.Vector2:
|
||||
DrawVector2Property(materialEditor, property);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
s_Label.text = property.displayName;
|
||||
s_Label.tooltip = s_Tooltips.GetValueOrDefault(property.name, null);
|
||||
materialEditor.ShaderProperty(property, s_Label);
|
||||
}
|
||||
|
||||
static void DrawVector2Property(MaterialEditor materialEditor, MaterialProperty property)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUI.showMixedValue = property.hasMixedValue;
|
||||
Vector2 newValue = EditorGUI.Vector2Field(GetRect(property), property.displayName, new Vector2(property.vectorValue.x, property.vectorValue.y));
|
||||
EditorGUI.showMixedValue = false;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
property.vectorValue = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawVector3Property(MaterialEditor materialEditor, MaterialProperty property)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUI.showMixedValue = property.hasMixedValue;
|
||||
Vector3 newValue = EditorGUI.Vector3Field(GetRect(property), property.displayName, new Vector3(property.vectorValue.x, property.vectorValue.y, property.vectorValue.z));
|
||||
EditorGUI.showMixedValue = false;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
property.vectorValue = newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ee5c95ce93114c1c909f5b1346d8bb0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,131 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.Rendering;
|
||||
using WaveHarmonic.Crest.Internal;
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
abstract class TexturePreview : ObjectPreview
|
||||
{
|
||||
public static bool AnyActivePreviews { get; private set; }
|
||||
|
||||
UnityEditor.Editor _Editor;
|
||||
RenderTexture _RenderTexture;
|
||||
RenderTextureDescriptor _OriginalDescriptor = new();
|
||||
Texture _Current;
|
||||
protected int _Slice;
|
||||
bool _First = true;
|
||||
|
||||
protected abstract Texture OriginalTexture { get; }
|
||||
protected virtual Texture ModifiedTexture { get; }
|
||||
|
||||
Texture Texture => ModifiedTexture != null ? ModifiedTexture : OriginalTexture;
|
||||
|
||||
protected virtual bool Flipped => false;
|
||||
|
||||
// Preview complains if not a certain set of formats.
|
||||
bool Incompatible => !(GraphicsFormatUtility.IsIEEE754Format(Texture.graphicsFormat)
|
||||
|| GraphicsFormatUtility.IsNormFormat(Texture.graphicsFormat));
|
||||
|
||||
public TexturePreview() { }
|
||||
|
||||
public override bool HasPreviewGUI()
|
||||
{
|
||||
AnyActivePreviews = false;
|
||||
return OriginalTexture;
|
||||
}
|
||||
|
||||
public override void Cleanup()
|
||||
{
|
||||
base.Cleanup();
|
||||
Object.DestroyImmediate(_Editor);
|
||||
if (_RenderTexture != null) _RenderTexture.Release();
|
||||
Object.DestroyImmediate(_RenderTexture);
|
||||
}
|
||||
|
||||
public override void OnPreviewSettings()
|
||||
{
|
||||
if (_First && Event.current.type == EventType.Repaint && !Application.isPlaying)
|
||||
{
|
||||
// Solves on enter edit mode:
|
||||
// ArgumentException: Getting control 2's position in a group with only 2 controls when doing repaint
|
||||
_First = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_Editor != null) _Editor.OnPreviewSettings();
|
||||
}
|
||||
|
||||
public override void OnPreviewGUI(Rect rect, GUIStyle background)
|
||||
{
|
||||
AnyActivePreviews = true;
|
||||
|
||||
// This check is in original.
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
{
|
||||
background.Draw(rect, false, false, false, false);
|
||||
}
|
||||
|
||||
var descriptor = Texture.GetDescriptor();
|
||||
|
||||
if (Helpers.RenderTextureNeedsUpdating(descriptor, _OriginalDescriptor))
|
||||
{
|
||||
_OriginalDescriptor = descriptor;
|
||||
|
||||
if (Incompatible)
|
||||
{
|
||||
descriptor.graphicsFormat = GraphicsFormat.R32G32B32A32_SFloat;
|
||||
}
|
||||
|
||||
Helpers.SafeCreateRenderTexture(ref _RenderTexture, descriptor);
|
||||
_RenderTexture.Create();
|
||||
Object.DestroyImmediate(_Editor);
|
||||
_Editor = UnityEditor.Editor.CreateEditor(_RenderTexture);
|
||||
// Reset for incompatible copy.
|
||||
descriptor = _OriginalDescriptor;
|
||||
}
|
||||
|
||||
// Name may change without texture changing (see SWS).
|
||||
_RenderTexture.name = Texture.name + " (Preview)";
|
||||
|
||||
if (Incompatible)
|
||||
{
|
||||
var temporary = RenderTexture.GetTemporary(descriptor);
|
||||
Graphics.CopyTexture(Texture, temporary);
|
||||
Helpers.Blit(temporary, _RenderTexture);
|
||||
RenderTexture.ReleaseTemporary(temporary);
|
||||
}
|
||||
else
|
||||
{
|
||||
Graphics.CopyTexture(Texture, _RenderTexture);
|
||||
}
|
||||
|
||||
_Editor.DrawPreview(rect);
|
||||
}
|
||||
|
||||
#if CREST_DEBUG
|
||||
public override void OnInteractivePreviewGUI(Rect rect, GUIStyle background)
|
||||
{
|
||||
OnPreviewGUI(rect, background);
|
||||
|
||||
// Show pixel value in preview.
|
||||
_Slice = Development.Utility.GetPreviewSlice(_Editor, Texture);
|
||||
var color = Development.Utility.InspectPixel(rect, OriginalTexture, Flipped, _Slice);
|
||||
var text = Development.Utility.GetInspectPixelString(color, OriginalTexture);
|
||||
EditorGUI.DropShadowLabel(new Rect(rect.x, rect.y, rect.width, 40), text);
|
||||
}
|
||||
#endif
|
||||
|
||||
void Allocate(Texture texture)
|
||||
{
|
||||
// LOD with buffered data like foam will recreate every frame freezing controls.
|
||||
if (_Editor != null && _Current == Texture) return;
|
||||
_Current = texture;
|
||||
Object.DestroyImmediate(_Editor);
|
||||
_Editor = UnityEditor.Editor.CreateEditor(texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1c817389e2ce240f69487a0e78fc7091
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,302 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
// How to use:
|
||||
// Use or inherit from Crest.Editor.Inspector to support validation messages.
|
||||
// Then create a static method with Validator attribute.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using WaveHarmonic.Crest.Internal;
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
using FixValidation = System.Action<SerializedObject, SerializedProperty>;
|
||||
|
||||
[System.AttributeUsage(System.AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
|
||||
sealed class Validator : System.Attribute
|
||||
{
|
||||
public readonly System.Type _Type;
|
||||
|
||||
public Validator(System.Type type)
|
||||
{
|
||||
_Type = type;
|
||||
}
|
||||
}
|
||||
|
||||
// Holds the shared list for messages
|
||||
static class ValidatedHelper
|
||||
{
|
||||
public enum MessageType
|
||||
{
|
||||
Error,
|
||||
Warning,
|
||||
Info,
|
||||
}
|
||||
|
||||
public struct HelpBoxMessage
|
||||
{
|
||||
public string _Message;
|
||||
public string _FixDescription;
|
||||
public Object _Object;
|
||||
public FixValidation _Action;
|
||||
public string _PropertyPath;
|
||||
}
|
||||
|
||||
// This is a shared resource. It will be cleared before use. It is only used by the HelpBox delegate since we
|
||||
// want to group them by severity (MessageType). Make sure length matches MessageType length.
|
||||
public static readonly List<HelpBoxMessage>[] s_Messages = new List<HelpBoxMessage>[]
|
||||
{
|
||||
new(),
|
||||
new(),
|
||||
new(),
|
||||
};
|
||||
|
||||
public delegate void ShowMessage(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)
|
||||
{
|
||||
// Never log info validation to console.
|
||||
if (type == MessageType.Info)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
message = $"Crest Validation: {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;
|
||||
}
|
||||
}
|
||||
|
||||
public static void HelpBox(string message, string fixDescription, MessageType type, Object @object = null, FixValidation action = null, string property = null)
|
||||
{
|
||||
s_Messages[(int)type].Add(new() { _Message = message, _FixDescription = fixDescription, _Object = @object, _Action = action, _PropertyPath = property });
|
||||
}
|
||||
|
||||
public static void Suppressed(string message, string fixDescription, MessageType type, Object @object = null, FixValidation action = null, string property = null)
|
||||
{
|
||||
}
|
||||
|
||||
public static T FixAttachComponent<T>(SerializedObject componentOrGameObject)
|
||||
where T : Component
|
||||
{
|
||||
return Undo.AddComponent<T>(EditorHelpers.GetGameObject(componentOrGameObject));
|
||||
}
|
||||
|
||||
internal static void FixSetMaterialOptionEnabled(SerializedObject material, string keyword, string floatParam, bool enabled)
|
||||
{
|
||||
var mat = material.targetObject as Material;
|
||||
Undo.RecordObject(mat, $"Enable keyword {keyword}");
|
||||
mat.SetBoolean(Shader.PropertyToID(floatParam), enabled);
|
||||
if (ArrayUtility.Contains(mat.shader.keywordSpace.keywordNames, keyword))
|
||||
{
|
||||
mat.SetKeyword(keyword, enabled);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void FixSetMaterialIntProperty(SerializedObject material, string label, string intParam, int value)
|
||||
{
|
||||
var mat = material.targetObject as Material;
|
||||
Undo.RecordObject(mat, $"change {label}");
|
||||
mat.SetInteger(intParam, value);
|
||||
}
|
||||
|
||||
public static void FixAddMissingMathPackage(SerializedObject _0, SerializedProperty _1)
|
||||
{
|
||||
PackageManagerHelpers.AddMissingPackage("com.unity.mathematics");
|
||||
}
|
||||
|
||||
public static void FixAddMissingBurstPackage(SerializedObject _0, SerializedProperty _1)
|
||||
{
|
||||
PackageManagerHelpers.AddMissingPackage("com.unity.burst");
|
||||
}
|
||||
|
||||
public static bool ValidateNoScale(Object @object, Transform transform, ShowMessage showMessage)
|
||||
{
|
||||
if (transform.lossyScale != Vector3.one)
|
||||
{
|
||||
showMessage
|
||||
(
|
||||
$"There must be no scale on the <i>{@object.GetType().Name}</i> Transform or any of its parents." +
|
||||
$"The current scale is <i>{transform.lossyScale}</i>.",
|
||||
"Reset the scale on this Transform and all parents to one.",
|
||||
MessageType.Error, @object
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValidateNoRotation(Object @object, Transform transform, ShowMessage showMessage)
|
||||
{
|
||||
if (transform.eulerAngles.magnitude > 0.0001f)
|
||||
{
|
||||
showMessage
|
||||
(
|
||||
$"There must be no rotation on the <i>{@object.GetType().Name}</i> Transform or any of its parents." +
|
||||
$"The current rotation is <i>{transform.eulerAngles}.</i>",
|
||||
"Reset the rotation on this Transform and all parents to zero.",
|
||||
MessageType.Error, @object
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static bool ValidateRenderer<T>
|
||||
(
|
||||
Component component,
|
||||
Renderer renderer,
|
||||
ShowMessage showMessage,
|
||||
bool checkShaderPasses,
|
||||
string shaderPrefix = null
|
||||
)
|
||||
where T : Renderer
|
||||
{
|
||||
if (renderer == null)
|
||||
{
|
||||
var type = typeof(T);
|
||||
var name = type.Name;
|
||||
|
||||
// Give users a hint as to what "Renderer" really means.
|
||||
if (type == typeof(Renderer))
|
||||
{
|
||||
name += " (Mesh, Trail etc)";
|
||||
}
|
||||
|
||||
showMessage
|
||||
(
|
||||
$"A <i>{name}</i> component is required but none is assigned.",
|
||||
"Provide a renderer.",
|
||||
MessageType.Error, component
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var materials = renderer.sharedMaterials;
|
||||
for (var i = 0; i < materials.Length; i++)
|
||||
{
|
||||
// Empty material slots is a user error. Unity complains about it so we should too.
|
||||
if (materials[i] == null)
|
||||
{
|
||||
showMessage
|
||||
(
|
||||
$"<i>{renderer.GetType().Name}</i> used by this input (<i>{component.GetType().Name}</i>) has empty material slots.",
|
||||
"Remove these slots or fill them with a material.",
|
||||
MessageType.Error, renderer
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (renderer is MeshRenderer)
|
||||
{
|
||||
renderer.gameObject.TryGetComponent<MeshFilter>(out var mf);
|
||||
if (mf == null)
|
||||
{
|
||||
showMessage
|
||||
(
|
||||
$"A <i>MeshRenderer</i> component is being used by this input but no <i>MeshFilter</i> component was found so there may not be any valid geometry to render.",
|
||||
"Attach a <i>MeshFilter</i> component.",
|
||||
MessageType.Error, renderer.gameObject,
|
||||
(_, _) => Undo.AddComponent<MeshFilter>(renderer.gameObject)
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
else if (mf.sharedMesh == null)
|
||||
{
|
||||
showMessage
|
||||
(
|
||||
$"A <i>MeshRenderer</i> component is being used by this input but no mesh is assigned to the <i>MeshFilter</i> component.",
|
||||
"Assign the geometry to be rendered to the <i>MeshFilter</i> component.",
|
||||
MessageType.Error, renderer.gameObject
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ValidateMaterial(renderer.gameObject, showMessage, renderer.sharedMaterial, shaderPrefix, checkShaderPasses))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValidateMaterial(GameObject gameObject, ShowMessage showMessage, Material material, string shaderPrefix, bool checkShaderPasses)
|
||||
{
|
||||
if (shaderPrefix == null && material == null)
|
||||
{
|
||||
showMessage
|
||||
(
|
||||
$"<i>Mesh Renderer</i> requires a material.",
|
||||
"Assign a material.",
|
||||
MessageType.Error, gameObject
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!material || material.shader && (!material.shader.name.StartsWithNoAlloc(shaderPrefix) && !material.shader.name.StartsWithNoAlloc($"Hidden/{shaderPrefix}") && !material.shader.name.Contains("/All/")))
|
||||
{
|
||||
showMessage
|
||||
(
|
||||
$"Shader assigned to water input expected to be of type <i>{shaderPrefix}</i>.",
|
||||
"Assign a material that uses a shader of this type.",
|
||||
MessageType.Error, gameObject
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (checkShaderPasses && material.passCount > 1)
|
||||
{
|
||||
showMessage
|
||||
(
|
||||
$"The shader <i>{material.shader.name}</i> for material <i>{material.name}</i> has multiple passes which might not work as expected as only the first pass is executed. " +
|
||||
"This can be ignored in most cases, like Shader Graph, as only one pass is often required.",
|
||||
"To have all passes execute then set <i>Shader Pass Index</i> to <i>-1</i>.",
|
||||
MessageType.Info, gameObject
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ExecuteValidators(object target, ShowMessage messenger)
|
||||
{
|
||||
var isValid = true;
|
||||
var type = target.GetType();
|
||||
var validators = TypeCache.GetMethodsWithAttribute<Validator>();
|
||||
foreach (var validator in validators)
|
||||
{
|
||||
var attribute = validator.GetCustomAttribute<Validator>();
|
||||
if (attribute._Type.IsAssignableFrom(type))
|
||||
{
|
||||
isValid = (bool)validator.Invoke(null, new object[] { target, messenger }) && isValid;
|
||||
}
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
public static bool ExecuteValidators(Object target)
|
||||
{
|
||||
return ExecuteValidators(target, DebugLog);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8e9a7f4ffb1bb41999902cd4310bf2d7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "WaveHarmonic.Crest.Shared.Editor",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:056ff2a5b2f124d468c6655552acdca5",
|
||||
"GUID:3eae0364be2026648bf74846acb8a731",
|
||||
"GUID:df380645f10b7bc4b97d4f5eb6303d95",
|
||||
"GUID:be0903cd8e1546f498710afdc59db5eb"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": false,
|
||||
"defineConstraints": [
|
||||
"UNITY_2022_3_OR_NEWER"
|
||||
],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1ab2a6c2a51cd4b43867788dbaee1a55
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user