Skip to content

Commit

Permalink
cxx-qt-gen: add support for choosing a custom parent class
Browse files Browse the repository at this point in the history
This then allows you to create a QQuickItem such as a QQuickPaintedItem.

Closes #474
  • Loading branch information
ahayzen-kdab committed Apr 5, 2023
1 parent ac33def commit 06b57f8
Show file tree
Hide file tree
Showing 15 changed files with 175 additions and 9 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

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

### Added

- Support for choosing a custom parent class, this allows for deriving from `QQuickItem` classes

### Changed

- `QDateTime` API to use `current_date_time` rather than `current_date`
Expand Down
10 changes: 9 additions & 1 deletion crates/cxx-qt-gen/src/generator/cpp/qobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ pub struct GeneratedCppQObject {
pub namespace_internals: String,
/// Base class of the QObject
pub base_class: String,
/// Parent class of the QObject
pub parent_class: String,
/// The blocks of the QObject
pub blocks: GeneratedCppQObjectBlocks,
}
Expand All @@ -86,6 +88,10 @@ impl GeneratedCppQObject {
.base_class
.clone()
.unwrap_or_else(|| "QObject".to_string()),
parent_class: qobject
.parent_class
.clone()
.unwrap_or_else(|| "QObject".to_string()),
blocks: GeneratedCppQObjectBlocks::from(qobject),
};

Expand Down Expand Up @@ -147,6 +153,7 @@ mod tests {
assert_eq!(cpp.cxx_qt_thread_ident, "MyObjectCxxQtThread");
assert_eq!(cpp.namespace_internals, "cxx_qt_my_object");
assert_eq!(cpp.base_class, "QObject");
assert_eq!(cpp.parent_class, "QObject");
assert_eq!(cpp.blocks.metaobjects.len(), 0);
}

