Skip to content

Commit 1c0d080

Browse files
authored
Support OnEditor in scripts (#79)
Support for the `OnEditor` wrapper type to be used in script properties.
1 parent 91c1391 commit 1c0d080

File tree

9 files changed

+214
-15
lines changed

9 files changed

+214
-15
lines changed

derive/src/enums.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,16 @@ pub fn script_enum_derive(input: proc_macro::TokenStream) -> proc_macro::TokenSt
136136
}
137137
}
138138

139+
impl #godot_types::prelude::Var for #enum_ident {
140+
fn get_property(&self) -> Self::Via {
141+
self.into()
142+
}
143+
144+
fn set_property(&mut self, value: Self::Via) {
145+
*self = #godot_types::meta::FromGodot::try_from_godot(value).unwrap();
146+
}
147+
}
148+
139149
#derive_export
140150
};
141151

derive/src/impl_attribute.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,13 @@ pub fn godot_script_impl(
6060
let arg_rust_type = arg.ty.as_ref();
6161
let arg_type = rust_to_variant_type(arg.ty.as_ref()).unwrap();
6262

63-
if is_context_type(arg.ty.as_ref()) {
63+
if is_context_type(arg.ty.as_ref()) {
6464
(
6565
quote!(),
6666

6767
quote_spanned!(arg.span() => ctx,)
6868
)
69-
} else {
69+
} else {
7070
(
7171
quote_spanned! {
7272
arg.span() =>

derive/src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ fn derive_get_field_dispatch(field: &SpannedValue<FieldOpts>) -> TokenStream {
279279

280280
quote_spanned! {field.ty.span()=>
281281
#[allow(clippy::needless_borrow)]
282-
#field_name => Some(#godot_types::prelude::ToGodot::to_variant(&#accessor)),
282+
#field_name => Some(#godot_types::prelude::ToGodot::to_variant(&::godot_rust_script::GetScriptProperty::get_property(&#accessor))),
283283
}
284284
}
285285

@@ -313,7 +313,9 @@ fn derive_set_field_dispatch(field: &SpannedValue<FieldOpts>) -> TokenStream {
313313

314314
let assignment = match opts.set {
315315
Some(setter) => quote_spanned!(setter.span()=> #setter(self, local_value)),
316-
None => quote_spanned!(field.ty.span() => self.#field_ident = local_value),
316+
None => {
317+
quote_spanned!(field.ty.span() => ::godot_rust_script::SetScriptProperty::set_property(&mut self.#field_ident, local_value))
318+
}
317319
};
318320

319321
quote! {

rust-script/build.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66

77
fn main() {
88
godot_bindings::emit_godot_version_cfg();
9-
}
9+
}

rust-script/src/interface.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
mod export;
8+
mod on_editor;
89
mod signals;
910

1011
use std::marker::PhantomData;
@@ -18,6 +19,7 @@ use godot::prelude::{ConvertError, Gd, Object, StringName, Variant};
1819
pub use crate::runtime::Context;
1920

2021
pub use export::GodotScriptExport;
22+
pub use on_editor::OnEditor;
2123
#[expect(deprecated)]
2224
pub use signals::{ScriptSignal, Signal};
2325

@@ -135,6 +137,20 @@ impl<T: GodotScript> ToGodot for RsRef<T> {
135137
}
136138
}
137139

140+
impl<'v, T: GodotScript> ::godot::prelude::Var for RsRef<T>
141+
where
142+
Self: GodotConvert<Via = <Self as ToGodot>::ToVia<'v>>,
143+
Self: 'v,
144+
{
145+
fn get_property(&self) -> Self::Via {
146+
<Self as ToGodot>::to_godot(self)
147+
}
148+
149+
fn set_property(&mut self, value: Self::Via) {
150+
<Self as FromGodot>::from_godot(value);
151+
}
152+
}
153+
138154
#[derive(thiserror::Error, Debug)]
139155
pub enum GodotScriptCastError {
140156
#[error("Object has no script attached!")]
@@ -196,6 +212,59 @@ impl<T: GodotScript, B: Inherits<T::Base> + Inherits<Object>> CastToScript<T> fo
196212
}
197213
}
198214

215+
/// Script property access indirection
216+
///
217+
/// gdext uses this kind of indirection to allow conversion of the actual property value into a godot compatible type when accessing the
218+
/// property from the engine. This Trait separates the `::godot::prelude::Var` trait into it's get and set components for more granular
219+
/// requirements on the property types.
220+
pub trait GetScriptProperty: GodotConvert {
221+
fn get_property(&self) -> Self::Via;
222+
}
223+
224+
/// Script property write indirection
225+
///
226+
/// gdext uses this kind of indirection to allow conversion of the actual property value from a godot compatible type when setting the
227+
/// property from the engine. This Trait separates the `::godot::prelude::Var` trait into it's get and set components for more granular
228+
/// requirements on the property types.
229+
pub trait SetScriptProperty: GodotConvert {
230+
fn set_property(&mut self, value: Self::Via);
231+
}
232+
233+
/// Unified property init strategy.
234+
///
235+
/// Most of the time we can initialize a script property with the `Default` trait. To support cases where `Default` is not implemented we
236+
/// can manually implement this trait.
237+
pub trait InitScriptProperty {
238+
fn init_property() -> Self;
239+
}
240+
241+
impl<T> GetScriptProperty for T
242+
where
243+
T: godot::prelude::Var,
244+
{
245+
fn get_property(&self) -> Self::Via {
246+
T::get_property(self)
247+
}
248+
}
249+
250+
impl<T> SetScriptProperty for T
251+
where
252+
T: godot::prelude::Var,
253+
{
254+
fn set_property(&mut self, value: Self::Via) {
255+
T::set_property(self, value);
256+
}
257+
}
258+
259+
impl<T> InitScriptProperty for T
260+
where
261+
T: Default,
262+
{
263+
fn init_property() -> Self {
264+
Default::default()
265+
}
266+
}
267+
199268
#[macro_export]
200269
macro_rules! define_script_root {
201270
() => {

rust-script/src/interface/export.rs

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@ use godot::builtin::{
1515
};
1616
use godot::classes::{Node, Resource};
1717
use godot::global::PropertyHint;
18-
use godot::meta::{ArrayElement, FromGodot, GodotConvert, GodotType, ToGodot};
18+
use godot::meta::{ArrayElement, GodotConvert, GodotType};
1919
use godot::obj::{EngineEnum, Gd};
2020
use godot::prelude::GodotClass;
21+
use godot::register::property::BuiltinExport;
2122
use godot::sys::GodotFfi;
2223

23-
use super::{GodotScript, RsRef};
24+
use super::{GodotScript, OnEditor, RsRef};
2425

25-
pub trait GodotScriptExport: GodotConvert + FromGodot + ToGodot {
26+
pub trait GodotScriptExport {
2627
fn hint_string(custom_hint: Option<PropertyHint>, custom_string: Option<String>) -> String;
2728

2829
fn hint(custom: Option<PropertyHint>) -> PropertyHint;
@@ -78,11 +79,7 @@ impl<T: GodotScript> GodotScriptExport for RsRef<T> {
7879

7980
impl<T: GodotScriptExport> GodotScriptExport for Option<T>
8081
where
81-
for<'v> T: 'v,
82-
for<'v> <<T as ToGodot>::ToVia<'v> as GodotType>::Ffi: godot::sys::GodotNullableFfi,
83-
for<'f> <<T as GodotConvert>::Via as GodotType>::ToFfi<'f>: godot::sys::GodotNullableFfi,
84-
<<T as GodotConvert>::Via as GodotType>::Ffi: godot::sys::GodotNullableFfi,
85-
for<'v, 'f> <<T as ToGodot>::ToVia<'v> as GodotType>::ToFfi<'f>: godot::sys::GodotNullableFfi,
82+
Self: GodotConvert + godot::prelude::Var,
8683
{
8784
fn hint_string(custom_hint: Option<PropertyHint>, custom_string: Option<String>) -> String {
8885
T::hint_string(custom_hint, custom_string)
@@ -113,6 +110,21 @@ impl<T: ArrayElement + GodotScriptExport + GodotType> GodotScriptExport for Arra
113110
}
114111
}
115112

113+
impl<T: GodotScriptExport> GodotScriptExport for OnEditor<T>
114+
where
115+
Self: GodotConvert + godot::prelude::Var,
116+
{
117+
fn hint_string(custom_hint: Option<PropertyHint>, custom_string: Option<String>) -> String {
118+
T::hint_string(custom_hint, custom_string)
119+
}
120+
121+
fn hint(custom: Option<PropertyHint>) -> PropertyHint {
122+
T::hint(custom)
123+
}
124+
}
125+
126+
impl<T: GodotScript> BuiltinExport for RsRef<T> {}
127+
116128
macro_rules! default_export {
117129
($ty:ty) => {
118130
impl GodotScriptExport for $ty {
@@ -193,7 +205,6 @@ default_export!(i8);
193205
default_export!(u32);
194206
default_export!(u16);
195207
default_export!(u8);
196-
default_export!(u64);
197208

198209
default_export!(Callable);
199210
default_export!(godot::builtin::Signal);
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
5+
*/
6+
7+
use std::{
8+
any::type_name,
9+
ops::{Deref, DerefMut},
10+
};
11+
12+
use godot::{
13+
classes::class_macros::sys::GodotNullableFfi,
14+
meta::{FromGodot, GodotConvert, GodotType, ToGodot},
15+
};
16+
17+
#[derive(Debug)]
18+
enum ValueState<T> {
19+
Invalid,
20+
Valid(T),
21+
}
22+
23+
#[derive(Debug)]
24+
pub struct OnEditor<T> {
25+
value: ValueState<T>,
26+
}
27+
28+
impl<T> Default for OnEditor<T> {
29+
fn default() -> Self {
30+
Self {
31+
value: ValueState::Invalid,
32+
}
33+
}
34+
}
35+
36+
impl<T: GodotConvert> GodotConvert for OnEditor<T>
37+
where
38+
for<'a> <T::Via as GodotType>::ToFfi<'a>: GodotNullableFfi,
39+
<T::Via as GodotType>::Ffi: GodotNullableFfi,
40+
{
41+
type Via = Option<T::Via>;
42+
}
43+
44+
impl<T> godot::prelude::Var for OnEditor<T>
45+
where
46+
for<'v> T: ToGodot<ToVia<'v> = <T as GodotConvert>::Via> + FromGodot + 'v,
47+
Self: GodotConvert<Via = Option<T::Via>>,
48+
{
49+
fn get_property(&self) -> Self::Via {
50+
match self.value {
51+
ValueState::Invalid => None,
52+
ValueState::Valid(ref value) => Some(value.to_godot()),
53+
}
54+
}
55+
56+
fn set_property(&mut self, value: Self::Via) {
57+
match value {
58+
Some(value) => self.value = ValueState::Valid(T::from_godot(value)),
59+
None => self.value = ValueState::Invalid,
60+
}
61+
}
62+
}
63+
64+
impl<T> Deref for OnEditor<T> {
65+
type Target = T;
66+
67+
fn deref(&self) -> &Self::Target {
68+
match self.value {
69+
ValueState::Invalid => panic!(
70+
"OnEditor property of type {} is uninitialized!",
71+
type_name::<T>()
72+
),
73+
74+
ValueState::Valid(ref value) => value,
75+
}
76+
}
77+
}
78+
79+
impl<T> DerefMut for OnEditor<T> {
80+
fn deref_mut(&mut self) -> &mut Self::Target {
81+
match self.value {
82+
ValueState::Invalid => panic!(
83+
"OnEditor property of type {} is uninitialized!",
84+
type_name::<T>()
85+
),
86+
87+
ValueState::Valid(ref mut value) => value,
88+
}
89+
}
90+
}

rust-script/src/interface/signals.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ use godot::obj::{Gd, GodotClass};
1717
use crate::static_script_registry::RustScriptPropDesc;
1818
use crate::{GodotScript, RsRef};
1919

20+
use super::GetScriptProperty;
21+
2022
pub trait SignalArguments {
2123
const COUNT: u8;
2224

@@ -220,3 +222,9 @@ impl<T: SignalArguments> ToGodot for ScriptSignal<T> {
220222
godot::builtin::Signal::from_object_signal(&self.host, self.name)
221223
}
222224
}
225+
226+
impl<T: SignalArguments> GetScriptProperty for ScriptSignal<T> {
227+
fn get_property(&self) -> Self::Via {
228+
self.to_godot()
229+
}
230+
}

rust-script/tests/script_derive.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ use godot::builtin::{Array, GString};
88
use godot::classes::{Node, Node3D};
99
use godot::obj::{Gd, NewAlloc};
1010
use godot_rust_script::{
11-
godot_script_impl, CastToScript, Context, GodotScript, GodotScriptEnum, RsRef, ScriptSignal,
11+
godot_script_impl, CastToScript, Context, GodotScript, GodotScriptEnum, OnEditor, RsRef,
12+
ScriptSignal,
1213
};
1314

1415
#[derive(Debug, Default, GodotScriptEnum)]
@@ -48,6 +49,9 @@ struct TestScript {
4849
#[export(ty = "Decal")]
4950
pub node_prop_2: Option<Gd<Node3D>>,
5051

52+
#[export(ty = "Decal")]
53+
pub node_prop_3: OnEditor<Gd<Node3D>>,
54+
5155
#[export]
5256
pub node_array: Array<Gd<Node3D>>,
5357

@@ -57,6 +61,11 @@ struct TestScript {
5761
#[export]
5862
pub custom_enum: ScriptEnum,
5963

64+
#[export]
65+
pub script_ref_opt: Option<RsRef<TestScript>>,
66+
67+
#[export]
68+
pub script_ref: OnEditor<RsRef<TestScript>>,
6069
base: Gd<<Self as GodotScript>::Base>,
6170
}
6271

0 commit comments

Comments
 (0)