From 07512285580c79d6edf74c143cd02236d1584a29 Mon Sep 17 00:00:00 2001 From: Andrew Hayzen Date: Mon, 28 Nov 2022 18:46:54 +0000 Subject: [PATCH] cxx-qt-gen: add support for nested objects (pointer types) Closes #299 --- CHANGELOG.md | 1 + book/src/concepts/nested_objects.md | 32 ++----- crates/cxx-qt-gen/src/generator/cpp/types.rs | 93 ++++++++++++++++++- examples/README.md | 1 + examples/qml_features/cpp/main.cpp | 3 + examples/qml_features/qml/main.qml | 4 + .../qml/pages/NestedQObjectsPage.qml | 57 ++++++++++++ examples/qml_features/qml/qml.qrc | 1 + examples/qml_features/rust/build.rs | 1 + examples/qml_features/rust/src/lib.rs | 1 + .../qml_features/rust/src/nested_qobjects.rs | 67 +++++++++++++ 11 files changed, 237 insertions(+), 24 deletions(-) create mode 100644 examples/qml_features/qml/pages/NestedQObjectsPage.qml create mode 100644 examples/qml_features/rust/src/nested_qobjects.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index f6628bcdc..dad43d786 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Support for container types: `QSet`, `QHash` - Support for further types: `QModelIndex` +- Support for nesting objects in properties, invokables, and signals with `*mut T` ### Fixed diff --git a/book/src/concepts/nested_objects.md b/book/src/concepts/nested_objects.md index 076ac97c3..fc7d42d88 100644 --- a/book/src/concepts/nested_objects.md +++ b/book/src/concepts/nested_objects.md @@ -7,35 +7,21 @@ SPDX-License-Identifier: MIT OR Apache-2.0 # Nested Objects -During the transition to CXX-Qt 0.4, Nested object support is temporarily removed. - -We are currently reworking the way nested objects work. -They will be back, and better than ever 😃. - - +```rust,ignore,noplayground +{{#include ../../../examples/qml_features/rust/src/nested_qobjects.rs:book_macro_code}} +``` diff --git a/crates/cxx-qt-gen/src/generator/cpp/types.rs b/crates/cxx-qt-gen/src/generator/cpp/types.rs index 63eac1d82..e0fa8d9cc 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/types.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/types.rs @@ -5,7 +5,7 @@ use std::collections::BTreeMap; use syn::{ - spanned::Spanned, Error, GenericArgument, PathArguments, PathSegment, Result, Type, + spanned::Spanned, Error, GenericArgument, PathArguments, PathSegment, Result, Type, TypePtr, TypeReference, }; @@ -76,6 +76,13 @@ fn to_cpp_string(ty: &Type, cxx_names_map: &BTreeMap) -> Result< is_const = if mutability.is_some() { "" } else { " const" }, ty = to_cpp_string(elem, cxx_names_map)? )), + Type::Ptr(TypePtr { + const_token, elem, .. + }) => Ok(format!( + "{is_const}{ty}*", + is_const = if const_token.is_some() { "const " } else { "" }, + ty = to_cpp_string(elem, cxx_names_map)? + )), _others => Err(Error::new( ty.span(), format!("Unsupported type, needs to be a TypePath: {:?}", _others), @@ -284,6 +291,54 @@ mod tests { ); } + #[test] + fn test_to_cpp_string_ref_const_ptr_mut_one_part() { + let ty = tokens_to_syn(quote! { &*mut T }); + assert_eq!( + to_cpp_string(&ty, &cxx_names_map_default()).unwrap(), + "T* const&" + ); + } + + #[test] + fn test_to_cpp_string_ref_const_ptr_const_one_part() { + let ty = tokens_to_syn(quote! { &*const T }); + assert_eq!( + to_cpp_string(&ty, &cxx_names_map_default()).unwrap(), + "const T* const&" + ); + } + + #[test] + fn test_to_cpp_string_ref_mut_ptr_mut_one_part() { + let ty = tokens_to_syn(quote! { &mut *mut T }); + assert_eq!(to_cpp_string(&ty, &cxx_names_map_default()).unwrap(), "T*&"); + } + + #[test] + fn test_to_cpp_string_ref_mut_ptr_const_one_part() { + let ty = tokens_to_syn(quote! { &mut *const T }); + assert_eq!( + to_cpp_string(&ty, &cxx_names_map_default()).unwrap(), + "const T*&" + ); + } + + #[test] + fn test_to_cpp_string_ptr_mut_one_part() { + let ty = tokens_to_syn(quote! { *mut T }); + assert_eq!(to_cpp_string(&ty, &cxx_names_map_default()).unwrap(), "T*"); + } + + #[test] + fn test_to_cpp_string_ptr_const_one_part() { + let ty = tokens_to_syn(quote! { *const T }); + assert_eq!( + to_cpp_string(&ty, &cxx_names_map_default()).unwrap(), + "const T*" + ); + } + #[test] fn test_to_cpp_string_templated_built_in() { let ty = tokens_to_syn(quote! { Vec }); @@ -311,6 +366,24 @@ mod tests { ); } + #[test] + fn test_to_cpp_string_templated_built_in_ptr_mut() { + let ty = tokens_to_syn(quote! { &Vec<*mut T> }); + assert_eq!( + to_cpp_string(&ty, &cxx_names_map_default()).unwrap(), + "::rust::Vec const&" + ); + } + + #[test] + fn test_to_cpp_string_templated_built_in_ptr_const() { + let ty = tokens_to_syn(quote! { &Vec<*const T> }); + assert_eq!( + to_cpp_string(&ty, &cxx_names_map_default()).unwrap(), + "::rust::Vec const&" + ); + } + #[test] fn test_to_cpp_string_templated_unknown_ref_mut() { let ty = tokens_to_syn(quote! { &mut UniquePtr }); @@ -320,6 +393,24 @@ mod tests { ); } + #[test] + fn test_to_cpp_string_templated_unknown_ptr_mut() { + let ty = tokens_to_syn(quote! { &mut UniquePtr<*mut T> }); + assert_eq!( + to_cpp_string(&ty, &cxx_names_map_default()).unwrap(), + "::std::unique_ptr&" + ); + } + + #[test] + fn test_to_cpp_string_templated_unknown_ptr_const() { + let ty = tokens_to_syn(quote! { &mut UniquePtr<*const T> }); + assert_eq!( + to_cpp_string(&ty, &cxx_names_map_default()).unwrap(), + "::std::unique_ptr&" + ); + } + #[test] fn test_to_cpp_string_mapped() { let ty = tokens_to_syn(quote! { A }); diff --git a/examples/README.md b/examples/README.md index 221fb7cb2..98085a880 100644 --- a/examples/README.md +++ b/examples/README.md @@ -19,6 +19,7 @@ Then we have multiple other examples available inside the projects * [How to (de)serialise QObjects](./qml_features/rust/src/serialisation.rs) * [Using Qt types such as QVariant](./qml_features/rust/src/types.rs) * [Defining multiple QObjects in a single bridge](./qml_features/rust/src/multiple_qobjects.rs) + * [Nested objects](./qml_features/rust/src/nested_qobjects.rs) * [Exposing the Rust objects via a QQmlExtensionPlugin](./qml_extension_plugin/plugin/) For documentation on how to use these features please visit our Book [https://kdab.github.io/cxx-qt/book/](https://kdab.github.io/cxx-qt/book/). diff --git a/examples/qml_features/cpp/main.cpp b/examples/qml_features/cpp/main.cpp index aa6e484e7..b35240ccc 100644 --- a/examples/qml_features/cpp/main.cpp +++ b/examples/qml_features/cpp/main.cpp @@ -10,6 +10,7 @@ #include "cxx-qt-gen/custom_base_class.cxxqt.h" #include "cxx-qt-gen/multiple_qobjects.cxxqt.h" +#include "cxx-qt-gen/nested_qobjects.cxxqt.h" #include "cxx-qt-gen/rust_containers.cxxqt.h" #include "cxx-qt-gen/rust_invokables.cxxqt.h" #include "cxx-qt-gen/rust_properties.cxxqt.h" @@ -53,6 +54,8 @@ main(int argc, char* argv[]) "com.kdab.cxx_qt.demo", 1, 0, "ThreadingWebsite"); // ANCHOR_END: book_namespace_register qmlRegisterType("com.kdab.cxx_qt.demo", 1, 0, "Types"); + qmlRegisterType("com.kdab.cxx_qt.demo", 1, 0, "InnerObject"); + qmlRegisterType("com.kdab.cxx_qt.demo", 1, 0, "OuterObject"); engine.load(url); diff --git a/examples/qml_features/qml/main.qml b/examples/qml_features/qml/main.qml index 016d635fa..ccc30b137 100644 --- a/examples/qml_features/qml/main.qml +++ b/examples/qml_features/qml/main.qml @@ -103,6 +103,10 @@ ApplicationWindow { name: "Multiple QObjects" source: "qrc:/pages/MultipleQObjectsPage.qml" } + ListElement { + name: "Nested QObjects" + source: "qrc:/pages/NestedQObjectsPage.qml" + } } } } diff --git a/examples/qml_features/qml/pages/NestedQObjectsPage.qml b/examples/qml_features/qml/pages/NestedQObjectsPage.qml new file mode 100644 index 000000000..ec0fa90cf --- /dev/null +++ b/examples/qml_features/qml/pages/NestedQObjectsPage.qml @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Andrew Hayzen +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 + +import com.kdab.cxx_qt.demo 1.0 + +Page { + header: ToolBar { + RowLayout { + anchors.fill: parent + + ToolButton { + text: qsTr("Increment") + + onClicked: outerObject.inner.counter += 1 + } + + + ToolButton { + text: qsTr("Reset") + + onClicked: outerObject.reset() + } + + ToolButton { + text: qsTr("Print") + + onClicked: outerObject.invokable(innerObject) + } + + Item { + Layout.fillWidth: true + } + } + } + + InnerObject { + id: innerObject + counter: 10 + } + + OuterObject { + id: outerObject + inner: innerObject + + onCalled: (inner) => console.warn("Signal called, inner value: ", inner.counter) + } + + Label { + anchors.centerIn: parent + text: innerObject.counter + } +} diff --git a/examples/qml_features/qml/qml.qrc b/examples/qml_features/qml/qml.qrc index 2f977f91b..a2cd70c45 100644 --- a/examples/qml_features/qml/qml.qrc +++ b/examples/qml_features/qml/qml.qrc @@ -13,6 +13,7 @@ SPDX-License-Identifier: MIT OR Apache-2.0 pages/CustomBaseClassPage.qml pages/InvokablesPage.qml pages/MultipleQObjectsPage.qml + pages/NestedQObjectsPage.qml pages/PropertiesPage.qml pages/SerialisationPage.qml pages/SignalsPage.qml diff --git a/examples/qml_features/rust/build.rs b/examples/qml_features/rust/build.rs index b6287f95d..d782af462 100644 --- a/examples/qml_features/rust/build.rs +++ b/examples/qml_features/rust/build.rs @@ -16,6 +16,7 @@ fn main() { .file("src/custom_base_class.rs") .file("src/invokables.rs") .file("src/multiple_qobjects.rs") + .file("src/nested_qobjects.rs") .file("src/serialisation.rs") .file("src/signals.rs") .file("src/properties.rs") diff --git a/examples/qml_features/rust/src/lib.rs b/examples/qml_features/rust/src/lib.rs index dbcfd4024..139122a94 100644 --- a/examples/qml_features/rust/src/lib.rs +++ b/examples/qml_features/rust/src/lib.rs @@ -8,6 +8,7 @@ mod containers; mod custom_base_class; mod invokables; mod multiple_qobjects; +mod nested_qobjects; mod properties; mod serialisation; mod signals; diff --git a/examples/qml_features/rust/src/nested_qobjects.rs b/examples/qml_features/rust/src/nested_qobjects.rs new file mode 100644 index 000000000..fc157bb24 --- /dev/null +++ b/examples/qml_features/rust/src/nested_qobjects.rs @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Andrew Hayzen +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +// ANCHOR: book_macro_code +#[cxx_qt::bridge(cxx_file_stem = "nested_qobjects")] +mod ffi { + // ANCHOR: book_extern_block + unsafe extern "C++" { + #[cxx_name = "InnerObject"] + type InnerObjectPtr = super::qobject::InnerObject; + } + // ANCHOR_END: book_extern_block + + #[cxx_qt::qobject] + #[derive(Default)] + pub struct InnerObject { + #[qproperty] + counter: i32, + } + + #[cxx_qt::qobject] + pub struct OuterObject { + #[qproperty] + inner: *mut InnerObjectPtr, + } + + impl Default for OuterObject { + fn default() -> Self { + Self { + inner: std::ptr::null_mut(), + } + } + } + + #[cxx_qt::qsignals(OuterObject)] + pub enum OuterSignals { + Called { inner: *mut InnerObjectPtr }, + } + + impl qobject::OuterObject { + #[qinvokable] + pub fn invokable(self: Pin<&mut Self>, inner: *mut InnerObjectPtr) { + if let Some(inner) = unsafe { inner.as_ref() } { + println!("Inner object: {}", inner.counter()); + } + + self.emit(OuterSignals::Called { inner }); + } + + #[qinvokable] + pub 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(); + self.emit(OuterSignals::Called { inner }); + } + } +} +// ANCHOR_END: book_macro_code