From d50012461d76d69884be82941f3b36ae915dea14 Mon Sep 17 00:00:00 2001 From: Andrew Hayzen Date: Wed, 5 Apr 2023 16:32:36 +0900 Subject: [PATCH] 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 --- CHANGELOG.md | 4 + .../cxx-qt-gen/src/generator/cpp/qobject.rs | 10 ++- crates/cxx-qt-gen/src/parser/qobject.rs | 12 ++- crates/cxx-qt-gen/src/writer/cpp/header.rs | 3 +- crates/cxx-qt-gen/src/writer/cpp/mod.rs | 7 +- crates/cxx-qt-gen/src/writer/cpp/source.rs | 3 +- .../test_inputs/passthrough_and_naming.rs | 2 +- .../test_outputs/passthrough_and_naming.cpp | 2 +- .../test_outputs/passthrough_and_naming.h | 2 +- examples/qml_features/CMakeLists.txt | 5 +- examples/qml_features/qml/main.qml | 4 + .../qml/pages/CustomParentClassPage.qml | 64 +++++++++++++++ examples/qml_features/qml/qml.qrc | 1 + examples/qml_features/rust/build.rs | 4 + .../rust/src/custom_parent_class.rs | 81 +++++++++++++++++++ examples/qml_features/rust/src/lib.rs | 1 + 16 files changed, 194 insertions(+), 11 deletions(-) create mode 100644 examples/qml_features/qml/pages/CustomParentClassPage.qml create mode 100644 examples/qml_features/rust/src/custom_parent_class.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 729ab33de..68b365ef4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/crates/cxx-qt-gen/src/generator/cpp/qobject.rs b/crates/cxx-qt-gen/src/generator/cpp/qobject.rs index 4c393cd67..fabe2c28b 100644 --- a/crates/cxx-qt-gen/src/generator/cpp/qobject.rs +++ b/crates/cxx-qt-gen/src/generator/cpp/qobject.rs @@ -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, } @@ -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), }; @@ -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); } @@ -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; } }; @@ -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); } diff --git a/crates/cxx-qt-gen/src/parser/qobject.rs b/crates/cxx-qt-gen/src/parser/qobject.rs index e8afb3bc5..0956ffcc9 100644 --- a/crates/cxx-qt-gen/src/parser/qobject.rs +++ b/crates/cxx-qt-gen/src/parser/qobject.rs @@ -36,6 +36,8 @@ pub struct QmlElementMetadata { pub struct ParsedQObject { /// The base class of the struct pub base_class: Option, + /// The parent class of the struct + pub parent_class: Option, /// 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, @@ -81,6 +83,11 @@ impl ParsedQObject { .get("e::format_ident!("base")) .map(|base| base.value()); + // Find if there is any parent class + let parent_class = attrs_map + .get("e::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("e::format_ident!("namespace")) @@ -105,6 +112,7 @@ impl ParsedQObject { Ok(Self { base_class, + parent_class, qobject_struct, namespace, signals: None, @@ -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] diff --git a/crates/cxx-qt-gen/src/writer/cpp/header.rs b/crates/cxx-qt-gen/src/writer/cpp/header.rs index 510643c69..8763fb694 100644 --- a/crates/cxx-qt-gen/src/writer/cpp/header.rs +++ b/crates/cxx-qt-gen/src/writer/cpp/header.rs @@ -74,7 +74,7 @@ fn qobjects_header(generated: &GeneratedCppBlocks) -> Vec { {metaobjects} public: - explicit {ident}(QObject* parent = nullptr); + explicit {ident}({parent_class}* parent = nullptr); ~{ident}(); {rust_ident} const& unsafeRust() const; {rust_ident}& unsafeRustMut(); @@ -104,6 +104,7 @@ fn qobjects_header(generated: &GeneratedCppBlocks) -> Vec { 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::>()), metatype = if generated.namespace.is_empty() { diff --git a/crates/cxx-qt-gen/src/writer/cpp/mod.rs b/crates/cxx-qt-gen/src/writer/cpp/mod.rs index 04234d55a..71e384880 100644 --- a/crates/cxx-qt-gen/src/writer/cpp/mod.rs +++ b/crates/cxx-qt-gen/src/writer/cpp/mod.rs @@ -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(), @@ -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)" @@ -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)" @@ -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(); @@ -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>()) diff --git a/crates/cxx-qt-gen/src/writer/cpp/source.rs b/crates/cxx-qt-gen/src/writer/cpp/source.rs index 9b756019d..25d50fc8d 100644 --- a/crates/cxx-qt-gen/src/writer/cpp/source.rs +++ b/crates/cxx-qt-gen/src/writer/cpp/source.rs @@ -24,7 +24,7 @@ fn qobjects_source(generated: &GeneratedCppBlocks) -> Vec { 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>()) @@ -73,6 +73,7 @@ fn qobjects_source(generated: &GeneratedCppBlocks) -> Vec { 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::>().join("\n"), } diff --git a/crates/cxx-qt-gen/test_inputs/passthrough_and_naming.rs b/crates/cxx-qt-gen/test_inputs/passthrough_and_naming.rs index c00b84e17..07089f89b 100644 --- a/crates/cxx-qt-gen/test_inputs/passthrough_and_naming.rs +++ b/crates/cxx-qt-gen/test_inputs/passthrough_and_naming.rs @@ -79,7 +79,7 @@ pub mod ffi { include!(); } - #[cxx_qt::qobject(base = "QStringListModel")] + #[cxx_qt::qobject(base = "QStringListModel", parent = "QQuickItem")] pub struct MyObject { #[qproperty] property_name: i32, diff --git a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.cpp b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.cpp index 267b8ac59..e552e0d66 100644 --- a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.cpp +++ b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.cpp @@ -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>()) diff --git a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.h b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.h index e1027be0c..524b93409 100644 --- a/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.h +++ b/crates/cxx-qt-gen/test_outputs/passthrough_and_naming.h @@ -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(); diff --git a/examples/qml_features/CMakeLists.txt b/examples/qml_features/CMakeLists.txt index 0e2aead0d..7165e903e 100644 --- a/examples/qml_features/CMakeLists.txt +++ b/examples/qml_features/CMakeLists.txt @@ -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) @@ -48,6 +48,7 @@ target_link_libraries(${APP_NAME}_lib INTERFACE Qt::Core Qt::Gui Qt::Qml + Qt::Quick Qt::QuickControls2 ) diff --git a/examples/qml_features/qml/main.qml b/examples/qml_features/qml/main.qml index 71e789a4c..cc0392999 100644 --- a/examples/qml_features/qml/main.qml +++ b/examples/qml_features/qml/main.qml @@ -112,6 +112,10 @@ ApplicationWindow { name: "Singleton" source: "qrc:/pages/SingletonPage.qml" } + ListElement { + name: "Custom Parent Class" + source: "qrc:/pages/CustomParentClassPage.qml" + } } } } diff --git a/examples/qml_features/qml/pages/CustomParentClassPage.qml b/examples/qml_features/qml/pages/CustomParentClassPage.qml new file mode 100644 index 000000000..9c69e9f60 --- /dev/null +++ b/examples/qml_features/qml/pages/CustomParentClassPage.qml @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Andrew Hayzen +// +// 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 + } + } +} diff --git a/examples/qml_features/qml/qml.qrc b/examples/qml_features/qml/qml.qrc index aab5c7461..a1fd3253d 100644 --- a/examples/qml_features/qml/qml.qrc +++ b/examples/qml_features/qml/qml.qrc @@ -11,6 +11,7 @@ SPDX-License-Identifier: MIT OR Apache-2.0 main.qml pages/ContainersPage.qml pages/CustomBaseClassPage.qml + pages/CustomParentClassPage.qml pages/InvokablesPage.qml pages/MultipleQObjectsPage.qml pages/NestedQObjectsPage.qml diff --git a/examples/qml_features/rust/build.rs b/examples/qml_features/rust/build.rs index 8403e3108..d305d5763 100644 --- a/examples/qml_features/rust/build.rs +++ b/examples/qml_features/rust/build.rs @@ -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") @@ -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 diff --git a/examples/qml_features/rust/src/custom_parent_class.rs b/examples/qml_features/rust/src/custom_parent_class.rs new file mode 100644 index 000000000..6642265c6 --- /dev/null +++ b/examples/qml_features/rust/src/custom_parent_class.rs @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Andrew Hayzen +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! This example shows how a custom parent class can be used to inherit from a QQuickItem based object. + +/// A CXX-Qt bridge which shows a custom parent class can be used +#[cxx_qt::bridge(cxx_file_stem = "custom_parent_class")] +mod ffi { + unsafe extern "C++" { + include!(); + + /// QColor from cxx_qt_lib + type QColor = cxx_qt_lib::QColor; + include!("cxx-qt-lib/qcolor.h"); + + /// QRectF from cxx_qt_lib + type QRectF = cxx_qt_lib::QRectF; + include!("cxx-qt-lib/qrectf.h"); + + /// QSizeF from cxx_qt_lib + type QSizeF = cxx_qt_lib::QSizeF; + include!("cxx-qt-lib/qsizef.h"); + + // Define the API from QPainter that we need + + /// QPainter from Qt + type QPainter; + include!(); + + /// QPainter::fillRect from Qt + #[rust_name = "fill_rect"] + fn fillRect(self: Pin<&mut QPainter>, rectangle: &QRectF, color: &QColor); + } + + /// A struct which inherits from QQuickPaintedItem + /// + /// Which has a parent of the type QQuickItem rather than QObject. + #[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 { + /// Override QQuickPaintedItem::paint to draw two rectangles in Rust using QPainter + #[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, + ); + } + } + } +} diff --git a/examples/qml_features/rust/src/lib.rs b/examples/qml_features/rust/src/lib.rs index ae54ad867..241f244a8 100644 --- a/examples/qml_features/rust/src/lib.rs +++ b/examples/qml_features/rust/src/lib.rs @@ -12,6 +12,7 @@ pub mod containers; pub mod custom_base_class; +pub mod custom_parent_class; pub mod invokables; pub mod multiple_qobjects; pub mod nested_qobjects;