Skip to content

File tree

4 files changed

+112
-1
lines changed

4 files changed

+112
-1
lines changed

gdnative-core/src/export/property.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,31 @@ impl PropertyUsage {
323323
}
324324
}
325325

326+
/// Marker for defining property groups.
327+
#[derive(Debug)]
328+
pub struct GroupMarker;
329+
330+
impl crate::core_types::ToVariant for GroupMarker {
331+
#[inline]
332+
fn to_variant(&self) -> Variant {
333+
Variant::nil()
334+
}
335+
}
336+
337+
impl crate::core_types::FromVariant for GroupMarker {
338+
#[inline]
339+
fn from_variant(variant: &Variant) -> Result<Self, FromVariantError> {
340+
if variant.is_nil() {
341+
Ok(Self)
342+
} else {
343+
Err(FromVariantError::InvalidVariantType {
344+
variant_type: variant.get_type(),
345+
expected: VariantType::Nil,
346+
})
347+
}
348+
}
349+
}
350+
326351
/// Placeholder type for exported properties with no backing field.
327352
///
328353
/// This is the go-to type whenever you want to expose a getter/setter to GDScript, which
@@ -591,4 +616,13 @@ mod impl_export {
591616
hint.unwrap_or_default().export_info()
592617
}
593618
}
619+
620+
impl Export for GroupMarker {
621+
type Hint = hint::GroupHint;
622+
623+
#[inline]
624+
fn export_info(hint: Option<Self::Hint>) -> ExportInfo {
625+
hint.unwrap_or_default().export_info()
626+
}
627+
}
594628
}

gdnative-core/src/export/property/hint.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,3 +465,29 @@ impl ArrayHint {
465465
}
466466
}
467467
}
468+
469+
/// Provides a hint prefix for group members.
470+
#[derive(Clone, Debug, Default)]
471+
pub struct GroupHint {
472+
prefix: String,
473+
}
474+
475+
impl GroupHint {
476+
/// Returns a `GroupHint` with a specific prefix.
477+
#[inline]
478+
pub fn new<S: ToString>(prefix: S) -> Self {
479+
Self {
480+
prefix: prefix.to_string(),
481+
}
482+
}
483+
484+
#[inline]
485+
pub fn export_info(self) -> ExportInfo {
486+
ExportInfo {
487+
variant_type: VariantType::Nil,
488+
// hint_kind: sys::godot_property_hint_GODOT_PROPERTY_HINT_TYPE_STRING,
489+
hint_kind: sys::godot_property_hint_GODOT_PROPERTY_HINT_NONE,
490+
hint_string: GodotString::from_str(&self.prefix),
491+
}
492+
}
493+
}

gdnative-derive/src/native_script.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,32 @@ pub(crate) fn derive_native_class(derive_input: &DeriveInput) -> Result<TokenStr
6666
.map(|(ident, config)| {
6767
let with_default = config
6868
.default
69+
.as_ref()
6970
.map(|default_value| quote!(.with_default(#default_value)));
70-
let with_hint = config.hint.map(|hint_fn| quote!(.with_hint(#hint_fn())));
71+
let with_hint = config
72+
.hint
73+
.as_ref()
74+
.map(|hint_fn| quote!(.with_hint(#hint_fn())))
75+
.or_else(|| {
76+
if let Some(Some(prefix)) = &config.group {
77+
Some(quote!(.with_hint(::gdnative::export::hint::GroupHint::new(#prefix))))
78+
} else {
79+
None
80+
}
81+
});
82+
7183
let with_usage = if config.no_editor {
7284
Some(quote!(.with_usage(::gdnative::export::PropertyUsage::NOEDITOR)))
85+
} else if config.group.is_some() {
86+
Some(quote!(.with_usage({
87+
::gdnative::export::PropertyUsage::DEFAULT
88+
| ::gdnative::export::PropertyUsage::GROUP
89+
})))
90+
} else if config.category {
91+
Some(quote!(.with_usage({
92+
::gdnative::export::PropertyUsage::DEFAULT
93+
| ::gdnative::export::PropertyUsage::CATEGORY
94+
})))
7395
} else {
7496
None
7597
};

gdnative-derive/src/native_script/property_args.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ pub struct PropertyAttrArgs {
2525
pub set: Option<PropertySet>,
2626
pub after_set: Option<syn::Path>,
2727
pub no_editor: bool,
28+
pub group: Option<Option<String>>,
29+
pub category: bool,
2830
}
2931

3032
pub struct PropertyAttrArgsBuilder {
@@ -39,6 +41,8 @@ pub struct PropertyAttrArgsBuilder {
3941
set: Option<PropertySet>,
4042
after_set: Option<syn::Path>,
4143
no_editor: bool,
44+
group: Option<Option<String>>,
45+
category: bool,
4246
}
4347

4448
impl PropertyAttrArgsBuilder {
@@ -55,6 +59,8 @@ impl PropertyAttrArgsBuilder {
5559
set: None,
5660
after_set: None,
5761
no_editor: false,
62+
group: None,
63+
category: false,
5864
}
5965
}
6066

@@ -120,6 +126,23 @@ impl PropertyAttrArgsBuilder {
120126
));
121127
}
122128
}
129+
"group" => {
130+
let string = if let syn::Lit::Str(lit_str) = &pair.lit {
131+
lit_str.value()
132+
} else {
133+
return Err(syn::Error::new(
134+
pair.span(),
135+
"group hint is not a string literal".to_string(),
136+
));
137+
};
138+
139+
if let Some(old) = self.group.replace(Some(string)) {
140+
return Err(syn::Error::new(
141+
pair.span(),
142+
format!("there is already a group set: {:?}", old),
143+
));
144+
}
145+
}
123146
"before_get" => {
124147
let string = if let syn::Lit::Str(lit_str) = &pair.lit {
125148
lit_str.value()
@@ -285,6 +308,10 @@ impl PropertyAttrArgsBuilder {
285308
pub fn add_path(&mut self, path: &syn::Path) -> Result<(), syn::Error> {
286309
if path.is_ident("no_editor") {
287310
self.no_editor = true;
311+
} else if path.is_ident("group") {
312+
self.group = Some(None);
313+
} else if path.is_ident("category") {
314+
self.category = true;
288315
} else if path.is_ident("get") {
289316
if let Some(get) = self.get.replace(PropertyGet::Default) {
290317
return Err(syn::Error::new(
@@ -324,6 +351,8 @@ impl PropertyAttrArgsBuilder {
324351
set: self.set,
325352
after_set: self.after_set,
326353
no_editor: self.no_editor,
354+
group: self.group,
355+
category: self.category,
327356
}
328357
}
329358
}

0 commit comments

Comments
 (0)