From d0a814cfbd8cefa843029bcdf97084307dbaf0d5 Mon Sep 17 00:00:00 2001 From: Jovan Gerodetti Date: Sat, 26 Jul 2025 22:16:07 +0200 Subject: [PATCH] Support OnEditor in scripts --- derive/src/enums.rs | 10 +++ derive/src/impl_attribute.rs | 4 +- derive/src/lib.rs | 6 +- rust-script/build.rs | 2 +- rust-script/src/interface.rs | 69 ++++++++++++++++++++ rust-script/src/interface/export.rs | 29 ++++++--- rust-script/src/interface/on_editor.rs | 90 ++++++++++++++++++++++++++ rust-script/src/interface/signals.rs | 8 +++ rust-script/tests/script_derive.rs | 11 +++- 9 files changed, 214 insertions(+), 15 deletions(-) create mode 100644 rust-script/src/interface/on_editor.rs diff --git a/derive/src/enums.rs b/derive/src/enums.rs index b8f323d..c3b2c08 100644 --- a/derive/src/enums.rs +++ b/derive/src/enums.rs @@ -136,6 +136,16 @@ pub fn script_enum_derive(input: proc_macro::TokenStream) -> proc_macro::TokenSt } } + impl #godot_types::prelude::Var for #enum_ident { + fn get_property(&self) -> Self::Via { + self.into() + } + + fn set_property(&mut self, value: Self::Via) { + *self = #godot_types::meta::FromGodot::try_from_godot(value).unwrap(); + } + } + #derive_export }; diff --git a/derive/src/impl_attribute.rs b/derive/src/impl_attribute.rs index 5159fa1..f3e2005 100644 --- a/derive/src/impl_attribute.rs +++ b/derive/src/impl_attribute.rs @@ -60,13 +60,13 @@ pub fn godot_script_impl( let arg_rust_type = arg.ty.as_ref(); let arg_type = rust_to_variant_type(arg.ty.as_ref()).unwrap(); - if is_context_type(arg.ty.as_ref()) { + if is_context_type(arg.ty.as_ref()) { ( quote!(), quote_spanned!(arg.span() => ctx,) ) - } else { + } else { ( quote_spanned! { arg.span() => diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 45fef51..c4a60cb 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -279,7 +279,7 @@ fn derive_get_field_dispatch(field: &SpannedValue) -> TokenStream { quote_spanned! {field.ty.span()=> #[allow(clippy::needless_borrow)] - #field_name => Some(#godot_types::prelude::ToGodot::to_variant(&#accessor)), + #field_name => Some(#godot_types::prelude::ToGodot::to_variant(&::godot_rust_script::GetScriptProperty::get_property(&#accessor))), } } @@ -313,7 +313,9 @@ fn derive_set_field_dispatch(field: &SpannedValue) -> TokenStream { let assignment = match opts.set { Some(setter) => quote_spanned!(setter.span()=> #setter(self, local_value)), - None => quote_spanned!(field.ty.span() => self.#field_ident = local_value), + None => { + quote_spanned!(field.ty.span() => ::godot_rust_script::SetScriptProperty::set_property(&mut self.#field_ident, local_value)) + } }; quote! { diff --git a/rust-script/build.rs b/rust-script/build.rs index 98d0e7a..51d03ac 100644 --- a/rust-script/build.rs +++ b/rust-script/build.rs @@ -6,4 +6,4 @@ fn main() { godot_bindings::emit_godot_version_cfg(); -} \ No newline at end of file +} diff --git a/rust-script/src/interface.rs b/rust-script/src/interface.rs index b40f6e9..48829ac 100644 --- a/rust-script/src/interface.rs +++ b/rust-script/src/interface.rs @@ -5,6 +5,7 @@ */ mod export; +mod on_editor; mod signals; use std::marker::PhantomData; @@ -18,6 +19,7 @@ use godot::prelude::{ConvertError, Gd, Object, StringName, Variant}; pub use crate::runtime::Context; pub use export::GodotScriptExport; +pub use on_editor::OnEditor; #[expect(deprecated)] pub use signals::{ScriptSignal, Signal}; @@ -135,6 +137,20 @@ impl ToGodot for RsRef { } } +impl<'v, T: GodotScript> ::godot::prelude::Var for RsRef +where + Self: GodotConvert::ToVia<'v>>, + Self: 'v, +{ + fn get_property(&self) -> Self::Via { + ::to_godot(self) + } + + fn set_property(&mut self, value: Self::Via) { + ::from_godot(value); + } +} + #[derive(thiserror::Error, Debug)] pub enum GodotScriptCastError { #[error("Object has no script attached!")] @@ -196,6 +212,59 @@ impl + Inherits> CastToScript fo } } +/// Script property access indirection +/// +/// gdext uses this kind of indirection to allow conversion of the actual property value into a godot compatible type when accessing the +/// property from the engine. This Trait separates the `::godot::prelude::Var` trait into it's get and set components for more granular +/// requirements on the property types. +pub trait GetScriptProperty: GodotConvert { + fn get_property(&self) -> Self::Via; +} + +/// Script property write indirection +/// +/// gdext uses this kind of indirection to allow conversion of the actual property value from a godot compatible type when setting the +/// property from the engine. This Trait separates the `::godot::prelude::Var` trait into it's get and set components for more granular +/// requirements on the property types. +pub trait SetScriptProperty: GodotConvert { + fn set_property(&mut self, value: Self::Via); +} + +/// Unified property init strategy. +/// +/// Most of the time we can initialize a script property with the `Default` trait. To support cases where `Default` is not implemented we +/// can manually implement this trait. +pub trait InitScriptProperty { + fn init_property() -> Self; +} + +impl GetScriptProperty for T +where + T: godot::prelude::Var, +{ + fn get_property(&self) -> Self::Via { + T::get_property(self) + } +} + +impl SetScriptProperty for T +where + T: godot::prelude::Var, +{ + fn set_property(&mut self, value: Self::Via) { + T::set_property(self, value); + } +} + +impl InitScriptProperty for T +where + T: Default, +{ + fn init_property() -> Self { + Default::default() + } +} + #[macro_export] macro_rules! define_script_root { () => { diff --git a/rust-script/src/interface/export.rs b/rust-script/src/interface/export.rs index 00224e3..51755e5 100644 --- a/rust-script/src/interface/export.rs +++ b/rust-script/src/interface/export.rs @@ -15,14 +15,15 @@ use godot::builtin::{ }; use godot::classes::{Node, Resource}; use godot::global::PropertyHint; -use godot::meta::{ArrayElement, FromGodot, GodotConvert, GodotType, ToGodot}; +use godot::meta::{ArrayElement, GodotConvert, GodotType}; use godot::obj::{EngineEnum, Gd}; use godot::prelude::GodotClass; +use godot::register::property::BuiltinExport; use godot::sys::GodotFfi; -use super::{GodotScript, RsRef}; +use super::{GodotScript, OnEditor, RsRef}; -pub trait GodotScriptExport: GodotConvert + FromGodot + ToGodot { +pub trait GodotScriptExport { fn hint_string(custom_hint: Option, custom_string: Option) -> String; fn hint(custom: Option) -> PropertyHint; @@ -78,11 +79,7 @@ impl GodotScriptExport for RsRef { impl GodotScriptExport for Option where - for<'v> T: 'v, - for<'v> <::ToVia<'v> as GodotType>::Ffi: godot::sys::GodotNullableFfi, - for<'f> <::Via as GodotType>::ToFfi<'f>: godot::sys::GodotNullableFfi, - <::Via as GodotType>::Ffi: godot::sys::GodotNullableFfi, - for<'v, 'f> <::ToVia<'v> as GodotType>::ToFfi<'f>: godot::sys::GodotNullableFfi, + Self: GodotConvert + godot::prelude::Var, { fn hint_string(custom_hint: Option, custom_string: Option) -> String { T::hint_string(custom_hint, custom_string) @@ -113,6 +110,21 @@ impl GodotScriptExport for Arra } } +impl GodotScriptExport for OnEditor +where + Self: GodotConvert + godot::prelude::Var, +{ + fn hint_string(custom_hint: Option, custom_string: Option) -> String { + T::hint_string(custom_hint, custom_string) + } + + fn hint(custom: Option) -> PropertyHint { + T::hint(custom) + } +} + +impl BuiltinExport for RsRef {} + macro_rules! default_export { ($ty:ty) => { impl GodotScriptExport for $ty { @@ -193,7 +205,6 @@ default_export!(i8); default_export!(u32); default_export!(u16); default_export!(u8); -default_export!(u64); default_export!(Callable); default_export!(godot::builtin::Signal); diff --git a/rust-script/src/interface/on_editor.rs b/rust-script/src/interface/on_editor.rs new file mode 100644 index 0000000..d6cc2a3 --- /dev/null +++ b/rust-script/src/interface/on_editor.rs @@ -0,0 +1,90 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +use std::{ + any::type_name, + ops::{Deref, DerefMut}, +}; + +use godot::{ + classes::class_macros::sys::GodotNullableFfi, + meta::{FromGodot, GodotConvert, GodotType, ToGodot}, +}; + +#[derive(Debug)] +enum ValueState { + Invalid, + Valid(T), +} + +#[derive(Debug)] +pub struct OnEditor { + value: ValueState, +} + +impl Default for OnEditor { + fn default() -> Self { + Self { + value: ValueState::Invalid, + } + } +} + +impl GodotConvert for OnEditor +where + for<'a> ::ToFfi<'a>: GodotNullableFfi, + ::Ffi: GodotNullableFfi, +{ + type Via = Option; +} + +impl godot::prelude::Var for OnEditor +where + for<'v> T: ToGodot = ::Via> + FromGodot + 'v, + Self: GodotConvert>, +{ + fn get_property(&self) -> Self::Via { + match self.value { + ValueState::Invalid => None, + ValueState::Valid(ref value) => Some(value.to_godot()), + } + } + + fn set_property(&mut self, value: Self::Via) { + match value { + Some(value) => self.value = ValueState::Valid(T::from_godot(value)), + None => self.value = ValueState::Invalid, + } + } +} + +impl Deref for OnEditor { + type Target = T; + + fn deref(&self) -> &Self::Target { + match self.value { + ValueState::Invalid => panic!( + "OnEditor property of type {} is uninitialized!", + type_name::() + ), + + ValueState::Valid(ref value) => value, + } + } +} + +impl DerefMut for OnEditor { + fn deref_mut(&mut self) -> &mut Self::Target { + match self.value { + ValueState::Invalid => panic!( + "OnEditor property of type {} is uninitialized!", + type_name::() + ), + + ValueState::Valid(ref mut value) => value, + } + } +} diff --git a/rust-script/src/interface/signals.rs b/rust-script/src/interface/signals.rs index 5e10220..f2e8195 100644 --- a/rust-script/src/interface/signals.rs +++ b/rust-script/src/interface/signals.rs @@ -17,6 +17,8 @@ use godot::obj::{Gd, GodotClass}; use crate::static_script_registry::RustScriptPropDesc; use crate::{GodotScript, RsRef}; +use super::GetScriptProperty; + pub trait SignalArguments { const COUNT: u8; @@ -220,3 +222,9 @@ impl ToGodot for ScriptSignal { godot::builtin::Signal::from_object_signal(&self.host, self.name) } } + +impl GetScriptProperty for ScriptSignal { + fn get_property(&self) -> Self::Via { + self.to_godot() + } +} diff --git a/rust-script/tests/script_derive.rs b/rust-script/tests/script_derive.rs index c527b4b..8215fe1 100644 --- a/rust-script/tests/script_derive.rs +++ b/rust-script/tests/script_derive.rs @@ -8,7 +8,8 @@ use godot::builtin::{Array, GString}; use godot::classes::{Node, Node3D}; use godot::obj::{Gd, NewAlloc}; use godot_rust_script::{ - godot_script_impl, CastToScript, Context, GodotScript, GodotScriptEnum, RsRef, ScriptSignal, + godot_script_impl, CastToScript, Context, GodotScript, GodotScriptEnum, OnEditor, RsRef, + ScriptSignal, }; #[derive(Debug, Default, GodotScriptEnum)] @@ -48,6 +49,9 @@ struct TestScript { #[export(ty = "Decal")] pub node_prop_2: Option>, + #[export(ty = "Decal")] + pub node_prop_3: OnEditor>, + #[export] pub node_array: Array>, @@ -57,6 +61,11 @@ struct TestScript { #[export] pub custom_enum: ScriptEnum, + #[export] + pub script_ref_opt: Option>, + + #[export] + pub script_ref: OnEditor>, base: Gd<::Base>, }