custom_attributes/
custom_attributes.rs

1//! Demonstrates how to register and access custom attributes on reflected types.
2
3use bevy::reflect::{Reflect, TypeInfo, Typed};
4use std::{any::TypeId, ops::RangeInclusive};
5
6fn main() {
7    // Bevy supports statically registering custom attribute data on reflected types,
8    // which can then be accessed at runtime via the type's `TypeInfo`.
9    // Attributes are registered using the `#[reflect(@...)]` syntax,
10    // where the `...` is any expression that resolves to a value which implements `Reflect`.
11    // Note that these attributes are stored based on their type:
12    // if two attributes have the same type, the second one will overwrite the first.
13
14    // Here is an example of registering custom attributes on a type:
15    #[derive(Reflect)]
16    struct Slider {
17        #[reflect(@RangeInclusive::<f32>::new(0.0, 1.0))]
18        // Alternatively, we could have used the `0.0..=1.0` syntax,
19        // but remember to ensure the type is the one you want!
20        #[reflect(@0.0..=1.0_f32)]
21        value: f32,
22    }
23
24    // Now, we can access the custom attributes at runtime:
25    let TypeInfo::Struct(type_info) = Slider::type_info() else {
26        panic!("expected struct");
27    };
28
29    let field = type_info.field("value").unwrap();
30
31    let range = field.get_attribute::<RangeInclusive<f32>>().unwrap();
32    assert_eq!(*range, 0.0..=1.0);
33
34    // And remember that our attributes can be any type that implements `Reflect`:
35    #[derive(Reflect)]
36    struct Required;
37
38    #[derive(Reflect, PartialEq, Debug)]
39    struct Tooltip(String);
40
41    impl Tooltip {
42        fn new(text: &str) -> Self {
43            Self(text.to_string())
44        }
45    }
46
47    #[derive(Reflect)]
48    #[reflect(@Required, @Tooltip::new("An ID is required!"))]
49    struct Id(u8);
50
51    let TypeInfo::TupleStruct(type_info) = Id::type_info() else {
52        panic!("expected struct");
53    };
54
55    // We can check if an attribute simply exists on our type:
56    assert!(type_info.has_attribute::<Required>());
57
58    // We can also get attribute data dynamically:
59    let some_type_id = TypeId::of::<Tooltip>();
60
61    let tooltip: &dyn Reflect = type_info.get_attribute_by_id(some_type_id).unwrap();
62    assert_eq!(
63        tooltip.downcast_ref::<Tooltip>(),
64        Some(&Tooltip::new("An ID is required!"))
65    );
66
67    // And again, attributes of the same type will overwrite each other:
68    #[derive(Reflect)]
69    enum Status {
70        // This will result in `false` being stored:
71        #[reflect(@true)]
72        #[reflect(@false)]
73        Disabled,
74        // This will result in `true` being stored:
75        #[reflect(@false)]
76        #[reflect(@true)]
77        Enabled,
78    }
79
80    let TypeInfo::Enum(type_info) = Status::type_info() else {
81        panic!("expected enum");
82    };
83
84    let disabled = type_info.variant("Disabled").unwrap();
85    assert!(!disabled.get_attribute::<bool>().unwrap());
86
87    let enabled = type_info.variant("Enabled").unwrap();
88    assert!(enabled.get_attribute::<bool>().unwrap());
89}