Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
bevy_reflect: Custom attributes (#11659)
# Objective As work on the editor starts to ramp up, it might be nice to start allowing types to specify custom attributes. These can be used to provide certain functionality to fields, such as ranges or controlling how data is displayed. A good example of this can be seen in [`bevy-inspector-egui`](https://github.com/jakobhellermann/bevy-inspector-egui) with its [`InspectorOptions`](https://docs.rs/bevy-inspector-egui/0.22.1/bevy_inspector_egui/struct.InspectorOptions.html): ```rust #[derive(Reflect, Default, InspectorOptions)] #[reflect(InspectorOptions)] struct Slider { #[inspector(min = 0.0, max = 1.0)] value: f32, } ``` Normally, as demonstrated in the example above, these attributes are handled by a derive macro and stored in a corresponding `TypeData` struct (i.e. `ReflectInspectorOptions`). Ideally, we would have a good way of defining this directly via reflection so that users don't need to create and manage a whole proc macro just to allow these sorts of attributes. And note that this doesn't have to just be for inspectors and editors. It can be used for things done purely on the code side of things. ## Solution Create a new method for storing attributes on fields via the `Reflect` derive. These custom attributes are stored in type info (e.g. `NamedField`, `StructInfo`, etc.). ```rust #[derive(Reflect)] struct Slider { #[reflect(@0.0..=1.0)] value: f64, } let TypeInfo::Struct(info) = Slider::type_info() else { panic!("expected struct info"); }; let field = info.field("value").unwrap(); let range = field.get_attribute::<RangeInclusive<f64>>().unwrap(); assert_eq!(*range, 0.0..=1.0); ``` ## TODO - [x] ~~Bikeshed syntax~~ Went with a type-based approach, prefixed by `@` for ease of parsing and flexibility - [x] Add support for custom struct/tuple struct field attributes - [x] Add support for custom enum variant field attributes - [x] ~~Add support for custom enum variant attributes (maybe?)~~ ~~Will require a larger refactor. Can be saved for a future PR if we really want it.~~ Actually, we apparently still have support for variant attributes despite not using them, so it was pretty easy to add lol. - [x] Add support for custom container attributes - [x] Allow custom attributes to store any reflectable value (not just `Lit`) - [x] ~~Store attributes in registry~~ This PR used to store these in attributes in the registry, however, it has since switched over to storing them in type info - [x] Add example ## Bikeshedding > [!note] > This section was made for the old method of handling custom attributes, which stored them by name (i.e. `some_attribute = 123`). The PR has shifted away from that, to a more type-safe approach. > > This section has been left for reference. There are a number of ways we can syntactically handle custom attributes. Feel free to leave a comment on your preferred one! Ideally we want one that is clear, readable, and concise since these will potentially see _a lot_ of use. Below is a small, non-exhaustive list of them. Note that the `skip_serializing` reflection attribute is added to demonstrate how each case plays with existing reflection attributes. <details> <summary>List</summary> ##### 1. `@(name = value)` > The `@` was chosen to make them stand out from other attributes and because the "at" symbol is a subtle pneumonic for "attribute". Of course, other symbols could be used (e.g. `$`, `#`, etc.). ```rust #[derive(Reflect)] struct Slider { #[reflect(@(min = 0.0, max = 1.0), skip_serializing)] #[[reflect(@(bevy_editor::hint = "Range: 0.0 to 1.0"))] value: f32, } ``` ##### 2. `@name = value` > This is my personal favorite. ```rust #[derive(Reflect)] struct Slider { #[reflect(@min = 0.0, @max = 1.0, skip_serializing)] #[[reflect(@bevy_editor::hint = "Range: 0.0 to 1.0")] value: f32, } ``` ##### 3. `custom_attr(name = value)` > `custom_attr` can be anything. Other possibilities include `with` or `tag`. ```rust #[derive(Reflect)] struct Slider { #[reflect(custom_attr(min = 0.0, max = 1.0), skip_serializing)] #[[reflect(custom_attr(bevy_editor::hint = "Range: 0.0 to 1.0"))] value: f32, } ``` ##### 4. `reflect_attr(name = value)` ```rust #[derive(Reflect)] struct Slider { #[reflect(skip_serializing)] #[reflect_attr(min = 0.0, max = 1.0)] #[[reflect_attr(bevy_editor::hint = "Range: 0.0 to 1.0")] value: f32, } ``` </details> --- ## Changelog - Added support for custom attributes on reflected types (i.e. `#[reflect(@foo::new("bar")]`)
- Loading branch information