From af7139027ba17e677a9462524a2fb5ee999e377d Mon Sep 17 00:00:00 2001 From: Jos van den Oever Date: Sun, 3 Sep 2017 22:06:50 +0200 Subject: [PATCH] Add a template for a Qt Widgets project --- templates/qt_widgets/CMakeLists.txt | 73 ++++++++++++ templates/qt_widgets/README.md | 14 +++ templates/qt_widgets/bindings.json | 19 +++ templates/qt_widgets/cmake/FindCargo.cmake | 10 ++ templates/qt_widgets/cmake/FindRust.cmake | 10 ++ .../cmake/FindRustQtBindingGenerator.cmake | 5 + templates/qt_widgets/rust/Cargo.toml | 11 ++ .../qt_widgets/rust/src/implementation.rs | 26 +++++ templates/qt_widgets/rust/src/interface.rs | 110 ++++++++++++++++++ templates/qt_widgets/rust/src/lib.rs | 5 + templates/qt_widgets/src/Bindings.cpp | 63 ++++++++++ templates/qt_widgets/src/Bindings.h | 28 +++++ templates/qt_widgets/src/main.cpp | 17 +++ 13 files changed, 391 insertions(+) create mode 100644 templates/qt_widgets/CMakeLists.txt create mode 100644 templates/qt_widgets/README.md create mode 100644 templates/qt_widgets/bindings.json create mode 100644 templates/qt_widgets/cmake/FindCargo.cmake create mode 100644 templates/qt_widgets/cmake/FindRust.cmake create mode 100644 templates/qt_widgets/cmake/FindRustQtBindingGenerator.cmake create mode 100644 templates/qt_widgets/rust/Cargo.toml create mode 100644 templates/qt_widgets/rust/src/implementation.rs create mode 100644 templates/qt_widgets/rust/src/interface.rs create mode 100644 templates/qt_widgets/rust/src/lib.rs create mode 100644 templates/qt_widgets/src/Bindings.cpp create mode 100644 templates/qt_widgets/src/Bindings.h create mode 100644 templates/qt_widgets/src/main.cpp diff --git a/templates/qt_widgets/CMakeLists.txt b/templates/qt_widgets/CMakeLists.txt new file mode 100644 index 0000000..9b95c58 --- /dev/null +++ b/templates/qt_widgets/CMakeLists.txt @@ -0,0 +1,73 @@ +project (my_rust_qt_widgets_project) + +cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) +cmake_policy(SET CMP0046 NEW) +cmake_policy(SET CMP0063 NEW) +LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +string(TOUPPER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_UPPER) +if(CMAKE_BUILD_TYPE_UPPER STREQUAL DEBUG) + set(RUST_TARGET_DIR target/debug/) + set(RUST_BUILD_FLAG) +else() + set(RUST_TARGET_DIR target/release/) + set(RUST_BUILD_FLAG --release) +endif() + +### find dependencies ### + +include(FeatureSummary) +find_package(Cargo REQUIRED) +find_package(Rust REQUIRED) +set(CMAKE_THREAD_PREFER_PTHREAD TRUE) +find_package(Threads REQUIRED) + +set(QT_MIN_VERSION "5.6.0") +find_package(Qt5 ${QT_MIN_VERSION} CONFIG + REQUIRED COMPONENTS + Widgets +) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTORCC ON) +find_package(RustQtBindingGenerator REQUIRED) + +feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) + +### build commands ### + +SET(RUST_DIR "${CMAKE_CURRENT_SOURCE_DIR}/rust") +SET(RUST_LIB "${RUST_DIR}/${RUST_TARGET_DIR}/librust.a") + +# generate c++ and rust code from bindings.json +add_custom_command( + OUTPUT "${RUST_DIR}/src/interface.rs" + "${CMAKE_CURRENT_SOURCE_DIR}/src/Bindings.h" + # if the cpp file is marked GENERATED, CMake will not check it for moc + # "${CMAKE_CURRENT_SOURCE_DIR}/src/Bindings.cpp" + COMMAND "${RustQtBindingGenerator_EXECUTABLE}" + "${CMAKE_CURRENT_SOURCE_DIR}/bindings.json" + DEPENDS bindings.json +) + +# compile the rust code into a static library +add_custom_command( + OUTPUT "${RUST_LIB}" + COMMAND ${Cargo_EXECUTABLE} build ${RUST_BUILD_FLAG} + DEPENDS rust/src/lib.rs + rust/src/implementation.rs + rust/src/interface.rs + WORKING_DIRECTORY "${RUST_DIR}" +) +add_custom_target(rust_target DEPENDS "${RUST_LIB}") + +list(APPEND Libs "${RUST_LIB}") +list(APPEND Libs Qt5::Widgets Threads::Threads ${CMAKE_DL_LIBS}) +set(SRCS src/main.cpp src/Bindings.cpp) +add_executable(MyExe ${SRCS}) +add_dependencies(MyExe rust_target) +target_link_libraries(MyExe ${Libs}) +set_target_properties(MyExe PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED ON +) diff --git a/templates/qt_widgets/README.md b/templates/qt_widgets/README.md new file mode 100644 index 0000000..83962bd --- /dev/null +++ b/templates/qt_widgets/README.md @@ -0,0 +1,14 @@ +Qt Widgets template project with Rust bindings + +This is a template project for writing a Qt Widgets GUI on top of Rust code. + +bindings.json defines the interface between the Qt and Rust code. + +Build this code with + +```bash +mkdir build +cd build +cmake .. +make +``` diff --git a/templates/qt_widgets/bindings.json b/templates/qt_widgets/bindings.json new file mode 100644 index 0000000..6467a0f --- /dev/null +++ b/templates/qt_widgets/bindings.json @@ -0,0 +1,19 @@ +{ + "cppFile": "src/Bindings.cpp", + "rust": { + "dir": "rust", + "interfaceModule": "interface", + "implementationModule": "implementation" + }, + "objects": { + "Simple": { + "type": "Object", + "properties": { + "message": { + "type": "QString", + "write": true + } + } + } + } +} diff --git a/templates/qt_widgets/cmake/FindCargo.cmake b/templates/qt_widgets/cmake/FindCargo.cmake new file mode 100644 index 0000000..6627d86 --- /dev/null +++ b/templates/qt_widgets/cmake/FindCargo.cmake @@ -0,0 +1,10 @@ +include(FindPackageHandleStandardArgs) +find_program(Cargo_EXECUTABLE cargo) +execute_process(COMMAND "${Cargo_EXECUTABLE}" --version + OUTPUT_VARIABLE Cargo_VERSION_OUTPUT) +STRING(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" + Cargo_VERSION "${Cargo_VERSION_OUTPUT}") +find_package_handle_standard_args(Cargo + REQUIRED_VARS Cargo_EXECUTABLE + VERSION_VAR Cargo_VERSION) +mark_as_advanced(Cargo_EXECUTABLE) diff --git a/templates/qt_widgets/cmake/FindRust.cmake b/templates/qt_widgets/cmake/FindRust.cmake new file mode 100644 index 0000000..1b67045 --- /dev/null +++ b/templates/qt_widgets/cmake/FindRust.cmake @@ -0,0 +1,10 @@ +include(FindPackageHandleStandardArgs) +find_program(Rust_EXECUTABLE rustc) +execute_process(COMMAND "${Rust_EXECUTABLE}" --version + OUTPUT_VARIABLE Rust_VERSION_OUTPUT) +STRING(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" + Rust_VERSION "${Rust_VERSION_OUTPUT}") +find_package_handle_standard_args(Rust + REQUIRED_VARS Rust_EXECUTABLE + VERSION_VAR Rust_VERSION) +mark_as_advanced(Rust_EXECUTABLE) diff --git a/templates/qt_widgets/cmake/FindRustQtBindingGenerator.cmake b/templates/qt_widgets/cmake/FindRustQtBindingGenerator.cmake new file mode 100644 index 0000000..4be2426 --- /dev/null +++ b/templates/qt_widgets/cmake/FindRustQtBindingGenerator.cmake @@ -0,0 +1,5 @@ +include(FindPackageHandleStandardArgs) +find_program(RustQtBindingGenerator_EXECUTABLE rust_qt_binding_generator) +find_package_handle_standard_args(RustQtBindingGenerator + REQUIRED_VARS RustQtBindingGenerator_EXECUTABLE) +mark_as_advanced(RustQtBindingGenerator_EXECUTABLE) diff --git a/templates/qt_widgets/rust/Cargo.toml b/templates/qt_widgets/rust/Cargo.toml new file mode 100644 index 0000000..7013c67 --- /dev/null +++ b/templates/qt_widgets/rust/Cargo.toml @@ -0,0 +1,11 @@ + +[package] +name = "rust" +version = "1.0.0" + +[dependencies] +libc = "*" + +[lib] +name = "rust" +crate-type = ["staticlib"] diff --git a/templates/qt_widgets/rust/src/implementation.rs b/templates/qt_widgets/rust/src/implementation.rs new file mode 100644 index 0000000..fb9c575 --- /dev/null +++ b/templates/qt_widgets/rust/src/implementation.rs @@ -0,0 +1,26 @@ +use interface::*; + +pub struct Simple { + emit: SimpleEmitter, + message: String, +} + +impl SimpleTrait for Simple { + fn new(emit: SimpleEmitter) -> Simple { + Simple { + emit: emit, + message: String::new(), + } + } + fn emit(&self) -> &SimpleEmitter { + &self.emit + } + fn message(&self) -> &str { + "Hello World!" + } + fn set_message(&mut self, value: String) { + self.message = value; + self.emit.message_changed(); + } +} + diff --git a/templates/qt_widgets/rust/src/interface.rs b/templates/qt_widgets/rust/src/interface.rs new file mode 100644 index 0000000..9ddf376 --- /dev/null +++ b/templates/qt_widgets/rust/src/interface.rs @@ -0,0 +1,110 @@ +/* generated by rust_qt_binding_generator */ +#![allow(unknown_lints)] +#![allow(mutex_atomic, needless_pass_by_value)] +use libc::{c_int, c_void, uint8_t, uint16_t}; +use std::slice; + +use std::sync::{Arc, Mutex}; +use std::ptr::null; + +use implementation::*; + + +#[repr(C)] +pub struct QString { + data: *const uint8_t, + len: c_int, +} + +#[repr(C)] +pub struct QStringIn { + data: *const uint16_t, + len: c_int, +} + +impl QStringIn { + fn convert(&self) -> String { + let data = unsafe { slice::from_raw_parts(self.data, self.len as usize) }; + String::from_utf16_lossy(data) + } +} + +impl<'a> From<&'a str> for QString { + fn from(string: &'a str) -> QString { + QString { + len: string.len() as c_int, + data: string.as_ptr(), + } + } +} + +impl<'a> From<&'a String> for QString { + fn from(string: &'a String) -> QString { + QString { + len: string.len() as c_int, + data: string.as_ptr(), + } + } +} + +pub struct SimpleQObject {} + +#[derive(Clone)] +pub struct SimpleEmitter { + qobject: Arc>, + message_changed: fn(*const SimpleQObject), +} + +unsafe impl Send for SimpleEmitter {} + +impl SimpleEmitter { + fn clear(&self) { + *self.qobject.lock().unwrap() = null(); + } + pub fn message_changed(&self) { + let ptr = *self.qobject.lock().unwrap(); + if !ptr.is_null() { + (self.message_changed)(ptr); + } + } +} + +pub trait SimpleTrait { + fn new(emit: SimpleEmitter) -> Self; + fn emit(&self) -> &SimpleEmitter; + fn message(&self) -> &str; + fn set_message(&mut self, value: String); +} + +#[no_mangle] +pub extern "C" fn simple_new( + simple: *mut SimpleQObject, + message_changed: fn(*const SimpleQObject), +) -> *mut Simple { + let simple_emit = SimpleEmitter { + qobject: Arc::new(Mutex::new(simple)), + message_changed: message_changed, + }; + let d_simple = Simple::new(simple_emit); + Box::into_raw(Box::new(d_simple)) +} + +#[no_mangle] +pub unsafe extern "C" fn simple_free(ptr: *mut Simple) { + Box::from_raw(ptr).emit().clear(); +} + +#[no_mangle] +pub unsafe extern "C" fn simple_message_get( + ptr: *const Simple, + p: *mut c_void, + set: fn(*mut c_void, QString), +) { + let data = (&*ptr).message(); + set(p, data.into()); +} + +#[no_mangle] +pub unsafe extern "C" fn simple_message_set(ptr: *mut Simple, v: QStringIn) { + (&mut *ptr).set_message(v.convert()); +} diff --git a/templates/qt_widgets/rust/src/lib.rs b/templates/qt_widgets/rust/src/lib.rs new file mode 100644 index 0000000..d17196f --- /dev/null +++ b/templates/qt_widgets/rust/src/lib.rs @@ -0,0 +1,5 @@ + +extern crate libc; + +pub mod interface; +mod implementation; diff --git a/templates/qt_widgets/src/Bindings.cpp b/templates/qt_widgets/src/Bindings.cpp new file mode 100644 index 0000000..7cf6e65 --- /dev/null +++ b/templates/qt_widgets/src/Bindings.cpp @@ -0,0 +1,63 @@ +/* generated by rust_qt_binding_generator */ +#include "Bindings.h" + +namespace { + + struct qstring_t { + private: + const void* data; + int len; + public: + qstring_t(const QString& v): + data(static_cast(v.utf16())), + len(v.size()) { + } + operator QString() const { + return QString::fromUtf8(static_cast(data), len); + } + }; + typedef void (*qstring_set)(QString*, qstring_t*); + void set_qstring(QString* v, qstring_t* val) { + *v = *val; + } + inline void simpleMessageChanged(Simple* o) + { + emit o->messageChanged(); + } +} +extern "C" { + Simple::Private* simple_new(Simple*, void (*)(Simple*)); + void simple_free(Simple::Private*); + void simple_message_get(const Simple::Private*, QString*, qstring_set); + void simple_message_set(Simple::Private*, qstring_t); +}; + +Simple::Simple(bool /*owned*/, QObject *parent): + QObject(parent), + m_d(0), + m_ownsPrivate(false) +{ +} + +Simple::Simple(QObject *parent): + QObject(parent), + m_d(simple_new(this, + simpleMessageChanged)), + m_ownsPrivate(true) +{ +} + +Simple::~Simple() { + if (m_ownsPrivate) { + simple_free(m_d); + } +} +QString Simple::message() const +{ + QString v; + simple_message_get(m_d, &v, set_qstring); + return v; +} +void Simple::setMessage(const QString& v) { + simple_message_set(m_d, v); +} diff --git a/templates/qt_widgets/src/Bindings.h b/templates/qt_widgets/src/Bindings.h new file mode 100644 index 0000000..6d2c6df --- /dev/null +++ b/templates/qt_widgets/src/Bindings.h @@ -0,0 +1,28 @@ +/* generated by rust_qt_binding_generator */ +#ifndef BINDINGS_H +#define BINDINGS_H + +#include +#include + +class Simple; + +class Simple : public QObject +{ + Q_OBJECT +public: + class Private; +private: + Private * m_d; + bool m_ownsPrivate; + Q_PROPERTY(QString message READ message WRITE setMessage NOTIFY messageChanged FINAL) + explicit Simple(bool owned, QObject *parent); +public: + explicit Simple(QObject *parent = nullptr); + ~Simple(); + QString message() const; + void setMessage(const QString& v); +signals: + void messageChanged(); +}; +#endif // BINDINGS_H diff --git a/templates/qt_widgets/src/main.cpp b/templates/qt_widgets/src/main.cpp new file mode 100644 index 0000000..ec04a28 --- /dev/null +++ b/templates/qt_widgets/src/main.cpp @@ -0,0 +1,17 @@ +#include "Bindings.h" +#include +#include + +int main(int argc, char* argv[]) +{ + QApplication app(argc, argv); + Simple simple; // This is the Rust object + QMessageBox msgBox; + msgBox.setText(simple.message()); + msgBox.connect(&msgBox, &QMessageBox::finished, &msgBox, []() { + QCoreApplication::quit(); + }); + msgBox.show(); + + return app.exec(); +}