namespace Editor; [CustomEditor( typeof( ulong ), WithAllAttributes = new[] { typeof( Model.BodyGroupMaskAttribute ) } )] public class BodyGroupsControlWidget : ControlWidget { // garry: I hate this, the way it looks, the way it works. // We should make it a single informational line, that pops up // a selector with more details and edits when you click it. public override bool SupportsMultiEdit => true; public BodyGroupsControlWidget( SerializedProperty property ) : base( property ) { Layout = Layout.Grid(); Layout.Spacing = 2; AcceptDrops = true; Rebuild(); } protected override void OnPaint() { } Model Model { get { if ( !SerializedProperty.TryGetAttribute( out var attr ) ) return null; var modelProperty = SerializedProperty.Parent.GetProperty( attr.ModelParameter ); if ( modelProperty is null ) return null; if ( modelProperty.PropertyType == typeof( string ) ) { var str = modelProperty.GetValue(); if ( string.IsNullOrWhiteSpace( str ) ) return default; return Model.Load( str ); } return modelProperty.GetValue( null ); } } /// /// Gets common body parts from multiple selected objects /// private HashSet GetCommonBodyParts() { HashSet commonBodyParts = null; foreach ( var property in SerializedProperty.MultipleProperties ) { if ( !property.TryGetAttribute( out var attr ) ) continue; var prop = property.Parent.GetProperty( attr.ModelParameter ); if ( prop is null ) continue; var model = prop.GetValue( null ); // // Has no body parts, return empty set // if ( model is null || model.Parts.All.Count() == 0 ) return new(); var modelBodyParts = new HashSet( model.Parts.All.Where( x => x.Choices.Count > 1 ) ); if ( commonBodyParts == null ) { commonBodyParts = modelBodyParts; } else { commonBodyParts.IntersectWith( modelBodyParts ); } if ( commonBodyParts.Count == 0 ) break; } return commonBodyParts ?? new(); } public void Rebuild() { Layout.Clear( true ); IEnumerable bodyParts; if ( SerializedProperty.IsMultipleValues ) { bodyParts = GetCommonBodyParts(); } else { // // Single selection - get body parts directly from the model // var model = Model; if ( model is null ) { Layout.Add( new Label( "No Model" ) ); return; } bodyParts = model.Parts.All.Where( x => x.Choices.Count > 1 ); } var totalParts = bodyParts.Sum( x => x.Choices.Count ); if ( totalParts <= 1 ) { Layout.Add( new Label( "None" ) ); return; } var v = SerializedProperty.GetValue(); var grid = Layout as GridLayout; grid.HorizontalSpacing = 8; grid.Spacing = 2; int i = 0; foreach ( var part in bodyParts ) { if ( part.Choices.Count <= 1 ) continue; grid.AddCell( 0, i, new Label( $"{part.Name}:", this ) ); var select = grid.AddCell( 1, i, new ComboBox( this ) ); foreach ( var c in part.Choices ) { select.AddItem( $"{c.Name}", onSelected: () => SetModelMask( part, c ), selected: (v & part.Mask) == c.Mask && !SerializedProperty.IsMultipleDifferentValues ); } i++; } grid.SetColumnStretch( 0, 1 ); FixedHeight = (Theme.RowHeight + grid.Spacing) * i; grid.Margin = new( 0, 0, 0, 2 ); } protected override void OnValueChanged() { Rebuild(); } private void SetModelMask( Model.BodyPart part, Model.BodyPart.Choice c ) { PropertyStartEdit(); var v = SerializedProperty.GetValue(); v = v & ~part.Mask; v = v | c.Mask; SerializedProperty.SetValue( v ); PropertyFinishEdit(); } protected override int ValueHash { get { var hc = new HashCode(); hc.Add( base.ValueHash ); hc.Add( Model ); if ( Model is not null ) { foreach ( var val in Model.Parts.All ) { hc.Add( val ); } } return hc.ToHashCode(); } } }