Skip to content

Commit a0985a7

Browse files
ahayzen-kdabBe-ing
authored andcommitted
book: document implemeting custom types
1 parent 060f7cf commit a0985a7

File tree

9 files changed

+169
-3
lines changed

9 files changed

+169
-3
lines changed

book/src/concepts/types.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,20 @@ If they are opaque, references or pointers must be used.
5252

5353
For examples of how to wrap Qt objects, explore the [`cxx-qt-lib` source code](https://github.com/KDAB/cxx-qt/tree/main/crates/cxx-qt-lib).
5454

55+
### Using a Custom Type with Containers or QVariant
56+
57+
To use a custom type with containers find the trait that the container uses, eg for `QSet<T>` there is a `QSetElement` trait and for `QHash<K, V>` there is a `QHashPair` trait.
58+
59+
Implement the trait for your custom type and then you can use the containers as described above.
60+
61+
To use a custom type with `QVariant` implement the `QVariantValue` trait for your custom type, as seen below, then it can be used as normal.
62+
63+
```rust,ignore
64+
{{#include ../../../examples/qml_features/rust/types.rs:book_qvariantvalue_impl}}
65+
```
66+
67+
A full example of implementing a custom struct with `QVariant` is shown in the [qml_features types example](https://github.com/KDAB/cxx-qt/blob/main/examples/qml_features/rust/src/types.rs).
68+
5569
## Opaque Type Conversions
5670

5771
If your type can't be marked as trivial for CXX, but the Qt API needs to take the type by value (eg `T` rather than `const T&` or `::std::unique_ptr<T>`),

examples/qml_features/CMakeLists.txt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ target_link_libraries(${CRATE} INTERFACE
5151
)
5252

5353
add_executable(${APP_NAME}
54+
cpp/custom_object.h
55+
cpp/custom_object.cpp
5456
cpp/main.cpp
5557
cpp/qabstractlistmodelcxx.h
5658
qml/qml.qrc
@@ -80,7 +82,12 @@ copy_qml_test(signals)
8082
copy_qml_test(threading)
8183
copy_qml_test(types)
8284

83-
add_executable(${APP_NAME}_test "tests/main.cpp")
85+
add_executable(${APP_NAME}_test
86+
cpp/custom_object.h
87+
cpp/custom_object.cpp
88+
cpp/qabstractlistmodelcxx.h
89+
tests/main.cpp
90+
)
8491
target_include_directories(${APP_NAME}_test PRIVATE cpp)
8592
target_link_libraries(${APP_NAME}_test PRIVATE
8693
${CRATE}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// clang-format off
2+
// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
3+
// clang-format on
4+
// SPDX-FileContributor: Andrew Hayzen <[email protected]>
5+
//
6+
// SPDX-License-Identifier: MIT OR Apache-2.0
7+
#include "custom_object.h"
8+
9+
bool
10+
qvariantCanConvertCustomStruct(const QVariant& variant)
11+
{
12+
return variant.canConvert<CustomStruct>();
13+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// clang-format off
2+
// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
3+
// clang-format on
4+
// SPDX-FileContributor: Andrew Hayzen <[email protected]>
5+
//
6+
// SPDX-License-Identifier: MIT OR Apache-2.0
7+
#pragma once
8+
9+
#include <QObject>
10+
#include <QVariant>
11+
12+
struct CustomStruct
13+
{
14+
int value;
15+
};
16+
Q_DECLARE_METATYPE(CustomStruct);
17+
18+
bool
19+
qvariantCanConvertCustomStruct(const QVariant& variant);
20+
21+
class CustomObject : public QObject
22+
{
23+
Q_OBJECT
24+
25+
Q_PROPERTY(int value MEMBER m_value)
26+
public:
27+
CustomObject(QObject* parent = nullptr)
28+
: QObject(parent)
29+
, m_value(0)
30+
{
31+
}
32+
33+
Q_INVOKABLE CustomStruct asStruct() const { return CustomStruct{ m_value }; }
34+
35+
private:
36+
int m_value;
37+
};

examples/qml_features/cpp/main.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
#include "cxx-qt-gen/threading_website.cxxqt.h"
1919
#include "cxx-qt-gen/types.cxxqt.h"
2020

21+
#include "custom_object.h"
22+
2123
int
2224
main(int argc, char* argv[])
2325
{
@@ -36,6 +38,8 @@ main(int argc, char* argv[])
3638
},
3739
Qt::QueuedConnection);
3840

41+
qRegisterMetaType<CustomStruct>("CustomStruct");
42+
qmlRegisterType<CustomObject>("com.kdab.cxx_qt.demo", 1, 0, "CustomObject");
3943
qmlRegisterType<RustContainers>(
4044
"com.kdab.cxx_qt.demo", 1, 0, "RustContainers");
4145
qmlRegisterType<CustomBaseClass>(

examples/qml_features/qml/pages/TypesPage.qml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ Page {
2626
property var booleanVariant: types.boolean
2727
property var pointVariant: types.point
2828
property url url: types.url
29+
property CustomObject customObject: CustomObject {
30+
value: 0
31+
}
2932
readonly property var urlVariant: url
3033

3134
onClicked: {
@@ -40,12 +43,15 @@ Page {
4043
case 2:
4144
url = types.url == "https://kdab.com" ? "https://github.com/kdab/cxx-qt" : "https://kdab.com"
4245
return urlVariant;
46+
case 3:
47+
customObject.value += 1;
48+
return customObject.asStruct();
4349
default:
4450
return null;
4551
}
4652
})());
4753

48-
counter = (counter + 1) % 3;
54+
counter = (counter + 1) % 4;
4955
}
5056
}
5157

@@ -84,5 +90,12 @@ Page {
8490
text: qsTr("QUrl: %1").arg(types.url)
8591
wrapMode: Text.Wrap
8692
}
93+
94+
Label {
95+
Layout.fillWidth: true
96+
horizontalAlignment: Text.AlignHCenter
97+
text: qsTr("CustomValue: %1").arg(types.customValue)
98+
wrapMode: Text.Wrap
99+
}
87100
}
88101
}

