diff --git a/gdnative-core/src/export/property.rs b/gdnative-core/src/export/property.rs index bc98b5662..eaf49004a 100644 --- a/gdnative-core/src/export/property.rs +++ b/gdnative-core/src/export/property.rs @@ -323,6 +323,31 @@ impl PropertyUsage { } } +/// Marker for defining property groups. +#[derive(Debug)] +pub struct GroupMarker; + +impl crate::core_types::ToVariant for GroupMarker { + #[inline] + fn to_variant(&self) -> Variant { + Variant::nil() + } +} + +impl crate::core_types::FromVariant for GroupMarker { + #[inline] + fn from_variant(variant: &Variant) -> Result { + if variant.is_nil() { + Ok(Self) + } else { + Err(FromVariantError::InvalidVariantType { + variant_type: variant.get_type(), + expected: VariantType::Nil, + }) + } + } +} + /// Placeholder type for exported properties with no backing field. /// /// This is the go-to type whenever you want to expose a getter/setter to GDScript, which @@ -591,4 +616,13 @@ mod impl_export { hint.unwrap_or_default().export_info() } } + + impl Export for GroupMarker { + type Hint = hint::GroupHint; + + #[inline] + fn export_info(hint: Option) -> ExportInfo { + hint.unwrap_or_default().export_info() + } + } } diff --git a/gdnative-core/src/export/property/hint.rs b/gdnative-core/src/export/property/hint.rs index 4de05f7bf..653e3b599 100644 --- a/gdnative-core/src/export/property/hint.rs +++ b/gdnative-core/src/export/property/hint.rs @@ -465,3 +465,29 @@ impl ArrayHint { } } } + +/// Provides a hint prefix for group members. +#[derive(Clone, Debug, Default)] +pub struct GroupHint { + prefix: String, +} + +impl GroupHint { + /// Returns a `GroupHint` with a specific prefix. + #[inline] + pub fn new(prefix: S) -> Self { + Self { + prefix: prefix.to_string(), + } + } + + #[inline] + pub fn export_info(self) -> ExportInfo { + ExportInfo { + variant_type: VariantType::Nil, + // hint_kind: sys::godot_property_hint_GODOT_PROPERTY_HINT_TYPE_STRING, + hint_kind: sys::godot_property_hint_GODOT_PROPERTY_HINT_NONE, + hint_string: GodotString::from_str(&self.prefix), + } + } +} diff --git a/gdnative-derive/src/native_script.rs b/gdnative-derive/src/native_script.rs index 4c615dddc..39ed68e77 100644 --- a/gdnative-derive/src/native_script.rs +++ b/gdnative-derive/src/native_script.rs @@ -66,10 +66,32 @@ pub(crate) fn derive_native_class(derive_input: &DeriveInput) -> Result, pub after_set: Option, pub no_editor: bool, + pub group: Option>, + pub category: bool, } pub struct PropertyAttrArgsBuilder { @@ -39,6 +41,8 @@ pub struct PropertyAttrArgsBuilder { set: Option, after_set: Option, no_editor: bool, + group: Option>, + category: bool, } impl PropertyAttrArgsBuilder { @@ -55,6 +59,8 @@ impl PropertyAttrArgsBuilder { set: None, after_set: None, no_editor: false, + group: None, + category: false, } } @@ -120,6 +126,23 @@ impl PropertyAttrArgsBuilder { )); } } + "group" => { + let string = if let syn::Lit::Str(lit_str) = &pair.lit { + lit_str.value() + } else { + return Err(syn::Error::new( + pair.span(), + "group hint is not a string literal".to_string(), + )); + }; + + if let Some(old) = self.group.replace(Some(string)) { + return Err(syn::Error::new( + pair.span(), + format!("there is already a group set: {:?}", old), + )); + } + } "before_get" => { let string = if let syn::Lit::Str(lit_str) = &pair.lit { lit_str.value() @@ -285,6 +308,10 @@ impl PropertyAttrArgsBuilder { pub fn add_path(&mut self, path: &syn::Path) -> Result<(), syn::Error> { if path.is_ident("no_editor") { self.no_editor = true; + } else if path.is_ident("group") { + self.group = Some(None); + } else if path.is_ident("category") { + self.category = true; } else if path.is_ident("get") { if let Some(get) = self.get.replace(PropertyGet::Default) { return Err(syn::Error::new( @@ -324,6 +351,8 @@ impl PropertyAttrArgsBuilder { set: self.set, after_set: self.after_set, no_editor: self.no_editor, + group: self.group, + category: self.category, } } }