bevy_reflect/
attributes.rs

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