bevy_reflect/
attributes.rs1use crate::Reflect;
2use bevy_utils::TypeIdMap;
3use core::fmt::{Debug, Formatter};
4use std::any::TypeId;
5
6#[derive(Default)]
34pub struct CustomAttributes {
35 attributes: TypeIdMap<CustomAttribute>,
36}
37
38impl CustomAttributes {
39 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 pub fn contains<T: Reflect>(&self) -> bool {
51 self.attributes.contains_key(&TypeId::of::<T>())
52 }
53
54 pub fn contains_by_id(&self, id: TypeId) -> bool {
56 self.attributes.contains_key(&id)
57 }
58
59 pub fn get<T: Reflect>(&self) -> Option<&T> {
61 self.attributes.get(&TypeId::of::<T>())?.value::<T>()
62 }
63
64 pub fn get_by_id(&self, id: TypeId) -> Option<&dyn Reflect> {
66 Some(self.attributes.get(&id)?.reflect_value())
67 }
68
69 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 pub fn len(&self) -> usize {
78 self.attributes.len()
79 }
80
81 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
119macro_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 pub fn get_attribute<T: $crate::Reflect>(&$self) -> Option<&T> {
150 $self.custom_attributes().get::<T>()
151 }
152
153 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}