Files
sbox-public/engine/Sandbox.System/SerializedObject/SerializedProperty.cs
s&box team 71f266059a Open source release
This commit imports the C# engine code and game files, excluding C++ source code.

[Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
2025-11-24 09:05:18 +00:00

598 lines
14 KiB
C#

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; }
/// <inheritdoc cref="IValid.IsValid"/>
public virtual bool IsValid => true;
/// <summary>
/// The source filename, if available
/// </summary>
public virtual string SourceFile { get; }
/// <summary>
/// The line in the source file, if available
/// </summary>
public virtual int SourceLine { get; }
/// <summary>
/// Returns true if the current set value differs from the actual value
/// </summary>
public virtual bool HasChanges { get; }
/// <summary>
/// Called when the property value is about to change.
/// </summary>
public PropertyPreChangeDelegate OnPreChange { get; set; }
/// <summary>
/// Called when the property value has changed.
/// </summary>
public PropertyChangedDelegate OnChanged { get; set; }
/// <summary>
/// Called when the property is about to be edited (eg. in a ControlWidget).
/// </summary>
public PropertyStartEditDelegate OnStartEdit { get; set; }
/// <summary>
/// Called when the property has finished being edited (eg. in a ControlWidget).
/// </summary>
public PropertyFinishEditDelegate OnFinishEdit { get; set; }
public SerializedProperty()
{
_as.Property = this;
}
//
// Accessors
//
public abstract void SetValue<T>( T value );
public virtual void SetValue<T>( T value, SerializedProperty source ) => SetValue( value );
public abstract T GetValue<T>( T defaultValue = default );
/// <summary>
/// Get the default value of a specific property type.
/// </summary>
/// <returns></returns>
public object GetDefault()
{
// DefaultValue codegen
if ( TryGetAttribute<DefaultValueAttribute>( 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
//
/// <summary>
/// Return true if the property has this attribute
/// </summary>
public bool HasAttribute<T>() where T : Attribute
{
return GetAttributes<T>().Any();
}
/// <summary>
/// Return true if the property has this attribute
/// </summary>
public bool HasAttribute( Type t )
{
return GetAttributes( t ).Any();
}
/// <summary>
/// Try to get this attribute from the property. Return false on fail.
/// </summary>
public bool TryGetAttribute<T>( out T attribute ) where T : Attribute
{
attribute = GetAttributes<T>().FirstOrDefault();
return attribute != null;
}
/// <summary>
/// Get all of these attributes from the property.
/// </summary>
public IEnumerable<T> GetAttributes<T>() where T : Attribute
{
return GetAttributes().OfType<T>();
}
/// <summary>
/// Get all of these attributes from the property.
/// </summary>
public IEnumerable<Attribute> GetAttributes( Type t )
{
return GetAttributes().Where( t.IsInstanceOfType );
}
/// <summary>
/// Get all attributes from the property.
/// </summary>
public virtual IEnumerable<Attribute> GetAttributes()
{
return Enumerable.Empty<Attribute>();
}
/// <summary>
/// Try to convert this property into a serialized object for further editing and exploration
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
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<string>();
set => Property.SetValue<string>( value );
}
public Vector2 Vector2
{
get => Property.GetValue<Vector2>();
set => Property.SetValue<Vector2>( value );
}
public Vector3 Vector3
{
get => Property.GetValue<Vector3>();
set => Property.SetValue<Vector3>( value );
}
public Rotation Rotation
{
get => Property.GetValue<Rotation>();
set => Property.SetValue<Rotation>( value );
}
public Angles Angles
{
get => Property.GetValue<Angles>();
set => Property.SetValue<Angles>( value );
}
public float Float
{
get => Property.GetValue<float>();
set => Property.SetValue<float>( value );
}
public double Double
{
get => Property.GetValue<double>();
set => Property.SetValue<double>( value );
}
public int Int
{
get => Property.GetValue<int>();
set => Property.SetValue<int>( value );
}
public long Long
{
get => Property.GetValue<long>();
set => Property.SetValue<long>( value );
}
public bool Bool
{
get => Property.GetValue<bool>();
set => Property.SetValue<bool>( value );
}
}
/// <summary>
/// True if this holds multiple values. That might all be the same.
/// </summary>
public virtual bool IsMultipleValues => false;
/// <summary>
/// True if this holds multiple values, and they're all different.
/// </summary>
public virtual bool IsMultipleDifferentValues => false;
/// <summary>
/// Get all properties if this holds multiple values
/// </summary>
public virtual IEnumerable<SerializedProperty> MultipleProperties
{
get
{
yield return this;
}
}
/// <summary>
/// Our value has changed, maybe our parent would like to know
/// </summary>
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 );
}
}
/// <summary>
/// Convert an object value to a T type
/// </summary>
protected T ValueToType<T>( 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<T>( (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<T>();
}
catch ( System.Exception )
{
return defaultValue;
}
}
/// <summary>
/// If this entry is a dictionary, we can get the key for it here
/// </summary>
public virtual SerializedProperty GetKey() => null;
// Func<SerializedObject, bool> _shouldShowCache;
/// <summary>
/// Returns true if this property should be shown in the inspector
/// </summary>
public bool ShouldShow()
{
if ( HasAttribute<HideAttribute>() ) return false;
if ( Parent is null ) return true;
var conditionals = GetAttributes<InspectorVisibilityAttribute>();
if ( !conditionals.Any() ) return true;
return !conditionals.All( x => x.TestCondition( Parent ) );
}
/// <summary>
/// Return true if this is a nullable value type
/// </summary>
public bool IsNullable
{
get
{
return Nullable.GetUnderlyingType( PropertyType ) is not null;
}
}
/// <summary>
/// If this is a nullable type, this will return the nullable target type
/// </summary>
public Type NullableType
{
get
{
return Nullable.GetUnderlyingType( PropertyType );
}
}
/// <summary>
/// True if the value is null
/// </summary>
public bool IsNull
{
get
{
return GetValue<object>() is null;
}
}
/// <summary>
/// If this is a nullable type, you can use this to toggle between it being null or the default value type
/// </summary>
public void SetNullState( bool isnull )
{
if ( !IsNullable )
return;
if ( IsNull == isnull )
return;
if ( isnull )
{
SetValue<object>( null );
}
else
{
SetValue( GetDefault() );
}
}
/// <summary>
/// If is method
/// </summary>
public virtual void Invoke()
{
// nothing
}
/// <summary>
/// Allows easily creating SerializedProperty classes that wrap other properties.
/// </summary>
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>( T defaultValue = default ) => ProxyTarget.GetValue( defaultValue );
public override void SetValue<T>( T value ) => ProxyTarget.SetValue( value );
public override IEnumerable<Attribute> GetAttributes() => ProxyTarget.GetAttributes();
}
/// <summary>
/// Create a serialized property that uses a getter and setter
/// </summary>
[Obsolete( "Best use TypeLibrary.CreateProperty" )]
public static SerializedProperty Create<T>( string title, Func<T> get, Action<T> set, Attribute[] attributes = null )
{
return new ActionBasedSerializedProperty<T>( title, title, "", get, set, attributes, null );
}
}
/// <summary>
/// Hide a property if a condition matches.
/// </summary>
public abstract class InspectorVisibilityAttribute : System.Attribute
{
public abstract bool TestCondition( SerializedObject so );
}
internal class ActionBasedSerializedProperty<T> : SerializedProperty
{
public Func<SerializedProperty, SerializedObject> PropertyToObject;
string _name;
string _title;
string _description;
string _groupName;
string _sourceFile;
int _sourceLine;
Func<T> _get;
Action<T> _set;
List<Attribute> _attributes;
SerializedObject _parent;
public ActionBasedSerializedProperty( string name, string title, string description, Func<T> get, Action<T> set, Attribute[] attributes, SerializedObject parent )
{
_name = name;
_title = title;
_description = description;
_get = get;
_set = set;
_attributes = new List<Attribute>( attributes ?? Array.Empty<Attribute>() );
_parent = parent;
_groupName = _attributes.OfType<IGroupAttribute>().FirstOrDefault()?.Value ?? _groupName;
_groupName = _attributes.OfType<ICategoryProvider>().FirstOrDefault()?.Value ?? _groupName;
_description = _attributes.OfType<IDescriptionAttribute>().FirstOrDefault()?.Value ?? _description;
_title = _attributes.OfType<ITitleProvider>().FirstOrDefault()?.Value ?? _title;
_sourceFile = _attributes.OfType<ISourcePathProvider>().FirstOrDefault()?.Path ?? _sourceFile;
_sourceLine = _attributes.OfType<ISourceLineProvider>().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>( U defaultValue = default ) => ValueToType<U>( _get() );
public override void SetValue<U>( U value ) => _set( ValueToType<T>( value ) );
public override IEnumerable<Attribute> GetAttributes() => _attributes;
public override bool TryGetAsObject( out SerializedObject obj )
{
obj = PropertyToObject?.Invoke( this ) ?? null;
return obj is not null;
}
}