Show an unkeyed struct's fields or its properties, never both
Unity's Quaternion showed eulerAngles beside x/y/z/w, Rect about eleven rows over four values, and Bounds center/extents/size/min/max: settable properties alias the fields they wrap. A struct with public fields now shows only those; a struct without shows its settable properties, so record structs and CultMath matrices (properties over private rows) still get clean rows and vectors show fields only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
baa8de0baf
commit
ccf9bd0c0c
3 changed files with 27 additions and 15 deletions
|
|
@ -552,16 +552,19 @@ namespace GameCult.Caching
|
||||||
var keyed = KeyedMembers(type);
|
var keyed = KeyedMembers(type);
|
||||||
if (keyed.Length > 0)
|
if (keyed.Length > 0)
|
||||||
return new CultInspectorShape(type, CultInspectorValueKind.Nested) { Members = keyed };
|
return new CultInspectorShape(type, CultInspectorValueKind.Nested) { Members = keyed };
|
||||||
// An unkeyed struct is edited in place: its public fields, readonly ones read-only, then its public properties with
|
// An unkeyed struct is edited in place. With public fields it shows only them, readonly ones read-only: its
|
||||||
// a setter, init-only ones read-only. Get-only properties are computed (a vector's swizzles) or kept by the
|
// properties alias those values (a Quaternion's eulerAngles, a Rect's min and max). Without, it shows its public
|
||||||
// in-place edit, so they are not rows.
|
// properties with a setter, init-only ones read-only (record structs, matrices over private rows). Get-only
|
||||||
|
// properties are computed or kept by the in-place edit, so they are never rows.
|
||||||
if (type.IsValueType)
|
if (type.IsValueType)
|
||||||
{
|
{
|
||||||
var members = type.GetFields(Public).OrderBy(field => field.MetadataToken)
|
var fields = type.GetFields(Public);
|
||||||
.Select(field => (Member: (MemberInfo)field, Type: field.FieldType, Assignable: !field.IsInitOnly))
|
var members = (fields.Length > 0
|
||||||
.Concat(type.GetProperties(Public).OrderBy(property => property.MetadataToken)
|
? fields.OrderBy(field => field.MetadataToken)
|
||||||
.Where(property => property.GetIndexParameters().Length == 0 && property.GetGetMethod() != null && property.GetSetMethod() != null)
|
.Select(field => (Member: (MemberInfo)field, Type: field.FieldType, Assignable: !field.IsInitOnly))
|
||||||
.Select(property => (Member: (MemberInfo)property, Type: property.PropertyType, Assignable: !IsInitOnly(property.GetSetMethod()!))))
|
: type.GetProperties(Public).OrderBy(property => property.MetadataToken)
|
||||||
|
.Where(property => property.GetIndexParameters().Length == 0 && property.GetGetMethod() != null && property.GetSetMethod() != null)
|
||||||
|
.Select(property => (Member: (MemberInfo)property, Type: property.PropertyType, Assignable: !IsInitOnly(property.GetSetMethod()!))))
|
||||||
.Select((member, slot) => new CultInspectorMember(member.Member, member.Type, slot, member.Assignable, MetadataOf(member.Member)))
|
.Select((member, slot) => new CultInspectorMember(member.Member, member.Type, slot, member.Assignable, MetadataOf(member.Member)))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
if (members.Length > 0)
|
if (members.Length > 0)
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ catalog slots, `CultInspector*` metadata, whether a member is assignable), the
|
||||||
shape of any value (string, integer, float, bool, enum, `CultRecordRef<T>` with
|
shape of any value (string, integer, float, bool, enum, `CultRecordRef<T>` with
|
||||||
its candidate records, rank-1 list or array, dictionary, `[Union]` with only
|
its candidate records, rank-1 list or array, dictionary, `[Union]` with only
|
||||||
its declared subtypes, nested keyed object, struct edited in place through its
|
its declared subtypes, nested keyed object, struct edited in place through its
|
||||||
public fields and settable properties, or unsupported with a reason), drawer claim resolution
|
public fields, or its settable properties when it has none, or unsupported with a reason), drawer claim resolution
|
||||||
(`CultInspectorDrawerClaims`), and the edit decisions: the key a dictionary
|
(`CultInspectorDrawerClaims`), and the edit decisions: the key a dictionary
|
||||||
entry keeps and the notice when a key is refused (null, an empty record
|
entry keeps and the notice when a key is refused (null, an empty record
|
||||||
reference, a duplicate: serialized the same as another key under the owning
|
reference, a duplicate: serialized the same as another key under the owning
|
||||||
|
|
@ -59,10 +59,12 @@ renderer and drawers to show and edit the same documents under the same rules.
|
||||||
|
|
||||||
## Drawers
|
## Drawers
|
||||||
|
|
||||||
Built in: every model shape above. A struct without `[Key]` members folds out
|
Built in: every model shape above. A struct without `[Key]` members is edited
|
||||||
to its public fields and its public properties with a setter, and is edited in
|
in place. With public fields it folds out to those fields only (readonly ones
|
||||||
place; readonly fields and `init` properties show read-only, and get-only
|
read-only), so aliasing properties such as `Quaternion.eulerAngles` or
|
||||||
properties are not shown (record structs work as written). Unity object
|
`Rect.min` add no rows; without public fields it folds out to its public
|
||||||
|
properties with a setter, `init` ones read-only (record structs and CultMath
|
||||||
|
matrices work as written). Get-only properties are never shown. Unity object
|
||||||
references draw as object fields. A value no drawer claims and no shape covers
|
references draw as object fields. A value no drawer claims and no shape covers
|
||||||
shows a red error row with the model's reason, as does a multi-dimensional
|
shows a red error row with the model's reason, as does a multi-dimensional
|
||||||
array.
|
array.
|
||||||
|
|
|
||||||
|
|
@ -255,8 +255,10 @@ namespace GameCult.Caching.Tests
|
||||||
Assert.That(open.Members.Select(member => member.Name), Is.EqualTo(new[] { "x", "y" }));
|
Assert.That(open.Members.Select(member => member.Name), Is.EqualTo(new[] { "x", "y" }));
|
||||||
Assert.That(open.Members.All(member => member.IsAssignable), Is.True);
|
Assert.That(open.Members.All(member => member.IsAssignable), Is.True);
|
||||||
(string, bool)[] Rows(Type type) => model.ShapeOf(type).Members.Select(member => (member.Name, member.IsReadOnly)).ToArray();
|
(string, bool)[] Rows(Type type) => model.ShapeOf(type).Members.Select(member => (member.Name, member.IsReadOnly)).ToArray();
|
||||||
Assert.That(Rows(typeof(InspectMixedStruct)), Is.EqualTo(new[] { ("x", false), ("y", true), ("Settable", false), ("Initable", true) }),
|
Assert.That(Rows(typeof(InspectMixedStruct)), Is.EqualTo(new[] { ("x", false), ("y", true) }),
|
||||||
"fields, then settable properties; readonly and init are read-only; get-only is not a row");
|
"a struct with public fields shows only them; its settable properties are aliases, not rows");
|
||||||
|
Assert.That(Rows(typeof(InspectPropertyStruct)), Is.EqualTo(new[] { ("Settable", false), ("Initable", true) }),
|
||||||
|
"without public fields, settable properties are rows; init is read-only; get-only is not a row");
|
||||||
Assert.That(Rows(typeof(InspectRecordStruct)), Is.EqualTo(new[] { ("A", false), ("B", false) }));
|
Assert.That(Rows(typeof(InspectRecordStruct)), Is.EqualTo(new[] { ("A", false), ("B", false) }));
|
||||||
Assert.That(Rows(typeof(InspectReadonlyRecordStruct)), Is.EqualTo(new[] { ("A", true), ("B", true) }));
|
Assert.That(Rows(typeof(InspectReadonlyRecordStruct)), Is.EqualTo(new[] { ("A", true), ("B", true) }));
|
||||||
object boxed = new InspectRecordStruct(1, 2f);
|
object boxed = new InspectRecordStruct(1, 2f);
|
||||||
|
|
@ -372,6 +374,11 @@ namespace GameCult.Caching.Tests
|
||||||
{
|
{
|
||||||
public float x;
|
public float x;
|
||||||
public readonly float y;
|
public readonly float y;
|
||||||
|
public float Alias { get => x; set => x = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct InspectPropertyStruct
|
||||||
|
{
|
||||||
public int Settable { get; set; }
|
public int Settable { get; set; }
|
||||||
public int Initable { get; init; }
|
public int Initable { get; init; }
|
||||||
public int Computed => Settable + 1;
|
public int Computed => Settable + 1;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue