bevy_reflect/
attributes.rs

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