diff --git a/CHANGELOG.md b/CHANGELOG.md index 44f42ad5f..ba59bd515 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add cxx-qt-lib-extras crate which contains: `QCommandLineOption`, `QCommandLineParser`, `QElapsedTimer`, `QApplication` - Serde support for `QString` (requires "serde" feature on cxx-qt-lib) - A new QuickControls module, which exposes `QQuickStyle`. This module is enabled by default and is behind the `qt_quickcontrols` feature. +- Add support for specifying read write and notify in qproperty macro, including support for custom user defined functions ### Changed diff --git a/book/src/bridge/extern_rustqt.md b/book/src/bridge/extern_rustqt.md index a3bbe2ae8..5e30e268a 100644 --- a/book/src/bridge/extern_rustqt.md +++ b/book/src/bridge/extern_rustqt.md @@ -131,7 +131,23 @@ Where `` is the name of the property. These setters and getters assure that the changed signal is emitted every time the property is edited. -> Note that in the future it will be possible to specify custom getters and setters +It is also possible to specify custom getters, setters and notify signals, using flags passed like so: +`#[qproperty(TYPE, NAME, read = myGetter, write = mySetter, notify = myOnChanged)]` + +> Note: currently the rust name must be in camel case or specified like `#[cxx_name = "my_getter"]` if not + +It is also possible to use any combination of custom functions or omit them entirely, but if flags are specified, read must be included as all properties need to be able to be read. + +Using the read flag will cause CXX-Qt to generate a getter function with an automatic name based off the property. e.g. `#[qproperty(i32, num, read)]` will have a getter function generated called get_num in Rust, and getNum in C++. + +If a custom function is specified, an implementation both in qobject::MyObject and and export in the bridge is expected. + +### Examples + +- `#[qproperty(TYPE, NAME, read)]` A read only prop +- `#[qproperty(TYPE, NAME, read = myGetter, write, notify)]` custom getter provided but will generate setter and on-changed +- `#[qproperty(TYPE, NAME)]` is syntactic sugar for `#[qproperty(TYPE, NAME, read, write, notify)]` +- `#[qproperty(TYPE, NAME, write)]` is an error as read was not explicitly passed ## Methods diff --git a/crates/cxx-qt-gen/Cargo.toml b/crates/cxx-qt-gen/Cargo.toml index d0b90eedf..4952752bc 100644 --- a/crates/cxx-qt-gen/Cargo.toml +++ b/crates/cxx-qt-gen/Cargo.toml @@ -6,7 +6,11 @@ [package] name = "cxx-qt-gen" version.workspace = true -authors = ["Andrew Hayzen ", "Gerhard de Clercq ", "Leon Matthes "] +authors = [ + "Andrew Hayzen ", + "Gerhard de Clercq ", + "Leon Matthes ", +] edition.workspace = true license.workspace = true description = "Code generation for integrating `cxx-qt` into higher level tools" diff --git a/crates/cxx-qt-gen/src/generator/cpp/property/getter.rs b/crates/cxx-qt-gen/src/generator/cpp/property/getter.rs index d9823cfe1..f209b5e52 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/property/getter.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/property/getter.rs @@ -3,33 +3,47 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 -use crate::generator::{cpp::fragment::CppFragment, naming::property::QPropertyNames}; +use crate::generator::{ + cpp::fragment::CppFragment, + naming::property::{NameState, QPropertyNames}, +}; use indoc::formatdoc; -pub fn generate(idents: &QPropertyNames, qobject_ident: &str, return_cxx_ty: &str) -> CppFragment { - CppFragment::Pair { - header: format!( - "{return_cxx_ty} const& {ident_getter}() const;", - ident_getter = idents.getter.cxx_unqualified() - ), - source: formatdoc!( - r#" - {return_cxx_ty} const& - {qobject_ident}::{ident_getter}() const - {{ - const ::rust::cxxqt1::MaybeLockGuard<{qobject_ident}> guard(*this); - return {ident_getter_wrapper}(); - }} - "#, - ident_getter = idents.getter.cxx_unqualified(), - ident_getter_wrapper = idents.getter_wrapper.cxx_unqualified(), - ), +pub fn generate( + idents: &QPropertyNames, + qobject_ident: &str, + return_cxx_ty: &str, +) -> Option { + if let (NameState::Auto(name), Some(getter_wrapper)) = (&idents.getter, &idents.getter_wrapper) + { + Some(CppFragment::Pair { + header: format!( + "{return_cxx_ty} const& {ident_getter}() const;", + ident_getter = name.cxx_unqualified() + ), + source: formatdoc!( + r#" + {return_cxx_ty} const& + {qobject_ident}::{ident_getter}() const + {{ + const ::rust::cxxqt1::MaybeLockGuard<{qobject_ident}> guard(*this); + return {ident_getter_wrapper}(); + }} + "#, + ident_getter = name.cxx_unqualified(), + ident_getter_wrapper = getter_wrapper.cxx_unqualified(), + ), + }) + } else { + None } } -pub fn generate_wrapper(idents: &QPropertyNames, cxx_ty: &str) -> CppFragment { - CppFragment::Header(format!( - "{cxx_ty} const& {ident_getter_wrapper}() const noexcept;", - ident_getter_wrapper = idents.getter_wrapper.cxx_unqualified() - )) +pub fn generate_wrapper(idents: &QPropertyNames, cxx_ty: &str) -> Option { + idents.getter_wrapper.as_ref().map(|name| { + CppFragment::Header(format!( + "{cxx_ty} const& {ident_getter_wrapper}() const noexcept;", + ident_getter_wrapper = name.cxx_unqualified() + )) + }) } diff --git a/crates/cxx-qt-gen/src/generator/cpp/property/meta.rs b/crates/cxx-qt-gen/src/generator/cpp/property/meta.rs index 1b3a11602..4527c60fe 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/property/meta.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/property/meta.rs @@ -7,12 +7,23 @@ use crate::generator::naming::property::QPropertyNames; /// Generate the metaobject line for a given property pub fn generate(idents: &QPropertyNames, cxx_ty: &str) -> String { + let mut parts = vec![format!( + "READ {ident_getter}", + ident_getter = idents.getter.cxx_unqualified() + )]; + + if let Some(setter) = &idents.setter { + parts.push(format!("WRITE {}", setter.cxx_unqualified())); + } + + if let Some(notify) = &idents.notify { + parts.push(format!("NOTIFY {}", notify.cxx_unqualified())); + } + format!( - "Q_PROPERTY({ty} {ident} READ {ident_getter} WRITE {ident_setter} NOTIFY {ident_notify})", + "Q_PROPERTY({ty} {ident} {meta_parts})", ty = cxx_ty, ident = idents.name.cxx_unqualified(), - ident_getter = idents.getter.cxx_unqualified(), - ident_setter = idents.setter.cxx_unqualified(), - ident_notify = idents.notify.cxx_unqualified() + meta_parts = parts.join(" ") ) } diff --git a/crates/cxx-qt-gen/src/generator/cpp/property/mod.rs b/crates/cxx-qt-gen/src/generator/cpp/property/mod.rs index 4a47f17ab..847c1850b 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/property/mod.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/property/mod.rs @@ -32,19 +32,26 @@ pub fn generate_cpp_properties( let cxx_ty = syn_type_to_cpp_type(&property.ty, type_names)?; generated.metaobjects.push(meta::generate(&idents, &cxx_ty)); - generated - .methods - .push(getter::generate(&idents, &qobject_ident, &cxx_ty)); - generated - .private_methods - .push(getter::generate_wrapper(&idents, &cxx_ty)); - generated - .methods - .push(setter::generate(&idents, &qobject_ident, &cxx_ty)); - generated - .private_methods - .push(setter::generate_wrapper(&idents, &cxx_ty)); - signals.push(signal::generate(&idents, qobject_idents)); + + if let Some(getter) = getter::generate(&idents, &qobject_ident, &cxx_ty) { + generated.methods.push(getter); + } + + if let Some(getter_wrapper) = getter::generate_wrapper(&idents, &cxx_ty) { + generated.private_methods.push(getter_wrapper); + } + + if let Some(setter) = setter::generate(&idents, &qobject_ident, &cxx_ty) { + generated.methods.push(setter) + } + + if let Some(setter_wrapper) = setter::generate_wrapper(&idents, &cxx_ty) { + generated.private_methods.push(setter_wrapper) + } + + if let Some(notify) = signal::generate(&idents, qobject_idents) { + signals.push(notify) + } } generated.append(&mut generate_cpp_signals( @@ -60,25 +67,75 @@ pub fn generate_cpp_properties( mod tests { use super::*; + use crate::parser::property::QPropertyFlags; + use crate::generator::naming::qobject::tests::create_qobjectname; use crate::CppFragment; use indoc::indoc; use pretty_assertions::assert_str_eq; use quote::format_ident; - use syn::parse_quote; + use syn::{parse_quote, ItemStruct}; + + #[test] + fn test_custom_setter() { + let mut input: ItemStruct = parse_quote! { + #[qproperty(i32, num, read, write = mySetter)] + struct MyStruct; + }; + let property = ParsedQProperty::parse(input.attrs.remove(0)).unwrap(); + + let properties = vec![property]; + + let qobject_idents = create_qobjectname(); + + let type_names = TypeNames::mock(); + let generated = generate_cpp_properties(&properties, &qobject_idents, &type_names).unwrap(); + + assert_eq!(generated.metaobjects.len(), 1); + assert_str_eq!( + generated.metaobjects[0], + "Q_PROPERTY(::std::int32_t num READ getNum WRITE mySetter)" + ); + + // Methods + assert_eq!(generated.methods.len(), 1); + let (header, source) = if let CppFragment::Pair { header, source } = &generated.methods[0] { + (header, source) + } else { + panic!("Expected pair!") + }; + + assert_str_eq!(header, "::std::int32_t const& getNum() const;"); + assert_str_eq!( + source, + indoc! {r#" + ::std::int32_t const& + MyObject::getNum() const + { + const ::rust::cxxqt1::MaybeLockGuard guard(*this); + return getNumWrapper(); + } + "#} + ); + } #[test] fn test_generate_cpp_properties() { + let mut input1: ItemStruct = parse_quote! { + #[qproperty(i32, trivial_property, read, write, notify)] + struct MyStruct; + }; + + let mut input2: ItemStruct = parse_quote! { + #[qproperty(UniquePtr, opaque_property)] + struct MyStruct; + }; + let properties = vec![ - ParsedQProperty { - ident: format_ident!("trivial_property"), - ty: parse_quote! { i32 }, - }, - ParsedQProperty { - ident: format_ident!("opaque_property"), - ty: parse_quote! { UniquePtr }, - }, + ParsedQProperty::parse(input1.attrs.remove(0)).unwrap(), + ParsedQProperty::parse(input2.attrs.remove(0)).unwrap(), ]; + let qobject_idents = create_qobjectname(); let mut type_names = TypeNames::mock(); @@ -97,6 +154,7 @@ mod tests { } else { panic!("Expected pair!") }; + assert_str_eq!(header, "::std::int32_t const& getTrivialProperty() const;"); assert_str_eq!( source, @@ -136,6 +194,7 @@ mod tests { } else { panic!("Expected pair!") }; + assert_str_eq!( header, "::std::unique_ptr const& getOpaqueProperty() const;" @@ -358,6 +417,7 @@ mod tests { let properties = vec![ParsedQProperty { ident: format_ident!("mapped_property"), ty: parse_quote! { A }, + flags: QPropertyFlags::default(), }]; let qobject_idents = create_qobjectname(); diff --git a/crates/cxx-qt-gen/src/generator/cpp/property/setter.rs b/crates/cxx-qt-gen/src/generator/cpp/property/setter.rs index db439907b..c9f9878c4 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/property/setter.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/property/setter.rs @@ -3,35 +3,47 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 -use crate::generator::{cpp::fragment::CppFragment, naming::property::QPropertyNames}; +use crate::generator::{ + cpp::fragment::CppFragment, + naming::property::{NameState, QPropertyNames}, +}; use indoc::formatdoc; -pub fn generate(idents: &QPropertyNames, qobject_ident: &str, cxx_ty: &str) -> CppFragment { - CppFragment::Pair { - header: format!( - "Q_SLOT void {ident_setter}({cxx_ty} const& value);", - ident_setter = idents.setter.cxx_unqualified(), - ), - source: formatdoc! { - r#" - void - {qobject_ident}::{ident_setter}({cxx_ty} const& value) - {{ - const ::rust::cxxqt1::MaybeLockGuard<{qobject_ident}> guard(*this); - {ident_setter_wrapper}(value); - }} - "#, - ident_setter = idents.setter.cxx_unqualified(), - ident_setter_wrapper = idents.setter_wrapper.cxx_unqualified(), - }, +pub fn generate(idents: &QPropertyNames, qobject_ident: &str, cxx_ty: &str) -> Option { + // Only generates setter code if the state provided is Auto (not custom provided by user) + if let (Some(NameState::Auto(setter)), Some(setter_wrapper)) = + (&idents.setter, &idents.setter_wrapper) + { + Some(CppFragment::Pair { + header: format!( + "Q_SLOT void {ident_setter}({cxx_ty} const& value);", + ident_setter = setter.cxx_unqualified(), + ), + source: formatdoc! { + r#" + void + {qobject_ident}::{ident_setter}({cxx_ty} const& value) + {{ + const ::rust::cxxqt1::MaybeLockGuard<{qobject_ident}> guard(*this); + {ident_setter_wrapper}(value); + }} + "#, + ident_setter = setter.cxx_unqualified(), + ident_setter_wrapper = setter_wrapper.cxx_unqualified(), + }, + }) + } else { + None } } -pub fn generate_wrapper(idents: &QPropertyNames, cxx_ty: &str) -> CppFragment { - CppFragment::Header(format!( - // Note that we pass T not const T& to Rust so that it is by-value - // https://github.com/KDAB/cxx-qt/issues/463 - "void {ident_setter_wrapper}({cxx_ty} value) noexcept;", - ident_setter_wrapper = idents.setter_wrapper.cxx_unqualified() - )) +pub fn generate_wrapper(idents: &QPropertyNames, cxx_ty: &str) -> Option { + idents.setter_wrapper.as_ref().map(|setter_wrapper| { + CppFragment::Header(format!( + // Note that we pass T not const T& to Rust so that it is by-value + // https://github.com/KDAB/cxx-qt/issues/463 + "void {ident_setter_wrapper}({cxx_ty} value) noexcept;", + ident_setter_wrapper = setter_wrapper.cxx_unqualified() + )) + }) } diff --git a/crates/cxx-qt-gen/src/generator/cpp/property/signal.rs b/crates/cxx-qt-gen/src/generator/cpp/property/signal.rs index 2ef864052..bc2ccbac1 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/property/signal.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/property/signal.rs @@ -6,24 +6,32 @@ use syn::ForeignItemFn; use crate::{ - generator::naming::{property::QPropertyNames, qobject::QObjectNames}, + generator::naming::{ + property::{NameState, QPropertyNames}, + qobject::QObjectNames, + }, parser::signals::ParsedSignal, }; -pub fn generate(idents: &QPropertyNames, qobject_idents: &QObjectNames) -> ParsedSignal { +pub fn generate(idents: &QPropertyNames, qobject_idents: &QObjectNames) -> Option { // We build our signal in the generation phase as we need to use the naming // structs to build the signal name - let cpp_class_rust = &qobject_idents.name.rust_unqualified(); - let notify_cpp = &idents.notify.cxx_unqualified(); - let notify_rust = idents.notify.rust_unqualified(); - let method: ForeignItemFn = syn::parse_quote! { - #[doc = "Notify for the Q_PROPERTY"] - #[cxx_name = #notify_cpp] - fn #notify_rust(self: Pin<&mut #cpp_class_rust>); - }; - ParsedSignal::from_property_method( - method, - idents.notify.clone(), - qobject_idents.name.rust_unqualified().clone(), - ) + if let Some(NameState::Auto(notify)) = &idents.notify { + let cpp_class_rust = &qobject_idents.name.rust_unqualified(); + let notify_cpp = notify.cxx_unqualified(); + let notify_rust = notify.rust_unqualified(); + let method: ForeignItemFn = syn::parse_quote! { + #[doc = "Notify for the Q_PROPERTY"] + #[cxx_name = #notify_cpp] + fn #notify_rust(self: Pin<&mut #cpp_class_rust>); + }; + + Some(ParsedSignal::from_property_method( + method, + notify.clone(), + qobject_idents.name.rust_unqualified().clone(), + )) + } else { + None + } } diff --git a/crates/cxx-qt-gen/src/generator/naming/property.rs b/crates/cxx-qt-gen/src/generator/naming/property.rs index 96bd38578..160343106 100644 --- a/crates/cxx-qt-gen/src/generator/naming/property.rs +++ b/crates/cxx-qt-gen/src/generator/naming/property.rs @@ -2,32 +2,89 @@ // SPDX-FileContributor: Andrew Hayzen // // SPDX-License-Identifier: MIT OR Apache-2.0 -use crate::{naming::Name, parser::property::ParsedQProperty}; +use crate::{ + naming::Name, + parser::property::{FlagState, ParsedQProperty}, +}; use convert_case::{Case, Casing}; use quote::format_ident; use syn::Ident; +use std::ops::Deref; + +#[derive(Debug)] +pub enum NameState { + Auto(Name), + Custom(Name), +} + +impl Deref for NameState { + type Target = Name; + + fn deref(&self) -> &Self::Target { + match self { + Self::Auto(name) => name, + Self::Custom(name) => name, + } + } +} + +impl NameState { + pub fn from_flag_with_auto_fn(state: &FlagState, auto_fn: impl Fn() -> Name) -> Self { + match state { + FlagState::Auto => Self::Auto(auto_fn()), + FlagState::Custom(ident) => Self::Custom(Name::new(ident.clone())), // TODO: replace this with some sort of type / method name lookup + } + } +} + /// Names for parts of a Q_PROPERTY pub struct QPropertyNames { pub name: Name, - pub getter: Name, - pub getter_wrapper: Name, - pub setter: Name, - pub setter_wrapper: Name, - pub notify: Name, + pub getter: NameState, + pub getter_wrapper: Option, + pub setter: Option, + pub setter_wrapper: Option, + pub notify: Option, } impl From<&ParsedQProperty> for QPropertyNames { fn from(property: &ParsedQProperty) -> Self { let property_name = property_name_from_rust_name(property.ident.clone()); - let getter = getter_name_from_property(&property_name); - let setter = setter_name_from_property(&property_name); + + // Cache flags as they are accessed multiple times + let flags = &property.flags; + + let getter = NameState::from_flag_with_auto_fn(&flags.read, || { + getter_name_from_property(&property_name) + }); + + let setter = flags.write.as_ref().map(|setter| { + NameState::from_flag_with_auto_fn(setter, || setter_name_from_property(&property_name)) + }); + + let notify = flags.notify.as_ref().map(|notify| { + NameState::from_flag_with_auto_fn(notify, || notify_name_from_property(&property_name)) + }); + + let setter_wrapper = if let Some(NameState::Auto(ref setter)) = setter { + Some(wrapper_name_from_function_name(setter)) + } else { + None + }; + + let getter_wrapper = if let NameState::Auto(ref getter) = getter { + Some(wrapper_name_from_function_name(getter)) + } else { + None + }; + Self { - getter_wrapper: wrapper_name_from_function_name(&getter), + getter_wrapper, getter, - setter_wrapper: wrapper_name_from_function_name(&setter), + setter_wrapper, setter, - notify: notify_name_from_property(&property_name), + notify, name: property_name, } } @@ -77,12 +134,14 @@ pub mod tests { use syn::parse_quote; use super::*; + use crate::parser::property::QPropertyFlags; pub fn create_i32_qpropertyname() -> QPropertyNames { let ty: syn::Type = parse_quote! { i32 }; let property = ParsedQProperty { ident: format_ident!("my_property"), ty, + flags: QPropertyFlags::default(), }; QPropertyNames::from(&property) } @@ -97,14 +156,20 @@ pub mod tests { names.getter.rust_unqualified(), &format_ident!("my_property") ); - assert_eq!(names.setter.cxx_unqualified(), "setMyProperty"); assert_eq!( - names.setter.rust_unqualified(), + names.setter.as_ref().unwrap().cxx_unqualified(), + "setMyProperty" + ); + assert_eq!( + names.setter.as_ref().unwrap().rust_unqualified(), &format_ident!("set_my_property") ); - assert_eq!(names.notify.cxx_unqualified(), "myPropertyChanged"); assert_eq!( - names.notify.rust_unqualified(), + names.notify.as_ref().unwrap().cxx_unqualified(), + "myPropertyChanged" + ); + assert_eq!( + names.notify.as_ref().unwrap().rust_unqualified(), &format_ident!("my_property_changed") ); } diff --git a/crates/cxx-qt-gen/src/generator/rust/property/getter.rs b/crates/cxx-qt-gen/src/generator/rust/property/getter.rs index a80731b2e..0bfc369af 100644 --- a/crates/cxx-qt-gen/src/generator/rust/property/getter.rs +++ b/crates/cxx-qt-gen/src/generator/rust/property/getter.rs @@ -5,7 +5,10 @@ use crate::{ generator::{ - naming::{property::QPropertyNames, qobject::QObjectNames}, + naming::{ + property::{NameState, QPropertyNames}, + qobject::QObjectNames, + }, rust::fragment::RustFragmentPair, }, naming::rust::syn_type_cxx_bridge_to_qualified, @@ -19,31 +22,37 @@ pub fn generate( qobject_idents: &QObjectNames, cxx_ty: &Type, type_names: &TypeNames, -) -> Result { - let cpp_class_name_rust = &qobject_idents.name.rust_unqualified(); - let getter_wrapper_cpp = idents.getter_wrapper.cxx_unqualified(); - let getter_rust = idents.getter.rust_unqualified(); - let ident = idents.name.rust_unqualified(); - let ident_str = ident.to_string(); - let qualified_ty = syn_type_cxx_bridge_to_qualified(cxx_ty, type_names)?; - let qualified_impl = type_names.rust_qualified(cpp_class_name_rust)?; +) -> Result> { + if let (NameState::Auto(getter), Some(getter_wrapper)) = + (&idents.getter, &idents.getter_wrapper) + { + let cpp_class_name_rust = &qobject_idents.name.rust_unqualified(); + let getter_wrapper_cpp = getter_wrapper.cxx_unqualified(); + let getter_rust = getter.rust_unqualified(); + let ident = idents.name.rust_unqualified(); + let ident_str = ident.to_string(); + let qualified_ty = syn_type_cxx_bridge_to_qualified(cxx_ty, type_names)?; + let qualified_impl = type_names.rust_qualified(cpp_class_name_rust)?; - Ok(RustFragmentPair { - cxx_bridge: vec![quote! { - extern "Rust" { - #[cxx_name = #getter_wrapper_cpp] - // TODO: Add #[namespace] of the QObject to the declaration - unsafe fn #getter_rust<'a>(self: &'a #cpp_class_name_rust) -> &'a #cxx_ty; - } - }], - implementation: vec![quote! { - impl #qualified_impl { - #[doc = "Getter for the Q_PROPERTY "] - #[doc = #ident_str] - pub fn #getter_rust(&self) -> &#qualified_ty { - &self.#ident + Ok(Some(RustFragmentPair { + cxx_bridge: vec![quote! { + extern "Rust" { + #[cxx_name = #getter_wrapper_cpp] + // TODO: Add #[namespace] of the QObject to the declaration + unsafe fn #getter_rust<'a>(self: &'a #cpp_class_name_rust) -> &'a #cxx_ty; } - } - }], - }) + }], + implementation: vec![quote! { + impl #qualified_impl { + #[doc = "Getter for the Q_PROPERTY "] + #[doc = #ident_str] + pub fn #getter_rust(&self) -> &#qualified_ty { + &self.#ident + } + } + }], + })) + } else { + Ok(None) + } } diff --git a/crates/cxx-qt-gen/src/generator/rust/property/mod.rs b/crates/cxx-qt-gen/src/generator/rust/property/mod.rs index ac676a7b0..4b557548d 100644 --- a/crates/cxx-qt-gen/src/generator/rust/property/mod.rs +++ b/crates/cxx-qt-gen/src/generator/rust/property/mod.rs @@ -31,26 +31,27 @@ pub fn generate_rust_properties( for property in properties { let idents = QPropertyNames::from(property); - // Getters - let getter = getter::generate(&idents, qobject_idents, &property.ty, type_names)?; - generated - .cxx_mod_contents - .append(&mut getter.cxx_bridge_as_items()?); - generated - .cxx_qt_mod_contents - .append(&mut getter.implementation_as_items()?); - - // Setters - let setter = setter::generate(&idents, qobject_idents, &property.ty, type_names)?; - generated - .cxx_mod_contents - .append(&mut setter.cxx_bridge_as_items()?); - generated - .cxx_qt_mod_contents - .append(&mut setter.implementation_as_items()?); - - // Signals - signals.push(signal::generate(&idents, qobject_idents)); + if let Some(getter) = getter::generate(&idents, qobject_idents, &property.ty, type_names)? { + generated + .cxx_mod_contents + .append(&mut getter.cxx_bridge_as_items()?); + generated + .cxx_qt_mod_contents + .append(&mut getter.implementation_as_items()?); + }; + + if let Some(setter) = setter::generate(&idents, qobject_idents, &property.ty, type_names)? { + generated + .cxx_mod_contents + .append(&mut setter.cxx_bridge_as_items()?); + generated + .cxx_qt_mod_contents + .append(&mut setter.implementation_as_items()?); + } + + if let Some(notify) = signal::generate(&idents, qobject_idents) { + signals.push(notify) + } } generated.append(&mut generate_rust_signals( @@ -67,6 +68,7 @@ pub fn generate_rust_properties( mod tests { use super::*; + use crate::parser::property::QPropertyFlags; use crate::{generator::naming::qobject::tests::create_qobjectname, tests::assert_tokens_eq}; use quote::format_ident; use syn::parse_quote; @@ -77,14 +79,17 @@ mod tests { ParsedQProperty { ident: format_ident!("trivial_property"), ty: parse_quote! { i32 }, + flags: QPropertyFlags::default(), }, ParsedQProperty { ident: format_ident!("opaque_property"), ty: parse_quote! { UniquePtr }, + flags: QPropertyFlags::default(), }, ParsedQProperty { ident: format_ident!("unsafe_property"), ty: parse_quote! { *mut T }, + flags: QPropertyFlags::default(), }, ]; let qobject_idents = create_qobjectname(); diff --git a/crates/cxx-qt-gen/src/generator/rust/property/setter.rs b/crates/cxx-qt-gen/src/generator/rust/property/setter.rs index ce759e717..499bc236b 100644 --- a/crates/cxx-qt-gen/src/generator/rust/property/setter.rs +++ b/crates/cxx-qt-gen/src/generator/rust/property/setter.rs @@ -5,7 +5,10 @@ use crate::{ generator::{ - naming::{property::QPropertyNames, qobject::QObjectNames}, + naming::{ + property::{NameState, QPropertyNames}, + qobject::QObjectNames, + }, rust::fragment::RustFragmentPair, }, naming::rust::{syn_type_cxx_bridge_to_qualified, syn_type_is_cxx_bridge_unsafe}, @@ -19,46 +22,63 @@ pub fn generate( qobject_idents: &QObjectNames, cxx_ty: &Type, type_names: &TypeNames, -) -> Result { +) -> Result> { let cpp_class_name_rust = &qobject_idents.name.rust_unqualified(); - let setter_wrapper_cpp = idents.setter_wrapper.cxx_unqualified(); - let setter_rust = &idents.setter.rust_unqualified(); - let ident = &idents.name.rust_unqualified(); - let ident_str = ident.to_string(); - let notify_ident = &idents.notify.rust_unqualified(); - let qualified_ty = syn_type_cxx_bridge_to_qualified(cxx_ty, type_names)?; - let qualified_impl = type_names.rust_qualified(cpp_class_name_rust)?; - // Determine if unsafe is required due to an unsafe type - let has_unsafe = if syn_type_is_cxx_bridge_unsafe(cxx_ty) { - quote! { unsafe } - } else { - quote! {} - }; + if let (Some(NameState::Auto(setter)), Some(setter_wrapper)) = + (&idents.setter, &idents.setter_wrapper) + { + let setter_wrapper_cpp = setter_wrapper.cxx_unqualified(); + + let setter_rust = setter.rust_unqualified(); + let ident = &idents.name.rust_unqualified(); + let ident_str = ident.to_string(); - Ok(RustFragmentPair { - cxx_bridge: vec![quote! { - extern "Rust" { - #[cxx_name = #setter_wrapper_cpp] - // TODO: Add #[namespace] of the QObject to the declaration - #has_unsafe fn #setter_rust(self: Pin<&mut #cpp_class_name_rust>, value: #cxx_ty); + // Generate a notify name if it was provided, otherwise return empty + let notify_binding = match &idents.notify { + Some(notify) => { + let notify_ident = notify.rust_unqualified(); + quote! {self.as_mut().#notify_ident();} } - }], - implementation: vec![quote! { - impl #qualified_impl { - #[doc = "Setter for the Q_PROPERTY "] - #[doc = #ident_str] - pub fn #setter_rust(mut self: core::pin::Pin<&mut Self>, value: #qualified_ty) { - use cxx_qt::CxxQtType; - if self.#ident == value { - // don't want to set the value again and reemit the signal, - // as this can cause binding loops - return; + None => quote! {}, + }; + + let qualified_ty = syn_type_cxx_bridge_to_qualified(cxx_ty, type_names)?; + let qualified_impl = type_names.rust_qualified(cpp_class_name_rust)?; + + // Determine if unsafe is required due to an unsafe type + let has_unsafe = if syn_type_is_cxx_bridge_unsafe(cxx_ty) { + quote! { unsafe } + } else { + quote! {} + }; + + Ok(Some(RustFragmentPair { + cxx_bridge: vec![quote! { + extern "Rust" { + #[cxx_name = #setter_wrapper_cpp] + // TODO: Add #[namespace] of the QObject to the declaration + #has_unsafe fn #setter_rust(self: Pin<&mut #cpp_class_name_rust>, value: #cxx_ty); + } + }], + implementation: vec![quote! { + impl #qualified_impl { + #[doc = "Setter for the Q_PROPERTY "] + #[doc = #ident_str] + pub fn #setter_rust(mut self: core::pin::Pin<&mut Self>, value: #qualified_ty) { + use cxx_qt::CxxQtType; + if self.#ident == value { + // don't want to set the value again and reemit the signal, + // as this can cause binding loops + return; + } + self.as_mut().rust_mut().#ident = value; + #notify_binding } - self.as_mut().rust_mut().#ident = value; - self.as_mut().#notify_ident(); } - } - }], - }) + }], + })) + } else { + Ok(None) + } } diff --git a/crates/cxx-qt-gen/src/generator/rust/property/signal.rs b/crates/cxx-qt-gen/src/generator/rust/property/signal.rs index 8064036f5..5070d39be 100644 --- a/crates/cxx-qt-gen/src/generator/rust/property/signal.rs +++ b/crates/cxx-qt-gen/src/generator/rust/property/signal.rs @@ -6,24 +6,33 @@ use syn::ForeignItemFn; use crate::{ - generator::naming::{property::QPropertyNames, qobject::QObjectNames}, + generator::naming::{ + property::{NameState, QPropertyNames}, + qobject::QObjectNames, + }, parser::signals::ParsedSignal, }; -pub fn generate(idents: &QPropertyNames, qobject_idents: &QObjectNames) -> ParsedSignal { +pub fn generate(idents: &QPropertyNames, qobject_idents: &QObjectNames) -> Option { // We build our signal in the generation phase as we need to use the naming // structs to build the signal name let cpp_class_rust = &qobject_idents.name.rust_unqualified(); - let notify_rust = &idents.notify.rust_unqualified(); - let notify_cpp_str = &idents.notify.cxx_unqualified(); - let method: ForeignItemFn = syn::parse_quote! { - #[doc = "Notify for the Q_PROPERTY"] - #[cxx_name = #notify_cpp_str] - fn #notify_rust(self: Pin<&mut #cpp_class_rust>); - }; - ParsedSignal::from_property_method( - method, - idents.notify.clone(), - qobject_idents.name.rust_unqualified().clone(), - ) + if let Some(NameState::Auto(notify)) = &idents.notify { + let notify_rust = notify.rust_unqualified(); + let notify_cpp_str = notify.cxx_unqualified(); + + let method: ForeignItemFn = syn::parse_quote! { + #[doc = "Notify for the Q_PROPERTY"] + #[cxx_name = #notify_cpp_str] + fn #notify_rust(self: Pin<&mut #cpp_class_rust>); + }; + + Some(ParsedSignal::from_property_method( + method, + notify.clone(), + qobject_idents.name.rust_unqualified().clone(), + )) + } else { + None + } } diff --git a/crates/cxx-qt-gen/src/naming/name.rs b/crates/cxx-qt-gen/src/naming/name.rs index cef617a96..d1d191b00 100644 --- a/crates/cxx-qt-gen/src/naming/name.rs +++ b/crates/cxx-qt-gen/src/naming/name.rs @@ -177,7 +177,7 @@ impl Name { /// Get the unqualified name of the type in Rust. /// This is either; /// - The rust_name attribute value, if one is provided - /// - The original ident, of no rust_name was provided + /// - The original ident, if no rust_name was provided pub fn rust_unqualified(&self) -> &Ident { &self.rust } diff --git a/crates/cxx-qt-gen/src/parser/property.rs b/crates/cxx-qt-gen/src/parser/property.rs index 93d4daf67..a1137c114 100644 --- a/crates/cxx-qt-gen/src/parser/property.rs +++ b/crates/cxx-qt-gen/src/parser/property.rs @@ -3,7 +3,44 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 -use syn::{parse::ParseStream, Attribute, Ident, Result, Token, Type}; +use syn::{ + parse::{Error, ParseStream}, + punctuated::Punctuated, + spanned::Spanned, + Attribute, Expr, Ident, Meta, MetaNameValue, Result, Token, Type, +}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum FlagState { + Auto, // Might need to refactor to also store the generated ident here + Custom(Ident), +} + +impl FlagState { + #[cfg(test)] + pub fn is_auto(&self) -> bool { + matches!(self, Self::Auto) + } +} + +/// Struct for storing the flags provided for a QProperty +#[derive(Debug)] +pub struct QPropertyFlags { + pub(crate) read: FlagState, + pub(crate) write: Option, + pub(crate) notify: Option, +} + +impl Default for QPropertyFlags { + /// Default represents the flags of the desugared version of ```#[qproperty(T, ident)]``` + fn default() -> Self { + Self { + read: FlagState::Auto, + write: Some(FlagState::Auto), + notify: Some(FlagState::Auto), + } + } +} /// Describes a single Q_PROPERTY for a struct pub struct ParsedQProperty { @@ -11,6 +48,39 @@ pub struct ParsedQProperty { pub ident: Ident, /// The [syn::Type] of the property pub ty: Type, + /// Property flag collection + pub flags: QPropertyFlags, +} + +fn parse_meta_name_value(name_value: &MetaNameValue) -> Result<(Ident, Ident)> { + let ident = name_value.path.require_ident()?.clone(); + let expr = &name_value.value; + let func_signature: Ident; + + if let Expr::Path(path_expr) = expr { + func_signature = path_expr.path.require_ident()?.clone(); + } else { + return Err(Error::new( + expr.span(), + "Function signature must be an identifier", + )); + } + + Ok((ident, func_signature)) +} + +fn parse_meta(meta: Meta) -> Result<(Ident, Option)> { + match meta { + Meta::Path(path) => Ok((path.require_ident()?.clone(), None)), + Meta::NameValue(name_value) => { + let (field, ident) = parse_meta_name_value(&name_value)?; + Ok((field, Some(ident))) + } + _ => Err(Error::new( + meta.span(), + "Invalid syntax, flags must be specified as either `read` or `read = my_getter`", + )), + } } impl ParsedQProperty { @@ -20,10 +90,66 @@ impl ParsedQProperty { let _comma = input.parse::()?; let ident = input.parse()?; - // TODO: later we'll need to parse setters and getters here - // which are key-value, hence this not being parsed as a list + if input.is_empty() { + // No flags passed so desugar: #[qproperty(T, ident)] -> #[qproperty(T, ident, read, write, notify)] + Ok(Self { + ident, + ty, + flags: QPropertyFlags::default(), + }) + } else { + let _comma = input.parse::()?; // Start of final identifiers + + let punctuated_flags: Punctuated = + Punctuated::parse_terminated(input)?; + + // Remove the commas and collect the individual meta items + let flags: Vec = punctuated_flags.clone().into_iter().collect(); - Ok(Self { ident, ty }) + let mut read = None; + let mut write = None; + let mut notify = None; + + // Create mutable closure to capture the variables for setting with the Meta values + let mut update_fields = |ident: &Ident, value: Option| -> Result<()> { + let variable = match ident.to_string().as_str() { + "read" => &mut read, + "write" => &mut write, + "notify" => &mut notify, + _ => { + return Err(Error::new( + ident.span(), + "Invalid flag passed, must be one of read, write, notify", + )); + } + }; + *variable = Some(value.map_or(FlagState::Auto, FlagState::Custom)); + + Ok(()) + }; + + for flag in flags { + let (field, maybe_value) = parse_meta(flag)?; + update_fields(&field, maybe_value)?; + } + + if let Some(read) = read { + Ok(Self { + ident, + ty, + flags: QPropertyFlags { + read, + write, + notify, + }, + }) + } else { + Err(Error::new( + punctuated_flags.span(), + "If flags are passed, read must be explicitly specified", + )) + } + } }) } } @@ -38,12 +164,92 @@ mod tests { #[test] fn test_parse_property() { let mut input: ItemStruct = parse_quote! { - #[qproperty(T, name)] + #[qproperty(T, name, read, write = myGetter,)] + struct MyStruct; + }; + let property = ParsedQProperty::parse(input.attrs.remove(0)).unwrap(); + assert_eq!(property.ident, format_ident!("name")); + assert_eq!(property.ty, parse_quote! { T }); + } + + #[test] + fn test_parse_flags_read() { + let mut input: ItemStruct = parse_quote! { + #[qproperty(T, name, read)] + struct MyStruct; + }; + let property = ParsedQProperty::parse(input.attrs.remove(0)).unwrap(); + assert_eq!(property.ident, format_ident!("name")); + assert_eq!(property.ty, parse_quote! { T }); + } + + #[test] + fn test_parse_flags_all() { + let mut input: ItemStruct = parse_quote! { + #[qproperty(T, name, read, write, notify)] + struct MyStruct; + }; + let property = ParsedQProperty::parse(input.attrs.remove(0)).unwrap(); + assert_eq!(property.ident, format_ident!("name")); + assert_eq!(property.ty, parse_quote! { T }); + + assert_eq!(property.flags.read, FlagState::Auto); + + assert_eq!(property.flags.notify, Some(FlagState::Auto)); + assert_eq!(property.flags.write, Some(FlagState::Auto)); + } + + #[test] + fn test_parse_flags_kw() { + let mut input: ItemStruct = parse_quote! { + #[qproperty(T, name, read = my_getter, write, notify = my_notifier)] struct MyStruct; }; let property = ParsedQProperty::parse(input.attrs.remove(0)).unwrap(); assert_eq!(property.ident, format_ident!("name")); assert_eq!(property.ty, parse_quote! { T }); + + assert_eq!( + property.flags.read, + FlagState::Custom(format_ident!("my_getter")) + ); + + assert_eq!(property.flags.write, Some(FlagState::Auto)); + + assert_eq!( + property.flags.notify, + Some(FlagState::Custom(format_ident!("my_notifier"))) + ); + } + + #[test] + fn test_parse_flags_invalid() { + let mut input: ItemStruct = parse_quote! { + #[qproperty(T, name, read = blah, a, notify = blahblah)] + struct MyStruct; + }; + let property = ParsedQProperty::parse(input.attrs.remove(0)); + assert!(property.is_err()) + } + + #[test] + fn test_parse_flags_missing_read() { + let mut input: ItemStruct = parse_quote! { + #[qproperty(T, name, notify = blahblah)] + struct MyStruct; + }; + let property = ParsedQProperty::parse(input.attrs.remove(0)); + assert!(property.is_err()); + } + + #[test] + fn test_parse_flags_invalid_literal() { + let mut input: ItemStruct = parse_quote! { + #[qproperty(T, name, notify = 3)] + struct MyStruct; + }; + let property = ParsedQProperty::parse(input.attrs.remove(0)); + assert!(property.is_err()); } #[test] @@ -77,9 +283,9 @@ mod tests { } #[test] - fn test_parse_property_no_type() { + fn test_parse_property_no_params() { let mut input: ItemStruct = parse_quote! { - #[qproperty(T)] + #[qproperty()] struct MyStruct; }; let property = ParsedQProperty::parse(input.attrs.remove(0)); diff --git a/crates/cxx-qt-gen/test_inputs/properties.rs b/crates/cxx-qt-gen/test_inputs/properties.rs index d7ec0204a..3d385f88d 100644 --- a/crates/cxx-qt-gen/test_inputs/properties.rs +++ b/crates/cxx-qt-gen/test_inputs/properties.rs @@ -11,6 +11,9 @@ mod ffi { #[derive(Default)] #[qproperty(i32, primitive)] #[qproperty(QPoint, trivial)] + #[qproperty(i32, custom_function_prop, read = my_getter, write = my_setter, notify)] + #[qproperty(i32, readonly_prop, read)] + #[qproperty(i32, custom_on_changed, read, write, notify = myOnChanged)] type MyObject = super::MyObjectRust; } } diff --git a/crates/cxx-qt-gen/test_outputs/properties.cpp b/crates/cxx-qt-gen/test_outputs/properties.cpp index 578a0a3c9..b1fe2f9cc 100644 --- a/crates/cxx-qt-gen/test_outputs/properties.cpp +++ b/crates/cxx-qt-gen/test_outputs/properties.cpp @@ -116,6 +116,66 @@ MyObject_trivialChangedConnect( } } // namespace cxx_qt::my_object::rust::cxxqtgen1 +// Define namespace otherwise we hit a GCC bug +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56480 +namespace rust::cxxqt1 { +template<> +SignalHandler<::cxx_qt::my_object::rust::cxxqtgen1:: + MyObjectCxxQtSignalParamscustomFunctionPropChanged*>:: + ~SignalHandler() noexcept +{ + if (data[0] == nullptr && data[1] == nullptr) { + return; + } + + drop_MyObject_signal_handler_customFunctionPropChanged(::std::move(*this)); +} + +template<> +template<> +void +SignalHandler<::cxx_qt::my_object::rust::cxxqtgen1:: + MyObjectCxxQtSignalParamscustomFunctionPropChanged*>:: +operator()(cxx_qt::my_object::MyObject& self) +{ + call_MyObject_signal_handler_customFunctionPropChanged(*this, self); +} + +static_assert( + alignof( + SignalHandler<::cxx_qt::my_object::rust::cxxqtgen1:: + MyObjectCxxQtSignalParamscustomFunctionPropChanged*>) <= + alignof(::std::size_t), + "unexpected aligment"); +static_assert( + sizeof( + SignalHandler<::cxx_qt::my_object::rust::cxxqtgen1:: + MyObjectCxxQtSignalParamscustomFunctionPropChanged*>) == + sizeof(::std::size_t[2]), + "unexpected size"); +} // namespace rust::cxxqt1 + +namespace cxx_qt::my_object::rust::cxxqtgen1 { +::QMetaObject::Connection +MyObject_customFunctionPropChangedConnect( + cxx_qt::my_object::MyObject& self, + ::cxx_qt::my_object::rust::cxxqtgen1:: + MyObjectCxxQtSignalHandlercustomFunctionPropChanged closure, + ::Qt::ConnectionType type) +{ + return ::QObject::connect( + &self, + &cxx_qt::my_object::MyObject::customFunctionPropChanged, + &self, + [&, closure = ::std::move(closure)]() mutable { + const ::rust::cxxqt1::MaybeLockGuard guard( + self); + closure.template operator()(self); + }, + type); +} +} // namespace cxx_qt::my_object::rust::cxxqtgen1 + namespace cxx_qt::my_object { ::std::int32_t const& MyObject::getPrimitive() const @@ -145,6 +205,27 @@ MyObject::setTrivial(QPoint const& value) setTrivialWrapper(value); } +::std::int32_t const& +MyObject::getReadonlyProp() const +{ + const ::rust::cxxqt1::MaybeLockGuard guard(*this); + return getReadonlyPropWrapper(); +} + +::std::int32_t const& +MyObject::getCustomOnChanged() const +{ + const ::rust::cxxqt1::MaybeLockGuard guard(*this); + return getCustomOnChangedWrapper(); +} + +void +MyObject::setCustomOnChanged(::std::int32_t const& value) +{ + const ::rust::cxxqt1::MaybeLockGuard guard(*this); + setCustomOnChangedWrapper(value); +} + MyObject::MyObject(QObject* parent) : QObject(parent) , ::rust::cxxqt1::CxxQtType( diff --git a/crates/cxx-qt-gen/test_outputs/properties.h b/crates/cxx-qt-gen/test_outputs/properties.h index 7efa9d489..b5e28e79b 100644 --- a/crates/cxx-qt-gen/test_outputs/properties.h +++ b/crates/cxx-qt-gen/test_outputs/properties.h @@ -21,6 +21,12 @@ using MyObjectCxxQtSignalHandlertrivialChanged = ::rust::cxxqt1::SignalHandler< struct MyObjectCxxQtSignalParamstrivialChanged*>; } // namespace cxx_qt::my_object::rust::cxxqtgen1 +namespace cxx_qt::my_object::rust::cxxqtgen1 { +using MyObjectCxxQtSignalHandlercustomFunctionPropChanged = + ::rust::cxxqt1::SignalHandler< + struct MyObjectCxxQtSignalParamscustomFunctionPropChanged*>; +} // namespace cxx_qt::my_object::rust::cxxqtgen1 + #include "cxx-qt-gen/ffi.cxx.h" namespace cxx_qt::my_object::rust::cxxqtgen1 { @@ -41,6 +47,15 @@ MyObject_trivialChangedConnect( ::Qt::ConnectionType type); } // namespace cxx_qt::my_object::rust::cxxqtgen1 +namespace cxx_qt::my_object::rust::cxxqtgen1 { +::QMetaObject::Connection +MyObject_customFunctionPropChangedConnect( + cxx_qt::my_object::MyObject& self, + ::cxx_qt::my_object::rust::cxxqtgen1:: + MyObjectCxxQtSignalHandlercustomFunctionPropChanged closure, + ::Qt::ConnectionType type); +} // namespace cxx_qt::my_object::rust::cxxqtgen1 + namespace cxx_qt::my_object { class MyObject : public QObject @@ -53,6 +68,11 @@ class MyObject NOTIFY primitiveChanged) Q_PROPERTY( QPoint trivial READ getTrivial WRITE setTrivial NOTIFY trivialChanged) + Q_PROPERTY(::std::int32_t customFunctionProp READ my_getter WRITE my_setter + NOTIFY customFunctionPropChanged) + Q_PROPERTY(::std::int32_t readonlyProp READ getReadonlyProp) + Q_PROPERTY(::std::int32_t customOnChanged READ getCustomOnChanged WRITE + setCustomOnChanged NOTIFY myOnChanged) virtual ~MyObject() = default; @@ -61,8 +81,12 @@ class MyObject Q_SLOT void setPrimitive(::std::int32_t const& value); QPoint const& getTrivial() const; Q_SLOT void setTrivial(QPoint const& value); + ::std::int32_t const& getReadonlyProp() const; + ::std::int32_t const& getCustomOnChanged() const; + Q_SLOT void setCustomOnChanged(::std::int32_t const& value); Q_SIGNAL void primitiveChanged(); Q_SIGNAL void trivialChanged(); + Q_SIGNAL void customFunctionPropChanged(); explicit MyObject(QObject* parent = nullptr); private: @@ -70,6 +94,9 @@ class MyObject void setPrimitiveWrapper(::std::int32_t value) noexcept; QPoint const& getTrivialWrapper() const noexcept; void setTrivialWrapper(QPoint value) noexcept; + ::std::int32_t const& getReadonlyPropWrapper() const noexcept; + ::std::int32_t const& getCustomOnChangedWrapper() const noexcept; + void setCustomOnChangedWrapper(::std::int32_t value) noexcept; }; static_assert(::std::is_base_of::value, diff --git a/crates/cxx-qt-gen/test_outputs/properties.rs b/crates/cxx-qt-gen/test_outputs/properties.rs index 68c14a2f6..9dda703a3 100644 --- a/crates/cxx-qt-gen/test_outputs/properties.rs +++ b/crates/cxx-qt-gen/test_outputs/properties.rs @@ -49,6 +49,18 @@ mod ffi { #[cxx_name = "setTrivialWrapper"] fn set_trivial(self: Pin<&mut MyObject>, value: QPoint); } + extern "Rust" { + #[cxx_name = "getReadonlyPropWrapper"] + unsafe fn readonly_prop<'a>(self: &'a MyObject) -> &'a i32; + } + extern "Rust" { + #[cxx_name = "getCustomOnChangedWrapper"] + unsafe fn custom_on_changed<'a>(self: &'a MyObject) -> &'a i32; + } + extern "Rust" { + #[cxx_name = "setCustomOnChangedWrapper"] + fn set_custom_on_changed(self: Pin<&mut MyObject>, value: i32); + } unsafe extern "C++" { #[doc = "Notify for the Q_PROPERTY"] #[cxx_name = "primitiveChanged"] @@ -113,6 +125,39 @@ mod ffi { self_value: Pin<&mut MyObject>, ); } + unsafe extern "C++" { + #[doc = "Notify for the Q_PROPERTY"] + #[cxx_name = "customFunctionPropChanged"] + fn custom_function_prop_changed(self: Pin<&mut MyObject>); + } + unsafe extern "C++" { + #[doc(hidden)] + #[namespace = "cxx_qt::my_object::rust::cxxqtgen1"] + type MyObjectCxxQtSignalHandlercustomFunctionPropChanged = + cxx_qt::signalhandler::CxxQtSignalHandler< + super::MyObjectCxxQtSignalClosurecustomFunctionPropChanged, + >; + #[doc(hidden)] + #[namespace = "cxx_qt::my_object::rust::cxxqtgen1"] + #[cxx_name = "MyObject_customFunctionPropChangedConnect"] + fn MyObject_connect_custom_function_prop_changed( + self_value: Pin<&mut MyObject>, + signal_handler: MyObjectCxxQtSignalHandlercustomFunctionPropChanged, + conn_type: CxxQtConnectionType, + ) -> CxxQtQMetaObjectConnection; + } + #[namespace = "cxx_qt::my_object::rust::cxxqtgen1"] + extern "Rust" { + #[doc(hidden)] + fn drop_MyObject_signal_handler_customFunctionPropChanged( + handler: MyObjectCxxQtSignalHandlercustomFunctionPropChanged, + ); + #[doc(hidden)] + fn call_MyObject_signal_handler_customFunctionPropChanged( + handler: &mut MyObjectCxxQtSignalHandlercustomFunctionPropChanged, + self_value: Pin<&mut MyObject>, + ); + } extern "Rust" { #[cxx_name = "createRs"] #[namespace = "cxx_qt::my_object::cxx_qt_my_object"] @@ -167,6 +212,32 @@ impl ffi::MyObject { self.as_mut().trivial_changed(); } } +impl ffi::MyObject { + #[doc = "Getter for the Q_PROPERTY "] + #[doc = "readonly_prop"] + pub fn readonly_prop(&self) -> &i32 { + &self.readonly_prop + } +} +impl ffi::MyObject { + #[doc = "Getter for the Q_PROPERTY "] + #[doc = "custom_on_changed"] + pub fn custom_on_changed(&self) -> &i32 { + &self.custom_on_changed + } +} +impl ffi::MyObject { + #[doc = "Setter for the Q_PROPERTY "] + #[doc = "custom_on_changed"] + pub fn set_custom_on_changed(mut self: core::pin::Pin<&mut Self>, value: i32) { + use cxx_qt::CxxQtType; + if self.custom_on_changed == value { + return; + } + self.as_mut().rust_mut().custom_on_changed = value; + self.as_mut().myOnChanged(); + } +} impl ffi::MyObject { #[doc = "Connect the given function pointer to the signal "] #[doc = "primitiveChanged"] @@ -285,6 +356,78 @@ cxx_qt::static_assertions::assert_eq_size!( cxx_qt::signalhandler::CxxQtSignalHandler, [usize; 2] ); +impl ffi::MyObject { + #[doc = "Connect the given function pointer to the signal "] + #[doc = "customFunctionPropChanged"] + #[doc = ", so that when the signal is emitted the function pointer is executed."] + pub fn connect_custom_function_prop_changed< + F: FnMut(core::pin::Pin<&mut ffi::MyObject>) + 'static, + >( + self: core::pin::Pin<&mut ffi::MyObject>, + mut closure: F, + conn_type: cxx_qt::ConnectionType, + ) -> cxx_qt::QMetaObjectConnectionGuard { + cxx_qt::QMetaObjectConnectionGuard::from( + ffi::MyObject_connect_custom_function_prop_changed( + self, + cxx_qt::signalhandler::CxxQtSignalHandler::< + MyObjectCxxQtSignalClosurecustomFunctionPropChanged, + >::new(Box::new(closure)), + conn_type, + ), + ) + } +} +impl ffi::MyObject { + #[doc = "Connect the given function pointer to the signal "] + #[doc = "customFunctionPropChanged"] + #[doc = ", so that when the signal is emitted the function pointer is executed."] + #[doc = "\n"] + #[doc = "Note that this method uses a AutoConnection connection type."] + pub fn on_custom_function_prop_changed< + F: FnMut(core::pin::Pin<&mut ffi::MyObject>) + 'static, + >( + self: core::pin::Pin<&mut ffi::MyObject>, + mut closure: F, + ) -> cxx_qt::QMetaObjectConnectionGuard { + cxx_qt::QMetaObjectConnectionGuard::from( + ffi::MyObject_connect_custom_function_prop_changed( + self, + cxx_qt::signalhandler::CxxQtSignalHandler::< + MyObjectCxxQtSignalClosurecustomFunctionPropChanged, + >::new(Box::new(closure)), + cxx_qt::ConnectionType::AutoConnection, + ), + ) + } +} +#[doc(hidden)] +pub struct MyObjectCxxQtSignalClosurecustomFunctionPropChanged {} +impl cxx_qt::signalhandler::CxxQtSignalHandlerClosure + for MyObjectCxxQtSignalClosurecustomFunctionPropChanged +{ + type Id = cxx::type_id!( + "::cxx_qt::my_object::rust::cxxqtgen1::MyObjectCxxQtSignalHandlercustomFunctionPropChanged" + ); + type FnType = dyn FnMut(core::pin::Pin<&mut ffi::MyObject>); +} +use core::mem::drop as drop_MyObject_signal_handler_customFunctionPropChanged; +fn call_MyObject_signal_handler_customFunctionPropChanged( + handler: &mut cxx_qt::signalhandler::CxxQtSignalHandler< + MyObjectCxxQtSignalClosurecustomFunctionPropChanged, + >, + self_value: core::pin::Pin<&mut ffi::MyObject>, +) { + handler.closure()(self_value); +} +cxx_qt::static_assertions::assert_eq_align!( + cxx_qt::signalhandler::CxxQtSignalHandler, + usize +); +cxx_qt::static_assertions::assert_eq_size!( + cxx_qt::signalhandler::CxxQtSignalHandler, + [usize; 2] +); impl cxx_qt::Locking for ffi::MyObject {} #[doc(hidden)] pub fn create_rs_my_object_rust() -> std::boxed::Box {