diff --git a/godot-codegen/src/generator/enums.rs b/godot-codegen/src/generator/enums.rs index 1599e48d7..4b1704b94 100644 --- a/godot-codegen/src/generator/enums.rs +++ b/godot-codegen/src/generator/enums.rs @@ -11,6 +11,7 @@ use std::collections::HashSet; +use heck::ToPascalCase; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; @@ -45,6 +46,7 @@ pub fn make_enum_definition_with( // Things needed for the type definition let derives = enum_.derives(); let enum_doc = make_enum_doc(enum_); + let enum_hint_string = make_enum_hint_string(enum_); let name = &enum_.name; // Values @@ -105,9 +107,28 @@ pub fn make_enum_definition_with( // Trait implementations. let engine_trait_impl = make_enum_engine_trait_impl(enum_, enum_bitmask.as_ref()); + let property_hint = if enum_.is_bitfield { + quote! { crate::global::PropertyHint::FLAGS } + } else { + quote! { crate::global::PropertyHint::ENUM } + }; let index_enum_impl = make_enum_index_impl(enum_); let bitwise_impls = make_enum_bitwise_operators(enum_, enum_bitmask.as_ref()); + let var_trait_set_property = if enum_.is_exhaustive { + quote! { + fn set_property(&mut self, value: Self::Via) { + *self = ::from_ord(value); + } + } + } else { + quote! { + fn set_property(&mut self, value: Self::Via) { + self.ord = value; + } + } + }; + quote! { #engine_trait_impl #index_enum_impl @@ -131,6 +152,23 @@ pub fn make_enum_definition_with( .ok_or_else(|| crate::meta::error::FromGodotError::InvalidEnum.into_error(via)) } } + + impl crate::registry::property::Var for #name { + fn get_property(&self) -> Self::Via{ + ::ord(*self) + } + + #var_trait_set_property + + fn var_hint() -> crate::meta::PropertyHintInfo{ + crate::meta::PropertyHintInfo{ + hint: #property_hint, + hint_string: crate::builtin::GString::from(#enum_hint_string), + } + } + } + + impl crate::registry::property::Export for #name {} } }); @@ -481,6 +519,17 @@ fn make_enum_doc(enum_: &Enum) -> Vec { docs } +/// Returns the hint string for the given enum. +/// +/// e.g.: "Left,Center,Right,Fill" +fn make_enum_hint_string(enum_: &Enum) -> String { + enum_ + .enumerators + .iter() + .map(|enumerator| enumerator.name.to_string().to_pascal_case()) + .collect::>() + .join(",") +} /// Creates a definition for `enumerator` of the type `enum_type`. /// diff --git a/godot-core/src/registry/property/phantom_var.rs b/godot-core/src/registry/property/phantom_var.rs index 8d60c2799..360b229b1 100644 --- a/godot-core/src/registry/property/phantom_var.rs +++ b/godot-core/src/registry/property/phantom_var.rs @@ -10,7 +10,7 @@ use std::fmt; use std::hash::{Hash, Hasher}; use std::marker::PhantomData; -use crate::meta::{ClassId, GodotConvert, GodotType, PropertyHintInfo}; +use crate::meta::{ClassId, GodotConvert, PropertyHintInfo}; use crate::registry::property::{Export, Var}; /// A zero-sized type for creating a property without a backing field, accessible only through custom getter/setter functions. @@ -55,15 +55,15 @@ use crate::registry::property::{Export, Var}; /// This field can now be accessed from GDScript as `banner.text`. // Bounds for T are somewhat un-idiomatically directly on the type, rather than impls. // This improves error messages in IDEs when using the type as a field. -pub struct PhantomVar(PhantomData); +pub struct PhantomVar(PhantomData); -impl GodotConvert for PhantomVar { - type Via = T; +impl GodotConvert for PhantomVar { + type Via = ::Via; } // `PhantomVar` supports only part of `Var`, but it has to implement it, otherwise we cannot implement `Export` either. // The `GodotClass` derive macro should ensure that the `Var` implementation is not used. -impl Var for PhantomVar { +impl Var for PhantomVar { fn get_property(&self) -> Self::Via { unreachable!("code generated by GodotClass should call the custom getter") } @@ -78,7 +78,7 @@ impl Var for PhantomVar { } // Reuse values from `T`, if any. -impl Export for PhantomVar { +impl Export for PhantomVar { fn export_hint() -> PropertyHintInfo { ::export_hint() } @@ -88,7 +88,7 @@ impl Export for PhantomVar { } } -impl Default for PhantomVar { +impl Default for PhantomVar { fn default() -> Self { Self(Default::default()) } @@ -97,49 +97,49 @@ impl Default for PhantomVar { // Like `PhantomData` from the Rust standard library, `PhantomVar` implements many common traits like `Eq` and `Hash` // to allow these traits to be derived on containing structs as well. -impl Clone for PhantomVar { +impl Clone for PhantomVar { fn clone(&self) -> Self { *self } } -impl Copy for PhantomVar {} +impl Copy for PhantomVar {} -impl fmt::Debug for PhantomVar { +impl fmt::Debug for PhantomVar { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_tuple("PhantomVar").finish() } } -impl PartialEq for PhantomVar { +impl PartialEq for PhantomVar { fn eq(&self, _other: &Self) -> bool { true } } -impl Eq for PhantomVar {} +impl Eq for PhantomVar {} -impl PartialOrd for PhantomVar { +impl PartialOrd for PhantomVar { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } -impl Ord for PhantomVar { +impl Ord for PhantomVar { fn cmp(&self, _other: &Self) -> Ordering { Ordering::Equal } } -impl Hash for PhantomVar { +impl Hash for PhantomVar { fn hash(&self, _state: &mut H) {} } // SAFETY: This type contains no data. -unsafe impl Send for PhantomVar {} +unsafe impl Send for PhantomVar {} // SAFETY: This type contains no data. -unsafe impl Sync for PhantomVar {} +unsafe impl Sync for PhantomVar {} /// This type exists only as a place to add `compile_fail` doctests for `PhantomVar`, which do not need to be in the public documentation. /// diff --git a/itest/godot/ManualFfiTests.gd b/itest/godot/ManualFfiTests.gd index fae4ed896..5e7bd8b4d 100644 --- a/itest/godot/ManualFfiTests.gd +++ b/itest/godot/ManualFfiTests.gd @@ -345,6 +345,18 @@ func test_phantom_var_writing_read_only(): obj.read_only = 1 assert_fail("HasPhantomVar.read_only should not be writable") +func test_phantom_var_enum(): + var obj := HasPhantomVar.new() + + assert_eq(obj.read_write_engine_enum, VerticalAlignment.VERTICAL_ALIGNMENT_CENTER) + assert_eq(obj.read_write_bit_enum, KeyModifierMask.KEY_MASK_ALT | KeyModifierMask.KEY_MASK_CTRL) + + obj.read_write_engine_enum = VerticalAlignment.VERTICAL_ALIGNMENT_TOP + obj.read_write_bit_enum = KeyModifierMask.KEY_MASK_ALT | KeyModifierMask.KEY_MASK_SHIFT + + assert_eq(obj.read_write_engine_enum, VerticalAlignment.VERTICAL_ALIGNMENT_TOP) + assert_eq(obj.read_write_bit_enum, KeyModifierMask.KEY_MASK_ALT | KeyModifierMask.KEY_MASK_SHIFT) + func test_option_export(): var obj := OptionExportFfiTest.new() diff --git a/itest/rust/src/object_tests/phantom_var_test.rs b/itest/rust/src/object_tests/phantom_var_test.rs index 851286dfb..9be71bd0b 100644 --- a/itest/rust/src/object_tests/phantom_var_test.rs +++ b/itest/rust/src/object_tests/phantom_var_test.rs @@ -17,7 +17,19 @@ struct HasPhantomVar { #[var(get = get_read_write, set = set_read_write)] read_write: PhantomVar, + #[var(get = get_engine_enum,set = set_engine_enum)] + read_write_engine_enum: PhantomVar, + + #[var(get = get_bit_enum,set = set_bit_enum)] + read_write_bit_enum: PhantomVar, + value: i64, + + #[init(val = godot::global::VerticalAlignment::CENTER)] + engine_enum_value: godot::global::VerticalAlignment, + + #[init(val = godot::global::KeyModifierMask::ALT|godot::global::KeyModifierMask::CTRL)] + bit_enum_value: godot::global::KeyModifierMask, } #[godot_api] @@ -36,4 +48,24 @@ impl HasPhantomVar { fn set_read_write(&mut self, value: i64) { self.value = value; } + + #[func] + fn get_engine_enum(&self) -> godot::global::VerticalAlignment { + self.engine_enum_value + } + + #[func] + fn set_engine_enum(&mut self, value: godot::global::VerticalAlignment) { + self.engine_enum_value = value; + } + + #[func] + fn get_bit_enum(&self) -> godot::global::KeyModifierMask { + self.bit_enum_value + } + + #[func] + fn set_bit_enum(&mut self, value: godot::global::KeyModifierMask) { + self.bit_enum_value = value; + } }