From be13c90a59b39e44f68abc505a9cc08133e94955 Mon Sep 17 00:00:00 2001 From: Andrew Hayzen Date: Thu, 8 Jun 2023 15:30:45 +0100 Subject: [PATCH] cxx-qt-gen: move #[qinvokable] to be inside extern "RustQt" Closes #558 --- CHANGELOG.md | 1 + book/src/concepts/inheritance.md | 1 + .../cxx-qt-gen/src/generator/cpp/invokable.rs | 20 +- .../src/generator/naming/invokable.rs | 17 +- .../src/generator/rust/invokable.rs | 94 ++----- .../src/generator/rust/property/mod.rs | 6 +- .../cxx-qt-gen/src/generator/rust/signals.rs | 10 +- crates/cxx-qt-gen/src/parser/cxxqtdata.rs | 33 ++- crates/cxx-qt-gen/src/parser/invokable.rs | 39 +-- crates/cxx-qt-gen/src/parser/qobject.rs | 93 +------ crates/cxx-qt-gen/src/syntax/implitemfn.rs | 72 ------ crates/cxx-qt-gen/src/syntax/mod.rs | 1 - crates/cxx-qt-gen/src/syntax/types.rs | 32 --- crates/cxx-qt-gen/src/writer/rust/mod.rs | 13 +- crates/cxx-qt-gen/test_inputs/invokables.rs | 39 ++- .../cxx-qt-gen/test_outputs/inheritance.cpp | 14 - crates/cxx-qt-gen/test_outputs/inheritance.h | 3 - crates/cxx-qt-gen/test_outputs/inheritance.rs | 57 +---- crates/cxx-qt-gen/test_outputs/invokables.rs | 103 +++----- .../test_outputs/passthrough_and_naming.cpp | 14 - .../test_outputs/passthrough_and_naming.h | 2 - .../test_outputs/passthrough_and_naming.rs | 69 ++--- crates/cxx-qt-gen/test_outputs/properties.rs | 7 +- crates/cxx-qt-gen/test_outputs/signals.cpp | 7 - crates/cxx-qt-gen/test_outputs/signals.h | 1 - crates/cxx-qt-gen/test_outputs/signals.rs | 44 ++-- crates/cxx-qt-macro/src/lib.rs | 14 +- examples/demo_threading/rust/src/lib.rs | 169 ++++++------ .../plugin/rust/src/lib.rs | 58 +++-- examples/qml_features/rust/src/containers.rs | 218 +++++++++------- .../rust/src/custom_base_class.rs | 242 +++++++++++------- examples/qml_features/rust/src/invokables.rs | 75 +++--- .../rust/src/multiple_qobjects.rs | 69 +++-- .../qml_features/rust/src/nested_qobjects.rs | 80 +++--- examples/qml_features/rust/src/properties.rs | 74 +++--- .../qml_features/rust/src/serialisation.rs | 58 +++-- examples/qml_features/rust/src/signals.rs | 122 +++++---- examples/qml_features/rust/src/singleton.rs | 19 +- examples/qml_features/rust/src/threading.rs | 142 +++++----- examples/qml_features/rust/src/types.rs | 47 ++-- examples/qml_minimal/rust/src/cxxqt_object.rs | 34 ++- tests/basic_cxx_qt/rust/src/data.rs | 36 ++- tests/basic_cxx_qt/rust/src/lib.rs | 115 +++++---- tests/basic_cxx_qt/rust/src/locking.rs | 65 +++-- 44 files changed, 1217 insertions(+), 1212 deletions(-) delete mode 100644 crates/cxx-qt-gen/src/syntax/implitemfn.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 8449dbc64..cb97f72cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Moved to `syn` 2.0 internally and for any exported `syn` types - `impl cxx_qt::Threading for qobject::T` now needs to be specified for `qt_thread()` to be available - `#[cxx_qt::qsignals]` and `#[cxx_qt::inherit]` are now used in an `extern "RustQt"` block as `#[qsignal]` and `#[inherit]` +- `#[qinvokable]` is now defined as a signature in `extern "RustQt"` ### Removed diff --git a/book/src/concepts/inheritance.md b/book/src/concepts/inheritance.md index 0a1b9efd5..602be2426 100644 --- a/book/src/concepts/inheritance.md +++ b/book/src/concepts/inheritance.md @@ -15,6 +15,7 @@ To support creating such subclasses directly from within Rust, CXX-Qt provides y To access the methods of a base class in Rust, use the `#[inherit]` macro. It can be placed in front of a function in a `extern "RustQt"` block in a `#[cxx_qt::bridge]`. +TODO: all of these examples need sorting out ```rust,ignore {{#include ../../../examples/qml_features/rust/src/custom_base_class.rs:book_inherit_qalm}} diff --git a/crates/cxx-qt-gen/src/generator/cpp/invokable.rs b/crates/cxx-qt-gen/src/generator/cpp/invokable.rs index ba030626f..cc181a4aa 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/invokable.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/invokable.rs @@ -161,14 +161,18 @@ mod tests { fn test_generate_cpp_invokables() { let invokables = vec![ ParsedQInvokable { - method: parse_quote! { fn void_invokable(&self) {} }, + method: parse_quote! { fn void_invokable(self: &qobject::MyObject); }, + qobject_ident: format_ident!("MyObject"), mutable: false, + safe: true, parameters: vec![], specifiers: HashSet::new(), }, ParsedQInvokable { - method: parse_quote! { fn trivial_invokable(&self, param: i32) -> i32 {} }, + method: parse_quote! { fn trivial_invokable(self: &qobject::MyObject, param: i32) -> i32; }, + qobject_ident: format_ident!("MyObject"), mutable: false, + safe: true, parameters: vec![ParsedFunctionParameter { ident: format_ident!("param"), ty: parse_quote! { i32 }, @@ -176,8 +180,10 @@ mod tests { specifiers: HashSet::new(), }, ParsedQInvokable { - method: parse_quote! { fn opaque_invokable(self: Pin<&mut Self>, param: &QColor) -> UniquePtr {} }, + method: parse_quote! { fn opaque_invokable(self: Pin<&mut qobject::MyObject>, param: &QColor) -> UniquePtr; }, + qobject_ident: format_ident!("MyObject"), mutable: true, + safe: true, parameters: vec![ParsedFunctionParameter { ident: format_ident!("param"), ty: parse_quote! { &QColor }, @@ -185,8 +191,10 @@ mod tests { specifiers: HashSet::new(), }, ParsedQInvokable { - method: parse_quote! { fn specifiers_invokable(&self, param: i32) -> i32 {} }, + method: parse_quote! { fn specifiers_invokable(self: &qobject::MyObject, param: i32) -> i32; }, + qobject_ident: format_ident!("MyObject"), mutable: false, + safe: true, parameters: vec![ParsedFunctionParameter { ident: format_ident!("param"), ty: parse_quote! { i32 }, @@ -298,8 +306,10 @@ mod tests { #[test] fn test_generate_cpp_invokables_mapped_cxx_name() { let invokables = vec![ParsedQInvokable { - method: parse_quote! { fn trivial_invokable(&self, param: A) -> B {} }, + method: parse_quote! { fn trivial_invokable(self: &qobject::MyObject, param: A) -> B; }, + qobject_ident: format_ident!("MyObject"), mutable: false, + safe: true, parameters: vec![ParsedFunctionParameter { ident: format_ident!("param"), ty: parse_quote! { i32 }, diff --git a/crates/cxx-qt-gen/src/generator/naming/invokable.rs b/crates/cxx-qt-gen/src/generator/naming/invokable.rs index 69070a07d..00e541853 100644 --- a/crates/cxx-qt-gen/src/generator/naming/invokable.rs +++ b/crates/cxx-qt-gen/src/generator/naming/invokable.rs @@ -5,7 +5,7 @@ use crate::{generator::naming::CombinedIdent, parser::invokable::ParsedQInvokable}; use convert_case::{Case, Casing}; use quote::format_ident; -use syn::{Ident, ImplItemFn}; +use syn::{ForeignItemFn, Ident}; /// Names for parts of a Q_INVOKABLE pub struct QInvokableName { @@ -19,8 +19,8 @@ impl From<&ParsedQInvokable> for QInvokableName { } } -impl From<&ImplItemFn> for QInvokableName { - fn from(method: &ImplItemFn) -> Self { +impl From<&ForeignItemFn> for QInvokableName { + fn from(method: &ForeignItemFn) -> Self { let ident = &method.sig.ident; Self { name: CombinedIdent::from_rust_function(ident.clone()), @@ -50,14 +50,13 @@ mod tests { #[test] fn test_from_impl_method() { - let item: ImplItemFn = parse_quote! { - fn my_invokable() { - - } - }; let parsed = ParsedQInvokable { - method: item, + method: parse_quote! { + fn my_invokable(self: &qobject::MyObject); + }, + qobject_ident: format_ident!("MyObject"), mutable: false, + safe: true, parameters: vec![], specifiers: HashSet::new(), }; diff --git a/crates/cxx-qt-gen/src/generator/rust/invokable.rs b/crates/cxx-qt-gen/src/generator/rust/invokable.rs index a02756398..ad53a7654 100644 --- a/crates/cxx-qt-gen/src/generator/rust/invokable.rs +++ b/crates/cxx-qt-gen/src/generator/rust/invokable.rs @@ -6,10 +6,7 @@ use crate::{ generator::{ naming::{invokable::QInvokableName, qobject::QObjectName}, - rust::{ - fragment::RustFragmentPair, qobject::GeneratedRustQObjectBlocks, - types::is_unsafe_cxx_type, - }, + rust::{fragment::RustFragmentPair, qobject::GeneratedRustQObjectBlocks}, }, parser::invokable::ParsedQInvokable, }; @@ -30,7 +27,6 @@ pub fn generate_rust_invokables( let wrapper_ident_cpp = idents.wrapper.cpp.to_string(); let wrapper_ident_rust = &idents.wrapper.rust; let invokable_ident_rust = &idents.name.rust; - let original_method = &invokable.method; let cpp_struct = if invokable.mutable { quote! { Pin<&mut #cpp_class_name_rust> } @@ -62,21 +58,13 @@ pub fn generate_rust_invokables( } else { quote! { return } }; - // Determine if unsafe is required due to an unsafe parameter or return type - let has_unsafe_param = invokable - .parameters - .iter() - .any(|parameter| is_unsafe_cxx_type(¶meter.ty)); - let has_unsafe_return = if let ReturnType::Type(_, ty) = return_type { - is_unsafe_cxx_type(ty) - } else { - false - }; - let has_unsafe = if has_unsafe_param || has_unsafe_return { - quote! { unsafe } - } else { - quote! {} - }; + + let mut unsafe_block = None; + let mut unsafe_call = Some(quote! { unsafe }); + if invokable.safe { + std::mem::swap(&mut unsafe_call, &mut unsafe_block); + } + let parameter_names = invokable .parameters .iter() @@ -85,9 +73,10 @@ pub fn generate_rust_invokables( let fragment = RustFragmentPair { cxx_bridge: vec![quote! { + // TODO: is an unsafe block valid? extern "Rust" { #[cxx_name = #wrapper_ident_cpp] - #has_unsafe fn #wrapper_ident_rust(#parameter_signatures) #return_type; + #unsafe_call fn #wrapper_ident_rust(#parameter_signatures) #return_type; } }], implementation: vec![ @@ -95,16 +84,11 @@ pub fn generate_rust_invokables( quote! { impl #rust_struct_name_rust { #[doc(hidden)] - pub #has_unsafe fn #wrapper_ident_rust(#parameter_signatures) #return_type { + pub #unsafe_call fn #wrapper_ident_rust(#parameter_signatures) #return_type { #has_return cpp.#invokable_ident_rust(#(#parameter_names),*); } } }, - quote! { - impl #cpp_class_name_rust { - #original_method - } - }, ], }; @@ -134,14 +118,18 @@ mod tests { fn test_generate_rust_invokables() { let invokables = vec![ ParsedQInvokable { - method: parse_quote! { fn void_invokable(&self) {} }, + method: parse_quote! { fn void_invokable(self: &qobject::MyObject); }, + qobject_ident: format_ident!("MyObject"), mutable: false, + safe: true, parameters: vec![], specifiers: HashSet::new(), }, ParsedQInvokable { - method: parse_quote! { fn trivial_invokable(&self, param: i32) -> i32 {} }, + method: parse_quote! { fn trivial_invokable(self: &qobject::MyObject, param: i32) -> i32; }, + qobject_ident: format_ident!("MyObject"), mutable: false, + safe: true, parameters: vec![ParsedFunctionParameter { ident: format_ident!("param"), ty: parse_quote! { i32 }, @@ -149,8 +137,10 @@ mod tests { specifiers: HashSet::new(), }, ParsedQInvokable { - method: parse_quote! { fn opaque_invokable(self: Pin<&mut Self>, param: &QColor) -> UniquePtr {} }, + method: parse_quote! { fn opaque_invokable(self: Pin<&mut qobject::MyObject>, param: &QColor) -> UniquePtr; }, + qobject_ident: format_ident!("MyObject"), mutable: true, + safe: true, parameters: vec![ParsedFunctionParameter { ident: format_ident!("param"), ty: parse_quote! { &QColor }, @@ -158,8 +148,10 @@ mod tests { specifiers: HashSet::new(), }, ParsedQInvokable { - method: parse_quote! { fn unsafe_invokable(&self, param: *mut T) -> *mut T {} }, + method: parse_quote! { unsafe fn unsafe_invokable(self: &qobject::MyObject, param: *mut T) -> *mut T; }, + qobject_ident: format_ident!("MyObject"), mutable: false, + safe: false, parameters: vec![ParsedFunctionParameter { ident: format_ident!("param"), ty: parse_quote! { *mut T }, @@ -172,7 +164,7 @@ mod tests { let generated = generate_rust_invokables(&invokables, &qobject_idents).unwrap(); assert_eq!(generated.cxx_mod_contents.len(), 4); - assert_eq!(generated.cxx_qt_mod_contents.len(), 8); + assert_eq!(generated.cxx_qt_mod_contents.len(), 4); // void_invokable assert_tokens_eq( @@ -195,14 +187,6 @@ mod tests { } }, ); - assert_tokens_eq( - &generated.cxx_qt_mod_contents[1], - quote! { - impl MyObjectQt { - fn void_invokable(&self) {} - } - }, - ); // trivial_invokable assert_tokens_eq( @@ -215,7 +199,7 @@ mod tests { }, ); assert_tokens_eq( - &generated.cxx_qt_mod_contents[2], + &generated.cxx_qt_mod_contents[1], quote! { impl MyObject { #[doc(hidden)] @@ -225,14 +209,6 @@ mod tests { } }, ); - assert_tokens_eq( - &generated.cxx_qt_mod_contents[3], - quote! { - impl MyObjectQt { - fn trivial_invokable(&self, param: i32) -> i32 {} - } - }, - ); // opaque_invokable assert_tokens_eq( @@ -245,7 +221,7 @@ mod tests { }, ); assert_tokens_eq( - &generated.cxx_qt_mod_contents[4], + &generated.cxx_qt_mod_contents[2], quote! { impl MyObject { #[doc(hidden)] @@ -255,14 +231,6 @@ mod tests { } }, ); - assert_tokens_eq( - &generated.cxx_qt_mod_contents[5], - quote! { - impl MyObjectQt { - fn opaque_invokable(self: Pin<&mut Self>, param: &QColor) -> UniquePtr {} - } - }, - ); // unsafe_invokable assert_tokens_eq( @@ -275,7 +243,7 @@ mod tests { }, ); assert_tokens_eq( - &generated.cxx_qt_mod_contents[6], + &generated.cxx_qt_mod_contents[3], quote! { impl MyObject { #[doc(hidden)] @@ -285,13 +253,5 @@ mod tests { } }, ); - assert_tokens_eq( - &generated.cxx_qt_mod_contents[7], - quote! { - impl MyObjectQt { - fn unsafe_invokable(&self, param: *mut T) -> *mut T {} - } - }, - ); } } 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 2168652e2..d1cd847aa 100644 --- a/crates/cxx-qt-gen/src/generator/rust/property/mod.rs +++ b/crates/cxx-qt-gen/src/generator/rust/property/mod.rs @@ -404,7 +404,7 @@ mod tests { #[doc = "\n"] #[doc = "Note that this method uses a AutoConnection connection type."] #[must_use] - fn on_trivial_property_changed(self: Pin<&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>, )) -> CxxQtQMetaObjectConnection + pub fn on_trivial_property_changed(self: Pin<&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>, )) -> CxxQtQMetaObjectConnection { self.connect_trivial_property_changed(func, CxxQtConnectionType::AutoConnection) } @@ -445,7 +445,7 @@ mod tests { #[doc = "\n"] #[doc = "Note that this method uses a AutoConnection connection type."] #[must_use] - fn on_opaque_property_changed(self: Pin<&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>, )) -> CxxQtQMetaObjectConnection + pub fn on_opaque_property_changed(self: Pin<&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>, )) -> CxxQtQMetaObjectConnection { self.connect_opaque_property_changed(func, CxxQtConnectionType::AutoConnection) } @@ -486,7 +486,7 @@ mod tests { #[doc = "\n"] #[doc = "Note that this method uses a AutoConnection connection type."] #[must_use] - fn on_unsafe_property_changed(self: Pin<&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>, )) -> CxxQtQMetaObjectConnection + pub fn on_unsafe_property_changed(self: Pin<&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>, )) -> CxxQtQMetaObjectConnection { self.connect_unsafe_property_changed(func, CxxQtConnectionType::AutoConnection) } diff --git a/crates/cxx-qt-gen/src/generator/rust/signals.rs b/crates/cxx-qt-gen/src/generator/rust/signals.rs index fb7adadd9..879530c1c 100644 --- a/crates/cxx-qt-gen/src/generator/rust/signals.rs +++ b/crates/cxx-qt-gen/src/generator/rust/signals.rs @@ -85,7 +85,7 @@ pub fn generate_rust_signals( #[doc = "\n"] #[doc = "Note that this method uses a AutoConnection connection type."] #[must_use] - fn #on_ident_rust(self: #self_type, func: fn(#self_type, #(#parameters),*)) -> CxxQtQMetaObjectConnection + pub fn #on_ident_rust(self: #self_type, func: fn(#self_type, #(#parameters),*)) -> CxxQtQMetaObjectConnection { self.#connect_ident_rust(func, CxxQtConnectionType::AutoConnection) } @@ -169,7 +169,7 @@ mod tests { #[doc = "\n"] #[doc = "Note that this method uses a AutoConnection connection type."] #[must_use] - fn on_ready(self: Pin<&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>, )) -> CxxQtQMetaObjectConnection + pub fn on_ready(self: Pin<&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>, )) -> CxxQtQMetaObjectConnection { self.connect_ready(func, CxxQtConnectionType::AutoConnection) } @@ -244,7 +244,7 @@ mod tests { #[doc = "\n"] #[doc = "Note that this method uses a AutoConnection connection type."] #[must_use] - fn on_data_changed(self: Pin<&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>, trivial: i32, opaque: UniquePtr)) -> CxxQtQMetaObjectConnection + pub fn on_data_changed(self: Pin<&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>, trivial: i32, opaque: UniquePtr)) -> CxxQtQMetaObjectConnection { self.connect_data_changed(func, CxxQtConnectionType::AutoConnection) } @@ -311,7 +311,7 @@ mod tests { #[doc = "\n"] #[doc = "Note that this method uses a AutoConnection connection type."] #[must_use] - fn on_unsafe_signal(self: Pin<&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>, param: *mut T)) -> CxxQtQMetaObjectConnection + pub fn on_unsafe_signal(self: Pin<&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>, param: *mut T)) -> CxxQtQMetaObjectConnection { self.connect_unsafe_signal(func, CxxQtConnectionType::AutoConnection) } @@ -377,7 +377,7 @@ mod tests { #[doc = "\n"] #[doc = "Note that this method uses a AutoConnection connection type."] #[must_use] - fn on_existing_signal(self: Pin<&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>, )) -> CxxQtQMetaObjectConnection + pub fn on_existing_signal(self: Pin<&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>, )) -> CxxQtQMetaObjectConnection { self.connect_existing_signal(func, CxxQtConnectionType::AutoConnection) } diff --git a/crates/cxx-qt-gen/src/parser/cxxqtdata.rs b/crates/cxx-qt-gen/src/parser/cxxqtdata.rs index 96ddd26ff..d80e7f6e0 100644 --- a/crates/cxx-qt-gen/src/parser/cxxqtdata.rs +++ b/crates/cxx-qt-gen/src/parser/cxxqtdata.rs @@ -17,6 +17,8 @@ use syn::{ TypePath, }; +use super::invokable::ParsedQInvokable; + #[derive(Default)] pub struct ParsedCxxMappings { /// Map of the cxx_name of any types defined in CXX extern blocks @@ -227,9 +229,17 @@ impl ParsedCxxQtData { self.with_qobject(&parsed_inherited_method.qobject_ident)? .inherited_methods .push(parsed_inherited_method); + // Test if the function is an invokable + } else if let Some(index) = attribute_find_path(&foreign_fn.attrs, &["qinvokable"]) + { + let parsed_invokable_method = + ParsedQInvokable::parse(foreign_fn, safe_call, index)?; + self.with_qobject(&parsed_invokable_method.qobject_ident)? + .invokables + .push(parsed_invokable_method); } - // TODO: test for qinvokable later + // TODO: non-signal/inherit/invokable functions should be exposed as C++ only methods } } @@ -247,18 +257,16 @@ impl ParsedCxxQtData { if let Some(qobject) = self.qobjects.get_mut(&path.segments[1].ident) { if imp.trait_.is_some() { qobject.parse_trait_impl(imp)?; - } else { - // Extract the ImplItem's from each Impl block - qobject.parse_impl_items(&imp.items)?; + return Ok(None); } + + // non trait impls fall through } else { return Err(Error::new( imp.span(), "No matching QObject found for the given qobject::T impl block.", )); } - - return Ok(None); // Find if we are an impl block for a qobject } else if let Some(qobject) = self.qobjects.get_mut(&path_to_single_ident(path)?) { qobject.others.push(Item::Impl(imp)); @@ -424,21 +432,22 @@ mod tests { let mut cxx_qt_data = create_parsed_cxx_qt_data(); let item: Item = parse_quote! { - impl qobject::MyObject { + unsafe extern "RustQt" { #[qinvokable] - fn invokable(&self) {} + fn invokable(self: &qobject::MyObject); - fn cpp_context() {} + fn cpp_context(); } }; let result = cxx_qt_data.parse_cxx_qt_item(item).unwrap(); assert!(result.is_none()); assert_eq!(cxx_qt_data.qobjects[&qobject_ident()].invokables.len(), 1); + // TODO: later we should support C++ context methods somewhere assert_eq!( cxx_qt_data.qobjects[&qobject_ident()] .passthrough_impl_items .len(), - 1 + 0 ); } @@ -447,9 +456,9 @@ mod tests { let mut cxx_qt_data = create_parsed_cxx_qt_data(); let item: Item = parse_quote! { - impl qobject::MyObject::Bad { + unsafe extern "RustQt" { #[qinvokable] - fn invokable() {} + fn invokable(self: &qobject::MyObject::Bad); } }; let result = cxx_qt_data.parse_cxx_qt_item(item); diff --git a/crates/cxx-qt-gen/src/parser/invokable.rs b/crates/cxx-qt-gen/src/parser/invokable.rs index a48e11df8..262463d54 100644 --- a/crates/cxx-qt-gen/src/parser/invokable.rs +++ b/crates/cxx-qt-gen/src/parser/invokable.rs @@ -5,10 +5,10 @@ use crate::{ parser::parameter::ParsedFunctionParameter, - syntax::{attribute::*, implitemfn::is_method_mutable_pin_of_self}, + syntax::{attribute::*, foreignmod, safety::Safety, types}, }; use std::collections::HashSet; -use syn::{Ident, ImplItemFn, LitStr, Result}; +use syn::{spanned::Spanned, Error, ForeignItemFn, Ident, LitStr, Result}; /// Describes a C++ specifier for the Q_INVOKABLE #[derive(Eq, Hash, PartialEq)] @@ -21,9 +21,13 @@ pub enum ParsedQInvokableSpecifiers { /// Describes a single Q_INVOKABLE for a struct pub struct ParsedQInvokable { /// The original [syn::ImplItemFn] of the invokable - pub method: ImplItemFn, + pub method: ForeignItemFn, + /// The type of the self argument + pub qobject_ident: Ident, /// Whether this invokable is mutable pub mutable: bool, + /// Whether the method is safe to call. + pub safe: bool, /// The parameters of the invokable pub parameters: Vec, /// Any specifiers that declared on the invokable @@ -31,16 +35,14 @@ pub struct ParsedQInvokable { } impl ParsedQInvokable { - pub fn try_parse(method: &ImplItemFn) -> Result> { - let index = attribute_find_path(&method.attrs, &["qinvokable"]); - - if index.is_none() { - return Ok(None); + pub fn parse(mut method: ForeignItemFn, safety: Safety, index: usize) -> Result { + if safety == Safety::Unsafe && method.sig.unsafety.is_none() { + return Err(Error::new( + method.span(), + "Inherited methods must be marked as unsafe or wrapped in an `unsafe extern \"C++\"` block!", + )); } - Ok(Some(Self::parse(method, index.unwrap())?)) - } - fn parse(method: &ImplItemFn, index: usize) -> Result { // Parse any C++ specifiers let mut specifiers = HashSet::new(); let attrs_map = attribute_tokens_to_map::( @@ -56,21 +58,24 @@ impl ParsedQInvokable { if attrs_map.contains_key("e::format_ident!("cxx_virtual")) { specifiers.insert(ParsedQInvokableSpecifiers::Virtual); } + method.attrs.remove(index); // Determine if the invokable is mutable - let mutable = is_method_mutable_pin_of_self(&method.sig); + 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(); - // Read the signal inputs into parameter blocks - let parameters = ParsedFunctionParameter::parse_all_without_receiver(&method.sig)?; + let parameters = ParsedFunctionParameter::parse_all_ignoring_receiver(&method.sig)?; + + let safe = method.sig.unsafety.is_none(); - // Remove the invokable attribute - let mut method = method.clone(); - method.attrs.remove(index); Ok(ParsedQInvokable { method, + qobject_ident, mutable, parameters, specifiers, + safe, }) } } diff --git a/crates/cxx-qt-gen/src/parser/qobject.rs b/crates/cxx-qt-gen/src/parser/qobject.rs index 95c8347a4..b6d4a16fd 100644 --- a/crates/cxx-qt-gen/src/parser/qobject.rs +++ b/crates/cxx-qt-gen/src/parser/qobject.rs @@ -17,8 +17,8 @@ use crate::{ syntax::path::path_compare_str, }; use syn::{ - spanned::Spanned, Error, Fields, Ident, ImplItem, ImplItemFn, Item, ItemImpl, ItemStruct, - LitStr, Result, Visibility, + spanned::Spanned, Error, Fields, Ident, ImplItem, Item, ItemImpl, ItemStruct, LitStr, Result, + Visibility, }; /// Metadata for registering QML element @@ -195,35 +195,6 @@ impl ParsedQObject { } } - fn parse_impl_method(&mut self, method: &ImplItemFn) -> Result<()> { - if let Some(invokable) = ParsedQInvokable::try_parse(method)? { - self.invokables.push(invokable); - } else { - self.passthrough_impl_items - .push(ImplItem::Fn(method.clone())); - } - Ok(()) - } - - /// Extract all methods (both invokable and non-invokable) from [syn::ImplItem]'s from each Impl block - /// - /// These will have come from a impl qobject::T block - pub fn parse_impl_items(&mut self, items: &[ImplItem]) -> Result<()> { - for item in items { - // Check if this item is a method - match item { - ImplItem::Fn(method) => { - self.parse_impl_method(method)?; - } - _ => { - self.passthrough_impl_items.push(item.clone()); - } - } - } - - Ok(()) - } - pub fn parse_trait_impl(&mut self, imp: ItemImpl) -> Result<()> { let (not, trait_path, _) = &imp .trait_ @@ -323,7 +294,7 @@ impl ParsedQObject { pub mod tests { use super::*; - use crate::parser::{invokable::ParsedQInvokableSpecifiers, tests::f64_type}; + use crate::parser::tests::f64_type; use syn::{parse_quote, ItemImpl, Visibility}; pub fn create_parsed_qobject() -> ParsedQObject { @@ -391,64 +362,6 @@ pub mod tests { assert_eq!(qobject.qobject_struct.fields.len(), 1); } - #[test] - fn test_parse_impl_items_valid() { - let mut qobject = create_parsed_qobject(); - let item: ItemImpl = parse_quote! { - impl T { - #[qinvokable] - fn invokable(&self, a: f64, b: f64) {} - - #[qinvokable] - fn invokable_with_return_type(self: Pin<&mut Self>) -> f64 {} - - #[qinvokable(cxx_final, cxx_override, cxx_virtual)] - fn invokable_with_specifiers(&self) -> f64 {} - - fn cpp_context(&self) {} - } - }; - assert!(qobject.parse_impl_items(&item.items).is_ok()); - assert_eq!(qobject.invokables.len(), 3); - assert_eq!(qobject.passthrough_impl_items.len(), 1); - assert!(!qobject.invokables[0].mutable); - assert_eq!(qobject.invokables[0].parameters.len(), 2); - assert_eq!(qobject.invokables[0].parameters[0].ident, "a"); - assert_eq!(qobject.invokables[0].parameters[0].ty, f64_type()); - assert_eq!(qobject.invokables[0].parameters[1].ident, "b"); - assert_eq!(qobject.invokables[0].parameters[1].ty, f64_type()); - assert!(qobject.invokables[1].mutable); - assert!(qobject.invokables[2] - .specifiers - .contains(&ParsedQInvokableSpecifiers::Final)); - assert!(qobject.invokables[2] - .specifiers - .contains(&ParsedQInvokableSpecifiers::Override)); - assert!(qobject.invokables[2] - .specifiers - .contains(&ParsedQInvokableSpecifiers::Virtual)); - } - - #[test] - fn test_parse_impl_items_invalid() { - let mut qobject = create_parsed_qobject(); - let item: ItemImpl = parse_quote! { - impl T { - const VALUE: i32 = 1; - - macro_code!(); - - type A = i32; - - #[qinvokable] - fn invokable() {} - - fn cpp_context() {} - } - }; - assert!(qobject.parse_impl_items(&item.items).is_err()); - } - #[test] fn test_parse_trait_impl_valid() { let mut qobject = create_parsed_qobject(); diff --git a/crates/cxx-qt-gen/src/syntax/implitemfn.rs b/crates/cxx-qt-gen/src/syntax/implitemfn.rs deleted file mode 100644 index b1329b0bd..000000000 --- a/crates/cxx-qt-gen/src/syntax/implitemfn.rs +++ /dev/null @@ -1,72 +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 syn::{FnArg, Receiver, Signature}; - -use super::types::{is_pin_mut, is_pin_of_self}; - -/// Return whether the first parameter of a method is Pin<&mut Self> -pub fn is_method_mutable_pin_of_self(signature: &Signature) -> bool { - match signature.inputs.first() { - Some(FnArg::Receiver(Receiver { ty, .. })) => { - // Check if Pin is mut and T is self - is_pin_mut(ty) && is_pin_of_self(ty) - } - _ => false, - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use syn::{parse_quote, ImplItemFn}; - - #[test] - fn test_is_method_mutable_self() { - let item: ImplItemFn = parse_quote! { - fn invokable(&self) {} - }; - assert!(!is_method_mutable_pin_of_self(&item.sig)); - - let item: ImplItemFn = parse_quote! { - fn invokable(&mut self) {} - }; - assert!(!is_method_mutable_pin_of_self(&item.sig)); - - let item: ImplItemFn = parse_quote! { - fn invokable_with_return_type(self: Pin<&mut Self>) -> f64 {} - }; - assert!(is_method_mutable_pin_of_self(&item.sig)); - - let item: ImplItemFn = parse_quote! { - fn invokable_with_return_type(mut self: Pin<&mut Self>) -> f64 {} - }; - assert!(is_method_mutable_pin_of_self(&item.sig)); - } - - #[test] - fn test_is_method_mutable_value() { - let item: ImplItemFn = parse_quote! { - fn invokable(value: T) {} - }; - assert!(!is_method_mutable_pin_of_self(&item.sig)); - - let item: ImplItemFn = parse_quote! { - fn invokable_with_return_type(value: Pin<&mut T>) -> f64 {} - }; - assert!(!is_method_mutable_pin_of_self(&item.sig)); - - let item: ImplItemFn = parse_quote! { - fn invokable_with_return_type(mut value: Pin<&mut T>) -> f64 {} - }; - assert!(!is_method_mutable_pin_of_self(&item.sig)); - - let item: ImplItemFn = parse_quote! { - fn invokable_with_return_type(mut value: T) -> f64 {} - }; - assert!(!is_method_mutable_pin_of_self(&item.sig)); - } -} diff --git a/crates/cxx-qt-gen/src/syntax/mod.rs b/crates/cxx-qt-gen/src/syntax/mod.rs index 436203024..8f1a5c72f 100644 --- a/crates/cxx-qt-gen/src/syntax/mod.rs +++ b/crates/cxx-qt-gen/src/syntax/mod.rs @@ -7,7 +7,6 @@ pub mod attribute; pub mod expr; pub mod fields; pub mod foreignmod; -pub mod implitemfn; pub mod path; mod qtfile; mod qtitem; diff --git a/crates/cxx-qt-gen/src/syntax/types.rs b/crates/cxx-qt-gen/src/syntax/types.rs index ddaa336e4..a18b7b14a 100644 --- a/crates/cxx-qt-gen/src/syntax/types.rs +++ b/crates/cxx-qt-gen/src/syntax/types.rs @@ -19,26 +19,6 @@ fn pin_path(ty: &Type) -> Option { None } -/// Determine if a given [syn::Type] has a mutable T in Pin -pub fn is_pin_mut(ty: &Type) -> bool { - if let Some(path) = pin_path(ty) { - // Read the contents of the T - if let Some(last) = path.segments.last() { - if let PathArguments::AngleBracketed(args) = &last.arguments { - if let Some(GenericArgument::Type(Type::Reference(TypeReference { - mutability: Some(_), - .. - }))) = args.args.first() - { - return true; - } - } - } - } - - false -} - /// Checks if the given type is a `Pin<&Self>` or `Pin<&mut Self>`. /// `Pin>` is currently not supported. pub fn is_pin_of_self(ty: &Type) -> bool { @@ -164,18 +144,6 @@ mod tests { assert!(!super::is_pin_of_self(&parse_quote! { Pin<&mut Foo> })); } - #[test] - fn test_is_pin_mut() { - assert!(!super::is_pin_mut(&parse_quote! { Pin<&Self> })); - assert!(super::is_pin_mut(&parse_quote! { Pin<&mut Self> })); - assert!(!super::is_pin_mut(&parse_quote! { Pin> })); - assert!(!super::is_pin_mut(&parse_quote! { Pin<&Self, Foo> })); - assert!(!super::is_pin_mut(&parse_quote! { Pin })); - assert!(!super::is_pin_mut(&parse_quote! { Pin })); - assert!(!super::is_pin_mut(&parse_quote! { Pin<&Foo> })); - assert!(super::is_pin_mut(&parse_quote! { Pin<&mut Foo> })); - } - fn assert_qobject_ident(ty: Type, expected_ident: &str, expected_mutability: bool) { let (ident, mutability) = super::extract_qobject_ident(&ty).unwrap(); assert_eq!(ident.to_string(), expected_ident); diff --git a/crates/cxx-qt-gen/src/writer/rust/mod.rs b/crates/cxx-qt-gen/src/writer/rust/mod.rs index 79487b6c9..565f1c337 100644 --- a/crates/cxx-qt-gen/src/writer/rust/mod.rs +++ b/crates/cxx-qt-gen/src/writer/rust/mod.rs @@ -178,7 +178,12 @@ pub fn write_rust(generated: &GeneratedRustBlocks) -> TokenStream { #cxx_mod #cxx_mod_visiblity use self::#cxx_qt_mod_ident::*; - mod #cxx_qt_mod_ident { + // TODO: for now mark as public + // as we need to reach the generated getters and setters + // but later we'll likely implement things outside the module + // + /// Internal CXX-Qt module, made public temporarily between API changes + pub mod #cxx_qt_mod_ident { use super::#cxx_mod_ident::*; use std::pin::Pin; use cxx_qt::CxxQtType; @@ -396,7 +401,8 @@ mod tests { } use self::cxx_qt_ffi::*; - mod cxx_qt_ffi { + #[doc = r" Internal CXX-Qt module, made public temporarily between API changes"] + pub mod cxx_qt_ffi { use super::ffi::*; use std::pin::Pin; use cxx_qt::CxxQtType; @@ -528,7 +534,8 @@ mod tests { } use self::cxx_qt_ffi::*; - mod cxx_qt_ffi { + #[doc = r" Internal CXX-Qt module, made public temporarily between API changes"] + pub mod cxx_qt_ffi { use super::ffi::*; use std::pin::Pin; use cxx_qt::CxxQtType; diff --git a/crates/cxx-qt-gen/test_inputs/invokables.rs b/crates/cxx-qt-gen/test_inputs/invokables.rs index 287a74414..017ed3a65 100644 --- a/crates/cxx-qt-gen/test_inputs/invokables.rs +++ b/crates/cxx-qt-gen/test_inputs/invokables.rs @@ -12,18 +12,46 @@ mod ffi { #[derive(Default)] pub struct MyObject; - impl qobject::MyObject { + unsafe extern "RustQt" { + #[qinvokable] + fn invokable(self: &qobject::MyObject); + + #[qinvokable] + fn invokable_mutable(self: Pin<&mut qobject::MyObject>); + + #[qinvokable] + fn invokable_parameters( + self: &qobject::MyObject, + opaque: &QColor, + trivial: &QPoint, + primitive: i32, + ); + #[qinvokable] + fn invokable_return_opaque(self: Pin<&mut qobject::MyObject>) -> UniquePtr; + + #[qinvokable] + fn invokable_return_trivial(self: Pin<&mut qobject::MyObject>) -> QPoint; + + #[qinvokable(cxx_final)] + fn invokable_final(self: &qobject::MyObject); + + #[qinvokable(cxx_override)] + fn invokable_override(self: &qobject::MyObject); + + #[qinvokable(cxx_virtual)] + fn invokable_virtual(self: &qobject::MyObject); + } + + impl qobject::MyObject { pub fn invokable(&self) { println!("invokable"); } - #[qinvokable] pub fn invokable_mutable(self: Pin<&mut Self>) { println!("This method is mutable!"); } - #[qinvokable] pub fn invokable_parameters(&self, opaque: &QColor, trivial: &QPoint, primitive: i32) { println!( "Red: {}, Point X: {}, Number: {}", @@ -33,27 +61,22 @@ mod ffi { ); } - #[qinvokable] pub fn invokable_return_opaque(self: Pin<&mut Self>) -> UniquePtr { Opaque::new() } - #[qinvokable] pub fn invokable_return_trivial(self: Pin<&mut Self>) -> QPoint { QPoint::new(1, 2) } - #[qinvokable(cxx_final)] pub fn invokable_final(&self) { println!("Final"); } - #[qinvokable(cxx_override)] pub fn invokable_override(&self) { println!("Override"); } - #[qinvokable(cxx_virtual)] pub fn invokable_virtual(&self) { println!("Virtual"); } diff --git a/crates/cxx-qt-gen/test_outputs/inheritance.cpp b/crates/cxx-qt-gen/test_outputs/inheritance.cpp index 4b31ed53c..bfbc06474 100644 --- a/crates/cxx-qt-gen/test_outputs/inheritance.cpp +++ b/crates/cxx-qt-gen/test_outputs/inheritance.cpp @@ -20,17 +20,3 @@ MyObject::unsafeRustMut() { return *m_rustObj; } - -QVariant -MyObject::data(QModelIndex const& _index, ::std::int32_t _role) const -{ - const ::std::lock_guard<::std::recursive_mutex> guard(*m_rustObjMutex); - return m_rustObj->dataWrapper(*this, _index, _role); -} - -bool -MyObject::hasChildren(QModelIndex const& _parent) const -{ - const ::std::lock_guard<::std::recursive_mutex> guard(*m_rustObjMutex); - return m_rustObj->hasChildrenWrapper(*this, _parent); -} diff --git a/crates/cxx-qt-gen/test_outputs/inheritance.h b/crates/cxx-qt-gen/test_outputs/inheritance.h index 614e69cc4..66e7a6193 100644 --- a/crates/cxx-qt-gen/test_outputs/inheritance.h +++ b/crates/cxx-qt-gen/test_outputs/inheritance.h @@ -23,9 +23,6 @@ class MyObject : public QAbstractItemModel MyObjectRust& unsafeRustMut(); public: - Q_INVOKABLE QVariant data(QModelIndex const& _index, - ::std::int32_t _role) const override; - Q_INVOKABLE bool hasChildren(QModelIndex const& _parent) const override; template bool hasChildrenCxxQtInherit(Args... args) const { diff --git a/crates/cxx-qt-gen/test_outputs/inheritance.rs b/crates/cxx-qt-gen/test_outputs/inheritance.rs index 374ef4320..113c90e62 100644 --- a/crates/cxx-qt-gen/test_outputs/inheritance.rs +++ b/crates/cxx-qt-gen/test_outputs/inheritance.rs @@ -6,6 +6,16 @@ mod inheritance { include!("cxx-qt-lib/qvariant.h"); type QVariant = cxx_qt_lib::QVariant; } + impl qobject::MyObject { + #[qinvokable(cxx_override)] + pub fn data(&self, _index: &QModelIndex, _role: i32) -> QVariant { + QVariant::default() + } + #[qinvokable(cxx_override)] + pub fn has_children(&self, _parent: &QModelIndex) -> bool { + false + } + } unsafe extern "C++" { include ! (< QtCore / QObject >); include!("cxx-qt-lib/qt.h"); @@ -36,19 +46,6 @@ mod inheritance { #[cxx_name = "MyObjectRust"] type MyObject; } - extern "Rust" { - #[cxx_name = "dataWrapper"] - fn data_wrapper( - self: &MyObject, - cpp: &MyObjectQt, - _index: &QModelIndex, - _role: i32, - ) -> QVariant; - } - extern "Rust" { - #[cxx_name = "hasChildrenWrapper"] - fn has_children_wrapper(self: &MyObject, cpp: &MyObjectQt, _parent: &QModelIndex) -> bool; - } unsafe extern "C++" { #[doc = " Inherited hasChildren from the base class"] #[cxx_name = "hasChildrenCxxQtInherit"] @@ -76,7 +73,8 @@ mod inheritance { } } use self::cxx_qt_inheritance::*; -mod cxx_qt_inheritance { +#[doc = r" Internal CXX-Qt module, made public temporarily between API changes"] +pub mod cxx_qt_inheritance { use super::inheritance::*; use cxx_qt::CxxQtType; use std::pin::Pin; @@ -103,37 +101,6 @@ mod cxx_qt_inheritance { } } } - impl MyObject { - #[doc(hidden)] - pub fn data_wrapper( - self: &MyObject, - cpp: &MyObjectQt, - _index: &QModelIndex, - _role: i32, - ) -> QVariant { - return cpp.data(_index, _role); - } - } - impl MyObjectQt { - pub fn data(&self, _index: &QModelIndex, _role: i32) -> QVariant { - QVariant::default() - } - } - impl MyObject { - #[doc(hidden)] - pub fn has_children_wrapper( - self: &MyObject, - cpp: &MyObjectQt, - _parent: &QModelIndex, - ) -> bool { - return cpp.has_children(_parent); - } - } - impl MyObjectQt { - pub fn has_children(&self, _parent: &QModelIndex) -> bool { - false - } - } impl cxx_qt::Locking for MyObjectQt {} impl cxx_qt::CxxQtType for MyObjectQt { type Rust = MyObject; diff --git a/crates/cxx-qt-gen/test_outputs/invokables.rs b/crates/cxx-qt-gen/test_outputs/invokables.rs index 88a9f7db7..db68b7ec8 100644 --- a/crates/cxx-qt-gen/test_outputs/invokables.rs +++ b/crates/cxx-qt-gen/test_outputs/invokables.rs @@ -7,6 +7,46 @@ mod ffi { include!("cxx-qt-lib/qpoint.h"); type QPoint = cxx_qt_lib::QPoint; } + impl qobject::MyObject { + pub fn invokable(&self) { + println!("invokable"); + } + pub fn invokable_mutable(self: Pin<&mut Self>) { + println!("This method is mutable!"); + } + pub fn invokable_parameters(&self, opaque: &QColor, trivial: &QPoint, primitive: i32) { + println!( + "Red: {}, Point X: {}, Number: {}", + opaque.red(), + trivial.x(), + primitive, + ); + } + pub fn invokable_return_opaque(self: Pin<&mut Self>) -> UniquePtr { + Opaque::new() + } + pub fn invokable_return_trivial(self: Pin<&mut Self>) -> QPoint { + QPoint::new(1, 2) + } + pub fn invokable_final(&self) { + println!("Final"); + } + pub fn invokable_override(&self) { + println!("Override"); + } + pub fn invokable_virtual(&self) { + println!("Virtual"); + } + pub fn cpp_context_method(&self) { + println!("C++ context method"); + } + pub fn cpp_context_method_mutable(self: Pin<&mut Self>) { + println!("mutable method"); + } + pub fn cpp_context_method_return_opaque(&self) -> UniquePtr { + Opaque::new() + } + } unsafe extern "C++" { include ! (< QtCore / QObject >); include!("cxx-qt-lib/qt.h"); @@ -124,7 +164,8 @@ mod ffi { } } use self::cxx_qt_ffi::*; -mod cxx_qt_ffi { +#[doc = r" Internal CXX-Qt module, made public temporarily between API changes"] +pub mod cxx_qt_ffi { use super::ffi::*; use cxx_qt::CxxQtType; use std::pin::Pin; @@ -143,22 +184,12 @@ mod cxx_qt_ffi { cpp.invokable(); } } - impl MyObjectQt { - pub fn invokable(&self) { - println!("invokable"); - } - } impl MyObject { #[doc(hidden)] pub fn invokable_mutable_wrapper(self: &mut MyObject, cpp: Pin<&mut MyObjectQt>) { cpp.invokable_mutable(); } } - impl MyObjectQt { - pub fn invokable_mutable(self: Pin<&mut Self>) { - println!("This method is mutable!"); - } - } impl MyObject { #[doc(hidden)] pub fn invokable_parameters_wrapper( @@ -171,16 +202,6 @@ mod cxx_qt_ffi { cpp.invokable_parameters(opaque, trivial, primitive); } } - impl MyObjectQt { - pub fn invokable_parameters(&self, opaque: &QColor, trivial: &QPoint, primitive: i32) { - println!( - "Red: {}, Point X: {}, Number: {}", - opaque.red(), - trivial.x(), - primitive, - ); - } - } impl MyObject { #[doc(hidden)] pub fn invokable_return_opaque_wrapper( @@ -190,11 +211,6 @@ mod cxx_qt_ffi { return cpp.invokable_return_opaque(); } } - impl MyObjectQt { - pub fn invokable_return_opaque(self: Pin<&mut Self>) -> UniquePtr { - Opaque::new() - } - } impl MyObject { #[doc(hidden)] pub fn invokable_return_trivial_wrapper( @@ -204,59 +220,24 @@ mod cxx_qt_ffi { return cpp.invokable_return_trivial(); } } - impl MyObjectQt { - pub fn invokable_return_trivial(self: Pin<&mut Self>) -> QPoint { - QPoint::new(1, 2) - } - } impl MyObject { #[doc(hidden)] pub fn invokable_final_wrapper(self: &MyObject, cpp: &MyObjectQt) { cpp.invokable_final(); } } - impl MyObjectQt { - pub fn invokable_final(&self) { - println!("Final"); - } - } impl MyObject { #[doc(hidden)] pub fn invokable_override_wrapper(self: &MyObject, cpp: &MyObjectQt) { cpp.invokable_override(); } } - impl MyObjectQt { - pub fn invokable_override(&self) { - println!("Override"); - } - } impl MyObject { #[doc(hidden)] pub fn invokable_virtual_wrapper(self: &MyObject, cpp: &MyObjectQt) { cpp.invokable_virtual(); } } - impl MyObjectQt { - pub fn invokable_virtual(&self) { - println!("Virtual"); - } - } - impl MyObjectQt { - pub fn cpp_context_method(&self) { - println!("C++ context method"); - } - } - impl MyObjectQt { - pub fn cpp_context_method_mutable(self: Pin<&mut Self>) { - println!("mutable method"); - } - } - impl MyObjectQt { - pub fn cpp_context_method_return_opaque(&self) -> UniquePtr { - Opaque::new() - } - } unsafe impl Send for MyObjectCxxQtThread {} impl MyObjectCxxQtThread { #[doc = r" Queue the given closure onto the Qt event loop for this QObject"] diff --git a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.cpp b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.cpp index 7f5779f9a..800e03181 100644 --- a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.cpp +++ b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.cpp @@ -58,13 +58,6 @@ MyObject::propertyNameChangedConnect(::rust::Fn func, type); } -void -MyObject::invokableName() -{ - const ::std::lock_guard<::std::recursive_mutex> guard(*m_rustObjMutex); - m_rustObj->invokableNameWrapper(*this); -} - void MyObject::emitReady() { @@ -142,13 +135,6 @@ SecondObject::propertyNameChangedConnect(::rust::Fn func, type); } -void -SecondObject::invokableName() -{ - - m_rustObj->invokableNameWrapper(*this); -} - void SecondObject::emitReady() { diff --git a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.h b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.h index 5886e2827..b1dedd112 100644 --- a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.h +++ b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.h @@ -41,7 +41,6 @@ class MyObject : public QStringListModel ::QMetaObject::Connection propertyNameChangedConnect( ::rust::Fn func, ::Qt::ConnectionType type); - Q_INVOKABLE void invokableName(); Q_SIGNAL void ready(); void emitReady(); ::QMetaObject::Connection readyConnect(::rust::Fn func, @@ -79,7 +78,6 @@ class SecondObject : public QObject ::QMetaObject::Connection propertyNameChangedConnect( ::rust::Fn func, ::Qt::ConnectionType type); - Q_INVOKABLE void invokableName(); Q_SIGNAL void ready(); void emitReady(); ::QMetaObject::Connection readyConnect(::rust::Fn func, diff --git a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.rs b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.rs index b2a902ef4..5b37cc678 100644 --- a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.rs +++ b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.rs @@ -44,6 +44,23 @@ pub mod ffi { unsafe extern "C++" { include ! (< QtCore / QStringListModel >); } + impl qobject::MyObject { + pub const MY_CONSTANT: i32 = 42; + type MyType = i32; + my_macro!(); + #[qinvokable] + pub fn invokable_name(self: Pin<&mut Self>) { + println!("Bye from Rust!"); + self.as_mut().set_property_name(5); + } + } + impl qobject::SecondObject { + #[qinvokable] + pub fn invokable_name(self: Pin<&mut Self>) { + println!("Bye from second Rust!"); + self.as_mut().set_property_name(5); + } + } unsafe extern "C++" { include ! (< QtCore / QObject >); include!("cxx-qt-lib/qt.h"); @@ -99,10 +116,6 @@ pub mod ffi { conn_type: CxxQtConnectionType, ) -> CxxQtQMetaObjectConnection; } - extern "Rust" { - #[cxx_name = "invokableNameWrapper"] - fn invokable_name_wrapper(self: &mut MyObject, cpp: Pin<&mut MyObjectQt>); - } unsafe extern "C++" { #[rust_name = "ready"] fn emitReady(self: Pin<&mut MyObjectQt>); @@ -173,10 +186,6 @@ pub mod ffi { conn_type: CxxQtConnectionType, ) -> CxxQtQMetaObjectConnection; } - extern "Rust" { - #[cxx_name = "invokableNameWrapper"] - fn invokable_name_wrapper(self: &mut SecondObject, cpp: Pin<&mut SecondObjectQt>); - } unsafe extern "C++" { #[my_attribute] #[rust_name = "ready"] @@ -211,7 +220,8 @@ pub mod ffi { } } pub use self::cxx_qt_ffi::*; -mod cxx_qt_ffi { +#[doc = r" Internal CXX-Qt module, made public temporarily between API changes"] +pub mod cxx_qt_ffi { use super::ffi::*; use cxx_qt::CxxQtType; use std::pin::Pin; @@ -290,34 +300,13 @@ mod cxx_qt_ffi { #[doc = "\n"] #[doc = "Note that this method uses a AutoConnection connection type."] #[must_use] - fn on_property_name_changed( + pub fn on_property_name_changed( self: Pin<&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>), ) -> CxxQtQMetaObjectConnection { self.connect_property_name_changed(func, CxxQtConnectionType::AutoConnection) } } - impl MyObject { - #[doc(hidden)] - pub fn invokable_name_wrapper(self: &mut MyObject, cpp: Pin<&mut MyObjectQt>) { - cpp.invokable_name(); - } - } - impl MyObjectQt { - pub fn invokable_name(self: Pin<&mut Self>) { - println!("Bye from Rust!"); - self.as_mut().set_property_name(5); - } - } - impl MyObjectQt { - pub const MY_CONSTANT: i32 = 42; - } - impl MyObjectQt { - type MyType = i32; - } - impl MyObjectQt { - my_macro!(); - } impl MyObjectQt { #[doc = "Connect the given function pointer to the signal "] #[doc = "ready"] @@ -325,7 +314,7 @@ mod cxx_qt_ffi { #[doc = "\n"] #[doc = "Note that this method uses a AutoConnection connection type."] #[must_use] - fn on_ready( + pub fn on_ready( self: Pin<&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>), ) -> CxxQtQMetaObjectConnection { @@ -405,25 +394,13 @@ mod cxx_qt_ffi { #[doc = "\n"] #[doc = "Note that this method uses a AutoConnection connection type."] #[must_use] - fn on_property_name_changed( + pub fn on_property_name_changed( self: Pin<&mut SecondObjectQt>, func: fn(Pin<&mut SecondObjectQt>), ) -> CxxQtQMetaObjectConnection { self.connect_property_name_changed(func, CxxQtConnectionType::AutoConnection) } } - impl SecondObject { - #[doc(hidden)] - pub fn invokable_name_wrapper(self: &mut SecondObject, cpp: Pin<&mut SecondObjectQt>) { - cpp.invokable_name(); - } - } - impl SecondObjectQt { - pub fn invokable_name(self: Pin<&mut Self>) { - println!("Bye from second Rust!"); - self.as_mut().set_property_name(5); - } - } impl SecondObjectQt { #[doc = "Connect the given function pointer to the signal "] #[doc = "ready"] @@ -431,7 +408,7 @@ mod cxx_qt_ffi { #[doc = "\n"] #[doc = "Note that this method uses a AutoConnection connection type."] #[must_use] - fn on_ready( + pub fn on_ready( self: Pin<&mut SecondObjectQt>, func: fn(Pin<&mut SecondObjectQt>), ) -> CxxQtQMetaObjectConnection { diff --git a/crates/cxx-qt-gen/test_outputs/properties.rs b/crates/cxx-qt-gen/test_outputs/properties.rs index 675421cec..2d51064d2 100644 --- a/crates/cxx-qt-gen/test_outputs/properties.rs +++ b/crates/cxx-qt-gen/test_outputs/properties.rs @@ -102,7 +102,8 @@ mod ffi { } } use self::cxx_qt_ffi::*; -mod cxx_qt_ffi { +#[doc = r" Internal CXX-Qt module, made public temporarily between API changes"] +pub mod cxx_qt_ffi { use super::ffi::*; use cxx_qt::CxxQtType; use std::pin::Pin; @@ -211,7 +212,7 @@ mod cxx_qt_ffi { #[doc = "\n"] #[doc = "Note that this method uses a AutoConnection connection type."] #[must_use] - fn on_primitive_changed( + pub fn on_primitive_changed( self: Pin<&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>), ) -> CxxQtQMetaObjectConnection { @@ -225,7 +226,7 @@ mod cxx_qt_ffi { #[doc = "\n"] #[doc = "Note that this method uses a AutoConnection connection type."] #[must_use] - fn on_trivial_changed( + pub fn on_trivial_changed( self: Pin<&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>), ) -> CxxQtQMetaObjectConnection { diff --git a/crates/cxx-qt-gen/test_outputs/signals.cpp b/crates/cxx-qt-gen/test_outputs/signals.cpp index 620702ebc..3d33b8583 100644 --- a/crates/cxx-qt-gen/test_outputs/signals.cpp +++ b/crates/cxx-qt-gen/test_outputs/signals.cpp @@ -23,13 +23,6 @@ MyObject::unsafeRustMut() return *m_rustObj; } -void -MyObject::invokable() -{ - const ::std::lock_guard<::std::recursive_mutex> guard(*m_rustObjMutex); - m_rustObj->invokableWrapper(*this); -} - void MyObject::emitReady() { diff --git a/crates/cxx-qt-gen/test_outputs/signals.h b/crates/cxx-qt-gen/test_outputs/signals.h index 4d0db43cc..49a44adec 100644 --- a/crates/cxx-qt-gen/test_outputs/signals.h +++ b/crates/cxx-qt-gen/test_outputs/signals.h @@ -27,7 +27,6 @@ class MyObject : public QObject MyObjectRust& unsafeRustMut(); public: - Q_INVOKABLE void invokable(); Q_SIGNAL void ready(); void emitReady(); ::QMetaObject::Connection readyConnect(::rust::Fn func, diff --git a/crates/cxx-qt-gen/test_outputs/signals.rs b/crates/cxx-qt-gen/test_outputs/signals.rs index 8093e1514..7f4bc90bf 100644 --- a/crates/cxx-qt-gen/test_outputs/signals.rs +++ b/crates/cxx-qt-gen/test_outputs/signals.rs @@ -5,6 +5,19 @@ mod ffi { include!("cxx-qt-lib/qpoint.h"); type QPoint = cxx_qt_lib::QPoint; } + impl qobject::MyObject { + #[qinvokable] + pub fn invokable(self: Pin<&mut Self>) { + self.as_mut().on_data_changed( + |_sender, _first, _second, _third, _fourth| { + println!("DataChanged"); + }, + cxx_qt_lib::ConnectionType::AutoConnection, + ); + self.as_mut() + .data_changed(1, Opaque::new(), QPoint::new(1, 2), &QPoint::new(1, 2)); + } + } unsafe extern "C++" { include ! (< QtCore / QObject >); include!("cxx-qt-lib/qt.h"); @@ -35,10 +48,6 @@ mod ffi { #[cxx_name = "MyObjectRust"] type MyObject; } - extern "Rust" { - #[cxx_name = "invokableWrapper"] - fn invokable_wrapper(self: &mut MyObject, cpp: Pin<&mut MyObjectQt>); - } unsafe extern "C++" { #[rust_name = "ready"] fn emitReady(self: Pin<&mut MyObjectQt>); @@ -128,7 +137,8 @@ mod ffi { } } use self::cxx_qt_ffi::*; -mod cxx_qt_ffi { +#[doc = r" Internal CXX-Qt module, made public temporarily between API changes"] +pub mod cxx_qt_ffi { use super::ffi::*; use cxx_qt::CxxQtType; use std::pin::Pin; @@ -136,24 +146,6 @@ mod cxx_qt_ffi { type UniquePtr = cxx::UniquePtr; #[derive(Default)] pub struct MyObject; - impl MyObject { - #[doc(hidden)] - pub fn invokable_wrapper(self: &mut MyObject, cpp: Pin<&mut MyObjectQt>) { - cpp.invokable(); - } - } - impl MyObjectQt { - pub fn invokable(self: Pin<&mut Self>) { - self.as_mut().on_data_changed( - |_sender, _first, _second, _third, _fourth| { - println!("DataChanged"); - }, - cxx_qt_lib::ConnectionType::AutoConnection, - ); - self.as_mut() - .data_changed(1, Opaque::new(), QPoint::new(1, 2), &QPoint::new(1, 2)); - } - } impl MyObjectQt { #[doc = "Connect the given function pointer to the signal "] #[doc = "ready"] @@ -161,7 +153,7 @@ mod cxx_qt_ffi { #[doc = "\n"] #[doc = "Note that this method uses a AutoConnection connection type."] #[must_use] - fn on_ready( + pub fn on_ready( self: Pin<&mut MyObjectQt>, func: fn(Pin<&mut MyObjectQt>), ) -> CxxQtQMetaObjectConnection { @@ -175,7 +167,7 @@ mod cxx_qt_ffi { #[doc = "\n"] #[doc = "Note that this method uses a AutoConnection connection type."] #[must_use] - fn on_data_changed( + pub fn on_data_changed( self: Pin<&mut MyObjectQt>, func: fn( Pin<&mut MyObjectQt>, @@ -195,7 +187,7 @@ mod cxx_qt_ffi { #[doc = "\n"] #[doc = "Note that this method uses a AutoConnection connection type."] #[must_use] - fn on_base_class_new_data( + pub fn on_base_class_new_data( self: Pin<&mut MyObjectQt>, func: fn( Pin<&mut MyObjectQt>, diff --git a/crates/cxx-qt-macro/src/lib.rs b/crates/cxx-qt-macro/src/lib.rs index 6a110fb89..ec510781a 100644 --- a/crates/cxx-qt-macro/src/lib.rs +++ b/crates/cxx-qt-macro/src/lib.rs @@ -18,20 +18,28 @@ use cxx_qt_gen::{write_rust, GeneratedRustBlocks, Parser}; /// /// ```rust /// #[cxx_qt::bridge(namespace = "cxx_qt::my_object")] -/// mod my_object { +/// mod ffi { /// #[cxx_qt::qobject] /// #[derive(Default)] /// # // Note that we can't use properties as this confuses the linker on Windows /// pub struct MyObject; /// -/// impl qobject::MyObject { +/// unsafe extern "RustQt" { /// #[qinvokable] -/// fn invokable(&self, a: i32, b: i32) -> i32 { +/// fn invokable(self: &qobject::MyObject, a: i32, b: i32) -> i32 { /// a + b /// } /// } /// } /// +/// # // TODO: this will change to qobject::MyObject once +/// # // https://github.com/KDAB/cxx-qt/issues/559 is done +/// impl ffi::MyObjectQt { +/// fn invokable(&self, a: i32, b: i32) -> i32 { +/// a + b +/// } +/// } +/// /// # // Note that we need a fake main for doc tests to build /// # fn main() {} /// ``` diff --git a/examples/demo_threading/rust/src/lib.rs b/examples/demo_threading/rust/src/lib.rs index 0e17bbfdc..55953913b 100644 --- a/examples/demo_threading/rust/src/lib.rs +++ b/examples/demo_threading/rust/src/lib.rs @@ -11,17 +11,11 @@ mod workers; // This mod defines our QObject called EnergyUsage #[cxx_qt::bridge(cxx_file_stem = "energy_usage", namespace = "cxx_qt::energy_usage")] mod ffi { - use super::{ - constants::{CHANNEL_NETWORK_COUNT, SENSOR_MAXIMUM_COUNT}, - network::NetworkServer, - workers::{AccumulatorWorker, SensorHashMap, SensorsWorker, TimeoutWorker}, - }; - use futures::executor::block_on; + use crate::{constants::SENSOR_MAXIMUM_COUNT, workers::SensorHashMap}; use std::{ - sync::{atomic::AtomicBool, mpsc::sync_channel, Arc, Mutex}, + sync::{Arc, Mutex}, thread::JoinHandle, }; - use uuid::Uuid; #[namespace = ""] unsafe extern "C++" { @@ -42,14 +36,14 @@ mod ffi { total_use: f64, /// The join handles of the running threads - join_handles: Option<[JoinHandle<()>; 4]>, + pub(crate) join_handles: Option<[JoinHandle<()>; 4]>, /// A HashMap of the currently connected sensors /// /// This uses an Arc inside the Mutex as well as outside so that the HashMap is only /// cloned when required. By using Arc::make_mut on the inner HashMap data is only cloned /// when mutating if another thread is still holding onto reference to the data. /// - sensors_map: Arc>>, + pub(crate) sensors_map: Arc>>, } impl Default for EnergyUsage { @@ -82,78 +76,101 @@ mod ffi { fn sensor_removed(self: Pin<&mut qobject::EnergyUsage>, uuid: QString); } - impl qobject::EnergyUsage { + unsafe extern "RustQt" { /// A Q_INVOKABLE that returns the current power usage for a given uuid #[qinvokable] - pub fn sensor_power(self: Pin<&mut Self>, uuid: &QString) -> f64 { - let sensors = SensorsWorker::read_sensors(self.sensors_map_mut()); - - if let Ok(uuid) = Uuid::parse_str(&uuid.to_string()) { - sensors.get(&uuid).map(|v| v.power).unwrap_or_default() - } else { - 0.0 - } - } + fn sensor_power(self: Pin<&mut qobject::EnergyUsage>, uuid: &QString) -> f64; /// A Q_INVOKABLE which starts the TCP server #[qinvokable] - pub fn start_server(mut self: Pin<&mut Self>) { - if self.rust().join_handles.is_some() { - println!("Already running a server!"); - return; - } + fn start_server(self: Pin<&mut qobject::EnergyUsage>); + } +} + +use crate::{ + constants::CHANNEL_NETWORK_COUNT, + network::NetworkServer, + workers::{AccumulatorWorker, SensorsWorker, TimeoutWorker}, +}; - // Create a channel which is used for passing valid network requests - // from the NetworkServer to the SensorsWorker - let (network_tx, network_rx) = sync_channel(CHANNEL_NETWORK_COUNT); - // Create an AtomicBool which the SensorsWorker uses to tell - // the AccumulatorWorker that the sensors have changed - let sensors_changed = Arc::new(AtomicBool::new(false)); - - // Make relevent clones so that we can pass them to the threads - let accumulator_sensors = Arc::clone(self.as_mut().sensors_map_mut()); - let accumulator_sensors_changed = Arc::clone(&sensors_changed); - let accumulator_qt_thread = self.qt_thread(); - let sensors = Arc::clone(self.as_mut().sensors_map_mut()); - let sensors_qt_thread = self.qt_thread(); - let timeout_sensors = Arc::clone(self.as_mut().sensors_map_mut()); - let timeout_network_tx = network_tx.clone(); - - // Start our threads - *self.join_handles_mut() = Some([ - // Create a TimeoutWorker - // If a sensor is not seen for N seconds then a disconnect is requested - std::thread::spawn(move || { - block_on(TimeoutWorker::run(timeout_network_tx, timeout_sensors)) - }), - // Create a AccumulatorWorker - // When sensor values change this creates accumulations of the data - // (such as total, average etc) and then requests an update to Qt - std::thread::spawn(move || { - block_on(AccumulatorWorker::run( - accumulator_sensors, - accumulator_sensors_changed, - accumulator_qt_thread, - )) - }), - // Create a SensorsWorker - // Reads network requests from the NetworkServer, collates the commands - // by mutating the sensors hashmap, and requests signal changes to Qt - std::thread::spawn(move || { - block_on(SensorsWorker::run( - network_rx, - sensors, - sensors_changed, - sensors_qt_thread, - )) - }), - // Create a NetworkServer - // Starts a TCP server which listens for requests and sends valid - // network requests to the SensorsWorker - std::thread::spawn(move || { - block_on(NetworkServer::listen("127.0.0.1:8080", network_tx)) - }), - ]); +use core::pin::Pin; +use cxx_qt::CxxQtType; +use cxx_qt_lib::QString; +use futures::executor::block_on; +use std::sync::{atomic::AtomicBool, mpsc::sync_channel, Arc}; +use uuid::Uuid; + +// TODO: this will change to qobject::EnergyUsage once +// https://github.com/KDAB/cxx-qt/issues/559 is done +impl ffi::EnergyUsageQt { + /// A Q_INVOKABLE that returns the current power usage for a given uuid + fn sensor_power(self: Pin<&mut Self>, uuid: &QString) -> f64 { + let sensors = SensorsWorker::read_sensors(self.sensors_map_mut()); + + if let Ok(uuid) = Uuid::parse_str(&uuid.to_string()) { + sensors.get(&uuid).map(|v| v.power).unwrap_or_default() + } else { + 0.0 + } + } + + /// A Q_INVOKABLE which starts the TCP server + fn start_server(mut self: Pin<&mut Self>) { + if self.rust().join_handles.is_some() { + println!("Already running a server!"); + return; } + + // Create a channel which is used for passing valid network requests + // from the NetworkServer to the SensorsWorker + let (network_tx, network_rx) = sync_channel(CHANNEL_NETWORK_COUNT); + // Create an AtomicBool which the SensorsWorker uses to tell + // the AccumulatorWorker that the sensors have changed + let sensors_changed = Arc::new(AtomicBool::new(false)); + + // Make relevent clones so that we can pass them to the threads + let accumulator_sensors = Arc::clone(self.as_mut().sensors_map_mut()); + let accumulator_sensors_changed = Arc::clone(&sensors_changed); + let accumulator_qt_thread = self.qt_thread(); + let sensors = Arc::clone(self.as_mut().sensors_map_mut()); + let sensors_qt_thread = self.qt_thread(); + let timeout_sensors = Arc::clone(self.as_mut().sensors_map_mut()); + let timeout_network_tx = network_tx.clone(); + + // Start our threads + *self.join_handles_mut() = Some([ + // Create a TimeoutWorker + // If a sensor is not seen for N seconds then a disconnect is requested + std::thread::spawn(move || { + block_on(TimeoutWorker::run(timeout_network_tx, timeout_sensors)) + }), + // Create a AccumulatorWorker + // When sensor values change this creates accumulations of the data + // (such as total, average etc) and then requests an update to Qt + std::thread::spawn(move || { + block_on(AccumulatorWorker::run( + accumulator_sensors, + accumulator_sensors_changed, + accumulator_qt_thread, + )) + }), + // Create a SensorsWorker + // Reads network requests from the NetworkServer, collates the commands + // by mutating the sensors hashmap, and requests signal changes to Qt + std::thread::spawn(move || { + block_on(SensorsWorker::run( + network_rx, + sensors, + sensors_changed, + sensors_qt_thread, + )) + }), + // Create a NetworkServer + // Starts a TCP server which listens for requests and sends valid + // network requests to the SensorsWorker + std::thread::spawn(move || { + block_on(NetworkServer::listen("127.0.0.1:8080", network_tx)) + }), + ]); } } diff --git a/examples/qml_extension_plugin/plugin/rust/src/lib.rs b/examples/qml_extension_plugin/plugin/rust/src/lib.rs index 43f8291c5..8e5c5cb55 100644 --- a/examples/qml_extension_plugin/plugin/rust/src/lib.rs +++ b/examples/qml_extension_plugin/plugin/rust/src/lib.rs @@ -60,33 +60,49 @@ mod ffi { } } - impl qobject::MyObject { + unsafe extern "RustQt" { #[qinvokable] - pub fn increment(self: Pin<&mut Self>) { - let new_number = self.number() + 1; - self.set_number(new_number); - } + pub fn increment(self: Pin<&mut qobject::MyObject>); #[qinvokable] - pub fn reset(mut self: Pin<&mut Self>) { - let data: DataSerde = serde_json::from_str(DEFAULT_STR).unwrap(); - self.as_mut().set_number(data.number); - self.as_mut().set_string(QString::from(&data.string)); - } + pub fn reset(self: Pin<&mut qobject::MyObject>); #[qinvokable] - pub fn serialize(&self) -> QString { - let data_serde = DataSerde::from(self.rust()); - let data_string = serde_json::to_string(&data_serde).unwrap(); - QString::from(&data_string) - } + pub fn serialize(self: &qobject::MyObject) -> QString; #[qinvokable] - pub fn grab_values(mut self: Pin<&mut Self>) { - let string = r#"{"number": 2, "string": "Goodbye!"}"#; - let data: DataSerde = serde_json::from_str(string).unwrap(); - self.as_mut().set_number(data.number); - self.as_mut().set_string(QString::from(&data.string)); - } + pub fn grab_values(self: Pin<&mut qobject::MyObject>); + } +} + +use core::pin::Pin; +use cxx_qt::CxxQtType; +use cxx_qt_lib::QString; + +// TODO: this will change to qobject::MyObject once +// https://github.com/KDAB/cxx-qt/issues/559 is done +impl ffi::MyObjectQt { + pub fn increment(self: Pin<&mut Self>) { + let new_number = self.number() + 1; + self.set_number(new_number); + } + + pub fn reset(mut self: Pin<&mut Self>) { + let data: DataSerde = serde_json::from_str(DEFAULT_STR).unwrap(); + self.as_mut().set_number(data.number); + self.as_mut().set_string(QString::from(&data.string)); + } + + pub fn serialize(&self) -> QString { + let data_serde = DataSerde::from(self.rust()); + let data_string = serde_json::to_string(&data_serde).unwrap(); + QString::from(&data_string) + } + + pub fn grab_values(mut self: Pin<&mut Self>) { + let string = r#"{"number": 2, "string": "Goodbye!"}"#; + let data: DataSerde = serde_json::from_str(string).unwrap(); + self.as_mut().set_number(data.number); + self.as_mut().set_string(QString::from(&data.string)); } } diff --git a/examples/qml_features/rust/src/containers.rs b/examples/qml_features/rust/src/containers.rs index b6f273965..9b62389c4 100644 --- a/examples/qml_features/rust/src/containers.rs +++ b/examples/qml_features/rust/src/containers.rs @@ -49,123 +49,153 @@ pub mod ffi { #[qproperty] string_vector: QString, - hash: QHash_QString_QVariant, - list: QList_i32, + pub(crate) hash: QHash_QString_QVariant, + pub(crate) list: QList_i32, // Expose as a Q_PROPERTY so that QML tests can ensure that QVariantMap works #[qproperty] map: QMap_QString_QVariant, - set: QSet_i32, - vector: QVector_i32, + pub(crate) set: QSet_i32, + pub(crate) vector: QVector_i32, } - impl qobject::RustContainers { + unsafe extern "RustQt" { /// Reset all the containers #[qinvokable] - pub fn reset(mut self: Pin<&mut Self>) { - self.as_mut().set_hash(QHash_QString_QVariant::default()); - self.as_mut().set_list(QList_i32::default()); - self.as_mut().set_map(QMap_QString_QVariant::default()); - self.as_mut().set_set(QSet_i32::default()); - self.as_mut().set_vector(QVector_i32::default()); - - self.update_strings(); - } + fn reset(self: Pin<&mut qobject::RustContainers>); /// Append the given number to the vector container #[qinvokable] - pub fn append_vector(mut self: Pin<&mut Self>, value: i32) { - self.as_mut().vector_mut().append(value); - - self.update_strings(); - } + fn append_vector(self: Pin<&mut qobject::RustContainers>, value: i32); /// Append the given number to the list container #[qinvokable] - pub fn append_list(mut self: Pin<&mut Self>, value: i32) { - self.as_mut().list_mut().append(value); - - self.update_strings(); - } + fn append_list(self: Pin<&mut qobject::RustContainers>, value: i32); /// Insert the given number into the set container #[qinvokable] - pub fn insert_set(mut self: Pin<&mut Self>, value: i32) { - self.as_mut().set_mut().insert(value); - - self.update_strings(); - } + fn insert_set(self: Pin<&mut qobject::RustContainers>, value: i32); /// Insert the given string and variant to the hash container #[qinvokable] - pub fn insert_hash(mut self: Pin<&mut Self>, key: QString, value: QVariant) { - self.as_mut().hash_mut().insert(key, value); - - self.update_strings(); - } + fn insert_hash(self: Pin<&mut qobject::RustContainers>, key: QString, value: QVariant); /// Insert the given string and variant to the map container #[qinvokable] - pub fn insert_map(mut self: Pin<&mut Self>, key: QString, value: QVariant) { - // SAFETY: map is a Q_PROPERTY so ensure we manually trigger changed - unsafe { - self.as_mut().map_mut().insert(key, value); - self.as_mut().map_changed(); - } - - self.update_strings(); - } + fn insert_map(self: Pin<&mut qobject::RustContainers>, key: QString, value: QVariant); + } +} + +use core::pin::Pin; +use cxx_qt_lib::{ + QHash, QHashPair_QString_QVariant, QList, QMap, QMapPair_QString_QVariant, QSet, QString, + QVariant, QVector, +}; + +// TODO: this will change to qobject::RustContainers once +// https://github.com/KDAB/cxx-qt/issues/559 is done +impl ffi::RustContainersQt { + /// Reset all the containers + fn reset(mut self: Pin<&mut Self>) { + self.as_mut() + .set_hash(QHash::::default()); + self.as_mut().set_list(QList::::default()); + self.as_mut() + .set_map(QMap::::default()); + self.as_mut().set_set(QSet::::default()); + self.as_mut().set_vector(QVector::::default()); + + self.update_strings(); + } + + /// Append the given number to the vector container + fn append_vector(mut self: Pin<&mut Self>, value: i32) { + self.as_mut().vector_mut().append(value); - fn update_strings(mut self: Pin<&mut Self>) { - let hash_items = self - .as_ref() - .hash() - .iter() - .map(|(key, value)| { - let value = value.value::().unwrap_or(0); - format!("{key} => {value}") - }) - .collect::>() - .join(", "); - self.as_mut().set_string_hash(QString::from(&hash_items)); - - let list_items = self - .as_ref() - .list() - .iter() - .map(|value| value.to_string()) - .collect::>() - .join(", "); - self.as_mut().set_string_list(QString::from(&list_items)); - - let map_items = self - .as_ref() - .map() - .iter() - .map(|(key, value)| { - let value = value.value::().unwrap_or(0); - format!("{key} => {value}") - }) - .collect::>() - .join(", "); - self.as_mut().set_string_map(QString::from(&map_items)); - - let set_items = self - .as_ref() - .set() - .iter() - .map(|value| value.to_string()) - .collect::>() - .join(", "); - self.as_mut().set_string_set(QString::from(&set_items)); - - let vector_items = self - .as_ref() - .vector() - .iter() - .map(|value| value.to_string()) - .collect::>() - .join(", "); - self.set_string_vector(QString::from(&vector_items)); + self.update_strings(); + } + + /// Append the given number to the list container + fn append_list(mut self: Pin<&mut Self>, value: i32) { + self.as_mut().list_mut().append(value); + + self.update_strings(); + } + + /// Insert the given number into the set container + fn insert_set(mut self: Pin<&mut Self>, value: i32) { + self.as_mut().set_mut().insert(value); + + self.update_strings(); + } + + /// Insert the given string and variant to the hash container + fn insert_hash(mut self: Pin<&mut Self>, key: QString, value: QVariant) { + self.as_mut().hash_mut().insert(key, value); + + self.update_strings(); + } + + /// Insert the given string and variant to the map container + fn insert_map(mut self: Pin<&mut Self>, key: QString, value: QVariant) { + // SAFETY: map is a Q_PROPERTY so ensure we manually trigger changed + unsafe { + self.as_mut().map_mut().insert(key, value); + self.as_mut().map_changed(); } + + self.update_strings(); + } + + fn update_strings(mut self: Pin<&mut Self>) { + let hash_items = self + .as_ref() + .hash() + .iter() + .map(|(key, value)| { + let value = value.value::().unwrap_or(0); + format!("{key} => {value}") + }) + .collect::>() + .join(", "); + self.as_mut().set_string_hash(QString::from(&hash_items)); + + let list_items = self + .as_ref() + .list() + .iter() + .map(|value| value.to_string()) + .collect::>() + .join(", "); + self.as_mut().set_string_list(QString::from(&list_items)); + + let map_items = self + .as_ref() + .map() + .iter() + .map(|(key, value)| { + let value = value.value::().unwrap_or(0); + format!("{key} => {value}") + }) + .collect::>() + .join(", "); + self.as_mut().set_string_map(QString::from(&map_items)); + + let set_items = self + .as_ref() + .set() + .iter() + .map(|value| value.to_string()) + .collect::>() + .join(", "); + self.as_mut().set_string_set(QString::from(&set_items)); + + let vector_items = self + .as_ref() + .vector() + .iter() + .map(|value| value.to_string()) + .collect::>() + .join(", "); + self.set_string_vector(QString::from(&vector_items)); } } diff --git a/examples/qml_features/rust/src/custom_base_class.rs b/examples/qml_features/rust/src/custom_base_class.rs index 17d70b25d..3d252a30f 100644 --- a/examples/qml_features/rust/src/custom_base_class.rs +++ b/examples/qml_features/rust/src/custom_base_class.rs @@ -42,8 +42,8 @@ pub mod ffi { #[derive(Default)] pub struct CustomBaseClass { // ANCHOR_END: book_qobject_base - id: u32, - vector: Vec<(u32, f64)>, + pub(crate) id: u32, + pub(crate) vector: Vec<(u32, f64)>, } // ANCHOR_END: book_inherit_qalm @@ -64,91 +64,28 @@ pub mod ffi { } // ANCHOR_END: book_qsignals_inherit - impl qobject::CustomBaseClass { + unsafe extern "RustQt" { /// Add a new row to the QAbstractListModel on the current thread #[qinvokable] - pub fn add(self: Pin<&mut Self>) { - self.add_cpp_context(); - } + fn add(self: Pin<&mut qobject::CustomBaseClass>); /// On a background thread, add a given number of rows to the QAbstractListModel #[qinvokable] - pub fn add_on_thread(self: Pin<&mut Self>, mut counter: i32) { - let qt_thread = self.qt_thread(); - - std::thread::spawn(move || { - while counter > 0 { - counter -= 1; - std::thread::sleep(std::time::Duration::from_millis(250)); - - // Use our add helper to add a row on the Qt event loop - // as seen in the threading demo channels could be used to pass info - qt_thread - .queue(|custom_base_class| { - custom_base_class.add_cpp_context(); - }) - .unwrap(); - } - }); - } - - fn add_cpp_context(mut self: Pin<&mut Self>) { - let count = self.vector().len(); - unsafe { - self.as_mut().begin_insert_rows( - &QModelIndex::default(), - count as i32, - count as i32, - ); - let id = *self.id(); - self.as_mut().set_id(id + 1); - self.as_mut().vector_mut().push((id, (id as f64) / 3.0)); - self.as_mut().end_insert_rows(); - } - } + fn add_on_thread(self: Pin<&mut qobject::CustomBaseClass>, mut counter: i32); /// Clear the rows in the QAbstractListModel // ANCHOR: book_inherit_clear #[qinvokable] - pub fn clear(mut self: Pin<&mut Self>) { - unsafe { - self.as_mut().begin_reset_model(); - self.as_mut().set_id(0); - self.as_mut().vector_mut().clear(); - self.as_mut().end_reset_model(); - } - } + pub fn clear(self: Pin<&mut qobject::CustomBaseClass>); // ANCHOR_END: book_inherit_clear /// Multiply the number in the row with the given index by the given factor #[qinvokable] - pub fn multiply(mut self: Pin<&mut Self>, index: i32, factor: f64) { - if let Some((_, value)) = self.as_mut().vector_mut().get_mut(index as usize) { - *value *= factor; - - // Emit dataChanged for the index and value role - let model_index = self.index(index, 0, &QModelIndex::default()); - let mut vector_roles = QVector_i32::default(); - vector_roles.append(1); - self.as_mut() - .data_changed(&model_index, &model_index, &vector_roles); - } - } + pub fn multiply(self: Pin<&mut qobject::CustomBaseClass>, index: i32, factor: f64); /// Remove the row with the given index #[qinvokable] - pub fn remove(mut self: Pin<&mut Self>, index: i32) { - if index < 0 || (index as usize) >= self.vector().len() { - return; - } - - unsafe { - self.as_mut() - .begin_remove_rows(&QModelIndex::default(), index, index); - self.as_mut().vector_mut().remove(index as usize); - self.as_mut().end_remove_rows(); - } - } + pub fn remove(self: Pin<&mut qobject::CustomBaseClass>, index: i32); } // ANCHOR: book_inherit_qalm_impl_unsafe @@ -206,50 +143,157 @@ pub mod ffi { // ANCHOR_END: book_inherit_qalm_impl_safe // QAbstractListModel implementation - impl qobject::CustomBaseClass { - /// i32 representing the id role - pub const ID_ROLE: i32 = 0; - /// i32 representing the value role - pub const VALUE_ROLE: i32 = 1; - + unsafe extern "RustQt" { // ANCHOR: book_inherit_data #[qinvokable(cxx_override)] - fn data(&self, index: &QModelIndex, role: i32) -> QVariant { - if let Some((id, value)) = self.rust().vector.get(index.row() as usize) { - return match role { - Self::ID_ROLE => QVariant::from(id), - Self::VALUE_ROLE => QVariant::from(value), - _ => QVariant::default(), - }; - } - - QVariant::default() - } + fn data(self: &qobject::CustomBaseClass, index: &QModelIndex, role: i32) -> QVariant; // ANCHOR_END: book_inherit_data /// Return whether the base class can fetch more // ANCHOR: book_inherit_can_fetch_more // Example of overriding a C++ virtual method and calling the base class implementation. #[qinvokable(cxx_override)] - pub fn can_fetch_more(&self, parent: &QModelIndex) -> bool { - self.base_can_fetch_more(parent) - } + fn can_fetch_more(self: &qobject::CustomBaseClass, parent: &QModelIndex) -> bool; // ANCHOR_END: book_inherit_can_fetch_more /// Return the role names for the QAbstractListModel #[qinvokable(cxx_override)] - pub fn role_names(&self) -> QHash_i32_QByteArray { - let mut roles = QHash_i32_QByteArray::default(); - roles.insert(Self::ID_ROLE, cxx_qt_lib::QByteArray::from("id")); - roles.insert(Self::VALUE_ROLE, cxx_qt_lib::QByteArray::from("value")); - roles - } + fn role_names(self: &qobject::CustomBaseClass) -> QHash_i32_QByteArray; /// Return the row count for the QAbstractListModel #[qinvokable(cxx_override)] - pub fn row_count(&self, _parent: &QModelIndex) -> i32 { - self.rust().vector.len() as i32 + fn row_count(self: &qobject::CustomBaseClass, _parent: &QModelIndex) -> i32; + } +} + +use core::pin::Pin; +use cxx_qt::CxxQtType; +use cxx_qt_lib::{QByteArray, QHash, QHashPair_i32_QByteArray, QModelIndex, QVariant, QVector}; + +// TODO: this will change to qobject::RustContainers once +// https://github.com/KDAB/cxx-qt/issues/559 is done +impl ffi::CustomBaseClassQt { + /// Add a new row to the QAbstractListModel on the current thread + pub fn add(self: Pin<&mut Self>) { + self.add_cpp_context(); + } + + /// On a background thread, add a given number of rows to the QAbstractListModel + pub fn add_on_thread(self: Pin<&mut Self>, mut counter: i32) { + let qt_thread = self.qt_thread(); + + std::thread::spawn(move || { + while counter > 0 { + counter -= 1; + std::thread::sleep(std::time::Duration::from_millis(250)); + + // Use our add helper to add a row on the Qt event loop + // as seen in the threading demo channels could be used to pass info + qt_thread + .queue(|custom_base_class| { + custom_base_class.add_cpp_context(); + }) + .unwrap(); + } + }); + } + + fn add_cpp_context(mut self: Pin<&mut Self>) { + let count = self.vector().len(); + unsafe { + self.as_mut() + .begin_insert_rows(&QModelIndex::default(), count as i32, count as i32); + let id = *self.id(); + self.as_mut().set_id(id + 1); + self.as_mut().vector_mut().push((id, (id as f64) / 3.0)); + self.as_mut().end_insert_rows(); + } + } + + /// Clear the rows in the QAbstractListModel + // ANCHOR: book_inherit_clear + pub fn clear(mut self: Pin<&mut Self>) { + unsafe { + self.as_mut().begin_reset_model(); + self.as_mut().set_id(0); + self.as_mut().vector_mut().clear(); + self.as_mut().end_reset_model(); + } + } + // ANCHOR_END: book_inherit_clear + + /// Multiply the number in the row with the given index by the given factor + pub fn multiply(mut self: Pin<&mut Self>, index: i32, factor: f64) { + if let Some((_, value)) = self.as_mut().vector_mut().get_mut(index as usize) { + *value *= factor; + + // Emit dataChanged for the index and value role + let model_index = self.index(index, 0, &QModelIndex::default()); + let mut vector_roles = QVector::::default(); + vector_roles.append(1); + self.as_mut() + .data_changed(&model_index, &model_index, &vector_roles); + } + } + + /// Remove the row with the given index + pub fn remove(mut self: Pin<&mut Self>, index: i32) { + if index < 0 || (index as usize) >= self.vector().len() { + return; + } + + unsafe { + self.as_mut() + .begin_remove_rows(&QModelIndex::default(), index, index); + self.as_mut().vector_mut().remove(index as usize); + self.as_mut().end_remove_rows(); + } + } +} + +// TODO: this will change to qobject::RustContainers once +// https://github.com/KDAB/cxx-qt/issues/559 is done +// +// QAbstractListModel implementation +impl ffi::CustomBaseClassQt { + /// i32 representing the id role + pub const ID_ROLE: i32 = 0; + /// i32 representing the value role + pub const VALUE_ROLE: i32 = 1; + + // ANCHOR: book_inherit_data + fn data(&self, index: &QModelIndex, role: i32) -> QVariant { + if let Some((id, value)) = self.rust().vector.get(index.row() as usize) { + return match role { + Self::ID_ROLE => QVariant::from(id), + Self::VALUE_ROLE => QVariant::from(value), + _ => QVariant::default(), + }; } + + QVariant::default() + } + // ANCHOR_END: book_inherit_data + + /// Return whether the base class can fetch more + // ANCHOR: book_inherit_can_fetch_more + // Example of overriding a C++ virtual method and calling the base class implementation. + pub fn can_fetch_more(&self, parent: &QModelIndex) -> bool { + self.base_can_fetch_more(parent) + } + // ANCHOR_END: book_inherit_can_fetch_more + + /// Return the role names for the QAbstractListModel + pub fn role_names(&self) -> QHash { + let mut roles = QHash::::default(); + roles.insert(Self::ID_ROLE, QByteArray::from("id")); + roles.insert(Self::VALUE_ROLE, QByteArray::from("value")); + roles + } + + /// Return the row count for the QAbstractListModel + pub fn row_count(&self, _parent: &QModelIndex) -> i32 { + self.rust().vector.len() as i32 } } // ANCHOR_END: book_macro_code diff --git a/examples/qml_features/rust/src/invokables.rs b/examples/qml_features/rust/src/invokables.rs index 201135131..07199adfc 100644 --- a/examples/qml_features/rust/src/invokables.rs +++ b/examples/qml_features/rust/src/invokables.rs @@ -18,9 +18,9 @@ pub mod ffi { /// A QObject which has Q_INVOKABLEs #[cxx_qt::qobject(qml_uri = "com.kdab.cxx_qt.demo", qml_version = "1.0")] pub struct RustInvokables { - red: f32, - green: f32, - blue: f32, + pub(crate) red: f32, + pub(crate) green: f32, + pub(crate) blue: f32, } impl Default for RustInvokables { @@ -34,43 +34,60 @@ pub mod ffi { } // ANCHOR: book_impl_qobject - impl qobject::RustInvokables { + unsafe extern "RustQt" { /// Immutable invokable method that returns the QColor #[qinvokable] - pub fn load_color(&self) -> QColor { - self.rust().as_qcolor() - } + fn load_color(self: &qobject::RustInvokables) -> QColor; /// Mutable invokable method that stores a color #[qinvokable] - pub fn store_color(self: Pin<&mut Self>, red: f32, green: f32, blue: f32) { - self.store_helper(red, green, blue); - } + fn store_color(self: Pin<&mut qobject::RustInvokables>, red: f32, green: f32, blue: f32); /// Mutable invokable method with no parameters that resets the color #[qinvokable] - pub fn reset(self: Pin<&mut Self>) { - self.store_helper(0.0, 0.4667, 0.7843); - } - - /// Mutable C++ context method that helps to store the color - pub fn store_helper(mut self: Pin<&mut Self>, red: f32, green: f32, blue: f32) { - self.as_mut().set_red(red); - self.as_mut().set_green(green); - self.as_mut().set_blue(blue); - } + fn reset(self: Pin<&mut qobject::RustInvokables>); } // ANCHOR_END: book_impl_qobject +} - impl RustInvokables { - /// Immutable Rust context method that returns the QColor - fn as_qcolor(&self) -> QColor { - QColor::from_rgb( - (self.red * 255.0).round() as i32, - (self.green * 255.0).round() as i32, - (self.blue * 255.0).round() as i32, - ) - } +use core::pin::Pin; +use cxx_qt::CxxQtType; +use cxx_qt_lib::QColor; + +// TODO: this will change to qobject::RustInvokables once +// https://github.com/KDAB/cxx-qt/issues/559 is done +impl ffi::RustInvokablesQt { + /// Immutable invokable method that returns the QColor + fn load_color(&self) -> QColor { + self.rust().as_qcolor() + } + + /// Mutable invokable method that stores a color + fn store_color(self: Pin<&mut Self>, red: f32, green: f32, blue: f32) { + self.store_helper(red, green, blue); + } + + /// Mutable invokable method with no parameters that resets the color + fn reset(self: Pin<&mut Self>) { + self.store_helper(0.0, 0.4667, 0.7843); + } + + /// Mutable C++ context method that helps to store the color + fn store_helper(mut self: Pin<&mut Self>, red: f32, green: f32, blue: f32) { + self.as_mut().set_red(red); + self.as_mut().set_green(green); + self.as_mut().set_blue(blue); + } +} + +impl RustInvokables { + /// Immutable Rust context method that returns the QColor + fn as_qcolor(&self) -> QColor { + QColor::from_rgb( + (self.red * 255.0).round() as i32, + (self.green * 255.0).round() as i32, + (self.blue * 255.0).round() as i32, + ) } } // ANCHOR_END: book_macro_code diff --git a/examples/qml_features/rust/src/multiple_qobjects.rs b/examples/qml_features/rust/src/multiple_qobjects.rs index 18bae1eff..574883ae9 100644 --- a/examples/qml_features/rust/src/multiple_qobjects.rs +++ b/examples/qml_features/rust/src/multiple_qobjects.rs @@ -48,21 +48,10 @@ pub mod ffi { fn rejected(self: Pin<&mut qobject::FirstObject>); } - impl qobject::FirstObject { + unsafe extern "RustQt" { /// A Q_INVOKABLE on the first QObject which increments a counter #[qinvokable] - pub fn increment(mut self: Pin<&mut Self>) { - let new_value = self.as_ref().counter() + 1; - self.as_mut().set_counter(new_value); - - if new_value % 2 == 0 { - self.as_mut().set_color(QColor::from_rgb(0, 0, 255)); - self.accepted(); - } else { - self.as_mut().set_color(QColor::from_rgb(255, 0, 0)); - self.rejected(); - } - } + fn increment(self: Pin<&mut qobject::FirstObject>); } /// The second QObject @@ -96,21 +85,49 @@ pub mod ffi { fn rejected(self: Pin<&mut qobject::SecondObject>); } - impl qobject::SecondObject { + unsafe extern "RustQt" { /// A Q_INVOKABLE on the second QObject which increments a counter #[qinvokable] - pub fn increment(mut self: Pin<&mut Self>) { - let new_value = self.as_ref().counter() + 1; - self.as_mut().set_counter(new_value); - - if new_value % 5 == 0 { - self.as_mut() - .set_url(QUrl::from("https://github.com/kdab/cxx-qt")); - self.accepted(); - } else { - self.as_mut().set_url(QUrl::from("https://kdab.com")); - self.rejected(); - } + fn increment(self: Pin<&mut qobject::SecondObject>); + } +} + +use core::pin::Pin; +use cxx_qt_lib::{QColor, QUrl}; + +// TODO: this will change to qobject::FirstObject once +// https://github.com/KDAB/cxx-qt/issues/559 is done +impl ffi::FirstObjectQt { + /// A Q_INVOKABLE on the first QObject which increments a counter + fn increment(mut self: Pin<&mut Self>) { + let new_value = self.as_ref().counter() + 1; + self.as_mut().set_counter(new_value); + + if new_value % 2 == 0 { + self.as_mut().set_color(QColor::from_rgb(0, 0, 255)); + self.accepted(); + } else { + self.as_mut().set_color(QColor::from_rgb(255, 0, 0)); + self.rejected(); + } + } +} + +// TODO: this will change to qobject::SecondObject once +// https://github.com/KDAB/cxx-qt/issues/559 is done +impl ffi::SecondObjectQt { + /// A Q_INVOKABLE on the second QObject which increments a counter + fn increment(mut self: Pin<&mut Self>) { + let new_value = self.as_ref().counter() + 1; + self.as_mut().set_counter(new_value); + + if new_value % 5 == 0 { + self.as_mut() + .set_url(QUrl::from("https://github.com/kdab/cxx-qt")); + self.accepted(); + } else { + self.as_mut().set_url(QUrl::from("https://kdab.com")); + self.rejected(); } } } diff --git a/examples/qml_features/rust/src/nested_qobjects.rs b/examples/qml_features/rust/src/nested_qobjects.rs index fbecbea97..15bcae873 100644 --- a/examples/qml_features/rust/src/nested_qobjects.rs +++ b/examples/qml_features/rust/src/nested_qobjects.rs @@ -52,54 +52,72 @@ pub mod ffi { unsafe fn called(self: Pin<&mut qobject::OuterObject>, inner: *mut CxxInnerObject); } - impl qobject::OuterObject { + unsafe extern "RustQt" { /// Initialise the QObject, creating a connection from one signal to another #[qinvokable] - pub fn initialise(self: Pin<&mut Self>) { - // Example of connecting a signal from one QObject to another QObject - // this causes OuterObject::Called to trigger InnerObject::Called - self.on_called(|qobject, obj| { - // We need to convert the *mut T to a Pin<&mut T> so that we can reach the methods - 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 - unsafe { - pinned_inner.called(obj); - } - } - }) - .release(); - } + fn initialise(self: Pin<&mut qobject::OuterObject>); /// Print the count of the given inner QObject // // This method needs to be unsafe otherwise clippy complains that the // public method might dereference the raw pointer. #[qinvokable] - pub unsafe fn print_count(self: Pin<&mut Self>, inner: *mut CxxInnerObject) { - if let Some(inner) = unsafe { inner.as_ref() } { - println!("Inner object's counter property: {}", inner.counter()); - } - - unsafe { - self.called(inner); - } - } + unsafe fn print_count(self: Pin<&mut qobject::OuterObject>, inner: *mut CxxInnerObject); /// Reset the counter of the inner QObject stored in the Q_PROPERTY #[qinvokable] - pub fn reset(self: Pin<&mut Self>) { + fn reset(self: Pin<&mut qobject::OuterObject>); + } +} + +use core::pin::Pin; + +// TODO: this will change to qobject::OuterObject once +// https://github.com/KDAB/cxx-qt/issues/559 is done +impl ffi::OuterObjectQt { + /// Initialise the QObject, creating a connection from one signal to another + fn initialise(self: Pin<&mut Self>) { + // Example of connecting a signal from one QObject to another QObject + // this causes OuterObject::Called to trigger InnerObject::Called + self.on_called(|qobject, obj| { // We need to convert the *mut T to a Pin<&mut T> so that we can reach the methods - if let Some(inner) = unsafe { self.inner().as_mut() } { + 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.set_counter(10); + unsafe { + pinned_inner.called(obj); + } } + }) + .release(); + } + + /// Print the count of the given inner QObject + // + // This method needs to be unsafe otherwise clippy complains that the + // public method might dereference the raw pointer. + unsafe fn print_count(self: Pin<&mut Self>, inner: *mut ffi::CxxInnerObject) { + if let Some(inner) = unsafe { inner.as_ref() } { + println!("Inner object's counter property: {}", inner.counter()); + } - // Retrieve *mut T - let inner = *self.inner(); - unsafe { self.called(inner) }; + unsafe { + self.called(inner); } } + + /// Reset the counter of the inner QObject stored in the Q_PROPERTY + fn reset(self: Pin<&mut Self>) { + // We need to convert the *mut T to a Pin<&mut T> so that we can reach the methods + if let Some(inner) = unsafe { self.inner().as_mut() } { + let pinned_inner = unsafe { Pin::new_unchecked(inner) }; + // Now pinned inner can be used as normal + pinned_inner.set_counter(10); + } + + // Retrieve *mut T + let inner = *self.inner(); + unsafe { self.called(inner) }; + } } // ANCHOR_END: book_macro_code diff --git a/examples/qml_features/rust/src/properties.rs b/examples/qml_features/rust/src/properties.rs index 5d21fd750..17b51f45e 100644 --- a/examples/qml_features/rust/src/properties.rs +++ b/examples/qml_features/rust/src/properties.rs @@ -27,7 +27,7 @@ pub mod ffi { /// A connected_url Q_PROPERTY #[qproperty] - connected_url: QUrl, + pub(crate) connected_url: QUrl, /// A previous_connected_url Q_PROPERTY #[qproperty] @@ -52,41 +52,55 @@ pub mod ffi { } // ANCHOR_END: book_properties_default - impl qobject::RustProperties { + unsafe extern "RustQt" { /// Connect to the given url #[qinvokable] - pub fn connect(mut self: Pin<&mut Self>, mut url: QUrl) { - // Check that the url starts with kdab - if url.to_string().starts_with("https://kdab.com") { - self.as_mut().set_connected(true); - self.as_mut().set_status_message(QString::from("Connected")); - - // Safety: - // We are directly modifying the Rust struct to avoid creating an extra QUrl. - // But using rust_mut() is unsafe as this does not trigger a signal change for the property - // So we need to manually call this ourselves. - unsafe { - std::mem::swap(&mut self.as_mut().rust_mut().connected_url, &mut url); - self.as_mut().connected_url_changed(); - } - // Then we can store the old url without having to temporarily store it - self.set_previous_connected_url(url); - } else { - self.as_mut().set_connected(false); - self.set_status_message(QString::from("URL does not start with https://kdab.com")); - } - } + fn connect(self: Pin<&mut qobject::RustProperties>, mut url: QUrl); /// Disconnect from the stored url #[qinvokable] - pub fn disconnect(mut self: Pin<&mut Self>) { + fn disconnect(self: Pin<&mut qobject::RustProperties>); + } +} + +use core::pin::Pin; +use cxx_qt::CxxQtType; +use cxx_qt_lib::{QString, QUrl}; + +// TODO: this will change to qobject::RustProperties once +// https://github.com/KDAB/cxx-qt/issues/559 is done +impl ffi::RustPropertiesQt { + /// Connect to the given url + fn connect(mut self: Pin<&mut Self>, mut url: QUrl) { + // Check that the url starts with kdab + if url.to_string().starts_with("https://kdab.com") { + self.as_mut().set_connected(true); + self.as_mut().set_status_message(QString::from("Connected")); + + // Safety: + // We are directly modifying the Rust struct to avoid creating an extra QUrl. + // But using rust_mut() is unsafe as this does not trigger a signal change for the property + // So we need to manually call this ourselves. + unsafe { + std::mem::swap(&mut self.as_mut().rust_mut().connected_url, &mut url); + self.as_mut().connected_url_changed(); + } + // Then we can store the old url without having to temporarily store it + self.set_previous_connected_url(url); + } else { self.as_mut().set_connected(false); - self.as_mut() - .set_status_message(QString::from("Disconnected")); - // Here we show how data can be cloned instead of using the unsafe API to swap the values - let previous_url = self.as_ref().connected_url().clone(); - self.as_mut().set_previous_connected_url(previous_url); - self.set_connected_url(QUrl::default()); + self.set_status_message(QString::from("URL does not start with https://kdab.com")); } } + + /// Disconnect from the stored url + fn disconnect(mut self: Pin<&mut Self>) { + self.as_mut().set_connected(false); + self.as_mut() + .set_status_message(QString::from("Disconnected")); + // Here we show how data can be cloned instead of using the unsafe API to swap the values + let previous_url = self.as_ref().connected_url().clone(); + self.as_mut().set_previous_connected_url(previous_url); + self.set_connected_url(QUrl::default()); + } } diff --git a/examples/qml_features/rust/src/serialisation.rs b/examples/qml_features/rust/src/serialisation.rs index 4d3bf2e61..b6b018008 100644 --- a/examples/qml_features/rust/src/serialisation.rs +++ b/examples/qml_features/rust/src/serialisation.rs @@ -71,35 +71,51 @@ pub mod ffi { } } - impl qobject::Serialisation { + unsafe extern "RustQt" { /// Retrieve the JSON form of this QObject #[qinvokable] - pub fn as_json_str(self: Pin<&mut Self>) -> QString { - let data_serde = DataSerde::from(self.rust()); - match serde_json::to_string(&data_serde) { - Ok(data_string) => QString::from(&data_string), - Err(err) => { - self.error(QString::from(&err.to_string())); - QString::default() - } - } - } + fn as_json_str(self: Pin<&mut qobject::Serialisation>) -> QString; /// From a given JSON string try to load values for the Q_PROPERTYs // ANCHOR: book_grab_values #[qinvokable] - pub fn from_json_str(mut self: Pin<&mut Self>, string: &QString) { - match serde_json::from_str::(&string.to_string()) { - Ok(data_serde) => { - self.as_mut().set_number(data_serde.number); - self.as_mut().set_string(QString::from(&data_serde.string)); - } - Err(err) => { - self.error(QString::from(&err.to_string())); - } + fn from_json_str(self: Pin<&mut qobject::Serialisation>, string: &QString); + // ANCHOR_END: book_grab_values + } +} + +use core::pin::Pin; +use cxx_qt::CxxQtType; +use cxx_qt_lib::QString; + +// TODO: this will change to qobject::Serialisation once +// https://github.com/KDAB/cxx-qt/issues/559 is done +impl ffi::SerialisationQt { + /// Retrieve the JSON form of this QObject + fn as_json_str(self: Pin<&mut Self>) -> QString { + let data_serde = DataSerde::from(self.rust()); + match serde_json::to_string(&data_serde) { + Ok(data_string) => QString::from(&data_string), + Err(err) => { + self.error(QString::from(&err.to_string())); + QString::default() + } + } + } + + /// From a given JSON string try to load values for the Q_PROPERTYs + // ANCHOR: book_grab_values + fn from_json_str(mut self: Pin<&mut Self>, string: &QString) { + match serde_json::from_str::(&string.to_string()) { + Ok(data_serde) => { + self.as_mut().set_number(data_serde.number); + self.as_mut().set_string(QString::from(&data_serde.string)); + } + Err(err) => { + self.error(QString::from(&err.to_string())); } } - // ANCHOR_END: book_grab_values } + // ANCHOR_END: book_grab_values } // ANCHOR_END: book_macro_code diff --git a/examples/qml_features/rust/src/signals.rs b/examples/qml_features/rust/src/signals.rs index baa898479..37efe6e74 100644 --- a/examples/qml_features/rust/src/signals.rs +++ b/examples/qml_features/rust/src/signals.rs @@ -9,8 +9,6 @@ // ANCHOR: book_macro_code #[cxx_qt::bridge(cxx_file_stem = "rust_signals")] pub mod ffi { - use cxx_qt_lib::ConnectionType; - unsafe extern "C++" { include!("cxx-qt-lib/qstring.h"); /// QString from cxx_qt_lib @@ -41,73 +39,89 @@ pub mod ffi { #[cxx_qt::qobject(qml_uri = "com.kdab.cxx_qt.demo", qml_version = "1.0")] #[derive(Default)] pub struct RustSignals { - connections: Option<[cxx_qt_lib::QMetaObjectConnection; 3]>, + pub(crate) connections: Option<[cxx_qt_lib::QMetaObjectConnection; 3]>, #[qproperty] logging_enabled: bool, } // ANCHOR: book_rust_obj_impl - impl qobject::RustSignals { + unsafe extern "RustQt" { /// Connect to the given url #[qinvokable] - 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.connected(url); - } else { - // Emit a signal to QML stating that the url was incorrect - self.error(QString::from("URL does not start with https://kdab.com")); - } - } + fn connect(self: Pin<&mut qobject::RustSignals>, url: &QUrl); /// Disconnect #[qinvokable] - pub fn disconnect(self: Pin<&mut Self>) { - // Emit a signal to QML stating that we have disconnected - self.disconnected(); - } + fn disconnect(self: Pin<&mut qobject::RustSignals>); /// Initialise the QObject, creating a connection reacting to the logging enabled property #[qinvokable] - pub fn initialise(self: Pin<&mut Self>) { - self.on_logging_enabled_changed(|mut qobject| { - // Determine if logging is enabled - if *qobject.as_ref().logging_enabled() { - // If no connections have been made, then create them - if qobject.as_ref().connections().is_none() { - // ANCHOR: book_signals_connect - let connections = [ - qobject.as_mut().on_connected(|_, url| { - println!("Connected: {}", url); - }), - qobject.as_mut().on_disconnected(|_| { - println!("Disconnected"); - }), - // Demonstration of connecting with a different connection type - qobject.as_mut().connect_error( - |_, message| { - println!("Error: {}", message); - }, - ConnectionType::QueuedConnection, - ), - ]; - qobject.as_mut().set_connections(Some(connections)); - // ANCHOR_END: book_signals_connect - } - } else { - // Disabling logging so disconnect - // ANCHOR: book_signals_disconnect - // By making connections None, we trigger a drop on the connections - // this then causes disconnections - qobject.as_mut().set_connections(None); - // ANCHOR_END: book_signals_disconnect - } - }) - .release(); - } + fn initialise(self: Pin<&mut qobject::RustSignals>); } // ANCHOR_END: book_rust_obj_impl } + +use core::pin::Pin; +use cxx_qt_lib::{ConnectionType, QString, QUrl}; + +// TODO: this will change to qobject::RustSignals once +// https://github.com/KDAB/cxx-qt/issues/559 is done +impl ffi::RustSignalsQt { + /// Connect to the given url + 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.connected(url); + } else { + // Emit a signal to QML stating that the url was incorrect + self.error(QString::from("URL does not start with https://kdab.com")); + } + } + + /// Disconnect + fn disconnect(self: Pin<&mut Self>) { + // Emit a signal to QML stating that we have disconnected + self.disconnected(); + } + + /// Initialise the QObject, creating a connection reacting to the logging enabled property + fn initialise(self: Pin<&mut Self>) { + self.on_logging_enabled_changed(|mut qobject| { + // Determine if logging is enabled + if *qobject.as_ref().logging_enabled() { + // If no connections have been made, then create them + if qobject.as_ref().connections().is_none() { + // ANCHOR: book_signals_connect + let connections = [ + qobject.as_mut().on_connected(|_, url| { + println!("Connected: {}", url); + }), + qobject.as_mut().on_disconnected(|_| { + println!("Disconnected"); + }), + // Demonstration of connecting with a different connection type + qobject.as_mut().connect_error( + |_, message| { + println!("Error: {}", message); + }, + ConnectionType::QueuedConnection, + ), + ]; + qobject.as_mut().set_connections(Some(connections)); + // ANCHOR_END: book_signals_connect + } + } else { + // Disabling logging so disconnect + // ANCHOR: book_signals_disconnect + // By making connections None, we trigger a drop on the connections + // this then causes disconnections + qobject.as_mut().set_connections(None); + // ANCHOR_END: book_signals_disconnect + } + }) + .release(); + } +} // ANCHOR_END: book_macro_code diff --git a/examples/qml_features/rust/src/singleton.rs b/examples/qml_features/rust/src/singleton.rs index c90ca2aa1..12fe50d2b 100644 --- a/examples/qml_features/rust/src/singleton.rs +++ b/examples/qml_features/rust/src/singleton.rs @@ -18,12 +18,21 @@ pub mod ffi { persistent_value: i32, } - impl qobject::RustSingleton { + unsafe extern "RustQt" { /// Increment the persistent value Q_PROPERTY of the QML_SINGLETON #[qinvokable] - pub fn increment(self: Pin<&mut Self>) { - let new_value = self.persistent_value() + 1; - self.set_persistent_value(new_value); - } + fn increment(self: Pin<&mut qobject::RustSingleton>); + } +} + +use core::pin::Pin; + +// TODO: this will change to qobject::RustSingleton once +// https://github.com/KDAB/cxx-qt/issues/559 is done +impl ffi::RustSingletonQt { + /// Increment the persistent value Q_PROPERTY of the QML_SINGLETON + fn increment(self: Pin<&mut Self>) { + let new_value = self.persistent_value() + 1; + self.set_persistent_value(new_value); } } diff --git a/examples/qml_features/rust/src/threading.rs b/examples/qml_features/rust/src/threading.rs index 54eefe1d3..18aaded93 100644 --- a/examples/qml_features/rust/src/threading.rs +++ b/examples/qml_features/rust/src/threading.rs @@ -31,7 +31,7 @@ pub mod ffi { #[qproperty] url: QUrl, - loading: std::sync::atomic::AtomicBool, + pub(crate) loading: std::sync::atomic::AtomicBool, } impl Default for ThreadingWebsite { @@ -50,76 +50,90 @@ pub mod ffi { impl cxx_qt::Threading for qobject::ThreadingWebsite {} // ANCHOR_END: book_threading_trait - impl qobject::ThreadingWebsite { + unsafe extern "RustQt" { /// Swap the URL between kdab.com and github.com #[qinvokable] - pub fn change_url(self: Pin<&mut Self>) { - let new_url = if self.url().to_string() == "https://kdab.com" { - "https://github.com/kdab/cxx-qt" - } else { - "https://kdab.com" - }; - self.set_url(QUrl::from(new_url)); - } + fn change_url(self: Pin<&mut qobject::ThreadingWebsite>); /// Simulate delay of a network request to retrieve the title of the website #[qinvokable] - pub fn fetch_title(mut self: Pin<&mut Self>) { - // Check that we aren't already retrieving a title - if self - .rust() - .loading - .compare_exchange( - false, - true, - std::sync::atomic::Ordering::SeqCst, - std::sync::atomic::Ordering::SeqCst, - ) - .is_err() - { - println!("Already fetching a title."); - return; - } + fn fetch_title(self: Pin<&mut qobject::ThreadingWebsite>); + } +} + +use core::pin::Pin; +use cxx_qt::CxxQtType; +use cxx_qt_lib::{QString, QUrl}; + +// TODO: this will change to qobject::ThreadingWebsite once +// https://github.com/KDAB/cxx-qt/issues/559 is done +impl ffi::ThreadingWebsiteQt { + /// Swap the URL between kdab.com and github.com + fn change_url(self: Pin<&mut Self>) { + let new_url = if self.url().to_string() == "https://kdab.com" { + "https://github.com/kdab/cxx-qt" + } else { + "https://kdab.com" + }; + self.set_url(QUrl::from(new_url)); + } - // Indicate that we are loading - self.as_mut().set_title(QString::from("Loading...")); - - // Fetch items we need to move into the thread - // ANCHOR: book_qt_thread - let qt_thread = self.qt_thread(); - // ANCHOR_END: book_qt_thread - let url = self.url().to_string(); - - // Spawn a Rust thread to simulate the slow network request - std::thread::spawn(move || { - // Wait for 1 second - std::thread::sleep(std::time::Duration::from_secs(1)); - - // Build the new title - let title = if url == "https://kdab.com" { - "KDAB".to_owned() - } else { - "GitHub".to_owned() - }; - - // ANCHOR: book_qt_thread_queue - // Queue a Rust closure to the Qt thread - qt_thread - .queue(move |mut qobject_website| { - // Update the title property of the QObject - qobject_website.as_mut().set_title(QString::from(&title)); - - // Indicate that we have finished loading the title - qobject_website - .as_ref() - .rust() - .loading - .store(false, std::sync::atomic::Ordering::Relaxed); - }) - .unwrap(); - // ANCHOR_END: book_qt_thread_queue - }); + /// Simulate delay of a network request to retrieve the title of the website + fn fetch_title(mut self: Pin<&mut Self>) { + // Check that we aren't already retrieving a title + if self + .rust() + .loading + .compare_exchange( + false, + true, + std::sync::atomic::Ordering::SeqCst, + std::sync::atomic::Ordering::SeqCst, + ) + .is_err() + { + println!("Already fetching a title."); + return; } + + // Indicate that we are loading + self.as_mut().set_title(QString::from("Loading...")); + + // Fetch items we need to move into the thread + // ANCHOR: book_qt_thread + let qt_thread = self.qt_thread(); + // ANCHOR_END: book_qt_thread + let url = self.url().to_string(); + + // Spawn a Rust thread to simulate the slow network request + std::thread::spawn(move || { + // Wait for 1 second + std::thread::sleep(std::time::Duration::from_secs(1)); + + // Build the new title + let title = if url == "https://kdab.com" { + "KDAB".to_owned() + } else { + "GitHub".to_owned() + }; + + // ANCHOR: book_qt_thread_queue + // Queue a Rust closure to the Qt thread + qt_thread + .queue(move |mut qobject_website| { + // Update the title property of the QObject + qobject_website.as_mut().set_title(QString::from(&title)); + + // Indicate that we have finished loading the title + qobject_website + .as_ref() + .rust() + .loading + .store(false, std::sync::atomic::Ordering::Relaxed); + }) + .unwrap(); + // ANCHOR_END: book_qt_thread_queue + }); } } // ANCHOR_END: book_macro_code diff --git a/examples/qml_features/rust/src/types.rs b/examples/qml_features/rust/src/types.rs index 9ab4eb222..b4d0c2e4c 100644 --- a/examples/qml_features/rust/src/types.rs +++ b/examples/qml_features/rust/src/types.rs @@ -97,29 +97,42 @@ pub mod ffi { } } - impl qobject::Types { + unsafe extern "RustQt" { /// Load the value from a QVariant #[qinvokable] - pub fn load_from_variant(self: Pin<&mut Self>, variant: &QVariant) { - if let Some(boolean) = variant.value::() { - self.set_boolean(boolean); - } else if let Some(point) = variant.value::() { - self.set_point(point); - } else if let Some(url) = variant.value::() { - self.set_url(url); - } else if let Some(custom) = variant.value::() { - self.set_custom_value(custom.value); - } else { - println!("Unknown QVariant type to load from"); - } - } + fn load_from_variant(self: Pin<&mut qobject::Types>, variant: &QVariant); /// Toggle the boolean Q_PROPERTY #[qinvokable] - pub fn toggle_boolean(self: Pin<&mut Self>) { - let new_boolean = !self.as_ref().boolean(); - self.set_boolean(new_boolean); + fn toggle_boolean(self: Pin<&mut qobject::Types>); + } +} + +use core::pin::Pin; +use cxx_qt_lib::{QPointF, QUrl, QVariant}; + +// TODO: this will change to qobject::Types once +// https://github.com/KDAB/cxx-qt/issues/559 is done +impl ffi::TypesQt { + /// Load the value from a QVariant + fn load_from_variant(self: Pin<&mut Self>, variant: &QVariant) { + if let Some(boolean) = variant.value::() { + self.set_boolean(boolean); + } else if let Some(point) = variant.value::() { + self.set_point(point); + } else if let Some(url) = variant.value::() { + self.set_url(url); + } else if let Some(custom) = variant.value::() { + self.set_custom_value(custom.value); + } else { + println!("Unknown QVariant type to load from"); } } + + /// Toggle the boolean Q_PROPERTY + fn toggle_boolean(self: Pin<&mut Self>) { + let new_boolean = !self.as_ref().boolean(); + self.set_boolean(new_boolean); + } } // ANCHOR_END: book_macro_code diff --git a/examples/qml_minimal/rust/src/cxxqt_object.rs b/examples/qml_minimal/rust/src/cxxqt_object.rs index 63adb34ee..c85cb5455 100644 --- a/examples/qml_minimal/rust/src/cxxqt_object.rs +++ b/examples/qml_minimal/rust/src/cxxqt_object.rs @@ -10,7 +10,7 @@ /// The bridge definition for our QObject #[cxx_qt::bridge] -pub mod my_object { +pub mod ffi { // ANCHOR_END: book_bridge_macro // ANCHOR: book_qstring_import @@ -44,20 +44,32 @@ pub mod my_object { // ANCHOR_END: book_rustobj_default // ANCHOR: book_rustobj_impl - impl qobject::MyObject { - /// Increment the number Q_PROPERTY + unsafe extern "RustQt" { #[qinvokable] - pub fn increment_number(self: Pin<&mut Self>) { - let previous = *self.as_ref().number(); - self.set_number(previous + 1); - } + fn increment_number(self: Pin<&mut qobject::MyObject>); - /// Print a log message with the given string and number #[qinvokable] - pub fn say_hi(&self, string: &QString, number: i32) { - println!("Hi from Rust! String is '{string}' and number is {number}"); - } + fn say_hi(self: &qobject::MyObject, string: &QString, number: i32); } // ANCHOR_END: book_rustobj_impl } + +use core::pin::Pin; +use cxx_qt_lib::QString; + +// TODO: this will change to qobject::MyObject once +// https://github.com/KDAB/cxx-qt/issues/559 is done +impl ffi::MyObjectQt { + /// Increment the number Q_PROPERTY + pub fn increment_number(self: Pin<&mut Self>) { + let previous = *self.as_ref().number(); + self.set_number(previous + 1); + } + + /// Print a log message with the given string and number + pub fn say_hi(&self, string: &QString, number: i32) { + println!("Hi from Rust! String is '{string}' and number is {number}"); + } +} + // ANCHOR_END: book_cxx_qt_module diff --git a/tests/basic_cxx_qt/rust/src/data.rs b/tests/basic_cxx_qt/rust/src/data.rs index 73f2f4640..455107431 100644 --- a/tests/basic_cxx_qt/rust/src/data.rs +++ b/tests/basic_cxx_qt/rust/src/data.rs @@ -57,21 +57,33 @@ mod ffi { } } - impl qobject::MyData { + unsafe extern "RustQt" { #[qinvokable] - pub fn as_json_str(&self) -> QString { - let data_serde = DataSerde::from(self.rust()); - let data_string = serde_json::to_string(&data_serde).unwrap(); - QString::from(&data_string) - } + fn as_json_str(self: &qobject::MyData) -> QString; #[qinvokable] - pub fn grab_values(mut self: Pin<&mut Self>) { - let string = r#"{"number": 2, "string": "Goodbye!"}"#; - let data_serde: DataSerde = serde_json::from_str(string).unwrap(); - self.as_mut().set_number(data_serde.number); - self.as_mut().set_string(QString::from(&data_serde.string)); - } + fn grab_values(self: Pin<&mut qobject::MyData>); + } +} + +use core::pin::Pin; +use cxx_qt::CxxQtType; +use cxx_qt_lib::QString; + +// TODO: this will change to qobject::MyData once +// https://github.com/KDAB/cxx-qt/issues/559 is done +impl ffi::MyDataQt { + pub fn as_json_str(&self) -> QString { + let data_serde = DataSerde::from(self.rust()); + let data_string = serde_json::to_string(&data_serde).unwrap(); + QString::from(&data_string) + } + + pub fn grab_values(mut self: Pin<&mut Self>) { + let string = r#"{"number": 2, "string": "Goodbye!"}"#; + let data_serde: DataSerde = serde_json::from_str(string).unwrap(); + self.as_mut().set_number(data_serde.number); + self.as_mut().set_string(QString::from(&data_serde.string)); } } // ANCHOR_END: book_macro_code diff --git a/tests/basic_cxx_qt/rust/src/lib.rs b/tests/basic_cxx_qt/rust/src/lib.rs index 22afbb90b..b496079bf 100644 --- a/tests/basic_cxx_qt/rust/src/lib.rs +++ b/tests/basic_cxx_qt/rust/src/lib.rs @@ -24,7 +24,7 @@ mod ffi { #[qproperty] string: QString, - update_call_count: i32, + pub(crate) update_call_count: i32, } impl Default for MyObject { @@ -40,66 +40,85 @@ mod ffi { // Enabling threading on the qobject impl cxx_qt::Threading for qobject::MyObject {} - impl qobject::MyObject { + unsafe extern "RustQt" { #[qinvokable] - pub fn double_number_self(self: Pin<&mut Self>) { - let value = self.number() * 2; - self.set_number(value); - } + fn double_number_self(self: Pin<&mut qobject::MyObject>); #[qinvokable] - pub fn double_number(&self, number: i32) -> i32 { - number * 2 - } + fn double_number(self: &qobject::MyObject, number: i32) -> i32; #[qinvokable] - pub fn say_hi(&self, string: &QString, number: i32) { - println!("Hi from Rust! String is {string} and number is {number}"); - } + fn say_hi(self: &qobject::MyObject, string: &QString, number: i32); #[qinvokable] - pub fn queue_test(self: Pin<&mut Self>) { - let qt_thread = self.qt_thread(); - qt_thread - .queue(|ctx| { - *ctx.update_call_count_mut() += 1; - }) - .unwrap(); - } + fn queue_test(self: Pin<&mut qobject::MyObject>); #[qinvokable] - pub fn queue_test_multi_thread(self: Pin<&mut Self>) { - static N_THREADS: usize = 100; - static N_REQUESTS: std::sync::atomic::AtomicUsize = - std::sync::atomic::AtomicUsize::new(0); - - let mut handles = Vec::new(); - for _ in 0..N_THREADS { - let qt_thread = self.qt_thread(); - handles.push(std::thread::spawn(move || { - qt_thread - .queue(|ctx| { - *ctx.update_call_count_mut() += 1; - }) - .unwrap(); - N_REQUESTS.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - })); - } + fn queue_test_multi_thread(self: Pin<&mut qobject::MyObject>); - for h in handles { - h.join().unwrap(); - } + #[qinvokable] + fn fetch_update_call_count(self: &qobject::MyObject) -> i32; + } +} + +use core::pin::Pin; +use cxx_qt::CxxQtType; +use cxx_qt_lib::QString; + +// TODO: this will change to qobject::MyObject once +// https://github.com/KDAB/cxx-qt/issues/559 is done +impl ffi::MyObjectQt { + fn double_number_self(self: Pin<&mut Self>) { + let value = self.number() * 2; + self.set_number(value); + } - // Make sure we actually ran all the threads - assert_eq!( - N_REQUESTS.load(std::sync::atomic::Ordering::Relaxed), - N_THREADS - ); + fn double_number(&self, number: i32) -> i32 { + number * 2 + } + + fn say_hi(&self, string: &QString, number: i32) { + println!("Hi from Rust! String is {string} and number is {number}"); + } + + fn queue_test(self: Pin<&mut Self>) { + let qt_thread = self.qt_thread(); + qt_thread + .queue(|ctx| { + *ctx.update_call_count_mut() += 1; + }) + .unwrap(); + } + + fn queue_test_multi_thread(self: Pin<&mut Self>) { + static N_THREADS: usize = 100; + static N_REQUESTS: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); + + let mut handles = Vec::new(); + for _ in 0..N_THREADS { + let qt_thread = self.qt_thread(); + handles.push(std::thread::spawn(move || { + qt_thread + .queue(|ctx| { + *ctx.update_call_count_mut() += 1; + }) + .unwrap(); + N_REQUESTS.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + })); } - #[qinvokable] - pub fn fetch_update_call_count(&self) -> i32 { - self.rust().update_call_count + for h in handles { + h.join().unwrap(); } + + // Make sure we actually ran all the threads + assert_eq!( + N_REQUESTS.load(std::sync::atomic::Ordering::Relaxed), + N_THREADS + ); + } + + fn fetch_update_call_count(&self) -> i32 { + self.rust().update_call_count } } diff --git a/tests/basic_cxx_qt/rust/src/locking.rs b/tests/basic_cxx_qt/rust/src/locking.rs index 2ffa72bc8..9a4eadaf2 100644 --- a/tests/basic_cxx_qt/rust/src/locking.rs +++ b/tests/basic_cxx_qt/rust/src/locking.rs @@ -6,53 +6,68 @@ /// Two QObject that allow for testing that locking works #[cxx_qt::bridge(cxx_file_stem = "locking")] pub mod ffi { - use std::{ - sync::atomic::{AtomicU32, Ordering}, - thread, - time::Duration, - }; + use std::sync::atomic::AtomicU32; /// A QObject which has cxx_qt::Locking #[cxx_qt::qobject] #[derive(Default)] pub struct RustLockingEnabled { - counter: AtomicU32, + pub(crate) counter: AtomicU32, } - impl qobject::RustLockingEnabled { + unsafe extern "RustQt" { #[qinvokable] - pub fn get_counter(&self) -> u32 { - self.counter().load(Ordering::Acquire) - } + fn get_counter(self: &qobject::RustLockingEnabled) -> u32; #[qinvokable] - pub fn increment(self: Pin<&mut Self>) { - let counter = self.as_ref().get_counter(); - thread::sleep(Duration::from_millis(100)); - self.counter().store(counter + 1, Ordering::Release); - } + fn increment(self: Pin<&mut qobject::RustLockingEnabled>); } /// A QObject which has !cxx_qt::Locking #[cxx_qt::qobject] #[derive(Default)] pub struct RustLockingDisabled { - counter: AtomicU32, + pub(crate) counter: AtomicU32, } unsafe impl !cxx_qt::Locking for qobject::RustLockingDisabled {} - impl qobject::RustLockingDisabled { + unsafe extern "RustQt" { #[qinvokable] - pub fn get_counter(&self) -> u32 { - self.counter().load(Ordering::Acquire) - } + fn get_counter(self: &qobject::RustLockingDisabled) -> u32; #[qinvokable] - pub fn increment(self: Pin<&mut Self>) { - let counter = self.as_ref().get_counter(); - thread::sleep(Duration::from_millis(100)); - self.counter().store(counter + 1, Ordering::Release); - } + fn increment(self: Pin<&mut qobject::RustLockingDisabled>); + } +} + +use core::pin::Pin; +use std::{sync::atomic::Ordering, thread, time::Duration}; + +// TODO: this will change to qobject::RustLockingEnabled once +// https://github.com/KDAB/cxx-qt/issues/559 is done +impl ffi::RustLockingEnabledQt { + fn get_counter(&self) -> u32 { + self.counter().load(Ordering::Acquire) + } + + fn increment(self: Pin<&mut Self>) { + let counter = self.as_ref().get_counter(); + thread::sleep(Duration::from_millis(100)); + self.counter().store(counter + 1, Ordering::Release); + } +} + +// TODO: this will change to qobject::RustLockingDisabled once +// https://github.com/KDAB/cxx-qt/issues/559 is done +impl ffi::RustLockingDisabledQt { + fn get_counter(&self) -> u32 { + self.counter().load(Ordering::Acquire) + } + + fn increment(self: Pin<&mut Self>) { + let counter = self.as_ref().get_counter(); + thread::sleep(Duration::from_millis(100)); + self.counter().store(counter + 1, Ordering::Release); } }