1use crate::Reflect;
4use alloc::boxed::Box;
5use bevy_platform::sync::Arc;
6use bevy_utils::TypeIdIndexMap;
7use core::{
8 any::TypeId,
9 fmt::{Debug, Formatter},
10};
11
12#[derive(Default, Clone)]
41pub struct CustomAttributes {
42 attributes: Option<Arc<TypeIdIndexMap<CustomAttribute>>>,
43}
44
45impl CustomAttributes {
46 fn new(attributes: TypeIdIndexMap<CustomAttribute>) -> Self {
47 Self {
48 attributes: if attributes.is_empty() {
49 None
50 } else {
51 Some(Arc::new(attributes))
52 },
53 }
54 }
55
56 pub fn contains<T: Reflect>(&self) -> bool {
58 self.attributes
59 .as_ref()
60 .is_some_and(|a| a.contains_key(&TypeId::of::<T>()))
61 }
62
63 pub fn contains_by_id(&self, id: TypeId) -> bool {
65 self.attributes
66 .as_ref()
67 .is_some_and(|a| a.contains_key(&id))
68 }
69
70 pub fn get<T: Reflect>(&self) -> Option<&T> {
72 self.attributes
73 .as_ref()
74 .and_then(|a| a.get(&TypeId::of::<T>())?.value::<T>())
75 }
76
77 pub fn get_by_id(&self, id: TypeId) -> Option<&dyn Reflect> {
79 self.attributes
80 .as_ref()
81 .and_then(|a| a.get(&id))
82 .map(CustomAttribute::reflect_value)
83 }
84
85 pub fn iter(&self) -> impl Iterator<Item = (&TypeId, &dyn Reflect)> {
87 self.attributes.iter().flat_map(|attributes| {
88 attributes
89 .iter()
90 .map(|(key, value)| (key, value.reflect_value()))
91 })
92 }
93
94 pub fn len(&self) -> usize {
96 self.attributes.as_ref().map(|a| a.len()).unwrap_or(0)
97 }
98
99 pub fn is_empty(&self) -> bool {
101 self.attributes.is_none()
102 }
103}
104
105impl Debug for CustomAttributes {
106 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
107 if let Some(attributes) = &self.attributes {
108 f.debug_set().entries(attributes.values()).finish()
109 } else {
110 f.debug_set().finish()
111 }
112 }
113}
114
115struct CustomAttribute {
116 value: Box<dyn Reflect>,
117}
118
119impl CustomAttribute {
120 pub fn new<T: Reflect>(value: T) -> Self {
122 Self {
123 value: Box::new(value),
124 }
125 }
126
127 pub fn value<T: Reflect>(&self) -> Option<&T> {
129 self.value.downcast_ref()
130 }
131
132 pub fn reflect_value(&self) -> &dyn Reflect {
134 &*self.value
135 }
136}
137
138impl Debug for CustomAttribute {
139 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
140 self.value.debug(f)
141 }
142}
143
144macro_rules! impl_custom_attribute_methods {
161 ($self:ident . $attributes:ident, $term:literal) => {
162 $crate::attributes::impl_custom_attribute_methods!($self, &$self.$attributes, "item");
163 };
164 ($self:ident, $attributes:expr, $term:literal) => {
165 #[doc = concat!("Returns the custom attributes for this ", $term, ".")]
166 pub fn custom_attributes(&$self) -> &$crate::attributes::CustomAttributes {
167 $attributes
168 }
169
170 pub fn get_attribute<T: $crate::Reflect>(&$self) -> Option<&T> {
174 $self.custom_attributes().get::<T>()
175 }
176
177 pub fn get_attribute_by_id(&$self, id: ::core::any::TypeId) -> Option<&dyn $crate::Reflect> {
181 $self.custom_attributes().get_by_id(id)
182 }
183
184 #[doc = concat!("Returns `true` if this ", $term, " has a custom attribute of the specified type.")]
185 #[doc = "\n\nFor dynamically checking if an attribute exists, see [`has_attribute_by_id`](Self::has_attribute_by_id)."]
186 pub fn has_attribute<T: $crate::Reflect>(&$self) -> bool {
187 $self.custom_attributes().contains::<T>()
188 }
189
190 #[doc = concat!("Returns `true` if this ", $term, " has a custom attribute with the specified [`TypeId`](::core::any::TypeId).")]
191 #[doc = "\n\nThis is the dynamic equivalent of [`has_attribute`](Self::has_attribute)"]
192 pub fn has_attribute_by_id(&$self, id: ::core::any::TypeId) -> bool {
193 $self.custom_attributes().contains_by_id(id)
194 }
195 };
196}
197
198#[derive(Default)]
208pub struct CustomAttributesBuilder {
209 attributes: TypeIdIndexMap<CustomAttribute>,
210}
211
212impl CustomAttributesBuilder {
213 pub fn new() -> Self {
215 Self::default()
216 }
217
218 pub fn attribute<T: Reflect>(self, value: T) -> Self {
220 self.attribute_erased(TypeId::of::<T>(), CustomAttribute::new(value))
221 }
222
223 #[inline(never)]
227 fn attribute_erased(mut self, type_id: TypeId, value: CustomAttribute) -> Self {
228 self.attributes.insert(type_id, value);
229 self
230 }
231
232 pub fn build(self) -> CustomAttributes {
234 CustomAttributes::new(self.attributes)
235 }
236}
237
238pub(crate) use impl_custom_attribute_methods;
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243 use crate::{enums::VariantInfo, info::Typed, TypeInfo};
244 use alloc::{format, string::String};
245 use core::ops::RangeInclusive;
246
247 #[derive(Reflect, PartialEq, Debug)]
248 struct Tooltip(String);
249
250 impl Tooltip {
251 fn new(value: impl Into<String>) -> Self {
252 Self(value.into())
253 }
254 }
255
256 #[test]
257 fn should_get_custom_attribute() {
258 let attributes = CustomAttributesBuilder::new().attribute(0.0..=1.0).build();
259
260 let value = attributes.get::<RangeInclusive<f64>>().unwrap();
261 assert_eq!(&(0.0..=1.0), value);
262 }
263
264 #[test]
265 fn should_get_custom_attribute_dynamically() {
266 let attributes = CustomAttributesBuilder::new()
267 .attribute(String::from("Hello, World!"))
268 .build();
269
270 let value = attributes.get_by_id(TypeId::of::<String>()).unwrap();
271 assert!(value
272 .reflect_partial_eq(&String::from("Hello, World!"))
273 .unwrap());
274 }
275
276 #[test]
277 fn should_iterate_custom_attribute() {
278 let empty_attributes = CustomAttributesBuilder::new().build();
279
280 assert!(empty_attributes.iter().next().is_none());
281
282 let attributes = CustomAttributesBuilder::new()
283 .attribute(1i32)
284 .attribute("string")
285 .build();
286
287 let mut iter = attributes.iter();
288
289 let (type_id, reflected) = iter.next().unwrap();
290
291 assert_eq!(TypeId::of::<i32>(), *type_id);
292 assert_eq!(1i32, *reflected.downcast_ref::<i32>().unwrap());
293
294 let (type_id, reflected) = iter.next().unwrap();
295
296 assert_eq!(TypeId::of::<&str>(), *type_id);
297 assert_eq!("string", *reflected.downcast_ref::<&str>().unwrap());
298
299 assert!(iter.next().is_none());
300 }
301
302 #[test]
303 fn should_debug_custom_attributes() {
304 let attributes = CustomAttributesBuilder::new().build();
305
306 let debug = format!("{attributes:?}");
307
308 assert_eq!(r#"{}"#, debug);
309
310 let attributes = CustomAttributesBuilder::new()
311 .attribute("My awesome custom attribute!")
312 .build();
313
314 let debug = format!("{attributes:?}");
315
316 assert_eq!(r#"{"My awesome custom attribute!"}"#, debug);
317
318 #[derive(Reflect)]
319 struct Foo {
320 value: i32,
321 }
322
323 let attributes = CustomAttributesBuilder::new()
324 .attribute(Foo { value: 42 })
325 .build();
326
327 let debug = format!("{attributes:?}");
328
329 assert_eq!(
330 r#"{bevy_reflect::attributes::tests::Foo { value: 42 }}"#,
331 debug
332 );
333 }
334
335 #[test]
336 fn should_derive_custom_attributes_on_struct_container() {
337 #[derive(Reflect)]
338 #[reflect(@Tooltip::new("My awesome custom attribute!"))]
339 struct Slider {
340 value: f32,
341 }
342
343 let TypeInfo::Struct(info) = Slider::type_info() else {
344 panic!("expected struct info");
345 };
346
347 let tooltip = info.get_attribute::<Tooltip>().unwrap();
348 assert_eq!(&Tooltip::new("My awesome custom attribute!"), tooltip);
349 }
350
351 #[test]
352 fn should_derive_custom_attributes_on_struct_fields() {
353 #[derive(Reflect)]
354 struct Slider {
355 #[reflect(@0.0..=1.0)]
356 #[reflect(@Tooltip::new("Range: 0.0 to 1.0"))]
357 value: f32,
358 }
359
360 let TypeInfo::Struct(info) = Slider::type_info() else {
361 panic!("expected struct info");
362 };
363
364 let field = info.field("value").unwrap();
365
366 let range = field.get_attribute::<RangeInclusive<f64>>().unwrap();
367 assert_eq!(&(0.0..=1.0), range);
368
369 let tooltip = field.get_attribute::<Tooltip>().unwrap();
370 assert_eq!(&Tooltip::new("Range: 0.0 to 1.0"), tooltip);
371 }
372
373 #[test]
374 fn should_derive_custom_attributes_on_tuple_container() {
375 #[derive(Reflect)]
376 #[reflect(@Tooltip::new("My awesome custom attribute!"))]
377 struct Slider(f32);
378
379 let TypeInfo::TupleStruct(info) = Slider::type_info() else {
380 panic!("expected tuple struct info");
381 };
382
383 let tooltip = info.get_attribute::<Tooltip>().unwrap();
384 assert_eq!(&Tooltip::new("My awesome custom attribute!"), tooltip);
385 }
386
387 #[test]
388 fn should_derive_custom_attributes_on_tuple_struct_fields() {
389 #[derive(Reflect)]
390 struct Slider(
391 #[reflect(@0.0..=1.0)]
392 #[reflect(@Tooltip::new("Range: 0.0 to 1.0"))]
393 f32,
394 );
395
396 let TypeInfo::TupleStruct(info) = Slider::type_info() else {
397 panic!("expected tuple struct info");
398 };
399
400 let field = info.field_at(0).unwrap();
401
402 let range = field.get_attribute::<RangeInclusive<f64>>().unwrap();
403 assert_eq!(&(0.0..=1.0), range);
404
405 let tooltip = field.get_attribute::<Tooltip>().unwrap();
406 assert_eq!(&Tooltip::new("Range: 0.0 to 1.0"), tooltip);
407 }
408
409 #[test]
410 fn should_derive_custom_attributes_on_enum_container() {
411 #[derive(Reflect)]
412 #[reflect(@Tooltip::new("My awesome custom attribute!"))]
413 enum Color {
414 Transparent,
415 Grayscale(f32),
416 Rgb { r: u8, g: u8, b: u8 },
417 }
418
419 let TypeInfo::Enum(info) = Color::type_info() else {
420 panic!("expected enum info");
421 };
422
423 let tooltip = info.get_attribute::<Tooltip>().unwrap();
424 assert_eq!(&Tooltip::new("My awesome custom attribute!"), tooltip);
425 }
426
427 #[test]
428 fn should_derive_custom_attributes_on_enum_variants() {
429 #[derive(Reflect, Debug, PartialEq)]
430 enum Display {
431 Toggle,
432 Slider,
433 Picker,
434 }
435
436 #[derive(Reflect)]
437 enum Color {
438 #[reflect(@Display::Toggle)]
439 Transparent,
440 #[reflect(@Display::Slider)]
441 Grayscale(f32),
442 #[reflect(@Display::Picker)]
443 Rgb { r: u8, g: u8, b: u8 },
444 }
445
446 let TypeInfo::Enum(info) = Color::type_info() else {
447 panic!("expected enum info");
448 };
449
450 let VariantInfo::Unit(transparent_variant) = info.variant("Transparent").unwrap() else {
451 panic!("expected unit variant");
452 };
453
454 let display = transparent_variant.get_attribute::<Display>().unwrap();
455 assert_eq!(&Display::Toggle, display);
456
457 let VariantInfo::Tuple(grayscale_variant) = info.variant("Grayscale").unwrap() else {
458 panic!("expected tuple variant");
459 };
460
461 let display = grayscale_variant.get_attribute::<Display>().unwrap();
462 assert_eq!(&Display::Slider, display);
463
464 let VariantInfo::Struct(rgb_variant) = info.variant("Rgb").unwrap() else {
465 panic!("expected struct variant");
466 };
467
468 let display = rgb_variant.get_attribute::<Display>().unwrap();
469 assert_eq!(&Display::Picker, display);
470 }
471
472 #[test]
473 fn should_derive_custom_attributes_on_enum_variant_fields() {
474 #[derive(Reflect)]
475 enum Color {
476 Transparent,
477 Grayscale(#[reflect(@0.0..=1.0_f32)] f32),
478 Rgb {
479 #[reflect(@0..=255u8)]
480 r: u8,
481 #[reflect(@0..=255u8)]
482 g: u8,
483 #[reflect(@0..=255u8)]
484 b: u8,
485 },
486 }
487
488 let TypeInfo::Enum(info) = Color::type_info() else {
489 panic!("expected enum info");
490 };
491
492 let VariantInfo::Tuple(grayscale_variant) = info.variant("Grayscale").unwrap() else {
493 panic!("expected tuple variant");
494 };
495
496 let field = grayscale_variant.field_at(0).unwrap();
497
498 let range = field.get_attribute::<RangeInclusive<f32>>().unwrap();
499 assert_eq!(&(0.0..=1.0), range);
500
501 let VariantInfo::Struct(rgb_variant) = info.variant("Rgb").unwrap() else {
502 panic!("expected struct variant");
503 };
504
505 let field = rgb_variant.field("g").unwrap();
506
507 let range = field.get_attribute::<RangeInclusive<u8>>().unwrap();
508 assert_eq!(&(0..=255), range);
509 }
510
511 #[test]
512 fn should_allow_unit_struct_attribute_values() {
513 #[derive(Reflect)]
514 struct Required;
515
516 #[derive(Reflect)]
517 struct Foo {
518 #[reflect(@Required)]
519 value: i32,
520 }
521
522 let TypeInfo::Struct(info) = Foo::type_info() else {
523 panic!("expected struct info");
524 };
525
526 let field = info.field("value").unwrap();
527 assert!(field.has_attribute::<Required>());
528 }
529
530 #[test]
531 fn should_accept_last_attribute() {
532 #[derive(Reflect)]
533 struct Foo {
534 #[reflect(@false)]
535 #[reflect(@true)]
536 value: i32,
537 }
538
539 let TypeInfo::Struct(info) = Foo::type_info() else {
540 panic!("expected struct info");
541 };
542
543 let field = info.field("value").unwrap();
544 assert!(field.get_attribute::<bool>().unwrap());
545 }
546}