bevy_reflect/
attributes.rs1use crate::Reflect;
4use alloc::boxed::Box;
5use bevy_utils::TypeIdMap;
6use core::{
7 any::TypeId,
8 fmt::{Debug, Formatter},
9};
10
11#[derive(Default)]
39pub struct CustomAttributes {
40 attributes: TypeIdMap<CustomAttribute>,
41}
42
43impl CustomAttributes {
44 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 pub fn contains<T: Reflect>(&self) -> bool {
56 self.attributes.contains_key(&TypeId::of::<T>())
57 }
58
59 pub fn contains_by_id(&self, id: TypeId) -> bool {
61 self.attributes.contains_key(&id)
62 }
63
64 pub fn get<T: Reflect>(&self) -> Option<&T> {
66 self.attributes.get(&TypeId::of::<T>())?.value::<T>()
67 }
68
69 pub fn get_by_id(&self, id: TypeId) -> Option<&dyn Reflect> {
71 Some(self.attributes.get(&id)?.reflect_value())
72 }
73
74 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 pub fn len(&self) -> usize {
83 self.attributes.len()
84 }
85
86 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 pub fn new<T: Reflect>(value: T) -> Self {
105 Self {
106 value: Box::new(value),
107 }
108 }
109
110 pub fn value<T: Reflect>(&self) -> Option<&T> {
112 self.value.downcast_ref()
113 }
114
115 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
127macro_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 pub fn get_attribute<T: $crate::Reflect>(&$self) -> Option<&T> {
157 $self.custom_attributes().get::<T>()
158 }
159
160 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}