Description
Motivation
For quality of life, it is sometimes advisable to group related properties in the editor. This is already possible with GDScript, and used extensively by the built-in node types in Godot.
Examples, and a first flawed attempt at introducing this to gdext can be seen in #214.
On Syntax
There are different proposals for syntax, each of which have their unique pros and cons. Some of these originate from the same issue in gdnative, godot-rust/gdnative#855.
Goals
- Rust code should not be limited with regards to item organization, field ordering, field naming etc. as to not violate expectations of developers coming from a Rust background.
- Type-safety must be upheld, illegal states must be unrepresentable.
- Verbosity should be kept to the minimum extent required to make the feature intuitive, understandable, and surprise-less.
#[export_group]
This was originally proposed in #214, but fails to achieve the first goal, as field naming and field ordering are mandated by group membership.
#[derive(GodotClass)]
struct Foo {
#[export_group(name = "My Things", prefix = "my_")]
#[export(...)]
my_x: i32,
#[export(...)]
my_y: i32,
}
Marker Fields
Originally proposed in godot-rust/gdnative#855 (comment), this has the same downside as the above, but with a different syntax.
Separate Structs Deriving ExportGroup
This would introduce a new derivable trait ExportGroup
which would allow use as a property type. The exported fields of the struct deriving this trait would be available as grouped properties.
#[derive(ExportGroup)]
#[group(name="Offset", prefix="offset_")]
struct OffsetGroup {
#[export]
x: f32,
#[export]
y: f32,
}
#[derive(GodotClass)]
#[class(init, base=Node)]
struct MyNode {
#[export(group)]
offsets: OffsetGroup,
}
A question arising here is whether to allow overriding the name and prefix at the site of usage, which would enable uses such as
#[derive(ExportGroup)]
struct MyImportantType {
#[export]
a: i32,
#[export]
b: GodotString,
}
#[derive(GodotClass)]
struct Foo {
#[export(group_name="Element 1", prefix="element_1_")]
el1: MyImportantType,
#[export(group_name="Element 2", prefix="element_2_")]
el2: MyImportantType,
}
which enables structs to be re-used as custom (domain specific) property types which are reusable over many structs.
Use Marker Structs
Similar to the previous approach, but require fields to be kept on the struct deriving GodotClass
.
#[derive(ExportGroup)]
#[group(name="Offset", prefix="offset_")]
struct OffsetGroup; // just a tag
#[derive(GodotClass)]
#[class(init, base=Node)]
struct MyNode {
#[export(group = OffsetGroup)]
offset_x: f32,
#[export(group = OffsetGroup)]
offset_y: f32,
}
Loses the reusability option of the previous approach, but more close to how the data would be laid out in GDScript.
String Based Groups
Like the previous approach, but instead of marker structs it uses string-based group identifiers which do not actually exist as Rust items.
#[derive(GodotClass)]
#[class(init, base=Node)]
#[group(name="Offset", prefix="offset_")]
struct MyNode {
#[export(group = "Offset")]
offset_x: f32,
#[export(group = "Offset")]
offset_y: f32,
}
Explicit Property Paths
I do not know much about gdnative, but this seems to be a way to achieve property groups there:
#[property(path = "foo/bar")]
creates a property available as bar
in a group foo
. This has the advantage of decoupling the notion of groups from Rust entirely, and making it purely aesthetic, but when reusability of domain-specific editor-editable types is a concern, this may actually be a downside.