Expand All @@ -155,7 +162,7 @@ mod tests {
let module: ItemMod = tokens_to_syn(quote! {
#[cxx_qt::bridge(namespace = "cxx_qt")]
mod ffi {
#[cxx_qt::qobject(base = "QStringListModel")]
#[cxx_qt::qobject(base = "QStringListModel", parent = "QQuickItem")]
pub struct MyObject;
}
});
Expand All @@ -168,6 +175,7 @@ mod tests {
.unwrap();
assert_eq!(cpp.namespace_internals, "cxx_qt::cxx_qt_my_object");
assert_eq!(cpp.base_class, "QStringListModel");
assert_eq!(cpp.parent_class, "QQuickItem");
assert_eq!(cpp.blocks.metaobjects.len(), 0);
}

Expand Down
12 changes: 11 additions & 1 deletion crates/cxx-qt-gen/src/parser/qobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ pub struct QmlElementMetadata {
pub struct ParsedQObject {
/// The base class of the struct
pub base_class: Option<String>,
/// The parent class of the struct
pub parent_class: Option<String>,
/// QObject struct that stores the invokables for the QObject
pub qobject_struct: ItemStruct,
/// The namespace of the QObject. If one isn't specified for the QObject,
Expand Down Expand Up @@ -81,6 +83,11 @@ impl ParsedQObject {
.get(&quote::format_ident!("base"))
.map(|base| base.value());

// Find if there is any parent class
let parent_class = attrs_map
.get(&quote::format_ident!("parent"))
.map(|parent| parent.value());

// Load the namespace, if it is empty then the ParsedCxxQtData will inject any global namespace
let namespace = attrs_map
.get(&quote::format_ident!("namespace"))
Expand All @@ -105,6 +112,7 @@ impl ParsedQObject {

Ok(Self {
base_class,
parent_class,
qobject_struct,
namespace,
signals: None,
Expand Down Expand Up @@ -282,18 +290,20 @@ pub mod tests {

let qobject = ParsedQObject::from_struct(&qobject_struct, 0).unwrap();
assert!(qobject.base_class.is_none());
assert!(qobject.parent_class.is_none());
assert!(qobject.qml_metadata.is_none());
}

#[test]
fn test_from_struct_base_class() {
let qobject_struct: ItemStruct = tokens_to_syn(quote! {
#[cxx_qt::qobject(base = "QStringListModel")]
#[cxx_qt::qobject(base = "QStringListModel", parent = "QQuickItem")]
pub struct MyObject;
});

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

#[test]
Expand Down
3 changes: 2 additions & 1 deletion crates/cxx-qt-gen/src/writer/cpp/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ fn qobjects_header(generated: &GeneratedCppBlocks) -> Vec<String> {
{metaobjects}
public:
explicit {ident}(QObject* parent = nullptr);
explicit {ident}({parent_class}* parent = nullptr);
~{ident}();
{rust_ident} const& unsafeRust() const;
{rust_ident}& unsafeRustMut();
Expand Down Expand Up @@ -104,6 +104,7 @@ fn qobjects_header(generated: &GeneratedCppBlocks) -> Vec<String> {
namespace_internals = qobject.namespace_internals,
rust_ident = qobject.rust_ident,
base_class = qobject.base_class,
parent_class = qobject.parent_class,
metaobjects = qobject.blocks.metaobjects.join("\n "),
methods = create_block("public", &qobject.blocks.methods.iter().filter_map(pair_as_header).collect::<Vec<&str>>()),
metatype = if generated.namespace.is_empty() {
Expand Down
7 changes: 5 additions & 2 deletions crates/cxx-qt-gen/src/writer/cpp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ mod tests {
cxx_qt_thread_ident: "MyObjectCxxQtThread".to_owned(),
namespace_internals: "cxx_qt::my_object::cxx_qt_my_object".to_owned(),
base_class: "QStringListModel".to_owned(),
parent_class: "QObject".to_owned(),
blocks: GeneratedCppQObjectBlocks {
metaobjects: vec![
"Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged)".to_owned(),
Expand Down Expand Up @@ -158,6 +159,7 @@ mod tests {
cxx_qt_thread_ident: "FirstObjectCxxQtThread".to_owned(),
namespace_internals: "cxx_qt::cxx_qt_first_object".to_owned(),
base_class: "QStringListModel".to_owned(),
parent_class: "QObject".to_owned(),
blocks: GeneratedCppQObjectBlocks {
metaobjects: vec![
"Q_PROPERTY(int longPropertyNameThatWrapsInClangFormat READ count WRITE setCount NOTIFY countChanged)"
Expand Down Expand Up @@ -195,6 +197,7 @@ mod tests {
cxx_qt_thread_ident: "SecondObjectCxxQtThread".to_owned(),
namespace_internals: "cxx_qt::cxx_qt_second_object".to_owned(),
base_class: "QStringListModel".to_owned(),
parent_class: "QQuickItem".to_owned(),
blocks: GeneratedCppQObjectBlocks {
metaobjects: vec![
"Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged)"
Expand Down Expand Up @@ -367,7 +370,7 @@ mod tests {
Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged)
public:
explicit SecondObject(QObject* parent = nullptr);
explicit SecondObject(QQuickItem* parent = nullptr);
~SecondObject();
SecondObjectRust const& unsafeRust() const;
SecondObjectRust& unsafeRustMut();
Expand Down Expand Up @@ -611,7 +614,7 @@ mod tests {
namespace cxx_qt {
SecondObject::SecondObject(QObject* parent)
SecondObject::SecondObject(QQuickItem* parent)
: QStringListModel(parent)
, m_rustObj(cxx_qt::cxx_qt_second_object::createRs())
, m_rustObjMutex(::std::make_shared<::std::recursive_mutex>())
Expand Down
3 changes: 2 additions & 1 deletion crates/cxx-qt-gen/src/writer/cpp/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ fn qobjects_source(generated: &GeneratedCppBlocks) -> Vec<String> {
formatdoc! { r#"
{namespace_start}
{ident}::{ident}(QObject* parent)
{ident}::{ident}({parent_class}* parent)
: {base_class}(parent)
, m_rustObj({namespace_internals}::createRs())
, m_rustObjMutex(::std::make_shared<::std::recursive_mutex>())
Expand Down Expand Up @@ -73,6 +73,7 @@ fn qobjects_source(generated: &GeneratedCppBlocks) -> Vec<String> {
namespace_end = namespace_end,
namespace_internals = qobject.namespace_internals,
base_class = qobject.base_class,
parent_class = qobject.parent_class,
rust_ident = qobject.rust_ident,
methods = qobject.blocks.methods.iter().filter_map(pair_as_source).collect::<Vec<String>>().join("\n"),
}
Expand Down
2 changes: 1 addition & 1 deletion crates/cxx-qt-gen/test_inputs/passthrough_and_naming.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ pub mod ffi {
include!(<QtCore/QStringListModel>);
}

#[cxx_qt::qobject(base = "QStringListModel")]
#[cxx_qt::qobject(base = "QStringListModel", parent = "QQuickItem")]
pub struct MyObject {
#[qproperty]
property_name: i32,
Expand Down
2 changes: 1 addition & 1 deletion crates/cxx-qt-gen/test_outputs/passthrough_and_naming.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace cxx_qt::multi_object {

MyObject::MyObject(QObject* parent)
MyObject::MyObject(QQuickItem* parent)
: QStringListModel(parent)
, m_rustObj(cxx_qt::multi_object::cxx_qt_my_object::createRs())
, m_rustObjMutex(::std::make_shared<::std::recursive_mutex>())
Expand Down
2 changes: 1 addition & 1 deletion crates/cxx-qt-gen/test_outputs/passthrough_and_naming.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class MyObject : public QStringListModel
setPropertyName NOTIFY propertyNameChanged)

public:
explicit MyObject(QObject* parent = nullptr);
explicit MyObject(QQuickItem* parent = nullptr);
~MyObject();
MyObjectRust const& unsafeRust() const;
MyObjectRust& unsafeRustMut();
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 @@ -112,6 +112,10 @@ ApplicationWindow {
name: "Singleton"
source: "qrc:/pages/SingletonPage.qml"
}
ListElement {
name: "Custom Parent Class"
source: "qrc:/pages/CustomParentClassPage.qml"
}
}
}
}
Expand Down
64 changes: 64 additions & 0 deletions examples/qml_features/qml/pages/CustomParentClassPage.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
// SPDX-FileContributor: Andrew Hayzen <[email protected]>
//
// 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("Red")

onClicked: customPainter.color = "red"
}

ToolButton {
text: qsTr("Green")

onClicked: customPainter.color = "green"
}

ToolButton {
text: qsTr("Blue")

onClicked: customPainter.color = "blue"
}

Item {
Layout.fillWidth: true
}
}
}


ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter

CustomParentClass {
id: customPainter
color: "red"
Layout.alignment: Qt.AlignHCenter
height: 200
width: 200

// TODO: once we can connect to signals on the Rust side, this could be done there
onColorChanged: update()
}

Label {
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
text: qsTr("In this demo the Rectangle is rendered in Rust by implementing a QQuickPaintedItem.")
wrapMode: Text.Wrap
}
}
}
1 change: 1 addition & 0 deletions examples/qml_features/qml/qml.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ SPDX-License-Identifier: MIT OR Apache-2.0
<file>main.qml</file>
<file>pages/ContainersPage.qml</file>
<file>pages/CustomBaseClassPage.qml</file>
<file>pages/CustomParentClassPage.qml</file>
<file>pages/InvokablesPage.qml</file>
<file>pages/MultipleQObjectsPage.qml</file>
<file>pages/NestedQObjectsPage.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 @@ -11,6 +11,7 @@ fn main() {
CxxQtBuilder::new()
.file("src/containers.rs")
.file("src/custom_base_class.rs")
.file("src/custom_parent_class.rs")
.file("src/invokables.rs")
.file("src/multiple_qobjects.rs")
.file("src/nested_qobjects.rs")
Expand Down
68 changes: 68 additions & 0 deletions examples/qml_features/rust/src/custom_parent_class.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
// SPDX-FileContributor: Andrew Hayzen <[email protected]>
//
// SPDX-License-Identifier: MIT OR Apache-2.0

#[cxx_qt::bridge(cxx_file_stem = "custom_parent_class")]
mod ffi {
unsafe extern "C++" {
include!(<QtQuick/QQuickPaintedItem>);

type QColor = cxx_qt_lib::QColor;
include!("cxx-qt-lib/qcolor.h");

type QRectF = cxx_qt_lib::QRectF;
include!("cxx-qt-lib/qrectf.h");

type QSizeF = cxx_qt_lib::QSizeF;
include!("cxx-qt-lib/qsizef.h");

// Define the API from QPainter that we need
type QPainter;
include!(<QtGui/QPainter>);

#[rust_name = "fill_rect"]
fn fillRect(self: Pin<&mut QPainter>, rectangle: &QRectF, color: &QColor);
}

#[cxx_qt::qobject(
base = "QQuickPaintedItem",
parent = "QQuickItem",
qml_uri = "com.kdab.cxx_qt.demo",
qml_version = "1.0"
)]
#[derive(Default)]
pub struct CustomParentClass {
#[qproperty]
color: QColor,
}

// Define that we need to inherit size() from the base class
#[cxx_qt::inherit]
unsafe extern "C++" {
fn size(self: &qobject::CustomParentClass) -> QSizeF;
}

impl qobject::CustomParentClass {
#[qinvokable(cxx_override)]
pub fn paint(self: Pin<&mut Self>, painter: *mut QPainter) {
// We need to convert the *mut QPainter to a Pin<&mut QPainter> so that we can reach the methods
if let Some(painter) = unsafe { painter.as_mut() } {
let mut pinned_painter = unsafe { Pin::new_unchecked(painter) };

// Now pinned painter can be used as normal
// to render a rectangle with two colours
let size = self.as_ref().size();
pinned_painter.as_mut().fill_rect(
&QRectF::new(0.0, 0.0, size.width() / 2.0, size.height()),
self.as_ref().color(),
);
let darker_color = self.as_ref().color().darker(150);
pinned_painter.as_mut().fill_rect(
&QRectF::new(size.width() / 2.0, 0.0, size.width() / 2.0, size.height()),
&darker_color,
);
}
}
}
}
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 @@ -6,6 +6,7 @@

mod containers;
mod custom_base_class;
mod custom_parent_class;
mod invokables;
mod multiple_qobjects;
mod nested_qobjects;
Expand Down

0 comments on commit 06b57f8

Please sign in to comment.