using Facepunch.ActionGraphs; using Sandbox.Internal; using static Sandbox.SerializedObject; namespace Sandbox; public abstract class SerializedProperty : IValid { public virtual SerializedObject Parent { get; } public virtual bool IsProperty => false; public virtual bool IsField => false; public virtual bool IsMethod => false; public virtual string Name { get; } public virtual string DisplayName { get; } public virtual string Description { get; } public virtual string GroupName { get; } public virtual int Order { get; } public virtual bool IsEditable { get; } = true; public virtual bool IsPublic { get; } = true; public virtual Type PropertyType { get; } /// public virtual bool IsValid => true; /// /// The source filename, if available /// public virtual string SourceFile { get; } /// /// The line in the source file, if available /// public virtual int SourceLine { get; } /// /// Returns true if the current set value differs from the actual value /// public virtual bool HasChanges { get; } /// /// Called when the property value is about to change. /// public PropertyPreChangeDelegate OnPreChange { get; set; } /// /// Called when the property value has changed. /// public PropertyChangedDelegate OnChanged { get; set; } /// /// Called when the property is about to be edited (eg. in a ControlWidget). /// public PropertyStartEditDelegate OnStartEdit { get; set; } /// /// Called when the property has finished being edited (eg. in a ControlWidget). /// public PropertyFinishEditDelegate OnFinishEdit { get; set; } public SerializedProperty() { _as.Property = this; } // // Accessors // public abstract void SetValue( T value ); public virtual void SetValue( T value, SerializedProperty source ) => SetValue( value ); public abstract T GetValue( T defaultValue = default ); /// /// Get the default value of a specific property type. /// /// public object GetDefault() { // DefaultValue codegen if ( TryGetAttribute( out var defaultValue ) ) { return defaultValue.Value; } var type = PropertyType; if ( IsNullable ) { type = NullableType; } if ( type == typeof( Color ) ) return Color.White; if ( type == typeof( Color32 ) ) return Color32.White; if ( type == typeof( ColorHsv ) ) return new ColorHsv( 0, 0, 1 ); if ( type.IsValueType ) { return Activator.CreateInstance( type ); } return null; } // // Property Access // /// /// Return true if the property has this attribute /// public bool HasAttribute() where T : Attribute { return GetAttributes().Any(); } /// /// Return true if the property has this attribute /// public bool HasAttribute( Type t ) { return GetAttributes( t ).Any(); } /// /// Try to get this attribute from the property. Return false on fail. /// public bool TryGetAttribute( out T attribute ) where T : Attribute { attribute = GetAttributes().FirstOrDefault(); return attribute != null; } /// /// Get all of these attributes from the property. /// public IEnumerable GetAttributes() where T : Attribute { return GetAttributes().OfType(); } /// /// Get all of these attributes from the property. /// public IEnumerable GetAttributes( Type t ) { return GetAttributes().Where( t.IsInstanceOfType ); } /// /// Get all attributes from the property. /// public virtual IEnumerable GetAttributes() { return Enumerable.Empty(); } /// /// Try to convert this property into a serialized object for further editing and exploration /// /// /// public virtual bool TryGetAsObject( out SerializedObject obj ) { obj = default; return false; } AsAccessor _as; public virtual ref AsAccessor As => ref _as; public struct AsAccessor { internal SerializedProperty Property; public string String { get => Property.GetValue(); set => Property.SetValue( value ); } public Vector2 Vector2 { get => Property.GetValue(); set => Property.SetValue( value ); } public Vector3 Vector3 { get => Property.GetValue(); set => Property.SetValue( value ); } public Rotation Rotation { get => Property.GetValue(); set => Property.SetValue( value ); } public Angles Angles { get => Property.GetValue(); set => Property.SetValue( value ); } public float Float { get => Property.GetValue(); set => Property.SetValue( value ); } public double Double { get => Property.GetValue(); set => Property.SetValue( value ); } public int Int { get => Property.GetValue(); set => Property.SetValue( value ); } public long Long { get => Property.GetValue(); set => Property.SetValue( value ); } public bool Bool { get => Property.GetValue(); set => Property.SetValue( value ); } } /// /// True if this holds multiple values. That might all be the same. /// public virtual bool IsMultipleValues => false; /// /// True if this holds multiple values, and they're all different. /// public virtual bool IsMultipleDifferentValues => false; /// /// Get all properties if this holds multiple values /// public virtual IEnumerable MultipleProperties { get { yield return this; } } /// /// Our value has changed, maybe our parent would like to know /// protected virtual void NoteChanged() { if ( OnChanged is not null ) { OnChanged( this ); } if ( Parent is not null ) { Parent.NoteChanged( this ); } } internal virtual void NoteChanged( SerializedProperty childProperty ) { if ( OnChanged is not null ) { OnChanged( childProperty ); } if ( Parent is not null ) { Parent.NoteChanged( childProperty ); } } protected virtual void NotePreChange() { if ( OnPreChange is not null ) { OnPreChange( this ); } if ( Parent is not null ) { Parent.NotePreChange( this ); } } internal virtual void NotePreChange( SerializedProperty childProperty ) { if ( OnPreChange is not null ) { OnPreChange( childProperty ); } if ( Parent is not null ) { Parent.NotePreChange( childProperty ); } } protected virtual void NoteStartEdit() { if ( OnStartEdit is not null ) { OnStartEdit( this ); } if ( Parent is not null ) { Parent.NoteStartEdit( this ); } } internal virtual void NoteStartEdit( SerializedProperty childProperty ) { if ( OnStartEdit is not null ) { OnStartEdit( childProperty ); } if ( Parent is not null ) { Parent.NoteStartEdit( childProperty ); } } protected virtual void NoteFinishEdit() { if ( OnFinishEdit is not null ) { OnFinishEdit( this ); } if ( Parent is not null ) { Parent.NoteFinishEdit( this ); } } internal virtual void NoteFinishEdit( SerializedProperty childProperty ) { if ( OnFinishEdit is not null ) { OnFinishEdit( childProperty ); } if ( Parent is not null ) { Parent.NoteFinishEdit( childProperty ); } } /// /// Convert an object value to a T type /// protected T ValueToType( object value, T defaultValue = default ) { try { if ( value is null ) return defaultValue; if ( value.GetType().IsAssignableTo( typeof( T ) ) ) return (T)value; if ( typeof( T ) == typeof( string ) ) return (T)(object)$"{value}"; if ( value.GetType() == typeof( string ) ) { return JsonSerializer.Deserialize( (string)value ); } // Convert.ChangeType doesn't support long to enum if ( typeof( T ).IsEnum && value is IConvertible ) { try { return (T)Enum.ToObject( typeof( T ), Convert.ToInt64( value ) ); } catch { return defaultValue; } } var converted = Convert.ChangeType( value, typeof( T ) ); if ( converted is not null ) return (T)converted; var jsonElement = JsonSerializer.SerializeToElement( value ); return jsonElement.Deserialize(); } catch ( System.Exception ) { return defaultValue; } } /// /// If this entry is a dictionary, we can get the key for it here /// public virtual SerializedProperty GetKey() => null; // Func _shouldShowCache; /// /// Returns true if this property should be shown in the inspector /// public bool ShouldShow() { if ( HasAttribute() ) return false; if ( Parent is null ) return true; var conditionals = GetAttributes(); if ( !conditionals.Any() ) return true; return !conditionals.All( x => x.TestCondition( Parent ) ); } /// /// Return true if this is a nullable value type /// public bool IsNullable { get { return Nullable.GetUnderlyingType( PropertyType ) is not null; } } /// /// If this is a nullable type, this will return the nullable target type /// public Type NullableType { get { return Nullable.GetUnderlyingType( PropertyType ); } } /// /// True if the value is null /// public bool IsNull { get { return GetValue() is null; } } /// /// If this is a nullable type, you can use this to toggle between it being null or the default value type /// public void SetNullState( bool isnull ) { if ( !IsNullable ) return; if ( IsNull == isnull ) return; if ( isnull ) { SetValue( null ); } else { SetValue( GetDefault() ); } } /// /// If is method /// public virtual void Invoke() { // nothing } /// /// Allows easily creating SerializedProperty classes that wrap other properties. /// public abstract class Proxy : SerializedProperty { protected abstract SerializedProperty ProxyTarget { get; } public override SerializedObject Parent => ProxyTarget.Parent; public override bool IsProperty => ProxyTarget.IsProperty; public override bool IsField => ProxyTarget.IsField; public override bool IsMethod => ProxyTarget.IsMethod; public override string Name => ProxyTarget.Name; public override string DisplayName => ProxyTarget.DisplayName; public override string Description => ProxyTarget.Description; public override string GroupName => ProxyTarget.GroupName; public override int Order => ProxyTarget.Order; public override bool IsEditable => ProxyTarget.IsEditable; public override bool IsPublic => ProxyTarget.IsPublic; public override Type PropertyType => ProxyTarget.PropertyType; public override string SourceFile => ProxyTarget.SourceFile; public override int SourceLine => ProxyTarget.SourceLine; public override bool HasChanges => ProxyTarget.HasChanges; public override bool IsValid => ProxyTarget.IsValid(); public override ref AsAccessor As => ref base.As; public override bool TryGetAsObject( out SerializedObject obj ) => ProxyTarget.TryGetAsObject( out obj ); public override T GetValue( T defaultValue = default ) => ProxyTarget.GetValue( defaultValue ); public override void SetValue( T value ) => ProxyTarget.SetValue( value ); public override IEnumerable GetAttributes() => ProxyTarget.GetAttributes(); } /// /// Create a serialized property that uses a getter and setter /// [Obsolete( "Best use TypeLibrary.CreateProperty" )] public static SerializedProperty Create( string title, Func get, Action set, Attribute[] attributes = null ) { return new ActionBasedSerializedProperty( title, title, "", get, set, attributes, null ); } } /// /// Hide a property if a condition matches. /// public abstract class InspectorVisibilityAttribute : System.Attribute { public abstract bool TestCondition( SerializedObject so ); } internal class ActionBasedSerializedProperty : SerializedProperty { public Func PropertyToObject; string _name; string _title; string _description; string _groupName; string _sourceFile; int _sourceLine; Func _get; Action _set; List _attributes; SerializedObject _parent; public ActionBasedSerializedProperty( string name, string title, string description, Func get, Action set, Attribute[] attributes, SerializedObject parent ) { _name = name; _title = title; _description = description; _get = get; _set = set; _attributes = new List( attributes ?? Array.Empty() ); _parent = parent; _groupName = _attributes.OfType().FirstOrDefault()?.Value ?? _groupName; _groupName = _attributes.OfType().FirstOrDefault()?.Value ?? _groupName; _description = _attributes.OfType().FirstOrDefault()?.Value ?? _description; _title = _attributes.OfType().FirstOrDefault()?.Value ?? _title; _sourceFile = _attributes.OfType().FirstOrDefault()?.Path ?? _sourceFile; _sourceLine = _attributes.OfType().FirstOrDefault()?.Line ?? _sourceLine; // todo - class location, readonly, order? } public override SerializedObject Parent => _parent; public override bool IsMethod => false; public override string Name => _name; public override string DisplayName => _title; public override string Description => _description; public override string GroupName => _groupName; public override bool IsEditable => true; public override int Order => 0; public override Type PropertyType => typeof( T ); public override string SourceFile => _sourceFile; public override int SourceLine => _sourceLine; public override bool HasChanges => false; public override ref AsAccessor As => ref base.As; public override U GetValue( U defaultValue = default ) => ValueToType( _get() ); public override void SetValue( U value ) => _set( ValueToType( value ) ); public override IEnumerable GetAttributes() => _attributes; public override bool TryGetAsObject( out SerializedObject obj ) { obj = PropertyToObject?.Invoke( this ) ?? null; return obj is not null; } }