bevy_reflect/
attributes.rs1use crate::Reflect;
2use alloc::boxed::Box;
3use bevy_utils::TypeIdMap;
4use core::{
5 any::TypeId,
6 fmt::{Debug, Formatter},
7};
8
9#[derive(Default)]
37pub struct CustomAttributes {
38 attributes: TypeIdMap<CustomAttribute>,
39}
40
41impl CustomAttributes {
42 pub fn with_attribute<T: Reflect>(mut self, value: T) -> Self {
46 self.attributes
47 .insert(TypeId::of::<T>(), CustomAttribute::new(value));
48
49 self
50 }
51
52 pub fn contains<T: Reflect>(&self) -> bool {
54 self.attributes.contains_key(&TypeId::of::<T>())
55 }
56
57 pub fn contains_by_id(&self, id: TypeId) -> bool {
59 self.attributes.contains_key(&id)
60 }
61
62 pub fn get<T: Reflect>(&self) -> Option<&T> {
64 self.attributes.get(&TypeId::of::<T>())?.value::<T>()
65 }
66
67 pub fn get_by_id(&self, id: TypeId) -> Option<&dyn Reflect> {
69 Some(self.attributes.get(&id)?.reflect_value())
70 }
71
72 pub fn iter(&self) -> impl ExactSizeIterator<Item = (&TypeId, &dyn Reflect)> {
74 self.attributes
75 .iter()
76 .map(|(key, value)| (key, value.reflect_value()))
77 }
78
79 pub fn len(&self) -> usize {
81 self.attributes.len()
82 }
83
84 pub fn is_empty(&self) -> bool {
86 self.attributes.is_empty()
87 }
88}
89
90impl Debug for CustomAttributes {
91 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
92 f.debug_set().entries(self.attributes.values()).finish()
93 }
94}
95
96struct CustomAttribute {
97 value: Box<dyn Reflect>,
98}
99
100impl CustomAttribute {
101 pub fn new<T: Reflect>(value: T) -> Self {
102 Self {
103 value: Box::new(value),
104 }
105 }
106
107 pub fn value<T: Reflect>(&self) -> Option<&T> {
108 self.value.downcast_ref()
109 }
110
111 pub fn reflect_value(&self) -> &dyn Reflect {
112 &*self.value
113 }
114}
115
116impl Debug for CustomAttribute {
117 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
118 self.value.debug(f)
119 }
120}
121
122macro_rules! impl_custom_attribute_methods {
139 ($self:ident . $attributes:ident, $term:literal) => {
140 $crate::attributes::impl_custom_attribute_methods!($self, &$self.$attributes, "item");
141 };
142 ($self:ident, $attributes:expr, $term:literal) => {
143 #[doc = concat!("Returns the custom attributes for this ", $term, ".")]
144 pub fn custom_attributes(&$self) -> &$crate::attributes::CustomAttributes {
145 $attributes
146 }
147
148 pub fn get_attribute<T: $crate::Reflect>(&$self) -> Option<&T> {
152 $self.custom_attributes().get::<T>()
153 }
154
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::{type_info::Typed, TypeInfo, VariantInfo};
182 use alloc::{format, string::String};
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}