diff --git a/Cargo.toml b/Cargo.toml index 397a99d4e..bbf11aa26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ members = [ "examples/demo_threading/rust", "examples/qml_features/rust", "examples/qml_minimal/rust", + "examples/widgets_minimal", "tests/basic_cxx_only/rust", "tests/basic_cxx_qt/rust", diff --git a/examples/widgets_minimal/Cargo.toml b/examples/widgets_minimal/Cargo.toml new file mode 100644 index 000000000..675f127ab --- /dev/null +++ b/examples/widgets_minimal/Cargo.toml @@ -0,0 +1,25 @@ +# SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +# SPDX-FileContributor: Andrew Hayzen +# +# SPDX-License-Identifier: MIT OR Apache-2.0 + +[package] +name = "widgets-minimal-no-cmake" +version = "0.1.0" +authors = [ + "Andrew Hayzen ", + "Be Wilson ", + "Gerhard de Clercq ", + "Leon Matthes " +] +edition = "2021" +license = "MIT OR Apache-2.0" + +[dependencies] +cxx.workspace = true +cxx-qt.workspace = true +cxx-qt-lib.workspace = true + +[build-dependencies] +# The link_qt_object_files feature is required for statically linking Qt 6. +cxx-qt-build = { workspace = true, features = [ "link_qt_object_files" ] } diff --git a/examples/widgets_minimal/build.rs b/examples/widgets_minimal/build.rs new file mode 100644 index 000000000..b53f03dcf --- /dev/null +++ b/examples/widgets_minimal/build.rs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Be Wilson +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use cxx_qt_build::CxxQtBuilder; + +fn main() { + CxxQtBuilder::new() + // Link Qt's Network library + // - Qt Core is always linked + // - Qt Gui is linked by enabling the qt_gui Cargo feature (default). + // - Qt Qml is linked by enabling the qt_qml Cargo feature (default). + // - Qt Qml requires linking Qt Network on macOS + .qt_module("Network") + .qt_module("Widgets") + // Generate C++ from the `#[cxx_qt::bridge]` module + .file("src/qapplication_cxx.rs") + .file("src/qpushbutton_cxx.rs") + .cc_builder(|cc| { + cc.include("./cpp"); + cc.file("./cpp/qapplication.cpp"); + cc.file("./cpp/qpushbutton.cpp"); + }) + .build(); +} diff --git a/examples/widgets_minimal/cpp/qapplication.cpp b/examples/widgets_minimal/cpp/qapplication.cpp new file mode 100644 index 000000000..732b72e49 --- /dev/null +++ b/examples/widgets_minimal/cpp/qapplication.cpp @@ -0,0 +1,26 @@ +// clang-format off +// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +// clang-format on +// SPDX-FileContributor: Andrew Hayzen +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +#include "qapplication.h" + +#include + +::std::unique_ptr +qapplicationNew(const QVector& args) +{ + // Ensure that our QVector has the same lifetime as the QGuiApplication + // by storing it inside a QObject that has QGuiApplication as it's parent + auto argsData = new ::rust::cxxqtlib1::ApplicationArgsData(args); + // Note that QGuiApplication uses a reference to an int for the size here + // so we need to ensure that reference remains valid + auto ptr = + ::std::make_unique(argsData->size(), argsData->data()); + Q_ASSERT(ptr != nullptr); + argsData->setParent(ptr.get()); + + return ptr; +} diff --git a/examples/widgets_minimal/cpp/qapplication.h b/examples/widgets_minimal/cpp/qapplication.h new file mode 100644 index 000000000..ac1cef75c --- /dev/null +++ b/examples/widgets_minimal/cpp/qapplication.h @@ -0,0 +1,22 @@ +// clang-format off +// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +// clang-format on +// SPDX-FileContributor: Andrew Hayzen +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +#pragma once + +#include + +#include + +::std::unique_ptr +qapplicationNew(const QVector& args); + +template +::std::int32_t +qapplicationExec(T& app) +{ + return static_cast<::std::int32_t>(app.exec()); +} diff --git a/examples/widgets_minimal/cpp/qpushbutton.cpp b/examples/widgets_minimal/cpp/qpushbutton.cpp new file mode 100644 index 000000000..951d1158a --- /dev/null +++ b/examples/widgets_minimal/cpp/qpushbutton.cpp @@ -0,0 +1,14 @@ +// clang-format off +// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +// clang-format on +// SPDX-FileContributor: Andrew Hayzen +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +#include "qpushbutton.h" + +::std::unique_ptr +qpushbuttonNew() +{ + return ::std::make_unique(); +} diff --git a/examples/widgets_minimal/cpp/qpushbutton.h b/examples/widgets_minimal/cpp/qpushbutton.h new file mode 100644 index 000000000..e338a9fc8 --- /dev/null +++ b/examples/widgets_minimal/cpp/qpushbutton.h @@ -0,0 +1,15 @@ +// clang-format off +// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +// clang-format on +// SPDX-FileContributor: Andrew Hayzen +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +#pragma once + +#include + +#include + +::std::unique_ptr +qpushbuttonNew(); diff --git a/examples/widgets_minimal/src/main.rs b/examples/widgets_minimal/src/main.rs new file mode 100644 index 000000000..8fca23496 --- /dev/null +++ b/examples/widgets_minimal/src/main.rs @@ -0,0 +1,38 @@ +// 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 provides demostrations of building a Cargo only CXX-Qt application + +mod qapplication_cxx; +mod qpushbutton_cxx; + +use cxx_qt_lib::QString; +use qapplication_cxx::QApplication; +use qpushbutton_cxx::QPushButton; + +fn main() { + let mut app = QApplication::new(); + + let mut push_button = QPushButton::new(); + + if let Some(mut push_button) = push_button.as_mut() { + push_button + .as_mut() + .set_text(&QString::from("Hello World!")); + + push_button + .as_mut() + .on_clicked(|_, _| { + println!("Button Clicked!"); + }) + .release(); + + push_button.show(); + } + + if let Some(app) = app.as_mut() { + app.exec(); + } +} diff --git a/examples/widgets_minimal/src/qapplication_cxx.rs b/examples/widgets_minimal/src/qapplication_cxx.rs new file mode 100644 index 000000000..3b0d8d307 --- /dev/null +++ b/examples/widgets_minimal/src/qapplication_cxx.rs @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Andrew Hayzen +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +#[cxx_qt::bridge(cxx_file_stem = "qapplication_cxx")] +mod ffi { + unsafe extern "C++Qt" { + include!(); + type QApplication; + + include!("cxx-qt-lib/qbytearray.h"); + type QByteArray = cxx_qt_lib::QByteArray; + include!("cxx-qt-lib/qvector.h"); + type QVector_QByteArray = cxx_qt_lib::QVector; + } + + unsafe extern "C++Qt" { + include!("qapplication.h"); + #[doc(hidden)] + #[rust_name = "qapplication_new"] + fn qapplicationNew(args: &QVector_QByteArray) -> UniquePtr; + + #[doc(hidden)] + #[rust_name = "qapplication_exec"] + fn qapplicationExec(app: Pin<&mut QApplication>) -> i32; + } +} + +impl ffi::QApplication { + pub fn new() -> cxx::UniquePtr { + let mut vector = cxx_qt_lib::QVector::::default(); + + // Construct an owned QVector of the args + // as we need the args_os data to outlive this method + // so we pass a QVector to C++ which is then stored + for arg in std::env::args_os() { + // Unix OsStrings can be directly converted to bytes. + #[cfg(unix)] + use std::os::unix::ffi::OsStrExt; + + // Windows OsStrings are WTF-8 encoded, so they need to be + // converted to UTF-8 Strings before being converted to bytes. + // https://simonsapin.github.io/wtf-8/ + #[cfg(windows)] + let arg = arg.to_string_lossy(); + + vector.append(ffi::QByteArray::from(arg.as_bytes())); + } + + ffi::qapplication_new(&vector) + } + + pub fn exec(self: core::pin::Pin<&mut Self>) -> i32 { + ffi::qapplication_exec(self) + } +} + +pub use ffi::QApplication; diff --git a/examples/widgets_minimal/src/qpushbutton_cxx.rs b/examples/widgets_minimal/src/qpushbutton_cxx.rs new file mode 100644 index 000000000..ecddc1a7c --- /dev/null +++ b/examples/widgets_minimal/src/qpushbutton_cxx.rs @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Andrew Hayzen +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +#[cxx_qt::bridge(cxx_file_stem = "qpushbutton_cxx")] +mod ffi { + + unsafe extern "C++Qt" { + include!(); + type QPushButton; + + include!("cxx-qt-lib/qstring.h"); + type QString = cxx_qt_lib::QString; + + #[qsignal] + #[allow(dead_code)] + fn clicked(self: Pin<&mut QPushButton>, checked: bool); + + #[rust_name = "set_text"] + fn setText(self: Pin<&mut QPushButton>, text: &QString); + + fn show(self: Pin<&mut QPushButton>); + } + + unsafe extern "C++Qt" { + include!("qpushbutton.h"); + + #[doc(hidden)] + #[cxx_name = "qpushbuttonNew"] + fn qpushbutton_new() -> UniquePtr; + } +} + +impl ffi::QPushButton { + pub fn new() -> cxx::UniquePtr { + ffi::qpushbutton_new() + } +} + +pub use ffi::QPushButton;