examples/qml_features/rust/src/types.rs

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,32 @@
44
// SPDX-License-Identifier: MIT OR Apache-2.0
55

66
// ANCHOR: book_macro_code
7+
#[repr(C)]
8+
pub struct CustomStruct {
9+
value: i32,
10+
}
11+
12+
unsafe impl cxx::ExternType for CustomStruct {
13+
type Id = cxx::type_id!("CustomStruct");
14+
type Kind = cxx::kind::Trivial;
15+
}
16+
17+
// ANCHOR: book_qvariantvalue_impl
18+
impl cxx_qt_lib::QVariantValue for ffi::CustomStruct {
19+
fn can_convert(variant: &cxx_qt_lib::QVariant) -> bool {
20+
ffi::qvariant_can_convert_custom_type(variant)
21+
}
22+
23+
fn construct(value: &Self) -> cxx_qt_lib::QVariant {
24+
ffi::qvariant_construct_custom_type(value)
25+
}
26+
27+
fn value(variant: &cxx_qt_lib::QVariant) -> Self {
28+
ffi::qvariant_value_custom_type(variant)
29+
}
30+
}
31+
// ANCHOR_END: book_qvariantvalue_impl
32+
733
// ANCHOR: book_cxx_file_stem
834
#[cxx_qt::bridge(cxx_file_stem = "types")]
935
mod ffi {
@@ -17,7 +43,25 @@ mod ffi {
1743
type QVariant = cxx_qt_lib::QVariant;
1844
}
1945

20-
// TODO: should we show how to do custom types?
46+
unsafe extern "C++" {
47+
include!("custom_object.h");
48+
type CustomStruct = super::CustomStruct;
49+
50+
#[rust_name = "qvariant_can_convert_custom_type"]
51+
fn qvariantCanConvertCustomStruct(variant: &QVariant) -> bool;
52+
}
53+
54+
// We can reuse the templates from cxx-qt-lib for the construct and value
55+
#[namespace = "rust::cxxqtlib1::qvariant"]
56+
unsafe extern "C++" {
57+
include!("cxx-qt-lib/qvariant.h");
58+
59+
#[rust_name = "qvariant_construct_custom_type"]
60+
fn qvariantConstruct(value: &CustomStruct) -> QVariant;
61+
#[rust_name = "qvariant_value_custom_type"]
62+
fn qvariantValue(variant: &QVariant) -> CustomStruct;
63+
}
64+
2165
#[cxx_qt::qobject]
2266
pub struct Types {
2367
#[qproperty]
@@ -26,6 +70,8 @@ mod ffi {
2670
point: QPointF,
2771
#[qproperty]
2872
url: QUrl,
73+
#[qproperty]
74+
custom_value: i32,
2975
}
3076

3177
impl Default for Types {
@@ -34,6 +80,7 @@ mod ffi {
3480
boolean: false,
3581
point: QPointF::new(1.0, 2.0),
3682
url: QUrl::from("https://kdab.com"),
83+
custom_value: 0,
3784
}
3885
}
3986
}
@@ -47,6 +94,8 @@ mod ffi {
4794
self.set_point(point);
4895
} else if let Some(url) = variant.try_value::<QUrl>() {
4996
self.set_url(url);
97+
} else if let Some(custom) = variant.try_value::<CustomStruct>() {
98+
self.set_custom_value(custom.value);
5099
} else {
51100
println!("Unknown QVariant type to load from");
52101
}

examples/qml_features/tests/main.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,18 @@
1818
#include "cxx-qt-gen/threading_website.cxxqt.h"
1919
#include "cxx-qt-gen/types.cxxqt.h"
2020

21+
#include "custom_object.h"
22+
2123
class Setup : public QObject
2224
{
2325
Q_OBJECT
2426

2527
public:
2628
Setup()
2729
{
30+
qRegisterMetaType<CustomStruct>("CustomStruct");
31+
qmlRegisterType<CustomObject>("com.kdab.cxx_qt.demo", 1, 0, "CustomObject");
32+
2833
qmlRegisterType<CustomBaseClass>(
2934
"com.kdab.cxx_qt.demo", 1, 0, "CustomBaseClass");
3035
qmlRegisterType<FirstObject>("com.kdab.cxx_qt.demo", 1, 0, "FirstObject");

examples/qml_features/tests/tst_types.qml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ TestCase {
2222
}
2323
}
2424

25+
Component {
26+
id: componentCustomObject
27+
28+
CustomObject {
29+
30+
}
31+
}
32+
2533
Component {
2634
id: componentSpy
2735

@@ -86,4 +94,20 @@ TestCase {
8694
compare(obj.boolean, true);
8795
compare(spy.count, 1);
8896
}
97+
98+
function test_variant_custom_type() {
99+
const obj = createTemporaryObject(componentTypes, null, {});
100+
const customObject = createTemporaryObject(componentCustomObject, null, {
101+
value: 1,
102+
});
103+
const customValueSpy = createTemporaryObject(componentSpy, null, {
104+
signalName: "customValueChanged",
105+
target: obj,
106+
});
107+
108+
compare(obj.customValue, 0);
109+
obj.loadFromVariant(customObject.asStruct());
110+
compare(obj.customValue, 1);
111+
compare(customValueSpy.count, 1);
112+
}
89113
}

0 commit comments

Comments
 (0)