bevy_reflect/
attributes.rs1use crate::Reflect;
2use bevy_utils::TypeIdMap;
3use core::{
4 any::TypeId,
5 fmt::{Debug, Formatter},
6};
7
8#[derive(Default)]
36pub struct CustomAttributes {
37 attributes: TypeIdMap<CustomAttribute>,
38}
39
40impl CustomAttributes {
41 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 pub fn contains<T: Reflect>(&self) -> bool {
53 self.attributes.contains_key(&TypeId::of::<T>())
54 }
55
56 pub fn contains_by_id(&self, id: TypeId) -> bool {
58 self.attributes.contains_key(&id)
59 }
60
61 pub fn get<T: Reflect>(&self) -> Option<&T> {
63 self.attributes.get(&TypeId::of::<T>())?.value::<T>()
64 }
65
66 pub fn get_by_id(&self, id: TypeId) -> Option<&dyn Reflect> {
68 Some(self.attributes.get(&id)?.reflect_value())
69 }
70
71 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 pub fn len(&self) -> usize {
80 self.attributes.len()
81 }
82
83 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
121macro_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 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 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}