Skip to content

Commit 06b57f8

Browse files
committed
cxx-qt-gen: add support for choosing a custom parent class
This then allows you to create a QQuickItem such as a QQuickPaintedItem. Closes #474
1 parent ac33def commit 06b57f8

File tree

15 files changed

+175
-9
lines changed

15 files changed

+175
-9
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414

1515
## [Unreleased](https://github.com/KDAB/cxx-qt/compare/v0.5.1...HEAD)
1616

17+
### Added
18+
19+
- Support for choosing a custom parent class, this allows for deriving from `QQuickItem` classes
20+
1721
### Changed
1822

1923
- `QDateTime` API to use `current_date_time` rather than `current_date`

crates/cxx-qt-gen/src/generator/cpp/qobject.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ pub struct GeneratedCppQObject {
6565
pub namespace_internals: String,
6666
/// Base class of the QObject
6767
pub base_class: String,
68+
/// Parent class of the QObject
69+
pub parent_class: String,
6870
/// The blocks of the QObject
6971
pub blocks: GeneratedCppQObjectBlocks,
7072
}
@@ -86,6 +88,10 @@ impl GeneratedCppQObject {
8688
.base_class
8789
.clone()
8890
.unwrap_or_else(|| "QObject".to_string()),
91+
parent_class: qobject
92+
.parent_class
93+
.clone()
94+
.unwrap_or_else(|| "QObject".to_string()),
8995
blocks: GeneratedCppQObjectBlocks::from(qobject),
9096
};
9197

@@ -147,6 +153,7 @@ mod tests {
147153
assert_eq!(cpp.cxx_qt_thread_ident, "MyObjectCxxQtThread");
148154
assert_eq!(cpp.namespace_internals, "cxx_qt_my_object");
149155
assert_eq!(cpp.base_class, "QObject");
156+
assert_eq!(cpp.parent_class, "QObject");
150157
assert_eq!(cpp.blocks.metaobjects.len(), 0);
151158
}
152159

