From d6424653d107b32c5ef07d579da0c623701f6ee5 Mon Sep 17 00:00:00 2001 From: Andrew Hayzen Date: Thu, 1 Jun 2023 15:55:28 +0100 Subject: [PATCH] WIP: cxx-qt-gen: move signals away from an enum to an extern "C++" block Related to #557 --- .../src/generator/cpp/property/mod.rs | 47 +++- .../src/generator/cpp/property/signal.rs | 14 -- .../cxx-qt-gen/src/generator/cpp/qobject.rs | 12 +- .../src/generator/naming/signals.rs | 46 ++-- .../src/generator/rust/property/mod.rs | 39 +++- .../src/generator/rust/property/signal.rs | 34 --- .../cxx-qt-gen/src/generator/rust/qobject.rs | 10 +- .../cxx-qt-gen/src/generator/rust/signals.rs | 88 +++----- crates/cxx-qt-gen/src/parser/cxxqtdata.rs | 82 ++++--- crates/cxx-qt-gen/src/parser/qobject.rs | 8 +- crates/cxx-qt-gen/src/parser/signals.rs | 212 ++++++++++++------ crates/cxx-qt-gen/src/syntax/attribute.rs | 52 ++--- crates/cxx-qt-gen/test_inputs/signals.rs | 26 +-- examples/demo_threading/rust/src/lib.rs | 12 +- .../rust/src/workers/sensors.rs | 30 +-- .../rust/src/custom_base_class.rs | 26 +-- .../rust/src/multiple_qobjects.rs | 28 +-- .../qml_features/rust/src/nested_qobjects.rs | 26 +-- .../qml_features/rust/src/serialisation.rs | 18 +- examples/qml_features/rust/src/signals.rs | 37 +-- 20 files changed, 424 insertions(+), 423 deletions(-) delete mode 100644 crates/cxx-qt-gen/src/generator/cpp/property/signal.rs delete mode 100644 crates/cxx-qt-gen/src/generator/rust/property/signal.rs 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 a2db670ea..7e81bbbb4 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/property/mod.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/property/mod.rs @@ -3,17 +3,21 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 -use crate::generator::{ - cpp::{qobject::GeneratedCppQObjectBlocks, types::CppType}, - naming::{property::QPropertyName, qobject::QObjectName}, -}; use crate::parser::{cxxqtdata::ParsedCxxMappings, property::ParsedQProperty}; -use syn::Result; +use crate::{ + generator::{ + cpp::{qobject::GeneratedCppQObjectBlocks, types::CppType}, + naming::{property::QPropertyName, qobject::QObjectName}, + }, + parser::signals::ParsedSignal, +}; +use syn::{ForeignItemFn, Result}; + +use super::signal::generate_cpp_signals; mod getter; mod meta; mod setter; -mod signal; pub fn generate_cpp_properties( properties: &Vec, @@ -21,7 +25,9 @@ pub fn generate_cpp_properties( cxx_mappings: &ParsedCxxMappings, ) -> Result { let mut generated = GeneratedCppQObjectBlocks::default(); + let mut signals = vec![]; let qobject_ident = qobject_idents.cpp_class.cpp.to_string(); + for property in properties { // Cache the idents as they are used in multiple places let idents = QPropertyName::from(property); @@ -34,9 +40,36 @@ pub fn generate_cpp_properties( generated .methods .push(setter::generate(&idents, &qobject_ident, &cxx_ty)); - generated.methods.push(signal::generate(&idents)); + + // Signals + // + // 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.cpp_class.rust; + let notify_cpp = &idents.notify.cpp; + let notify_rust_str = idents.notify.rust.to_string(); + let method: ForeignItemFn = syn::parse_quote! { + #[doc = "Notify for the Q_PROPERTY"] + #[rust_name = #notify_rust_str] + fn #notify_cpp(self: Pin<&mut #cpp_class_rust>); + }; + signals.push(ParsedSignal { + method, + qobject_ident: qobject_idents.cpp_class.rust.clone(), + mutable: true, + safe: true, + parameters: vec![], + ident: idents.notify, + inherit: false, + }); } + generated.append(&mut generate_cpp_signals( + &signals, + qobject_idents, + cxx_mappings, + )?); + Ok(generated) } diff --git a/crates/cxx-qt-gen/src/generator/cpp/property/signal.rs b/crates/cxx-qt-gen/src/generator/cpp/property/signal.rs deleted file mode 100644 index ed4ff8294..000000000 --- a/crates/cxx-qt-gen/src/generator/cpp/property/signal.rs +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company -// SPDX-FileContributor: Andrew Hayzen -// -// SPDX-License-Identifier: MIT OR Apache-2.0 - -use crate::generator::naming::property::QPropertyName; -use crate::CppFragment; - -pub fn generate(idents: &QPropertyName) -> CppFragment { - CppFragment::Header(format!( - "Q_SIGNAL void {ident_notify}();", - ident_notify = idents.notify.cpp - )) -} diff --git a/crates/cxx-qt-gen/src/generator/cpp/qobject.rs b/crates/cxx-qt-gen/src/generator/cpp/qobject.rs index c635596b4..f22b4f140 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/qobject.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/qobject.rs @@ -106,13 +106,11 @@ impl GeneratedCppQObject { &qobject_idents, cxx_mappings, )?); - if let Some(signals_enum) = &qobject.signals { - generated.blocks.append(&mut generate_cpp_signals( - &signals_enum.signals, - &qobject_idents, - cxx_mappings, - )?); - } + generated.blocks.append(&mut generate_cpp_signals( + &qobject.signals, + &qobject_idents, + cxx_mappings, + )?); generated.blocks.append(&mut inherit::generate( &qobject.inherited_methods, &qobject.base_class, diff --git a/crates/cxx-qt-gen/src/generator/naming/signals.rs b/crates/cxx-qt-gen/src/generator/naming/signals.rs index 67ad81c48..cc89ef5ea 100644 --- a/crates/cxx-qt-gen/src/generator/naming/signals.rs +++ b/crates/cxx-qt-gen/src/generator/naming/signals.rs @@ -10,6 +10,7 @@ use syn::Ident; /// Names for parts of a Q_SIGNAL pub struct QSignalName { + // TODO: this is removed pub enum_name: Ident, pub name: CombinedIdent, pub emit_name: CombinedIdent, @@ -19,19 +20,12 @@ pub struct QSignalName { impl From<&ParsedSignal> for QSignalName { fn from(signal: &ParsedSignal) -> Self { - // Check if there is a cxx ident that should be used - let cxx_ident = if let Some(cxx_name) = &signal.cxx_name { - format_ident!("{}", cxx_name) - } else { - signal.ident.clone() - }; - Self { - enum_name: signal.ident.clone(), - name: CombinedIdent::from_signal(&signal.ident, &cxx_ident), - emit_name: CombinedIdent::emit_from_signal(&signal.ident, &cxx_ident), - connect_name: CombinedIdent::connect_from_signal(&signal.ident, &cxx_ident), - on_name: on_from_signal(&signal.ident), + enum_name: signal.ident.rust.clone(), + name: CombinedIdent::from_signal(&signal.ident), + emit_name: CombinedIdent::emit_from_signal(&signal.ident), + connect_name: CombinedIdent::connect_from_signal(&signal.ident), + on_name: on_from_signal(&signal.ident.rust), } } } @@ -42,28 +36,30 @@ fn on_from_signal(ident: &Ident) -> Ident { impl CombinedIdent { /// For a given signal ident generate the Rust and C++ names - fn from_signal(ident: &Ident, cxx_ident: &Ident) -> Self { + fn from_signal(ident: &CombinedIdent) -> Self { Self { - cpp: format_ident!("{}", cxx_ident.to_string().to_case(Case::Camel)), + cpp: format_ident!("{}", ident.cpp.to_string().to_case(Case::Camel)), // Note that signal names are in camel case so we need to convert to snake and can't clone - rust: format_ident!("{}", ident.to_string().to_case(Case::Snake)), + rust: format_ident!("{}", ident.rust.to_string().to_case(Case::Snake)), } } /// For a given signal ident generate the Rust and C++ emit name - fn emit_from_signal(ident: &Ident, cxx_ident: &Ident) -> Self { + fn emit_from_signal(ident: &CombinedIdent) -> Self { Self { - cpp: format_ident!("emit{}", cxx_ident.to_string().to_case(Case::Pascal)), - rust: format_ident!("emit_{}", ident.to_string().to_case(Case::Snake)), + cpp: format_ident!("emit{}", ident.cpp.to_string().to_case(Case::Pascal)), + // Note that the Rust emit name is the same name as the signal for now + // in the future this emit wrapper in C++ will be removed. + rust: format_ident!("{}", ident.rust.to_string().to_case(Case::Snake)), } } - fn connect_from_signal(ident: &Ident, cxx_ident: &Ident) -> Self { + fn connect_from_signal(ident: &CombinedIdent) -> Self { Self { // Use signalConnect instead of onSignal here so that we don't // create a C++ name that is similar to the QML naming scheme for signals - cpp: format_ident!("{}Connect", cxx_ident.to_string().to_case(Case::Camel)), - rust: format_ident!("connect_{}", ident.to_string().to_case(Case::Snake)), + cpp: format_ident!("{}Connect", ident.cpp.to_string().to_case(Case::Camel)), + rust: format_ident!("connect_{}", ident.rust.to_string().to_case(Case::Snake)), } } } @@ -75,9 +71,9 @@ mod tests { #[test] fn test_parsed_signal() { let qsignal = ParsedSignal { - ident: format_ident!("DataChanged"), + // TODO: this won't be pascal + ident: CombinedIdent { cpp: format_ident!("DataChanged"), rust: format_ident!("DataChanged") }, parameters: vec![], - cxx_name: None, inherit: false, }; @@ -98,9 +94,9 @@ mod tests { #[test] fn test_parsed_signal_existing_cxx_name() { let qsignal = ParsedSignal { - ident: format_ident!("ExistingSignal"), + // TODO: this won't be pascal + ident: CombinedIdent { cpp: format_ident!("ExistingSignal"), rust: format_ident!("baseName") }, parameters: vec![], - cxx_name: Some("baseName".to_owned()), inherit: true, }; 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 7ee37c7d5..dbd59edeb 100644 --- a/crates/cxx-qt-gen/src/generator/rust/property/mod.rs +++ b/crates/cxx-qt-gen/src/generator/rust/property/mod.rs @@ -5,22 +5,24 @@ pub mod getter; pub mod setter; -pub mod signal; use crate::{ generator::{ naming::{property::QPropertyName, qobject::QObjectName}, rust::qobject::GeneratedRustQObjectBlocks, }, - parser::property::ParsedQProperty, + parser::{property::ParsedQProperty, signals::ParsedSignal}, }; -use syn::Result; +use syn::{ForeignItemFn, Result}; + +use super::signals::generate_rust_signals; pub fn generate_rust_properties( properties: &Vec, qobject_idents: &QObjectName, ) -> Result { let mut generated = GeneratedRustQObjectBlocks::default(); + let mut signals = vec![]; for property in properties { let idents = QPropertyName::from(property); @@ -44,15 +46,32 @@ pub fn generate_rust_properties( .append(&mut setter.implementation_as_items()?); // Signals - let notify = signal::generate(&idents, qobject_idents); - generated - .cxx_mod_contents - .append(&mut notify.cxx_bridge_as_items()?); - generated - .cxx_qt_mod_contents - .append(&mut notify.implementation_as_items()?); + // + // We build our signal in the generation phase as we need to use the naming + // structs to build the signal name + // + // TODO: make a common method on ParsedSignal::from_idents + let cpp_class_rust = &qobject_idents.cpp_class.rust; + let notify_cpp = &idents.notify.cpp; + let notify_rust_str = idents.notify.rust.to_string(); + let method: ForeignItemFn = syn::parse_quote! { + #[doc = "Notify for the Q_PROPERTY"] + #[rust_name = #notify_rust_str] + fn #notify_cpp(self: Pin<&mut #cpp_class_rust>); + }; + signals.push(ParsedSignal { + method, + qobject_ident: qobject_idents.cpp_class.rust.clone(), + mutable: true, + safe: true, + parameters: vec![], + ident: idents.notify, + inherit: false, + }); } + generated.append(&mut generate_rust_signals(&signals, qobject_idents)?); + Ok(generated) } diff --git a/crates/cxx-qt-gen/src/generator/rust/property/signal.rs b/crates/cxx-qt-gen/src/generator/rust/property/signal.rs deleted file mode 100644 index 7f1e6d784..000000000 --- a/crates/cxx-qt-gen/src/generator/rust/property/signal.rs +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company -// SPDX-FileContributor: Andrew Hayzen -// -// SPDX-License-Identifier: MIT OR Apache-2.0 - -use crate::generator::{ - naming::{property::QPropertyName, qobject::QObjectName}, - rust::fragment::RustFragmentPair, -}; -use quote::quote; - -pub fn generate(idents: &QPropertyName, qobject_idents: &QObjectName) -> RustFragmentPair { - let cpp_class_name_rust = &qobject_idents.cpp_class.rust; - let notify_cpp = &idents.notify.cpp; - let notify_rust = idents.notify.rust.to_string(); - let ident_str = idents.name.rust.to_string(); - let getter_mutable_rust_str = idents.getter_mutable.rust.to_string(); - - RustFragmentPair { - cxx_bridge: vec![quote! { - unsafe extern "C++" { - #[doc = "Notify signal for the Q_PROPERTY"] - #[doc = #ident_str] - #[doc = "\n"] - #[doc = "This can be used to manually notify a change when the unsafe mutable getter,"] - #[doc = #getter_mutable_rust_str] - #[doc = ", is used."] - #[rust_name = #notify_rust] - fn #notify_cpp(self: Pin<&mut #cpp_class_name_rust>); - } - }], - implementation: vec![], - } -} diff --git a/crates/cxx-qt-gen/src/generator/rust/qobject.rs b/crates/cxx-qt-gen/src/generator/rust/qobject.rs index 416b50791..599d24bb2 100644 --- a/crates/cxx-qt-gen/src/generator/rust/qobject.rs +++ b/crates/cxx-qt-gen/src/generator/rust/qobject.rs @@ -89,12 +89,10 @@ impl GeneratedRustQObject { &qobject_idents, &qobject.inherited_methods, )?); - - if let Some(signals_enum) = &qobject.signals { - generated - .blocks - .append(&mut generate_rust_signals(signals_enum, &qobject_idents)?); - } + generated.blocks.append(&mut generate_rust_signals( + &qobject.signals, + &qobject_idents, + )?); // If this type is a singleton then we need to add an include if let Some(qml_metadata) = &qobject.qml_metadata { diff --git a/crates/cxx-qt-gen/src/generator/rust/signals.rs b/crates/cxx-qt-gen/src/generator/rust/signals.rs index 9c3ad063d..01214ec40 100644 --- a/crates/cxx-qt-gen/src/generator/rust/signals.rs +++ b/crates/cxx-qt-gen/src/generator/rust/signals.rs @@ -11,64 +11,51 @@ use crate::{ types::is_unsafe_cxx_type, }, }, - parser::signals::ParsedSignalsEnum, + parser::signals::ParsedSignal, }; use proc_macro2::TokenStream; use quote::quote; -use syn::{Ident, Result, Type}; +use syn::Result; pub fn generate_rust_signals( - signals_enum: &ParsedSignalsEnum, + signals: &Vec, qobject_idents: &QObjectName, ) -> Result { let mut generated = GeneratedRustQObjectBlocks::default(); - let cpp_class_name_rust = &qobject_idents.cpp_class.rust; - let signal_enum_ident = &signals_enum.ident; - let mut signal_matches = vec![]; - - // Add the original enum into the implementation - generated - .cxx_qt_mod_contents - .push(syn::Item::Enum(signals_enum.item.clone())); + let qobject_name = &qobject_idents.cpp_class.rust; // Create the methods for the other signals - for signal in &signals_enum.signals { + for signal in signals { let idents = QSignalName::from(signal); - let signal_ident_rust = idents.enum_name; let signal_ident_cpp_str = idents.name.cpp.to_string(); let emit_ident_cpp = &idents.emit_name.cpp; - let emit_ident_rust = &idents.emit_name.rust; let emit_ident_rust_str = idents.emit_name.rust.to_string(); let connect_ident_cpp = idents.connect_name.cpp; let connect_ident_rust = idents.connect_name.rust; let connect_ident_rust_str = connect_ident_rust.to_string(); let on_ident_rust = idents.on_name; - let mut parameters = signal + let parameters = signal .parameters .iter() .map(|parameter| { let ident = ¶meter.ident; - let mut ty = parameter.ty.clone(); - // Remove any lifetime from the signal, as this will be related - // to the enum. For the CXX methods these can just be - // normal references with inferred lifetimes. - if let Type::Reference(ty) = &mut ty { - ty.lifetime = None; - } + let ty = ¶meter.ty; quote! { #ident: #ty } }) .collect::>(); - let parameter_signatures = if signal.parameters.is_empty() { - quote! { self: Pin<&mut #cpp_class_name_rust> } + + let self_type = if signal.mutable { + quote! { Pin<&mut #qobject_name> } } else { - quote! { self: Pin<&mut #cpp_class_name_rust>, #(#parameters),* } + quote! { &#qobject_name } }; - let parameter_names = signal - .parameters - .iter() - .map(|parameter| parameter.ident.clone()) - .collect::>(); + + let mut unsafe_block = None; + let mut unsafe_call = Some(quote! { unsafe }); + if signal.safe { + std::mem::swap(&mut unsafe_call, &mut unsafe_block); + } // Determine if unsafe is required due to an unsafe parameter let has_unsafe = if signal @@ -81,21 +68,16 @@ pub fn generate_rust_signals( quote! {} }; - // Add the self context to parameters as this is used for the connection function pointer - parameters.insert( - 0, - quote! { - Pin<&mut #cpp_class_name_rust> - }, - ); + let attrs = &signal.method.attrs; let fragment = RustFragmentPair { cxx_bridge: vec![ + // TODO: this will not call our wrapper in the future quote! { - unsafe extern "C++" { - #[doc(hidden)] + #unsafe_block extern "C++" { + #(#attrs)* #[rust_name = #emit_ident_rust_str] - #has_unsafe fn #emit_ident_cpp(#parameter_signatures); + #unsafe_call fn #emit_ident_cpp(self: #self_type, #(#parameters),*); } }, quote! { @@ -105,30 +87,27 @@ pub fn generate_rust_signals( #[doc = ", so that when the signal is emitted the function pointer is executed."] #[must_use] #[rust_name = #connect_ident_rust_str] - fn #connect_ident_cpp(self: Pin<&mut #cpp_class_name_rust>, func: #has_unsafe fn(#(#parameters),*), conn_type: CxxQtConnectionType) -> CxxQtQMetaObjectConnection; + fn #connect_ident_cpp(self: #self_type, func: #has_unsafe fn(#self_type, #(#parameters),*), conn_type: CxxQtConnectionType) -> CxxQtQMetaObjectConnection; } }, ], // Note we do not need the #has_unsafe here as this only needs to be in the CXX bridge // otherwise the function pointer itself needs to be unsafe implementation: vec![quote! { - impl #cpp_class_name_rust { + impl #qobject_name { #[doc = "Connect the given function pointer to the signal "] #[doc = #signal_ident_cpp_str] #[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."] #[must_use] - fn #on_ident_rust(self: Pin<&mut #cpp_class_name_rust>, func: fn(#(#parameters),*)) -> CxxQtQMetaObjectConnection + fn #on_ident_rust(self: #self_type, func: fn(#self_type, #(#parameters),*)) -> CxxQtQMetaObjectConnection { self.#connect_ident_rust(func, CxxQtConnectionType::AutoConnection) } } }], }; - signal_matches.push(quote! { - #signal_enum_ident::#signal_ident_rust { #(#parameter_names),* } => #has_unsafe { self.#emit_ident_rust(#(#parameter_names),*) } - }); generated .cxx_mod_contents @@ -138,23 +117,6 @@ pub fn generate_rust_signals( .append(&mut fragment.implementation_as_items()?); } - // Add the Rust method using the enum to call the methods - let qobject_ident_str = qobject_idents.rust_struct.rust.to_string(); - let signal_enum_ident_str = signal_enum_ident.to_string(); - generated.cxx_qt_mod_contents.push(syn::parse2(quote! { - impl #cpp_class_name_rust { - #[doc = "Emit the signal from the enum "] - #[doc = #signal_enum_ident_str] - #[doc = " on the QObject "] - #[doc = #qobject_ident_str] - pub fn emit(self: Pin<&mut Self>, signal: #signal_enum_ident) { - match signal { - #(#signal_matches),* - } - } - } - })?); - Ok(generated) } diff --git a/crates/cxx-qt-gen/src/parser/cxxqtdata.rs b/crates/cxx-qt-gen/src/parser/cxxqtdata.rs index 262a3cef3..5af7b95e6 100644 --- a/crates/cxx-qt-gen/src/parser/cxxqtdata.rs +++ b/crates/cxx-qt-gen/src/parser/cxxqtdata.rs @@ -4,15 +4,12 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use crate::syntax::foreignmod::{foreign_mod_to_foreign_item_types, verbatim_to_foreign_mod}; -use crate::syntax::{ - attribute::{attribute_find_path, attribute_tokens_to_ident}, - path::path_to_single_ident, -}; +use crate::syntax::{attribute::attribute_find_path, path::path_to_single_ident}; use crate::{ parser::{ - inherit::{InheritMethods, ParsedInheritedMethod}, + inherit::{InheritMethods, MaybeInheritMethods, ParsedInheritedMethod}, qobject::ParsedQObject, - signals::ParsedSignalsEnum, + signals::{MaybeSignalMethods, ParsedSignal, SignalMethods}, }, syntax::expr::expr_to_string, }; @@ -20,12 +17,10 @@ use proc_macro2::TokenStream; use quote::ToTokens; use std::collections::BTreeMap; use syn::{ - spanned::Spanned, Attribute, Error, Ident, Item, ItemEnum, ItemForeignMod, ItemImpl, Result, - Type, TypePath, + spanned::Spanned, Attribute, Error, Ident, Item, ItemForeignMod, ItemImpl, Result, Type, + TypePath, }; -use super::inherit::MaybeInheritMethods; - #[derive(Default)] pub struct ParsedCxxMappings { /// Map of the cxx_name of any types defined in CXX extern blocks @@ -184,7 +179,6 @@ impl ParsedCxxQtData { /// Otherwise return the [syn::Item] to pass through to CXX pub fn parse_cxx_qt_item(&mut self, item: Item) -> Result> { match item { - Item::Enum(item_enum) => self.parse_enum(item_enum), Item::Impl(imp) => self.parse_impl(imp), // Ignore structs which are qobjects Item::Struct(s) if self.qobjects.contains_key(&s.ident) => Ok(None), @@ -200,14 +194,24 @@ impl ParsedCxxQtData { } fn try_parse_inherit_verbatim(&mut self, tokens: TokenStream) -> Result> { - let try_parse: MaybeInheritMethods = syn::parse2(tokens)?; + let try_parse: MaybeInheritMethods = syn::parse2(tokens.clone())?; match try_parse { MaybeInheritMethods::Found(inherited) => { self.add_inherited_methods(inherited)?; Ok(None) } - MaybeInheritMethods::PassThrough(item) => Ok(Some(item)), + MaybeInheritMethods::PassThrough(_item) => { + let try_parse: MaybeSignalMethods = syn::parse2(tokens)?; + + match try_parse { + MaybeSignalMethods::Found(signals) => { + self.add_signal_methods(signals)?; + Ok(None) + } + MaybeSignalMethods::PassThrough(item) => Ok(Some(item)), + } + } } } @@ -236,6 +240,30 @@ impl ParsedCxxQtData { Ok(()) } + fn parse_signals_mod(&mut self, tokens: TokenStream) -> Result<()> { + let signals: SignalMethods = syn::parse2(tokens)?; + + self.add_signal_methods(signals) + } + + fn add_signal_methods(&mut self, signals: SignalMethods) -> Result<()> { + for method in signals.base_functions.into_iter() { + let parsed_signal_method = ParsedSignal::parse(method, signals.safety)?; + + if let Some(ref mut qobject) = + self.qobjects.get_mut(&parsed_signal_method.qobject_ident) + { + qobject.signals.push(parsed_signal_method); + } else { + return Err(Error::new_spanned( + parsed_signal_method.qobject_ident, + "No QObject with this name found.", + )); + } + } + Ok(()) + } + fn parse_foreign_mod(&mut self, mut foreign_mod: ItemForeignMod) -> Result> { // Check if the foreign mod has cxx_qt::inherit on it if let Some(index) = attribute_find_path(&foreign_mod.attrs, &["cxx_qt", "inherit"]) { @@ -245,29 +273,17 @@ impl ParsedCxxQtData { self.parse_inherit_mod(foreign_mod.into_token_stream())?; return Ok(None); } - Ok(Some(Item::ForeignMod(foreign_mod))) - } - /// Parse a [syn::ItemEnum] into the qobjects if it's a CXX-Qt signal - /// otherwise return as a [syn::Item] to pass through. - fn parse_enum(&mut self, item_enum: ItemEnum) -> Result> { - // Check if the enum has cxx_qt::qsignals(T) - if let Some(index) = attribute_find_path(&item_enum.attrs, &["cxx_qt", "qsignals"]) { - let ident = attribute_tokens_to_ident(&item_enum.attrs[index])?; - // Find the matching QObject for the enum - if let Some(qobject) = self.qobjects.get_mut(&ident) { - qobject.signals = Some(ParsedSignalsEnum::from(&item_enum, index)?); - return Ok(None); - } else { - return Err(Error::new( - item_enum.span(), - "No matching QObject found for the given cxx_qt::qsignals enum.", - )); - } + // Check if the foreign mod has cxx_qt::qsignals on it + if let Some(index) = attribute_find_path(&foreign_mod.attrs, &["cxx_qt", "qsignals"]) { + // Remove the signals attribute + foreign_mod.attrs.remove(index); + + self.parse_signals_mod(foreign_mod.into_token_stream())?; + return Ok(None); } - // Passthrough this unknown enum - Ok(Some(Item::Enum(item_enum))) + Ok(Some(Item::ForeignMod(foreign_mod))) } /// Parse a [syn::ItemImpl] into the qobjects if it's a CXX-Qt implementation diff --git a/crates/cxx-qt-gen/src/parser/qobject.rs b/crates/cxx-qt-gen/src/parser/qobject.rs index 445df39c2..21290993d 100644 --- a/crates/cxx-qt-gen/src/parser/qobject.rs +++ b/crates/cxx-qt-gen/src/parser/qobject.rs @@ -12,7 +12,7 @@ use crate::{ inherit::ParsedInheritedMethod, invokable::ParsedQInvokable, property::{ParsedQProperty, ParsedRustField}, - signals::ParsedSignalsEnum, + signals::ParsedSignal, }, syntax::path::path_compare_str, }; @@ -44,8 +44,8 @@ pub struct ParsedQObject { /// The namespace of the QObject. If one isn't specified for the QObject, /// this will be the same as the module pub namespace: String, - /// Representation of the Signals enum that defines the Q_SIGNALS for the QObject - pub signals: Option, + /// Representation of the Q_SIGNALS for the QObject + pub signals: Vec, /// List of invokables that need to be implemented on the C++ object in Rust /// /// These will also be exposed as Q_INVOKABLE on the C++ object @@ -112,7 +112,7 @@ impl ParsedQObject { base_class, qobject_struct, namespace, - signals: None, + signals: vec![], invokables: vec![], inherited_methods: vec![], passthrough_impl_items: vec![], diff --git a/crates/cxx-qt-gen/src/parser/signals.rs b/crates/cxx-qt-gen/src/parser/signals.rs index d4e447cb2..d53eae917 100644 --- a/crates/cxx-qt-gen/src/parser/signals.rs +++ b/crates/cxx-qt-gen/src/parser/signals.rs @@ -4,91 +4,167 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use crate::parser::parameter::ParsedFunctionParameter; +use crate::syntax::attribute::attribute_find_path; use crate::syntax::expr::expr_to_string; -use crate::syntax::{attribute::attribute_find_path, fields::fields_to_named_fields_mut}; -use syn::{Ident, ItemEnum, Result, Variant}; +use crate::syntax::foreignmod; +use crate::syntax::safety::Safety; +use crate::{generator::naming::CombinedIdent, syntax::types}; +use quote::format_ident; +use syn::Attribute; +use syn::{ + parse::{Parse, ParseStream}, + spanned::Spanned, + Error, ForeignItem, ForeignItemFn, Ident, Item, ItemForeignMod, LitStr, Result, Token, +}; + +/// Used when parsing a syn::Item::Verbatim, that we suspect may be a `#[cxx_qt::qsignals]` block, +/// but we don't yet know whether this is actually the case. +/// This is the case if `#[cxx_qt::qsignals]` is used with `unsafe extern "C++"`. +pub enum MaybeSignalMethods { + /// We found a `#[cxx_qt::qsignals]` block + Found(SignalMethods), + /// `#[cxx_qt::qsignals]` block not found, pass this Item through to outside code! + PassThrough(Item), +} + +impl Parse for MaybeSignalMethods { + fn parse(input: ParseStream) -> Result { + let lookahead = input.fork(); + if let Ok(attribute) = lookahead.call(Attribute::parse_outer) { + if attribute_find_path(attribute.as_slice(), &["cxx_qt", "qsignals"]).is_some() { + input.call(Attribute::parse_outer)?; + let methods = input.parse::()?; + return Ok(Self::Found(methods)); + } + } + + Ok(Self::PassThrough(input.parse()?)) + } +} + +/// This type is used when parsing the `#[cxx_qt::qsignals]` macro contents into raw ForeignItemFn items +pub struct SignalMethods { + pub safety: Safety, + pub base_functions: Vec, +} + +impl Parse for SignalMethods { + fn parse(input: ParseStream) -> Result { + let mut base_functions = Vec::new(); + + // Ensure that any attributes on the block have been removed + // + // Otherwise parsing of unsafe can fail due to #[doc] + let attrs = input.call(Attribute::parse_outer)?; + if !attrs.is_empty() { + return Err(Error::new( + attrs.first().span(), + "Unexpected attribute on #[cxx_qt::inherit] block.", + )); + } + + // This looks somewhat counter-intuitive, but if we add `unsafe` + // to the `extern "C++"` block, the contained functions will be safe to call. + let safety = if input.peek(Token![unsafe]) { + Safety::Safe + } else { + Safety::Unsafe + }; + if safety == Safety::Safe { + input.parse::()?; + } + + let extern_block = input.parse::()?; + if extern_block.abi.name != Some(LitStr::new("C++", extern_block.abi.span())) { + return Err(Error::new( + extern_block.abi.span(), + "qsignals blocks must be marked with `extern \"C++\"`", + )); + } + + for item in extern_block.items { + match item { + ForeignItem::Fn(function) => { + base_functions.push(function); + } + _ => { + return Err(Error::new( + item.span(), + "Only functions are allowed in #[cxx_qt::qsignals] blocks", + )) + } + } + } + + Ok(SignalMethods { + safety, + base_functions, + }) + } +} /// Describes an individual Signal pub struct ParsedSignal { - /// The name of the signal - pub ident: Ident, + /// The original [syn::ForeignItemFn] of the signal declaration + pub method: ForeignItemFn, + /// The type of the self argument + pub qobject_ident: Ident, + /// whether the signal is marked as mutable + pub mutable: bool, + /// Whether the method is safe to call. + pub safe: bool, /// The parameters of the signal pub parameters: Vec, - /// The name of the signal in C++ - pub cxx_name: Option, + /// The name of the signal + pub ident: CombinedIdent, /// If the signal is defined in the base class pub inherit: bool, } impl ParsedSignal { - pub fn from(variant: &mut Variant) -> Result { - // Find cxx_name and inherit - let inherit = if let Some(index) = attribute_find_path(&variant.attrs, &["inherit"]) { - // Remove the attribute from the original enum - // so that it doesn't end up in the Rust generation - variant.attrs.remove(index); - true - } else { - false - }; - let cxx_name = if let Some(index) = attribute_find_path(&variant.attrs, &["cxx_name"]) { - let str = expr_to_string(&variant.attrs[index].meta.require_name_value()?.value)?; - // Remove the attribute from the original enum - // so that it doesn't end up in the Rust generation - variant.attrs.remove(index); - Some(str) - } else { - None - }; + pub fn parse(mut method: ForeignItemFn, safety: Safety) -> Result { + if safety == Safety::Unsafe && method.sig.unsafety.is_none() { + return Err(Error::new( + method.span(), + "qsignals methods must be marked as unsafe or wrapped in an `unsafe extern \"C++\"` block!", + )); + } - // Read the fields into parameter blocks - let parameters = fields_to_named_fields_mut(&mut variant.fields)? - .into_iter() - .map(|field| { - Ok(ParsedFunctionParameter { - ident: field.ident.clone().unwrap(), - ty: field.ty.clone(), - }) - }) - .collect::>>()?; - - Ok(ParsedSignal { - ident: variant.ident.clone(), - parameters, - cxx_name, - inherit, - }) - } -} + let mut inherit = false; -/// Describes a Signals enum for a QObject -pub struct ParsedSignalsEnum { - /// The name of the signals enum - pub ident: Ident, - /// The original enum for the signals - pub item: ItemEnum, - /// A list of the signals defined in the enum - pub signals: Vec, -} + let self_receiver = foreignmod::self_type_from_foreign_fn(&method.sig)?; + let (qobject_ident, mutability) = types::extract_qobject_ident(&self_receiver.ty)?; + let mutable = mutability.is_some(); + + let parameters = ParsedFunctionParameter::parse_all_ignoring_receiver(&method.sig)?; + + let mut ident = CombinedIdent::from_rust_function(method.sig.ident.clone()); -impl ParsedSignalsEnum { - /// Constructs a ParsedSignals object from a given [syn::ItemEnum] block - pub fn from(item: &ItemEnum, attr_index: usize) -> Result { - // Remove the attribute index as we have processed it - let mut item = item.clone(); - item.attrs.remove(attr_index); + if let Some(index) = attribute_find_path(&method.attrs, &["cxx_name"]) { + ident.cpp = format_ident!( + "{}", + expr_to_string(&method.attrs[index].meta.require_name_value()?.value)? + ); - let signals = item - .variants - // Note we use mut here so that any attributes can be removed - .iter_mut() - .map(ParsedSignal::from) - .collect::>>()?; + method.attrs.remove(index); + } + + if let Some(index) = attribute_find_path(&method.attrs, &["inherit"]) { + inherit = true; + + method.attrs.remove(index); + } + + let safe = method.sig.unsafety.is_none(); Ok(Self { - ident: item.ident.clone(), - item, - signals, + method, + qobject_ident, + mutable, + parameters, + ident, + safe, + inherit, }) } } diff --git a/crates/cxx-qt-gen/src/syntax/attribute.rs b/crates/cxx-qt-gen/src/syntax/attribute.rs index e5ee77569..c3740469b 100644 --- a/crates/cxx-qt-gen/src/syntax/attribute.rs +++ b/crates/cxx-qt-gen/src/syntax/attribute.rs @@ -7,10 +7,10 @@ use crate::syntax::path::path_compare_str; use proc_macro2::Span; use std::collections::HashMap; use syn::{ - ext::IdentExt, + // ext::IdentExt, parse::{Parse, ParseStream}, spanned::Spanned, - Attribute, Error, Ident, Meta, Result, Token, + Attribute, Error, Meta, Result, Token, }; /// Representation of a key and an optional value in an attribute, eg `attribute(key = value)` or `attribute(key)` @@ -43,31 +43,31 @@ pub fn attribute_find_path(attrs: &[Attribute], path: &[&str]) -> Option None } -/// Returns the [syn::Ident] T from attribute(T) and errors if there is none or many -pub fn attribute_tokens_to_ident(attr: &Attribute) -> Result { - let attrs = attribute_tokens_to_list(attr)?; - if attrs.len() == 1 { - Ok(attrs[0].clone()) - } else { - Err(Error::new( - attr.span(), - "Expected only one ident in the attribute", - )) - } -} +// /// Returns the [syn::Ident] T from attribute(T) and errors if there is none or many +// pub fn attribute_tokens_to_ident(attr: &Attribute) -> Result { +// let attrs = attribute_tokens_to_list(attr)?; +// if attrs.len() == 1 { +// Ok(attrs[0].clone()) +// } else { +// Err(Error::new( +// attr.span(), +// "Expected only one ident in the attribute", +// )) +// } +// } -/// Returns the list of [syn::Ident]'s A, B, C from attribute(A, B, C) -/// and errors if there is a parser error -pub fn attribute_tokens_to_list(attr: &Attribute) -> Result> { - attr.meta - .require_list()? - .parse_args_with(|input: ParseStream| -> Result> { - Ok(input - .parse_terminated(Ident::parse_any, Token![,])? - .into_iter() - .collect::>()) - }) -} +// /// Returns the list of [syn::Ident]'s A, B, C from attribute(A, B, C) +// /// and errors if there is a parser error +// pub fn attribute_tokens_to_list(attr: &Attribute) -> Result> { +// attr.meta +// .require_list()? +// .parse_args_with(|input: ParseStream| -> Result> { +// Ok(input +// .parse_terminated(Ident::parse_any, Token![,])? +// .into_iter() +// .collect::>()) +// }) +// } /// Whether the attribute has a default value if there is one missing /// diff --git a/crates/cxx-qt-gen/test_inputs/signals.rs b/crates/cxx-qt-gen/test_inputs/signals.rs index 3581ed07a..21b92e184 100644 --- a/crates/cxx-qt-gen/test_inputs/signals.rs +++ b/crates/cxx-qt-gen/test_inputs/signals.rs @@ -6,23 +6,27 @@ mod ffi { type QPoint = cxx_qt_lib::QPoint; } - #[cxx_qt::qsignals(MyObject)] - enum MySignals<'a> { - Ready, - DataChanged { + #[cxx_qt::qsignals] + unsafe extern "C++" { + fn ready(self: Pin<&mut qobject::MyObject>); + + fn data_changed( + self: Pin<&mut qobject::MyObject>, first: i32, second: UniquePtr, third: QPoint, fourth: &'a QPoint, - }, + ); + #[cxx_name = "newData"] #[inherit] - BaseClassNewData { + fn base_class_new_data( + self: Pin<&mut qobject::MyObject>, first: i32, second: UniquePtr, third: QPoint, fourth: &'a QPoint, - }, + ); } #[cxx_qt::qobject] @@ -38,12 +42,8 @@ mod ffi { }, cxx_qt_lib::ConnectionType::AutoConnection, ); - self.as_mut().emit(MySignals::DataChanged { - first: 1, - second: Opaque::new(), - third: QPoint::new(1, 2), - fourth: &QPoint::new(1, 2), - }); + self.as_mut() + .data_changed(1, Opaque::new(), QPoint::new(1, 2), &QPoint::new(1, 2)); } } } diff --git a/examples/demo_threading/rust/src/lib.rs b/examples/demo_threading/rust/src/lib.rs index a60405225..8c766c8cd 100644 --- a/examples/demo_threading/rust/src/lib.rs +++ b/examples/demo_threading/rust/src/lib.rs @@ -70,16 +70,14 @@ mod ffi { // Enabling threading on the qobject impl cxx_qt::Threading for qobject::EnergyUsage {} - /// Define Q_SIGNALS that are created on the QObject - #[cxx_qt::qsignals(EnergyUsage)] - #[allow(clippy::enum_variant_names)] - pub enum EnergySignals { + #[cxx_qt::qsignals] + unsafe extern "C++" { /// A new sensor has been detected - SensorAdded { uuid: QString }, + fn sensor_added(self: Pin<&mut qobject::EnergyUsage>, uuid: QString); /// A value on an existing sensor has changed - SensorChanged { uuid: QString }, + fn sensor_changed(self: Pin<&mut qobject::EnergyUsage>, uuid: QString); /// An existing sensor has been removed - SensorRemoved { uuid: QString }, + fn sensor_removed(self: Pin<&mut qobject::EnergyUsage>, uuid: QString); } impl qobject::EnergyUsage { diff --git a/examples/demo_threading/rust/src/workers/sensors.rs b/examples/demo_threading/rust/src/workers/sensors.rs index fcfbf0d73..ebd5bb2e9 100644 --- a/examples/demo_threading/rust/src/workers/sensors.rs +++ b/examples/demo_threading/rust/src/workers/sensors.rs @@ -8,7 +8,6 @@ use crate::{ constants::SENSOR_MAXIMUM_COUNT, ffi::{EnergyUsageCxxQtThread, EnergyUsageQt}, network::NetworkChannel, - EnergySignals, }; use cxx_qt_lib::QString; use std::{ @@ -89,14 +88,9 @@ impl SensorsWorker { // Queue a Signal that the sensor has been removed to Qt qt_thread .queue( - move |mut qobject_energy_usage: std::pin::Pin< - &mut EnergyUsageQt, - >| { - qobject_energy_usage.as_mut().emit( - EnergySignals::SensorRemoved { - uuid: QString::from(&uuid.to_string()), - }, - ); + move |qobject_energy_usage: std::pin::Pin<&mut EnergyUsageQt>| { + qobject_energy_usage + .sensor_removed(QString::from(&uuid.to_string())); }, ) .unwrap(); @@ -125,28 +119,22 @@ impl SensorsWorker { if is_occupied { qt_thread .queue( - move |mut qobject_energy_usage: std::pin::Pin< + move |qobject_energy_usage: std::pin::Pin< &mut EnergyUsageQt, >| { - qobject_energy_usage.as_mut().emit( - EnergySignals::SensorChanged { - uuid: QString::from(&uuid.to_string()), - }, - ); + qobject_energy_usage + .sensor_changed(QString::from(&uuid.to_string())); }, ) .unwrap(); } else { qt_thread .queue( - move |mut qobject_energy_usage: std::pin::Pin< + move |qobject_energy_usage: std::pin::Pin< &mut EnergyUsageQt, >| { - qobject_energy_usage.as_mut().emit( - EnergySignals::SensorAdded { - uuid: QString::from(&uuid.to_string()), - }, - ); + qobject_energy_usage + .sensor_added(QString::from(&uuid.to_string())); }, ) .unwrap(); diff --git a/examples/qml_features/rust/src/custom_base_class.rs b/examples/qml_features/rust/src/custom_base_class.rs index 437fca87d..3e8d5d314 100644 --- a/examples/qml_features/rust/src/custom_base_class.rs +++ b/examples/qml_features/rust/src/custom_base_class.rs @@ -50,20 +50,17 @@ pub mod ffi { // Enabling threading on the qobject impl cxx_qt::Threading for qobject::CustomBaseClass {} - /// The signals for our QAbstractListModel struct // ANCHOR: book_qsignals_inherit - #[cxx_qt::qsignals(CustomBaseClass)] - pub enum Signals<'a> { + #[cxx_qt::qsignals] + unsafe extern "C++" { /// Inherit the DataChanged signal from the QAbstractListModel base class #[inherit] - DataChanged { - /// Top left affected index - top_left: &'a QModelIndex, - /// Bottom right affected index - bottom_right: &'a QModelIndex, - /// Roles that have been modified - roles: &'a QVector_i32, - }, + fn data_changed( + self: Pin<&mut qobject::CustomBaseClass>, + top_left: &QModelIndex, + bottom_right: &QModelIndex, + roles: &QVector_i32, + ); } // ANCHOR_END: book_qsignals_inherit @@ -133,11 +130,8 @@ pub mod ffi { let model_index = self.index(index, 0, &QModelIndex::default()); let mut vector_roles = QVector_i32::default(); vector_roles.append(1); - self.as_mut().emit(Signals::DataChanged { - top_left: &model_index, - bottom_right: &model_index, - roles: &vector_roles, - }); + self.as_mut() + .data_changed(&model_index, &model_index, &vector_roles); } } diff --git a/examples/qml_features/rust/src/multiple_qobjects.rs b/examples/qml_features/rust/src/multiple_qobjects.rs index b28f6a15b..899a023df 100644 --- a/examples/qml_features/rust/src/multiple_qobjects.rs +++ b/examples/qml_features/rust/src/multiple_qobjects.rs @@ -38,13 +38,13 @@ pub mod ffi { // Enabling threading on the qobject impl cxx_qt::Threading for qobject::FirstObject {} - /// Signals for the first QObject - #[cxx_qt::qsignals(FirstObject)] - pub enum FirstSignals { + #[cxx_qt::qsignals] + unsafe extern "C++" { /// Accepted Q_SIGNAL - Accepted, + fn accepted(self: Pin<&mut qobject::FirstObject>); + /// Rejected Q_SIGNAL - Rejected, + fn rejected(self: Pin<&mut qobject::FirstObject>); } impl qobject::FirstObject { @@ -56,10 +56,10 @@ pub mod ffi { if new_value % 2 == 0 { self.as_mut().set_color(QColor::from_rgb(0, 0, 255)); - self.emit(FirstSignals::Accepted); + self.accepted(); } else { self.as_mut().set_color(QColor::from_rgb(255, 0, 0)); - self.emit(FirstSignals::Rejected); + self.rejected(); } } } @@ -85,13 +85,13 @@ pub mod ffi { // Enabling threading on the qobject impl cxx_qt::Threading for qobject::SecondObject {} - /// Signals for the second QObject - #[cxx_qt::qsignals(SecondObject)] - pub enum SecondSignals { + #[cxx_qt::qsignals] + unsafe extern "C++" { /// Accepted Q_SIGNAL - Accepted, + fn accepted(self: Pin<&mut qobject::SecondObject>); + /// Rejected Q_SIGNAL - Rejected, + fn rejected(self: Pin<&mut qobject::SecondObject>); } impl qobject::SecondObject { @@ -104,10 +104,10 @@ pub mod ffi { if new_value % 5 == 0 { self.as_mut() .set_url(QUrl::from("https://github.com/kdab/cxx-qt")); - self.emit(SecondSignals::Accepted); + self.accepted(); } else { self.as_mut().set_url(QUrl::from("https://kdab.com")); - self.emit(SecondSignals::Rejected); + self.rejected(); } } } diff --git a/examples/qml_features/rust/src/nested_qobjects.rs b/examples/qml_features/rust/src/nested_qobjects.rs index 27001b4a8..881f47576 100644 --- a/examples/qml_features/rust/src/nested_qobjects.rs +++ b/examples/qml_features/rust/src/nested_qobjects.rs @@ -25,14 +25,10 @@ pub mod ffi { counter: i32, } - /// Signals for the inner QObject - #[cxx_qt::qsignals(InnerObject)] - pub enum InnerSignals { + #[cxx_qt::qsignals] + extern "C++" { /// A signal showing how to refer to another QObject as an argument - Called { - /// Inner QObject being referred to - inner: *mut CxxInnerObject, - }, + unsafe fn called(self: Pin<&mut qobject::InnerObject>, inner: *mut CxxInnerObject); } /// The outer QObject which has a Q_PROPERTY pointing to the inner QObject @@ -50,14 +46,10 @@ pub mod ffi { } } - /// Signals for the outer QObject - #[cxx_qt::qsignals(OuterObject)] - pub enum OuterSignals { + #[cxx_qt::qsignals] + extern "C++" { /// A signal showing how to refer to another QObject as an argument - Called { - /// Inner QObject being referred to - inner: *mut CxxInnerObject, - }, + unsafe fn called(self: Pin<&mut qobject::OuterObject>, inner: *mut CxxInnerObject); } impl qobject::OuterObject { @@ -71,7 +63,7 @@ pub mod ffi { if let Some(inner) = unsafe { qobject.inner().as_mut() } { let pinned_inner = unsafe { Pin::new_unchecked(inner) }; // Now pinned inner can be used as normal - pinned_inner.emit(InnerSignals::Called { inner: obj }); + unsafe { pinned_inner.called(obj); } } }) .release(); @@ -87,7 +79,7 @@ pub mod ffi { println!("Inner object's counter property: {}", inner.counter()); } - self.emit(OuterSignals::Called { inner }); + unsafe { self.called(inner); } } /// Reset the counter of the inner QObject stored in the Q_PROPERTY @@ -102,7 +94,7 @@ pub mod ffi { // Retrieve *mut T let inner = *self.inner(); - self.emit(OuterSignals::Called { inner }); + unsafe { self.called(inner) }; } } } diff --git a/examples/qml_features/rust/src/serialisation.rs b/examples/qml_features/rust/src/serialisation.rs index 107d3dbc6..a1092f348 100644 --- a/examples/qml_features/rust/src/serialisation.rs +++ b/examples/qml_features/rust/src/serialisation.rs @@ -48,14 +48,10 @@ pub mod ffi { pub string: QString, } - /// Signals for the QObject - #[cxx_qt::qsignals(Serialisation)] - pub enum Connection { + #[cxx_qt::qsignals] + unsafe extern "C++" { /// An error signal - Error { - /// The message of the error - message: QString, - }, + fn error(self: Pin<&mut qobject::Serialisation>, message: QString); } impl Default for Serialisation { @@ -83,9 +79,7 @@ pub mod ffi { match serde_json::to_string(&data_serde) { Ok(data_string) => QString::from(&data_string), Err(err) => { - self.emit(Connection::Error { - message: QString::from(&err.to_string()), - }); + self.error(QString::from(&err.to_string())); QString::default() } } @@ -101,9 +95,7 @@ pub mod ffi { self.as_mut().set_string(QString::from(&data_serde.string)); } Err(err) => { - self.emit(Connection::Error { - message: QString::from(&err.to_string()), - }); + self.error(QString::from(&err.to_string())); } } } diff --git a/examples/qml_features/rust/src/signals.rs b/examples/qml_features/rust/src/signals.rs index 5e8fe3373..6e80da703 100644 --- a/examples/qml_features/rust/src/signals.rs +++ b/examples/qml_features/rust/src/signals.rs @@ -20,28 +20,17 @@ pub mod ffi { type QUrl = cxx_qt_lib::QUrl; } - /// Q_SIGNALs for the QObject // ANCHOR: book_signals_enum - #[cxx_qt::qsignals(RustSignals)] - pub enum Connection<'a> { + #[cxx_qt::qsignals] + unsafe extern "C++" { /// A Q_SIGNAL emitted when a connection occurs - Connected { - /// The url for the connection - url: &'a QUrl, - }, + fn connected(self: Pin<&mut qobject::RustSignals>, url: &QUrl); + /// A Q_SIGNAL emitted when a disconnect occurs - Disconnected, + fn disconnected(self: Pin<&mut qobject::RustSignals>); + /// A Q_SIGNAL emitted when an error occurs - Error { - /// The message of the error - message: QString, - }, - // Example of using #[inherit] so that connections to the logging_enabled property can be made - #[inherit] - // We don't ever emit this enum, so silence clippy warnings - #[allow(dead_code)] - /// The Q_SIGNAL emitted when the Q_PROPERTY logging_enabled changes - LoggingEnabledChanged, + fn error(self: Pin<&mut qobject::RustSignals>, message: QString); } // ANCHOR_END: book_signals_enum @@ -60,24 +49,22 @@ pub mod ffi { impl qobject::RustSignals { /// Connect to the given url #[qinvokable] - pub fn connect(mut self: Pin<&mut Self>, url: &QUrl) { + pub fn connect(self: Pin<&mut Self>, url: &QUrl) { // Check that the url starts with kdab if url.to_string().starts_with("https://kdab.com") { // Emit a signal to QML stating that we have connected - self.as_mut().emit(Connection::Connected { url }); + self.connected(url); } else { // Emit a signal to QML stating that the url was incorrect - self.emit(Connection::Error { - message: QString::from("URL does not start with https://kdab.com"), - }); + self.error(QString::from("URL does not start with https://kdab.com")); } } /// Disconnect #[qinvokable] - pub fn disconnect(mut self: Pin<&mut Self>) { + pub fn disconnect(self: Pin<&mut Self>) { // Emit a signal to QML stating that we have disconnected - self.as_mut().emit(Connection::Disconnected); + self.disconnected(); } /// Initialise the QObject, creating a connection reacting to the logging enabled property