Skip to content

Commit

Permalink
cxx-qt-gen: add support for nested objects (pointer types)
Browse files Browse the repository at this point in the history
Closes KDAB#299
  • Loading branch information
ahayzen-kdab committed Dec 19, 2022
1 parent 52f593a commit 6afdd77
Show file tree
Hide file tree
Showing 11 changed files with 237 additions and 24 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Support for container types: `QSet<T>`, `QHash<K, V>`, `QVector<T>`
- Support for further types: `QModelIndex`, `QPersistentModelIndex`
- Support for nesting objects in properties, invokables, and signals with `*mut T`

### Fixed

Expand Down
32 changes: 9 additions & 23 deletions book/src/concepts/nested_objects.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 Qt objects can be nested as properties or parameters of each other.

A nested object is referred to by it's path relative to `crate`, the second last segment needs `cxx_qt_` as the start of the module name, and then `CppObj` as the last segment. Eg `crate::mymod::cxx_qt_secondary_object::CppObj` refers a `mymod.rs` which contains a module `secondary_object` with [CXX-Qt macros](../qobject/macro.md).
A nested object is referred to by using a pointer to it's QObject representation.

To use this as a property in another object write `secondary_object: crate::mymod::cxx_qt_secondary_object::CppObj` as the property.
For use as a parameter in an invokable write `secondary_object: &mut crate::mymod::cxx_qt_secondary_object::CppObj` as the parameter. Then the `secondary_object` parameter can be used via the normal [`CppObj`](../qobject/generated-qobject.md) methods.
The following example shows a nested object as a property and parameter.
First define a type with an extern block for your bridge, this should point to the `qobject::T` of the QObject and the `cxx_name` should match the QObject name.

```rust,ignore,noplayground
#include ../../../examples/qml_features/rust/src/nested.rs:book_macro_code
{{#include ../../../examples/qml_features/rust/src/nested_qobjects.rs:book_extern_block}}
```

Note that until nested objects are `UniquePtr<T>` on the Rust side we need to use `cxx_qt_` as a prefix in the last module path to reach the correct `CppObj`.
Note that nested objects cannot be used as return types yet ( [https://github.com/KDAB/cxx-qt/issues/66](https://github.com/KDAB/cxx-qt/issues/66) ).
This can then be used as a property, invokable parameter, or signal parameter by using `*mut T`. As seen in the example below which nests `InnerObject` into `OuterObject`.

Note that nested objects are ignored from (de)serialisation ( [https://github.com/KDAB/cxx-qt/issues/35](https://github.com/KDAB/cxx-qt/issues/35) ).
> Note that to reach mutable invokables and property setters of the nested object
> `*mut T` needs to be convered to `Pin<&mut T>`.
Note that nested objects cannot be used in signals ( [https://github.com/KDAB/cxx-qt/issues/73](https://github.com/KDAB/cxx-qt/issues/73) ).
Note that we may allow for `super::` to be used in the future ( [https://github.com/KDAB/cxx-qt/issues/44](https://github.com/KDAB/cxx-qt/issues/44) ).
TODO: once we have borrow_rust_obj() explain it's purpose of reaching the other objects RustObj [https://github.com/KDAB/cxx-qt/issues/30](https://github.com/KDAB/cxx-qt/issues/30) ).
-->
```rust,ignore,noplayground
{{#include ../../../examples/qml_features/rust/src/nested_qobjects.rs:book_macro_code}}
```
93 changes: 92 additions & 1 deletion crates/cxx-qt-gen/src/generator/cpp/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use std::collections::BTreeMap;
use syn::{
spanned::Spanned, Error, Expr, GenericArgument, Lit, PathArguments, PathSegment, Result,
ReturnType, Type, TypeArray, TypeBareFn, TypeReference, TypeSlice,
ReturnType, Type, TypeArray, TypeBareFn, TypePtr, TypeReference, TypeSlice,
};

/// A helper for describing a C++ type
Expand Down Expand Up @@ -111,6 +111,13 @@ fn to_cpp_string(ty: &Type, cxx_names_map: &BTreeMap<String, String>) -> Result<
Ok(ty_strings.join("::"))
}
}
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)?
)),
Type::Reference(TypeReference {
mutability, elem, ..
}) => {
Expand Down Expand Up @@ -342,6 +349,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<f64> });
Expand Down Expand Up @@ -369,6 +424,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<T*> 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 T*> const&"
);
}

#[test]
fn test_to_cpp_string_templated_unknown_ref_mut() {
let ty = tokens_to_syn(quote! { &mut UniquePtr<QColor> });
Expand All @@ -378,6 +451,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<T*>&"
);
}

#[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<const T*>&"
);
}

#[test]
fn test_to_cpp_string_mapped() {
let ty = tokens_to_syn(quote! { A });
Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/).
3 changes: 3 additions & 0 deletions examples/qml_features/cpp/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -53,6 +54,8 @@ main(int argc, char* argv[])
"com.kdab.cxx_qt.demo", 1, 0, "ThreadingWebsite");
// ANCHOR_END: book_namespace_register
qmlRegisterType<Types>("com.kdab.cxx_qt.demo", 1, 0, "Types");
qmlRegisterType<InnerObject>("com.kdab.cxx_qt.demo", 1, 0, "InnerObject");
qmlRegisterType<OuterObject>("com.kdab.cxx_qt.demo", 1, 0, "OuterObject");

engine.load(url);

Expand Down
4 changes: 4 additions & 0 deletions examples/qml_features/qml/main.qml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ ApplicationWindow {
name: "Multiple QObjects"
source: "qrc:/pages/MultipleQObjectsPage.qml"
}
ListElement {
name: "Nested QObjects"
source: "qrc:/pages/NestedQObjectsPage.qml"
}
}
}
}
Expand Down
57 changes: 57 additions & 0 deletions examples/qml_features/qml/pages/NestedQObjectsPage.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
// SPDX-FileContributor: Andrew Hayzen <andrew.hayzen@kdab.com>
//
// 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
}
}
1 change: 1 addition & 0 deletions examples/qml_features/qml/qml.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ SPDX-License-Identifier: MIT OR Apache-2.0
<file>pages/CustomBaseClassPage.qml</file>
<file>pages/InvokablesPage.qml</file>
<file>pages/MultipleQObjectsPage.qml</file>
<file>pages/NestedQObjectsPage.qml</file>
<file>pages/PropertiesPage.qml</file>
<file>pages/SerialisationPage.qml</file>
<file>pages/SignalsPage.qml</file>
Expand Down
1 change: 1 addition & 0 deletions examples/qml_features/rust/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
1 change: 1 addition & 0 deletions examples/qml_features/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod containers;
mod custom_base_class;
mod invokables;
mod multiple_qobjects;
mod nested_qobjects;
mod properties;
mod serialisation;
mod signals;
Expand Down
67 changes: 67 additions & 0 deletions examples/qml_features/rust/src/nested_qobjects.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
// SPDX-FileContributor: Andrew Hayzen <andrew.hayzen@kdab.com>
//
// 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

0 comments on commit 6afdd77

Please sign in to comment.