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/naming/invokable.rs b/crates/cxx-qt-gen/src/generator/naming/invokable.rs index 69070a07d..369527ad6 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,10 +50,8 @@ mod tests { #[test] fn test_from_impl_method() { - let item: ImplItemFn = parse_quote! { - fn my_invokable() { - - } + let item: ForeignItemFn = parse_quote! { + fn my_invokable(); }; let parsed = ParsedQInvokable { method: item, diff --git a/crates/cxx-qt-gen/src/generator/rust/invokable.rs b/crates/cxx-qt-gen/src/generator/rust/invokable.rs index a02756398..e5ee3a24a 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 - } - }, ], }; diff --git a/crates/cxx-qt-gen/src/generator/rust/signals.rs b/crates/cxx-qt-gen/src/generator/rust/signals.rs index fb7adadd9..098b3d1d4 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) } diff --git a/crates/cxx-qt-gen/src/parser/cxxqtdata.rs b/crates/cxx-qt-gen/src/parser/cxxqtdata.rs index 96ddd26ff..7c9dbb42e 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)); 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..fa6a71425 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_ 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..fb79990d0 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; 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/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..e85fe3a00 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, QVector, QVariant}; + +// 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..cbcb811b4 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::{QString, QUrl, ConnectionType}; + +// 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); } }