use crate::meta::{ClassId, FromGodot, GodotConvert, GodotNullableType, ToGodot};
mod phantom_var;
pub use phantom_var::PhantomVar;
#[doc(alias = "property")]
#[diagnostic::on_unimplemented(
message = "`#[var]` properties require `Var` trait; #[export] ones require `Export` trait",
label = "type cannot be used as a property",
note = "see also: https://godot-rust.github.io/book/register/properties.html"
)]
pub trait Var: GodotConvert {
type PubType;
fn var_get(field: &Self) -> Self::Via;
fn var_set(field: &mut Self, value: Self::Via);
fn var_pub_get(field: &Self) -> Self::PubType;
fn var_pub_set(field: &mut Self, value: Self::PubType);
}
pub trait SimpleVar: ToGodot + FromGodot + Clone {}
impl<T> Var for T
where
T: SimpleVar,
{
type PubType = Self;
fn var_get(field: &Self) -> Self::Via {
<T as ToGodot>::to_godot_owned(field)
}
fn var_set(field: &mut Self, value: Self::Via) {
*field = <T as FromGodot>::from_godot(value);
}
fn var_pub_get(field: &Self) -> Self::PubType {
field.clone()
}
fn var_pub_set(field: &mut Self, value: Self::PubType) {
*field = value;
}
}
#[doc(alias = "property")]
#[diagnostic::on_unimplemented(
message = "`#[var]` properties require `Var` trait; #[export] ones require `Export` trait",
label = "type cannot be used as a property",
note = "see also: https://godot-rust.github.io/book/register/properties.html",
note = "`Gd` and `DynGd` cannot be exported directly; wrap them in `Option<...>` or `OnEditor<...>`."
)]
pub trait Export: Var {
#[allow(clippy::wrong_self_convention)]
#[doc(hidden)]
fn as_node_class() -> Option<ClassId> {
None
}
}
pub trait BuiltinExport {}
fn __var_doctests() {}
fn __export_doctests() {}
impl<T> Var for Option<T>
where
T: Var + FromGodot,
Option<T>: GodotConvert<Via = Option<T::Via>>,
{
type PubType = Option<T::Via>;
fn var_get(field: &Self) -> Self::Via {
field.as_ref().map(T::var_get)
}
fn var_set(field: &mut Self, value: Self::Via) {
match value {
Some(via) => match field {
Some(inner) => T::var_set(inner, via),
None => *field = Some(T::from_godot(via)),
},
None => *field = None,
}
}
fn var_pub_get(field: &Self) -> Self::PubType {
Self::var_get(field)
}
fn var_pub_set(field: &mut Self, value: Self::PubType) {
Self::var_set(field, value)
}
}
impl<T> Export for Option<T>
where
T: Export,
Option<T>: Var,
{
}
impl<T: GodotNullableType> BuiltinExport for Option<T> {}
pub mod export_fns {
use godot_ffi::VariantType;
use crate::builtin::GString;
use crate::meta::GodotConvert;
use crate::meta::shape::{GodotElementShape, GodotShape};
use crate::obj::EngineEnum;
use crate::registry::info::{PropertyHint, PropertyHintInfo};
use crate::registry::property::Export;
use crate::sys;
macro_rules! comma_separate_boolean_idents {
($( $ident:ident),* $(,)?) => {
{
let mut strings = Vec::new();
$(
if $ident {
strings.push(stringify!($ident));
}
)*
strings.join(",")
}
};
}
#[allow(clippy::too_many_arguments)]
pub fn export_range(
min: f64,
max: f64,
step: Option<f64>,
or_greater: bool,
or_less: bool,
exp: bool,
radians_as_degrees: bool,
degrees: bool,
hide_slider: bool,
suffix: Option<String>,
) -> PropertyHintInfo {
let hint_beginning = if let Some(step) = step {
format!("{min},{max},{step}")
} else {
format!("{min},{max}")
};
let rest = comma_separate_boolean_idents!(
or_greater,
or_less,
exp,
radians_as_degrees,
degrees,
hide_slider
);
let mut hint_string = hint_beginning;
if !rest.is_empty() {
hint_string.push_str(&format!(",{rest}"));
}
if let Some(suffix) = suffix {
hint_string.push_str(&format!(",suffix:{suffix}"));
}
PropertyHintInfo {
hint: PropertyHint::RANGE,
hint_string: GString::from(&hint_string),
}
}
#[doc(hidden)]
pub struct ExportValueWithKey<T> {
variant: String,
key: Option<T>,
}
impl<T: std::fmt::Display> ExportValueWithKey<T> {
fn as_hint_string(&self) -> String {
crate::meta::shape::format_hint_entry(&self.variant, self.key.as_ref())
}
fn slice_as_hint_string<V>(values: &[V]) -> String
where
for<'a> &'a V: Into<Self>,
{
let values = values
.iter()
.map(|v| v.into().as_hint_string())
.collect::<Vec<_>>();
values.join(",")
}
}
impl<T, S> From<&(S, Option<T>)> for ExportValueWithKey<T>
where
T: Clone,
S: AsRef<str>,
{
fn from((variant, key): &(S, Option<T>)) -> Self {
Self {
variant: variant.as_ref().into(),
key: key.clone(),
}
}
}
type ExportEnumEntry = ExportValueWithKey<i64>;
pub fn export_enum<T>(variants: &[T]) -> PropertyHintInfo
where
for<'a> &'a T: Into<ExportEnumEntry>,
{
let hint_string: String = ExportEnumEntry::slice_as_hint_string(variants);
PropertyHintInfo {
hint: PropertyHint::ENUM,
hint_string: GString::from(&hint_string),
}
}
pub fn export_exp_easing(attenuation: bool, positive_only: bool) -> PropertyHintInfo {
let hint_string = comma_separate_boolean_idents!(attenuation, positive_only);
PropertyHintInfo {
hint: PropertyHint::EXP_EASING,
hint_string: GString::from(&hint_string),
}
}
type BitFlag = ExportValueWithKey<u32>;
pub fn export_flags<T>(bits: &[T]) -> PropertyHintInfo
where
for<'a> &'a T: Into<BitFlag>,
{
let hint_string = BitFlag::slice_as_hint_string(bits);
PropertyHintInfo {
hint: PropertyHint::FLAGS,
hint_string: GString::from(&hint_string),
}
}
pub fn export_file_or_dir<T: Export>(
is_file: bool,
is_global: bool,
filter: impl AsRef<str>,
) -> PropertyHintInfo {
let filter = filter.as_ref();
sys::strict_assert!(is_file || filter.is_empty());
export_file_or_dir_inner(&T::Via::godot_shape(), is_file, is_global, filter)
}
fn export_file_or_dir_inner(
shape: &GodotShape,
is_file: bool,
is_global: bool,
filter: &str,
) -> PropertyHintInfo {
let hint = match (is_file, is_global) {
(true, true) => PropertyHint::GLOBAL_FILE,
(true, false) => PropertyHint::FILE,
(false, true) => PropertyHint::GLOBAL_DIR,
(false, false) => PropertyHint::DIR,
};
match shape {
GodotShape::Builtin {
variant_type: VariantType::STRING,
..
} => PropertyHintInfo {
hint,
hint_string: GString::from(filter),
},
#[cfg(since_api = "4.3")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.3")))]
GodotShape::Builtin {
variant_type: VariantType::PACKED_STRING_ARRAY,
..
} => to_string_array_hint(hint, filter),
#[cfg(since_api = "4.3")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.3")))]
GodotShape::TypedArray {
element:
GodotElementShape::Builtin {
variant_type: VariantType::STRING,
},
} => to_string_array_hint(hint, filter),
_ => {
let attribute_name = hint.as_str().to_lowercase();
#[cfg(since_api = "4.3")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.3")))]
panic!(
"#[export({attribute_name})] only supports GString, Array<String> or PackedStringArray field types\n\
encountered: {shape:?}"
);
#[cfg(before_api = "4.3")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.3")))]
panic!(
"#[export({attribute_name})] only supports GString type prior to Godot 4.3\n\
encountered: {shape:?}"
);
}
}
}
fn to_string_array_hint(hint: PropertyHint, filter: &str) -> PropertyHintInfo {
PropertyHintInfo {
hint: PropertyHint::TYPE_STRING,
hint_string: GString::from(&crate::meta::shape::format_elements_typed(
VariantType::STRING,
hint,
filter,
)),
}
}
pub fn export_placeholder<S: AsRef<str>>(placeholder: S) -> PropertyHintInfo {
PropertyHintInfo {
hint: PropertyHint::PLACEHOLDER_TEXT,
hint_string: GString::from(placeholder.as_ref()),
}
}
macro_rules! default_export_funcs {
(
$( $function_name:ident => $property_hint:ident, )*
) => {
$(
pub fn $function_name() -> PropertyHintInfo {
PropertyHintInfo {
hint: PropertyHint::$property_hint,
hint_string: GString::new()
}
}
)*
};
}
default_export_funcs!(
export_storage => NONE, export_flags_2d_physics => LAYERS_2D_PHYSICS,
export_flags_2d_render => LAYERS_2D_RENDER,
export_flags_2d_navigation => LAYERS_2D_NAVIGATION,
export_flags_3d_physics => LAYERS_3D_PHYSICS,
export_flags_3d_render => LAYERS_3D_RENDER,
export_flags_3d_navigation => LAYERS_3D_NAVIGATION,
export_multiline => MULTILINE_TEXT,
export_color_no_alpha => COLOR_NO_ALPHA,
);
}
mod export_impls {
use super::*;
use crate::builtin::*;
macro_rules! impl_property_by_godot_convert {
($Ty:ty, no_export) => {
impl SimpleVar for $Ty {}
};
($Ty:ty) => {
impl SimpleVar for $Ty {}
impl_property_by_godot_convert!(@export $Ty);
impl_property_by_godot_convert!(@builtin $Ty);
};
(@export $Ty:ty) => {
impl Export for $Ty {}
};
(@builtin $Ty:ty) => {
impl BuiltinExport for $Ty {}
}
}
impl_property_by_godot_convert!(Aabb);
impl_property_by_godot_convert!(Rect2);
impl_property_by_godot_convert!(Rect2i);
impl_property_by_godot_convert!(Basis);
impl_property_by_godot_convert!(Transform2D);
impl_property_by_godot_convert!(Transform3D);
impl_property_by_godot_convert!(Projection);
impl_property_by_godot_convert!(Vector2);
impl_property_by_godot_convert!(Vector2i);
impl_property_by_godot_convert!(Vector3);
impl_property_by_godot_convert!(Vector3i);
impl_property_by_godot_convert!(Vector4);
impl_property_by_godot_convert!(Vector4i);
impl_property_by_godot_convert!(Quaternion);
impl_property_by_godot_convert!(Plane);
impl_property_by_godot_convert!(GString);
impl_property_by_godot_convert!(StringName);
impl_property_by_godot_convert!(NodePath);
impl_property_by_godot_convert!(Color);
impl_property_by_godot_convert!(Variant);
impl_property_by_godot_convert!(f64);
impl_property_by_godot_convert!(i64);
impl_property_by_godot_convert!(bool);
impl_property_by_godot_convert!(f32);
impl_property_by_godot_convert!(i32);
impl_property_by_godot_convert!(i16);
impl_property_by_godot_convert!(i8);
impl_property_by_godot_convert!(u32);
impl_property_by_godot_convert!(u16);
impl_property_by_godot_convert!(u8);
impl_property_by_godot_convert!(Callable, no_export);
impl_property_by_godot_convert!(Signal, no_export);
impl_property_by_godot_convert!(Rid, no_export);
}