Files
sbox-public/game/addons/tools/Code/Widgets/ControlWidgets/BodyGroupsControlWidget.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

191 lines
4.0 KiB
C#

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<Model.BodyGroupMaskAttribute>( 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<string>();
if ( string.IsNullOrWhiteSpace( str ) ) return default;
return Model.Load( str );
}
return modelProperty.GetValue<Model>( null );
}
}
/// <summary>
/// Gets common body parts from multiple selected objects
/// </summary>
private HashSet<Model.BodyPart> GetCommonBodyParts()
{
HashSet<Model.BodyPart> commonBodyParts = null;
foreach ( var property in SerializedProperty.MultipleProperties )
{
if ( !property.TryGetAttribute<Model.BodyGroupMaskAttribute>( out var attr ) )
continue;
var prop = property.Parent.GetProperty( attr.ModelParameter );
if ( prop is null )
continue;
var model = prop.GetValue<Model>( null );
//
// Has no body parts, return empty set
//
if ( model is null || model.Parts.All.Count() == 0 )
return new();
var modelBodyParts = new HashSet<Model.BodyPart>( 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<Model.BodyPart> 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<ulong>();
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<ulong>();
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();
}
}
}