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 authored and Be-ing committed May 23, 2023
1 parent 575ecf5 commit d500124
Show file tree
Hide file tree
Showing 16 changed files with 194 additions and 11 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Allow associated constants, types and macro invocations within `impl qobject::T` blocks
- Ensure that generated Rust code works when `#![deny(missing_docs)]` is enabled

### Added

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

### Changed

- Pretty-print errors messages when build script fails
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 @@ -145,6 +151,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 @@ -153,7 +160,7 @@ mod tests {
let module: ItemMod = parse_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 @@ -166,6 +173,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 @@ -278,18 +286,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 = parse_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
5 changes: 3 additions & 2 deletions examples/qml_features/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

if(NOT USE_QT5)
find_package(Qt6 COMPONENTS Core Gui Qml QuickControls2 QmlImportScanner QuickTest Test)
find_package(Qt6 COMPONENTS Core Gui Qml Quick QuickControls2 QmlImportScanner QuickTest Test)
endif()
if(NOT Qt6_FOUND)
find_package(Qt5 5.15 COMPONENTS Core Gui Qml QuickControls2 QmlImportScanner QuickTest Test REQUIRED)
find_package(Qt5 5.15 COMPONENTS Core Gui Qml Quick QuickControls2 QmlImportScanner QuickTest Test REQUIRED)
endif()
get_target_property(QMAKE Qt::qmake IMPORTED_LOCATION)

Expand Down Expand Up @@ -48,6 +48,7 @@ target_link_libraries(${APP_NAME}_lib INTERFACE
Qt::Core
Qt::Gui
Qt::Qml
Qt::Quick
Qt::QuickControls2
)

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
4 changes: 4 additions & 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 All @@ -28,6 +29,9 @@ fn main() {
cc.file("../cpp/custom_object.cpp");
})
.qobject_header("../cpp/custom_object.h")
// Ensure that Quick module is linked, so that cargo test can work.
// In a CMake project this isn't required as the linking happens in CMake.
.qt_module("Quick")
.build();
}
// ANCHOR_END: book_build_rs
Loading

0 comments on commit d500124

Please sign in to comment.