namespace Sandbox; /// /// An object (or data) that can be accessed as an object /// public class MultiSerializedObject : SerializedObject { List children = new List(); private static TResult SelectDistincSingleOrFallback( IEnumerable source, Func selector, TResult fallback ) { // Last 2 elements is all we need to check if there is more than 2 we want to return fallback var distinct = source.Select( selector ).Distinct().Take( 2 ); var isSingle = distinct.Count() == 1; return isSingle ? distinct.FirstOrDefault() : fallback; } public override string TypeIcon => SelectDistincSingleOrFallback( children, x => x.TypeIcon, "multiple-icon" ); public override string TypeName => SelectDistincSingleOrFallback( children, x => x.TypeName, "MultipleTypes" ); public override string TypeTitle => SelectDistincSingleOrFallback( children, x => x.TypeTitle, "Multiple Objects" ); public override bool IsValid => children.Where( x => x.IsValid() ).Any(); public MultiSerializedObject() { PropertyList = new List(); } /// /// Add an object. Don't forget to rebuild after editing! /// public void Add( SerializedObject obj ) { obj.OnPropertyChanged += p => OnPropertyChanged?.Invoke( p ); obj.OnPropertyPreChange += p => OnPropertyPreChange?.Invoke( p ); obj.OnPropertyStartEdit += p => OnPropertyStartEdit?.Invoke( p ); obj.OnPropertyFinishEdit += p => OnPropertyFinishEdit?.Invoke( p ); children.Add( obj ); } /// /// Rebuild the object after modifying. This updates PropertyList. /// public void Rebuild() { PropertyList.Clear(); SerializedProperty[] childProperties = children.SelectMany( x => x ).ToArray(); var groupedProperties = childProperties.GroupBy( x => x.Name ).ToArray(); foreach ( var group in groupedProperties ) { if ( group.Count() == 1 ) { PropertyList.Add( group.First() ); continue; } var msp = new MultiSerializedProperty( this, group.Select( x => x ) ); PropertyList.Add( msp ); } } /// /// True if the target is multiple objects /// public override bool IsMultipleTargets => children.Count > 1; /// /// A list of actual target objects - if applicable /// public override IEnumerable Targets => children.Where( x => x.IsValid() ).SelectMany( x => x.Targets ); } class MultiSerializedProperty : SerializedProperty { readonly List properties; readonly MultiSerializedObject parent; public override SerializedObject Parent => parent; public override string Name => properties.First().Name; public override string DisplayName => properties.First().DisplayName; public override string Description => properties.First().Description; public override string GroupName => properties.First().GroupName; public override int Order => properties.First().Order; public override bool IsEditable => properties.First().IsEditable; public override bool IsPublic => properties.First().IsPublic; public override Type PropertyType => properties.First().PropertyType; public override string SourceFile => properties.First().SourceFile; public override int SourceLine => properties.First().SourceLine; public override bool HasChanges => properties.First().HasChanges; public override bool IsProperty => properties.First().IsProperty; public override bool IsField => properties.First().IsField; public override bool IsMethod => properties.First().IsMethod; public override IEnumerable GetAttributes() { // TODO: maybe this should be attributes that all targets share? return properties?.FirstOrDefault()?.GetAttributes() ?? base.GetAttributes(); } public override bool TryGetAsObject( out SerializedObject obj ) { var mso = new MultiSerializedObject(); int count = 0; foreach ( var e in properties ) { if ( e.TryGetAsObject( out var o ) ) { mso.Add( o ); count++; } } mso.Rebuild(); obj = mso; return count > 0; } public MultiSerializedProperty( MultiSerializedObject parent, IEnumerable enumerable ) { this.parent = parent; properties = enumerable.ToList(); } public override T GetValue( T defaultValue = default ) { return properties.First().GetValue( defaultValue ); } public override void SetValue( T value ) { NotePreChange(); foreach ( var prop in properties ) { prop.SetValue( value ); } NoteChanged(); } protected override void NoteStartEdit() { foreach ( var prop in properties ) { prop.NoteStartEdit( prop ); } } internal override void NoteStartEdit( SerializedProperty childProperty ) { foreach ( var prop in properties ) { prop.NoteStartEdit( prop ); } } protected override void NoteFinishEdit() { foreach ( var prop in properties ) { prop.NoteFinishEdit( prop ); } } internal override void NoteFinishEdit( SerializedProperty childProperty ) { foreach ( var prop in properties ) { prop.NoteFinishEdit( prop ); } } public override bool IsMultipleValues => properties.Count > 1; public override bool IsMultipleDifferentValues => properties.Select( x => x.GetValue() ).Distinct().Count() > 1; public override IEnumerable MultipleProperties => properties; }