|
5 | 5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
6 | 6 | */
|
7 | 7 |
|
8 |
| -use crate::builtin::{GString, Variant}; |
| 8 | +use crate::builtin::Variant; |
9 | 9 | use crate::meta::error::ConvertError;
|
10 | 10 | use crate::meta::{ClassName, FromGodot, GodotConvert, PropertyHintInfo, ToGodot};
|
11 | 11 | use crate::obj::guards::DynGdRef;
|
12 |
| -use crate::obj::{bounds, AsDyn, Bounds, DynGdMut, Gd, GodotClass, Inherits}; |
| 12 | +use crate::obj::{bounds, AsDyn, Bounds, DynGdMut, Gd, GodotClass, Inherits, OnEditor}; |
13 | 13 | use crate::registry::class::{get_dyn_property_hint_string, try_dynify_object};
|
14 | 14 | use crate::registry::property::{object_export_element_type_string, Export, Var};
|
15 | 15 | use crate::{meta, sys};
|
@@ -136,6 +136,25 @@ use std::{fmt, ops};
|
136 | 136 | /// godot-rust achieves this thanks to the registration done by `#[godot_dyn]`: the library knows for which classes `Health` is implemented,
|
137 | 137 | /// and it can query the dynamic type of the object. Based on that type, it can find the `impl Health` implementation matching the correct class.
|
138 | 138 | /// Behind the scenes, everything is wired up correctly so that you can restore the original `DynGd` even after it has passed through Godot.
|
| 139 | +/// |
| 140 | +/// # `#[export]` for `DynGd<T, D>` |
| 141 | +/// |
| 142 | +/// Exporting `DynGd<T, D>` is possible only via algebraic types such as [`OnEditor`] or [`Option`]. |
| 143 | +/// `DynGd<T, D>` can also be exported directly as an element of an array such as `Array<DynGd<T, D>>`. |
| 144 | +/// |
| 145 | +/// Since `DynGd<T, D>` expresses shared functionality `D` among classes inheriting `T`, |
| 146 | +/// `#[export]` for `DynGd<T, D>` where `T` is a concrete Rust class is not allowed. |
| 147 | +/// Consider using `Gd<T>` instead in such cases. |
| 148 | +/// |
| 149 | +/// ## Node based classes |
| 150 | +/// |
| 151 | +/// `#[export]` for a `DynGd<T, D>` works identically to `#[export]` `Gd<T>` for `T` inheriting Node classes. |
| 152 | +/// Godot will report an error if the conversion fails, but it will only do so when accessing the given value. |
| 153 | +/// |
| 154 | +/// ## Resource based classes |
| 155 | +/// |
| 156 | +/// `#[export]` for a `DynGd<T, D>` allows you to limit the available choices to implementors of a given trait `D` whose base inherits the specified `T` |
| 157 | +/// (for example, `#[export] DynGd<Resource, dyn MyTrait>` won't include Rust classes with an Object base, even if they implement `MyTrait`). |
139 | 158 | pub struct DynGd<T, D>
|
140 | 159 | where
|
141 | 160 | // T does _not_ require AsDyn<D> here. Otherwise, it's impossible to upcast (without implementing the relation for all base classes).
|
@@ -273,6 +292,22 @@ where
|
273 | 292 | pub fn into_gd(self) -> Gd<T> {
|
274 | 293 | self.obj
|
275 | 294 | }
|
| 295 | + |
| 296 | + /// Implementation shared between `OnEditor<DynGd<T, D>>` and `Option<DynGd<T, D>>`. |
| 297 | + #[doc(hidden)] |
| 298 | + fn set_property(&mut self, value: <Self as GodotConvert>::Via) |
| 299 | + where |
| 300 | + D: 'static, |
| 301 | + { |
| 302 | + // `set_property` can't be delegated to Gd<T>, since we have to set `erased_obj` as well. |
| 303 | + *self = <Self as FromGodot>::from_godot(value); |
| 304 | + } |
| 305 | + |
| 306 | + /// Implementation shared between `OnEditor<DynGd<T, D>>` and `Option<DynGd<T, D>>`. |
| 307 | + #[doc(hidden)] |
| 308 | + fn get_property(&self) -> <Self as GodotConvert>::Via { |
| 309 | + self.obj.to_godot() |
| 310 | + } |
276 | 311 | }
|
277 | 312 |
|
278 | 313 | impl<T, D> DynGd<T, D>
|
@@ -484,33 +519,90 @@ where
|
484 | 519 | }
|
485 | 520 | }
|
486 | 521 |
|
487 |
| -impl<T, D> Var for DynGd<T, D> |
| 522 | +impl<T, D> Var for Option<DynGd<T, D>> |
| 523 | +where |
| 524 | + T: GodotClass, |
| 525 | + D: ?Sized + 'static, |
| 526 | +{ |
| 527 | + fn get_property(&self) -> Self::Via { |
| 528 | + self.as_ref().map(|this| this.get_property()) |
| 529 | + } |
| 530 | + |
| 531 | + fn set_property(&mut self, value: Self::Via) { |
| 532 | + match (value, self.as_mut()) { |
| 533 | + (Some(new_value), Some(current_value)) => current_value.set_property(new_value), |
| 534 | + (Some(new_value), _) => *self = Some(<DynGd<T, D> as FromGodot>::from_godot(new_value)), |
| 535 | + (None, _) => *self = None, |
| 536 | + } |
| 537 | + } |
| 538 | +} |
| 539 | + |
| 540 | +/// `#[export]` for `Option<DynGd<T, D>>` is available only for `T` being Engine class (such as Node or Resource). |
| 541 | +/// |
| 542 | +/// Consider exporting `Option<Gd<T>>` instead of `Option<DynGd<T, D>>` for user-declared GDExtension classes. |
| 543 | +impl<T, D> Export for Option<DynGd<T, D>> |
| 544 | +where |
| 545 | + T: GodotClass + Bounds<Exportable = bounds::Yes, Declarer = bounds::DeclEngine>, |
| 546 | + D: ?Sized + 'static, |
| 547 | +{ |
| 548 | + fn export_hint() -> PropertyHintInfo { |
| 549 | + PropertyHintInfo::export_dyn_gd::<T, D>() |
| 550 | + } |
| 551 | + |
| 552 | + #[doc(hidden)] |
| 553 | + fn as_node_class() -> Option<ClassName> { |
| 554 | + PropertyHintInfo::object_as_node_class::<T>() |
| 555 | + } |
| 556 | +} |
| 557 | + |
| 558 | +impl<T, D> Default for OnEditor<DynGd<T, D>> |
| 559 | +where |
| 560 | + T: GodotClass, |
| 561 | + D: ?Sized + 'static, |
| 562 | +{ |
| 563 | + fn default() -> Self { |
| 564 | + OnEditor::gd_invalid() |
| 565 | + } |
| 566 | +} |
| 567 | + |
| 568 | +impl<T, D> GodotConvert for OnEditor<DynGd<T, D>> |
| 569 | +where |
| 570 | + T: GodotClass, |
| 571 | + D: ?Sized, |
| 572 | +{ |
| 573 | + type Via = Option<<DynGd<T, D> as GodotConvert>::Via>; |
| 574 | +} |
| 575 | + |
| 576 | +impl<T, D> Var for OnEditor<DynGd<T, D>> |
488 | 577 | where
|
489 | 578 | T: GodotClass,
|
490 | 579 | D: ?Sized + 'static,
|
491 | 580 | {
|
492 | 581 | fn get_property(&self) -> Self::Via {
|
493 |
| - self.obj.get_property() |
| 582 | + OnEditor::<DynGd<T, D>>::get_property_inner(self, <DynGd<T, D>>::get_property) |
494 | 583 | }
|
495 | 584 |
|
496 | 585 | fn set_property(&mut self, value: Self::Via) {
|
497 | 586 | // `set_property` can't be delegated to Gd<T>, since we have to set `erased_obj` as well.
|
498 |
| - *self = <Self as FromGodot>::from_godot(value); |
| 587 | + OnEditor::<DynGd<T, D>>::set_property_inner(self, value, <DynGd<T, D>>::set_property) |
499 | 588 | }
|
500 | 589 | }
|
501 | 590 |
|
502 |
| -impl<T, D> Export for DynGd<T, D> |
| 591 | +/// `#[export]` for `OnEditor<DynGd<T, D>>` is available only for `T` being Engine class (such as Node or Resource). |
| 592 | +/// |
| 593 | +/// Consider exporting `OnEditor<Gd<T>>` instead of `OnEditor<DynGd<T, D>>` for user-declared GDExtension classes. |
| 594 | +impl<T, D> Export for OnEditor<DynGd<T, D>> |
503 | 595 | where
|
504 |
| - T: GodotClass + Bounds<Exportable = bounds::Yes>, |
| 596 | + OnEditor<DynGd<T, D>>: Var, |
| 597 | + T: GodotClass + Bounds<Exportable = bounds::Yes, Declarer = bounds::DeclEngine>, |
505 | 598 | D: ?Sized + 'static,
|
506 | 599 | {
|
507 | 600 | fn export_hint() -> PropertyHintInfo {
|
508 |
| - PropertyHintInfo { |
509 |
| - hint_string: GString::from(get_dyn_property_hint_string::<T, D>()), |
510 |
| - ..<Gd<T> as Export>::export_hint() |
511 |
| - } |
| 601 | + PropertyHintInfo::export_dyn_gd::<T, D>() |
512 | 602 | }
|
| 603 | + |
| 604 | + #[doc(hidden)] |
513 | 605 | fn as_node_class() -> Option<ClassName> {
|
514 |
| - <Gd<T> as Export>::as_node_class() |
| 606 | + PropertyHintInfo::object_as_node_class::<T>() |
515 | 607 | }
|
516 | 608 | }
|
0 commit comments