bevy_reflect/
attributes.rs

1use crate::Reflect;
2use bevy_utils::TypeIdMap;
3use core::{
4    any::TypeId,
5    fmt::{Debug, Formatter},
6};
7
8/// A collection of custom attributes for a type, field, or variant.
9///
10/// These attributes can be created with the [`Reflect` derive macro].
11///
12/// Attributes are stored by their [`TypeId`].
13/// Because of this, there can only be one attribute per type.
14///
15/// # Example
16///
17/// ```
18/// # use bevy_reflect::{Reflect, Typed, TypeInfo};
19/// use std::ops::RangeInclusive;
20/// #[derive(Reflect)]
21/// struct Slider {
22///   #[reflect(@RangeInclusive::<f32>::new(0.0, 1.0))]
23///   value: f32
24/// }
25///
26/// let TypeInfo::Struct(info) = <Slider as Typed>::type_info() else {
27///   panic!("expected struct info");
28/// };
29///
30/// let range = info.field("value").unwrap().get_attribute::<RangeInclusive<f32>>().unwrap();
31/// assert_eq!(0.0..=1.0, *range);
32/// ```
33///
34/// [`Reflect` derive macro]: derive@crate::Reflect
35#[derive(Default)]
36pub struct CustomAttributes {
37    attributes: TypeIdMap<CustomAttribute>,
38}
39
40impl CustomAttributes {
41    /// Inserts a custom attribute into the collection.
42    ///
43    /// Note that this will overwrite any existing attribute of the same type.
44    pub fn with_attribute<T: Reflect>(mut self, value: T) -> Self {
45        self.attributes
46            .insert(TypeId::of::<T>(), CustomAttribute::new(value));
47
48        self
49    }
50
51    /// Returns `true` if this collection contains a custom attribute of the specified type.
52    pub fn contains<T: Reflect>(&self) -> bool {
53        self.attributes.contains_key(&TypeId::of::<T>())
54    }
55
56    /// Returns `true` if this collection contains a custom attribute with the specified [`TypeId`].
57    pub fn contains_by_id(&self, id: TypeId) -> bool {
58        self.attributes.contains_key(&id)
59    }
60
61    /// Gets a custom attribute by type.
62    pub fn get<T: Reflect>(&self) -> Option<&T> {
63        self.attributes.get(&TypeId::of::<T>())?.value::<T>()
64    }
65
66    /// Gets a custom attribute by its [`TypeId`].
67    pub fn get_by_id(&self, id: TypeId) -> Option<&dyn Reflect> {
68        Some(self.attributes.get(&id)?.reflect_value())
69    }
70
71    /// Returns an iterator over all custom attributes.
72    pub fn iter(&self) -> impl ExactSizeIterator<Item = (&TypeId, &dyn Reflect)> {
73        self.attributes
74            .iter()
75            .map(|(key, value)| (key, value.reflect_value()))
76    }
77
78    /// Returns the number of custom attributes in this collection.
79    pub fn len(&self) -> usize {
80        self.attributes.len()
81    }
82
83    /// Returns `true` if this collection is empty.
84    pub fn is_empty(&self) -> bool {
85        self.attributes.is_empty()
86    }
87}
88
89impl Debug for CustomAttributes {
90    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
91        f.debug_set().entries(self.attributes.values()).finish()
92    }
93}
94
95struct CustomAttribute {
96    value: Box<dyn Reflect>,
97}
98
99impl CustomAttribute {
100    pub fn new<T: Reflect>(value: T) -> Self {
101        Self {
102            value: Box::new(value),
103        }
104    }
105
106    pub fn value<T: Reflect>(&self) -> Option<&T> {
107        self.value.downcast_ref()
108    }
109
110    pub fn reflect_value(&self) -> &dyn Reflect {
111        &*self.value
112    }
113}
114
115impl Debug for CustomAttribute {
116    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
117        self.value.debug(f)
118    }
119}
120
121/// Implements methods for accessing custom attributes.
122///
123/// Implements the following methods:
124///
125/// * `fn custom_attributes(&self) -> &CustomAttributes`
126/// * `fn get_attribute<T: Reflect>(&self) -> Option<&T>`
127/// * `fn get_attribute_by_id(&self, id: TypeId) -> Option<&dyn Reflect>`
128/// * `fn has_attribute<T: Reflect>(&self) -> bool`
129/// * `fn has_attribute_by_id(&self, id: TypeId) -> bool`
130///
131/// # Params
132///
133/// * `$self` - The name of the variable containing the custom attributes (usually `self`).
134/// * `$attributes` - The name of the field containing the [`CustomAttributes`].
135/// * `$term` - (Optional) The term used to describe the type containing the custom attributes.
136///   This is purely used to generate better documentation. Defaults to `"item"`.
137macro_rules! impl_custom_attribute_methods {
138    ($self:ident . $attributes:ident, $term:literal) => {
139        $crate::attributes::impl_custom_attribute_methods!($self, &$self.$attributes, "item");
140    };
141    ($self:ident, $attributes:expr, $term:literal) => {
142        #[doc = concat!("Returns the custom attributes for this ", $term, ".")]
143        pub fn custom_attributes(&$self) -> &$crate::attributes::CustomAttributes {
144            $attributes
145        }
146
147        /// Gets a custom attribute by type.
148        ///
149        /// For dynamically accessing an attribute, see [`get_attribute_by_id`](Self::get_attribute_by_id).
150        pub fn get_attribute<T: $crate::Reflect>(&$self) -> Option<&T> {
151            $self.custom_attributes().get::<T>()
152        }
153
154        #[allow(rustdoc::redundant_explicit_links)]
155        /// Gets a custom attribute by its [`TypeId`](std::any::TypeId).
156        ///
157        /// This is the dynamic equivalent of [`get_attribute`](Self::get_attribute).
158        pub fn get_attribute_by_id(&$self, id: ::core::any::TypeId) -> Option<&dyn $crate::Reflect> {
159            $self.custom_attributes().get_by_id(id)
160        }
161
162        #[doc = concat!("Returns `true` if this ", $term, " has a custom attribute of the specified type.")]
163        #[doc = "\n\nFor dynamically checking if an attribute exists, see [`has_attribute_by_id`](Self::has_attribute_by_id)."]
164        pub fn has_attribute<T: $crate::Reflect>(&$self) -> bool {
165            $self.custom_attributes().contains::<T>()
166        }
167
168        #[doc = concat!("Returns `true` if this ", $term, " has a custom attribute with the specified [`TypeId`](::core::any::TypeId).")]
169        #[doc = "\n\nThis is the dynamic equivalent of [`has_attribute`](Self::has_attribute)"]
170        pub fn has_attribute_by_id(&$self, id: ::core::any::TypeId) -> bool {
171            $self.custom_attributes().contains_by_id(id)
172        }
173    };
174}
175
176pub(crate) use impl_custom_attribute_methods;
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181    use crate as bevy_reflect;
182    use crate::{type_info::Typed, TypeInfo, VariantInfo};
183    use core::ops::RangeInclusive;
184
185    #[derive(Reflect, PartialEq, Debug)]
186    struct Tooltip(String);
187
188    impl Tooltip {
189        fn new(value: impl Into<String>) -> Self {
190            Self(value.into())
191        }
192    }
193
194    #[test]
195    fn should_get_custom_attribute() {
196        let attributes = CustomAttributes::default().with_attribute(0.0..=1.0);
197
198        let value = attributes.get::<RangeInclusive<f64>>().unwrap();
199        assert_eq!(&(0.0..=1.0), value);
200    }
201
202    #[test]
203    fn should_get_custom_attribute_dynamically() {
204        let attributes = CustomAttributes::default().with_attribute(String::from("Hello, World!"));
205
206        let value = attributes.get_by_id(TypeId::of::<String>()).unwrap();
207        assert!(value
208            .reflect_partial_eq(&String::from("Hello, World!"))
209            .unwrap());
210    }
211
212    #[test]
213    fn should_debug_custom_attributes() {
214        let attributes = CustomAttributes::default().with_attribute("My awesome custom attribute!");
215
216        let debug = format!("{:?}", attributes);
217
218        assert_eq!(r#"{"My awesome custom attribute!"}"#, debug);
219
220        #[derive(Reflect)]
221        struct Foo {
222            value: i32,
223        }
224
225        let attributes = CustomAttributes::default().with_attribute(Foo { value: 42 });
226
227        let debug = format!("{:?}", attributes);
228
229        assert_eq!(
230            r#"{bevy_reflect::attributes::tests::Foo { value: 42 }}"#,
231            debug
232        );
233    }
234
235    #[test]
236    fn should_derive_custom_attributes_on_struct_container() {
237        #[derive(Reflect)]
238        #[reflect(@Tooltip::new("My awesome custom attribute!"))]
239        struct Slider {
240            value: f32,
241        }
242
243        let TypeInfo::Struct(info) = Slider::type_info() else {
244            panic!("expected struct info");
245        };
246
247        let tooltip = info.get_attribute::<Tooltip>().unwrap();
248        assert_eq!(&Tooltip::new("My awesome custom attribute!"), tooltip);
249    }
250
251    #[test]
252    fn should_derive_custom_attributes_on_struct_fields() {
253        #[derive(Reflect)]
254        struct Slider {
255            #[reflect(@0.0..=1.0)]
256            #[reflect(@Tooltip::new("Range: 0.0 to 1.0"))]
257            value: f32,
258        }
259
260        let TypeInfo::Struct(info) = Slider::type_info() else {
261            panic!("expected struct info");
262        };
263
264        let field = info.field("value").unwrap();
265
266        let range = field.get_attribute::<RangeInclusive<f64>>().unwrap();
267        assert_eq!(&(0.0..=1.0), range);
268
269        let tooltip = field.get_attribute::<Tooltip>().unwrap();
270        assert_eq!(&Tooltip::new("Range: 0.0 to 1.0"), tooltip);
271    }
272
273    #[test]
274    fn should_derive_custom_attributes_on_tuple_container() {
275        #[derive(Reflect)]
276        #[reflect(@Tooltip::new("My awesome custom attribute!"))]
277        struct Slider(f32);
278
279        let TypeInfo::TupleStruct(info) = Slider::type_info() else {
280            panic!("expected tuple struct info");
281        };
282
283        let tooltip = info.get_attribute::<Tooltip>().unwrap();
284        assert_eq!(&Tooltip::new("My awesome custom attribute!"), tooltip);
285    }
286
287    #[test]
288    fn should_derive_custom_attributes_on_tuple_struct_fields() {
289        #[derive(Reflect)]
290        struct Slider(
291            #[reflect(@0.0..=1.0)]
292            #[reflect(@Tooltip::new("Range: 0.0 to 1.0"))]
293            f32,
294        );
295
296        let TypeInfo::TupleStruct(info) = Slider::type_info() else {
297            panic!("expected tuple struct info");
298        };
299
300        let field = info.field_at(0).unwrap();
301
302        let range = field.get_attribute::<RangeInclusive<f64>>().unwrap();
303        assert_eq!(&(0.0..=1.0), range);
304
305        let tooltip = field.get_attribute::<Tooltip>().unwrap();
306        assert_eq!(&Tooltip::new("Range: 0.0 to 1.0"), tooltip);
307    }
308
309    #[test]
310    fn should_derive_custom_attributes_on_enum_container() {
311        #[derive(Reflect)]
312        #[reflect(@Tooltip::new("My awesome custom attribute!"))]
313        enum Color {
314            Transparent,
315            Grayscale(f32),
316            Rgb { r: u8, g: u8, b: u8 },
317        }
318
319        let TypeInfo::Enum(info) = Color::type_info() else {
320            panic!("expected enum info");
321        };
322
323        let tooltip = info.get_attribute::<Tooltip>().unwrap();
324        assert_eq!(&Tooltip::new("My awesome custom attribute!"), tooltip);
325    }
326
327    #[test]
328    fn should_derive_custom_attributes_on_enum_variants() {
329        #[derive(Reflect, Debug, PartialEq)]
330        enum Display {
331            Toggle,
332            Slider,
333            Picker,
334        }
335
336        #[derive(Reflect)]
337        enum Color {
338            #[reflect(@Display::Toggle)]
339            Transparent,
340            #[reflect(@Display::Slider)]
341            Grayscale(f32),
342            #[reflect(@Display::Picker)]
343            Rgb { r: u8, g: u8, b: u8 },
344        }
345
346        let TypeInfo::Enum(info) = Color::type_info() else {
347            panic!("expected enum info");
348        };
349
350        let VariantInfo::Unit(transparent_variant) = info.variant("Transparent").unwrap() else {
351            panic!("expected unit variant");
352        };
353
354        let display = transparent_variant.get_attribute::<Display>().unwrap();
355        assert_eq!(&Display::Toggle, display);
356
357        let VariantInfo::Tuple(grayscale_variant) = info.variant("Grayscale").unwrap() else {
358            panic!("expected tuple variant");
359        };
360
361        let display = grayscale_variant.get_attribute::<Display>().unwrap();
362        assert_eq!(&Display::Slider, display);
363
364        let VariantInfo::Struct(rgb_variant) = info.variant("Rgb").unwrap() else {
365            panic!("expected struct variant");
366        };
367
368        let display = rgb_variant.get_attribute::<Display>().unwrap();
369        assert_eq!(&Display::Picker, display);
370    }
371
372    #[test]
373    fn should_derive_custom_attributes_on_enum_variant_fields() {
374        #[derive(Reflect)]
375        enum Color {
376            Transparent,
377            Grayscale(#[reflect(@0.0..=1.0_f32)] f32),
378            Rgb {
379                #[reflect(@0..=255u8)]
380                r: u8,
381                #[reflect(@0..=255u8)]
382                g: u8,
383                #[reflect(@0..=255u8)]
384                b: u8,
385            },
386        }
387
388        let TypeInfo::Enum(info) = Color::type_info() else {
389            panic!("expected enum info");
390        };
391
392        let VariantInfo::Tuple(grayscale_variant) = info.variant("Grayscale").unwrap() else {
393            panic!("expected tuple variant");
394        };
395
396        let field = grayscale_variant.field_at(0).unwrap();
397
398        let range = field.get_attribute::<RangeInclusive<f32>>().unwrap();
399        assert_eq!(&(0.0..=1.0), range);
400
401        let VariantInfo::Struct(rgb_variant) = info.variant("Rgb").unwrap() else {
402            panic!("expected struct variant");
403        };
404
405        let field = rgb_variant.field("g").unwrap();
406
407        let range = field.get_attribute::<RangeInclusive<u8>>().unwrap();
408        assert_eq!(&(0..=255), range);
409    }
410
411    #[test]
412    fn should_allow_unit_struct_attribute_values() {
413        #[derive(Reflect)]
414        struct Required;
415
416        #[derive(Reflect)]
417        struct Foo {
418            #[reflect(@Required)]
419            value: i32,
420        }
421
422        let TypeInfo::Struct(info) = Foo::type_info() else {
423            panic!("expected struct info");
424        };
425
426        let field = info.field("value").unwrap();
427        assert!(field.has_attribute::<Required>());
428    }
429
430    #[test]
431    fn should_accept_last_attribute() {
432        #[derive(Reflect)]
433        struct Foo {
434            #[reflect(@false)]
435            #[reflect(@true)]
436            value: i32,
437        }
438
439        let TypeInfo::Struct(info) = Foo::type_info() else {
440            panic!("expected struct info");
441        };
442
443        let field = info.field("value").unwrap();
444        assert!(field.get_attribute::<bool>().unwrap());
445    }
446}