@@ -155,7 +162,7 @@ mod tests {
155162
let module: ItemMod = tokens_to_syn(quote! {
156163
#[cxx_qt::bridge(namespace = "cxx_qt")]
157164
mod ffi {
158-
#[cxx_qt::qobject(base = "QStringListModel")]
165+
#[cxx_qt::qobject(base = "QStringListModel", parent = "QQuickItem")]
159166
pub struct MyObject;
160167
}
161168
});
@@ -168,6 +175,7 @@ mod tests {
168175
.unwrap();
169176
assert_eq!(cpp.namespace_internals, "cxx_qt::cxx_qt_my_object");
170177
assert_eq!(cpp.base_class, "QStringListModel");
178+
assert_eq!(cpp.parent_class, "QQuickItem");
171179
assert_eq!(cpp.blocks.metaobjects.len(), 0);
172180
}
173181

crates/cxx-qt-gen/src/parser/qobject.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ pub struct QmlElementMetadata {
3636
pub struct ParsedQObject {
3737
/// The base class of the struct
3838
pub base_class: Option<String>,
39+
/// The parent class of the struct
40+
pub parent_class: Option<String>,
3941
/// QObject struct that stores the invokables for the QObject
4042
pub qobject_struct: ItemStruct,
4143
/// The namespace of the QObject. If one isn't specified for the QObject,
@@ -81,6 +83,11 @@ impl ParsedQObject {
8183
.get(&quote::format_ident!("base"))
8284
.map(|base| base.value());
8385

86+
// Find if there is any parent class
87+
let parent_class = attrs_map
88+
.get(&quote::format_ident!("parent"))
89+
.map(|parent| parent.value());
90+
8491
// Load the namespace, if it is empty then the ParsedCxxQtData will inject any global namespace
8592
let namespace = attrs_map
8693
.get(&quote::format_ident!("namespace"))
@@ -105,6 +112,7 @@ impl ParsedQObject {
105112

106113
Ok(Self {
107114
base_class,
115+
parent_class,
108116
qobject_struct,
109117
namespace,
110118
signals: None,
@@ -282,18 +290,20 @@ pub mod tests {
282290

283291
let qobject = ParsedQObject::from_struct(&qobject_struct, 0).unwrap();
284292
assert!(qobject.base_class.is_none());
293+
assert!(qobject.parent_class.is_none());
285294
assert!(qobject.qml_metadata.is_none());
286295
}
287296

288297
#[test]
289298
fn test_from_struct_base_class() {
290299
let qobject_struct: ItemStruct = tokens_to_syn(quote! {
291-
#[cxx_qt::qobject(base = "QStringListModel")]
300+
#[cxx_qt::qobject(base = "QStringListModel", parent = "QQuickItem")]
292301
pub struct MyObject;
293302
});
294303

295304
let qobject = ParsedQObject::from_struct(&qobject_struct, 0).unwrap();
296305
assert_eq!(qobject.base_class.as_ref().unwrap(), "QStringListModel");
306+
assert_eq!(qobject.parent_class.as_ref().unwrap(), "QQuickItem");
297307
}
298308

299309
#[test]

crates/cxx-qt-gen/src/writer/cpp/header.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ fn qobjects_header(generated: &GeneratedCppBlocks) -> Vec<String> {
7474
{metaobjects}
7575
7676
public:
77-
explicit {ident}(QObject* parent = nullptr);
77+
explicit {ident}({parent_class}* parent = nullptr);
7878
~{ident}();
7979
{rust_ident} const& unsafeRust() const;
8080
{rust_ident}& unsafeRustMut();
@@ -104,6 +104,7 @@ fn qobjects_header(generated: &GeneratedCppBlocks) -> Vec<String> {
104104
namespace_internals = qobject.namespace_internals,
105105
rust_ident = qobject.rust_ident,
106106
base_class = qobject.base_class,
107+
parent_class = qobject.parent_class,
107108
metaobjects = qobject.blocks.metaobjects.join("\n "),
108109
methods = create_block("public", &qobject.blocks.methods.iter().filter_map(pair_as_header).collect::<Vec<&str>>()),
109110
metatype = if generated.namespace.is_empty() {

crates/cxx-qt-gen/src/writer/cpp/mod.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ mod tests {
6060
cxx_qt_thread_ident: "MyObjectCxxQtThread".to_owned(),
6161
namespace_internals: "cxx_qt::my_object::cxx_qt_my_object".to_owned(),
6262
base_class: "QStringListModel".to_owned(),
63+
parent_class: "QObject".to_owned(),
6364
blocks: GeneratedCppQObjectBlocks {
6465
metaobjects: vec![
6566
"Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged)".to_owned(),
@@ -158,6 +159,7 @@ mod tests {
158159
cxx_qt_thread_ident: "FirstObjectCxxQtThread".to_owned(),
159160
namespace_internals: "cxx_qt::cxx_qt_first_object".to_owned(),
160161
base_class: "QStringListModel".to_owned(),
162+
parent_class: "QObject".to_owned(),
161163
blocks: GeneratedCppQObjectBlocks {
162164
metaobjects: vec![
163165
"Q_PROPERTY(int longPropertyNameThatWrapsInClangFormat READ count WRITE setCount NOTIFY countChanged)"
@@ -195,6 +197,7 @@ mod tests {
195197
cxx_qt_thread_ident: "SecondObjectCxxQtThread".to_owned(),
196198
namespace_internals: "cxx_qt::cxx_qt_second_object".to_owned(),
197199
base_class: "QStringListModel".to_owned(),
200+
parent_class: "QQuickItem".to_owned(),
198201
blocks: GeneratedCppQObjectBlocks {
199202
metaobjects: vec![
200203
"Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged)"
@@ -367,7 +370,7 @@ mod tests {
367370
Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged)
368371
369372
public:
370-
explicit SecondObject(QObject* parent = nullptr);
373+
explicit SecondObject(QQuickItem* parent = nullptr);
371374
~SecondObject();
372375
SecondObjectRust const& unsafeRust() const;
373376
SecondObjectRust& unsafeRustMut();
@@ -611,7 +614,7 @@ mod tests {
611614
612615
namespace cxx_qt {
613616
614-
SecondObject::SecondObject(QObject* parent)
617+
SecondObject::SecondObject(QQuickItem* parent)
615618
: QStringListModel(parent)
616619
, m_rustObj(cxx_qt::cxx_qt_second_object::createRs())
617620
, m_rustObjMutex(::std::make_shared<::std::recursive_mutex>())

crates/cxx-qt-gen/src/writer/cpp/source.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ fn qobjects_source(generated: &GeneratedCppBlocks) -> Vec<String> {
2424
formatdoc! { r#"
2525
{namespace_start}
2626
27-
{ident}::{ident}(QObject* parent)
27+
{ident}::{ident}({parent_class}* parent)
2828
: {base_class}(parent)
2929
, m_rustObj({namespace_internals}::createRs())
3030
, m_rustObjMutex(::std::make_shared<::std::recursive_mutex>())
@@ -73,6 +73,7 @@ fn qobjects_source(generated: &GeneratedCppBlocks) -> Vec<String> {
7373
namespace_end = namespace_end,
7474
namespace_internals = qobject.namespace_internals,
7575
base_class = qobject.base_class,
76+
parent_class = qobject.parent_class,
7677
rust_ident = qobject.rust_ident,
7778
methods = qobject.blocks.methods.iter().filter_map(pair_as_source).collect::<Vec<String>>().join("\n"),
7879
}

crates/cxx-qt-gen/test_inputs/passthrough_and_naming.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ pub mod ffi {
7979
include!(<QtCore/QStringListModel>);
8080
}
8181

82-
#[cxx_qt::qobject(base = "QStringListModel")]
82+
#[cxx_qt::qobject(base = "QStringListModel", parent = "QQuickItem")]
8383
pub struct MyObject {
8484
#[qproperty]
8585
property_name: i32,

crates/cxx-qt-gen/test_outputs/passthrough_and_naming.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace cxx_qt::multi_object {
44

5-
MyObject::MyObject(QObject* parent)
5+
MyObject::MyObject(QQuickItem* parent)
66
: QStringListModel(parent)
77
, m_rustObj(cxx_qt::multi_object::cxx_qt_my_object::createRs())
88
, m_rustObjMutex(::std::make_shared<::std::recursive_mutex>())

crates/cxx-qt-gen/test_outputs/passthrough_and_naming.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class MyObject : public QStringListModel
2828
setPropertyName NOTIFY propertyNameChanged)
2929

3030
public:
31-
explicit MyObject(QObject* parent = nullptr);
31+
explicit MyObject(QQuickItem* parent = nullptr);
3232
~MyObject();
3333
MyObjectRust const& unsafeRust() const;
3434
MyObjectRust& unsafeRustMut();

examples/qml_features/qml/main.qml

+4
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@ ApplicationWindow {
112112
name: "Singleton"
113113
source: "qrc:/pages/SingletonPage.qml"
114114
}
115+
ListElement {
116+
name: "Custom Parent Class"
117+
source: "qrc:/pages/CustomParentClassPage.qml"
118+
}
115119
}
116120
}
117121
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
2+
// SPDX-FileContributor: Andrew Hayzen <[email protected]>
3+
//
4+
// SPDX-License-Identifier: MIT OR Apache-2.0
5+
import QtQuick 2.12
6+
import QtQuick.Controls 2.12
7+
import QtQuick.Layouts 1.12
8+
9+
import com.kdab.cxx_qt.demo 1.0
10+
11+
Page {
12+
header: ToolBar {
13+
RowLayout {
14+
anchors.fill: parent
15+
16+
ToolButton {
17+
text: qsTr("Red")
18+
19+
onClicked: customPainter.color = "red"
20+
}
21+
22+
ToolButton {
23+
text: qsTr("Green")
24+
25+
onClicked: customPainter.color = "green"
26+
}
27+
28+
ToolButton {
29+
text: qsTr("Blue")
30+
31+
onClicked: customPainter.color = "blue"
32+
}
33+
34+
Item {
35+
Layout.fillWidth: true
36+
}
37+
}
38+
}
39+
40+
41+
ColumnLayout {
42+
anchors.left: parent.left
43+
anchors.right: parent.right
44+
anchors.verticalCenter: parent.verticalCenter
45+
46+
CustomParentClass {
47+
id: customPainter
48+
color: "red"
49+
Layout.alignment: Qt.AlignHCenter
50+
height: 200
51+
width: 200
52+
53+
// TODO: once we can connect to signals on the Rust side, this could be done there
54+
onColorChanged: update()
55+
}
56+
57+
Label {
58+
Layout.fillWidth: true
59+
horizontalAlignment: Text.AlignHCenter
60+
text: qsTr("In this demo the Rectangle is rendered in Rust by implementing a QQuickPaintedItem.")
61+
wrapMode: Text.Wrap
62+
}
63+
}
64+
}

examples/qml_features/qml/qml.qrc

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ SPDX-License-Identifier: MIT OR Apache-2.0
1111
<file>main.qml</file>
1212
<file>pages/ContainersPage.qml</file>
1313
<file>pages/CustomBaseClassPage.qml</file>
14+
<file>pages/CustomParentClassPage.qml</file>
1415
<file>pages/InvokablesPage.qml</file>
1516
<file>pages/MultipleQObjectsPage.qml</file>
1617
<file>pages/NestedQObjectsPage.qml</file>

examples/qml_features/rust/build.rs

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ fn main() {
1111
CxxQtBuilder::new()
1212
.file("src/containers.rs")
1313
.file("src/custom_base_class.rs")
14+
.file("src/custom_parent_class.rs")
1415
.file("src/invokables.rs")
1516
.file("src/multiple_qobjects.rs")
1617
.file("src/nested_qobjects.rs")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
2+
// SPDX-FileContributor: Andrew Hayzen <[email protected]>
3+
//
4+
// SPDX-License-Identifier: MIT OR Apache-2.0
5+
6+
#[cxx_qt::bridge(cxx_file_stem = "custom_parent_class")]
7+
mod ffi {
8+
unsafe extern "C++" {
9+
include!(<QtQuick/QQuickPaintedItem>);
10+
11+
type QColor = cxx_qt_lib::QColor;
12+
include!("cxx-qt-lib/qcolor.h");
13+
14+
type QRectF = cxx_qt_lib::QRectF;
15+
include!("cxx-qt-lib/qrectf.h");
16+
17+
type QSizeF = cxx_qt_lib::QSizeF;
18+
include!("cxx-qt-lib/qsizef.h");
19+
20+
// Define the API from QPainter that we need
21+
type QPainter;
22+
include!(<QtGui/QPainter>);
23+
24+
#[rust_name = "fill_rect"]
25+
fn fillRect(self: Pin<&mut QPainter>, rectangle: &QRectF, color: &QColor);
26+
}
27+
28+
#[cxx_qt::qobject(
29+
base = "QQuickPaintedItem",
30+
parent = "QQuickItem",
31+
qml_uri = "com.kdab.cxx_qt.demo",
32+
qml_version = "1.0"
33+
)]
34+
#[derive(Default)]
35+
pub struct CustomParentClass {
36+
#[qproperty]
37+
color: QColor,
38+
}
39+
40+
// Define that we need to inherit size() from the base class
41+
#[cxx_qt::inherit]
42+
unsafe extern "C++" {
43+
fn size(self: &qobject::CustomParentClass) -> QSizeF;
44+
}
45+
46+
impl qobject::CustomParentClass {
47+
#[qinvokable(cxx_override)]
48+
pub fn paint(self: Pin<&mut Self>, painter: *mut QPainter) {
49+
// We need to convert the *mut QPainter to a Pin<&mut QPainter> so that we can reach the methods
50+
if let Some(painter) = unsafe { painter.as_mut() } {
51+
let mut pinned_painter = unsafe { Pin::new_unchecked(painter) };
52+
53+
// Now pinned painter can be used as normal
54+
// to render a rectangle with two colours
55+
let size = self.as_ref().size();
56+
pinned_painter.as_mut().fill_rect(
57+
&QRectF::new(0.0, 0.0, size.width() / 2.0, size.height()),
58+
self.as_ref().color(),
59+
);
60+
let darker_color = self.as_ref().color().darker(150);
61+
pinned_painter.as_mut().fill_rect(
62+
&QRectF::new(size.width() / 2.0, 0.0, size.width() / 2.0, size.height()),
63+
&darker_color,
64+
);
65+
}
66+
}
67+
}
68+
}

examples/qml_features/rust/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
mod containers;
88
mod custom_base_class;
9+
mod custom_parent_class;
910
mod invokables;
1011
mod multiple_qobjects;
1112
mod nested_qobjects;

0 commit comments

Comments
 (0)