diff --git a/.gitignore b/.gitignore --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ /docker_home/ /tests/rust_*/Cargo.lock /tests/rust_*/target/ + +# tutorial build +/build/tutorial/ \ No newline at end of file diff --git a/tutorial/SUMMARY.md b/tutorial/SUMMARY.md new file mode 100644 --- /dev/null +++ b/tutorial/SUMMARY.md @@ -0,0 +1,5 @@ +# Summary + +[Rust Qt Binding Generator](./rust_qt_binding_generator.md) + +- [Rust and QML: a timely example](./time_for_rust_and_qml.md) diff --git a/tutorial/book.toml b/tutorial/book.toml new file mode 100644 --- /dev/null +++ b/tutorial/book.toml @@ -0,0 +1,8 @@ +[book] +title = "The official Rust Qt Binding Generator Guide" +author = "Jos van den Oever" +description = "Giving Rust a cute wrapping!" +src = "." + +[build] +build-dir = "../build/tutorial" diff --git a/tutorial/demo.png b/tutorial/demo.png new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@ + + Qt Widgets (main.cpp) / Qt Quick (main.qml) + ⟵ UI code, written by hand + + + src/Binding.h + ⟵ generated from
binding.json + + + src/Binding.cpp + + + rust/src/interface.rs + + + rust/src/implementation.rs + ⟵ Rust code, written by hand + + + +To combine Qt and Rust, write an interface in a JSON file. From that, the generator creates Qt code and Rust code. The Qt code can be used directly. The Rust code has two files: interface and implementation. The interface can be used directly. + +```json +{ + "cppFile": "src/Binding.cpp", + "rust": { + "dir": "rust", + "interfaceModule": "interface", + "implementationModule": "implementation" + }, + "objects": { + "Greeting": { + "type": "Object", + "properties": { + "message": { + "type": "QString", + "write": true + } + } + } + } +} +``` + +This file describes an binding with one object, `Greeting`. `Greeting` has one property: `message`. It is a writable property. + +The Rust Qt Binding Generator will create binding source code from this description: + +``` +$ rust_qt_binding_generator binding.json +``` + +This will create four files: + +* *src/Binding.h* +* *src/Binding.cpp* +* *rust/src/interface.rs* +* rust/src/implementation.rs + +Only `implementation.rs` should be changed. The other files are the binding. `implementation.rs` is initialy created with a simple implementation that is shown here with some comments. + +```rust +// rust/src/implementation.rs + +use interface::*; + +/// A Greeting +pub struct Greeting { + /// Emit signals to the Qt code. + emit: GreetingEmitter, + /// The message of the greeting. + message: String, +} + +/// Implementation of the binding +/// GreetingTrait is defined in interface.rs +impl GreetingTrait for Greeting { + /// Create a new greeting with default data. + fn new(emit: GreetingEmitter) -> Greeting { + Greeting { + emit: emit, + message: "Hello World!".into(), + } + } + /// The emitter can emit signals to the Qt code. + fn emit(&self) -> &GreetingEmitter { + &self.emit + } + /// Get the message of the Greeting + fn message(&self) -> &str { + &self.message + } + /// Set the message of the Greeting + fn set_message(&mut self, value: String) { + self.message = value; + self.emit.message_changed(); + } +} +``` + +The building block of Qt and QML projects are QObject and the Model View classes. `rust_qt_binding_generator` reads a JSON file to generate QObject or QAbstractItemModel classes that call into generated Rust files. For each type from the JSON file, a Rust trait is generated that should be implemented. + +This way, Rust code can be called from Qt and QML projects. + +### Qt Widgets with Rust + +This C++ code uses the Rust code written above. + +```cpp +#include "Binding.h" +#include +int main() { + Greeting greeting; + qDebug() << greeting.message(); + return 0; +} +``` + +### Qt Quick with Rust + +This Qt Quick (QML) code uses the Rust code written above. + +```qml +Rectangle { + Greeting { + id: rust + } + Text { + text: rust.message + } +} +``` + +## Demo application + +The project comes with a demo application that show a Qt user interface based on Rust. It uses all of the features of Object, List and Tree. Reading the demo code is a good way to get started. + +
+ Qt Widgets UI with Rust logic +
Qt Widgets UI with Rust logic
+
+ +
+ Qt Quick Controls UI with Rust logic +
Qt Quick Controls UI with Rust logic
+
+ +
+ Qt Quick Controls 2 UI with Rust logic +
Qt Quick Controls 2 UI with Rust logic
+
+ +## Docker development environment + +To get started quickly, the project comes with a `Dockerfile`. You can start a docker session with the required dependencies with `./docker/docker-bash-session.sh`. + +## More information + +* [Rust Qt Binding Generator](https://cgit.kde.org/rust-qt-binding-generator.git/about) +* [Qt](http://doc.qt.io/) +* [Qt Examples and tutorials](http://doc.qt.io/qt-5/qtexamplesandtutorials.html) +* [The QML Book](https://qmlbook.github.io/) diff --git a/tutorial/rust_qt_binding_generator.svg b/tutorial/rust_qt_binding_generator.svg new file mode 100644 --- /dev/null +++ b/tutorial/rust_qt_binding_generator.svg @@ -0,0 +1,34 @@ + + + + + + image/svg+xml + + Rust Qt Binding Generator + + + + + + + + + diff --git a/tutorial/time.png b/tutorial/time.png new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@ + Time for Rust and QML +
Time for Rust and QML
+ + +`qmlscene` can run any plain QML files. If you have QML plugins installed, these can be used too. You can make plugins that are implemented in Rust, but we'll not go into that now. + + +## Set up a QML project with Rust + +Before we can replace the `QtObject`, we have to set up a project. Rust Qt Binding Generator comes with a template project for QML in the folder [`templates/qt_quick`](https://commits.kde.org/rust-qt-binding-generator?path=templates/qt_quick). + +You can get set up like so. You will need to have Qt, Rust and CMake installed. + +First build `rust_qt_binding_generator`. + +``` +$ git clone git://anongit.kde.org/rust-qt-binding-generator +$ mkdir build +$ cd rust-qt-binding-generator/build +$ cmake .. +$ make rust_qt_binding_generator +$ export PATH=$PATH:$PWD/src +``` + +Now build and run the template project. + +``` +$ mkdir ../templates/qt_quick/build +$ cd ../templates/qt_quick/build +$ cmake .. +$ make +$ ./MyExe +``` + +You will be greeted with a 'Hello World' application. + + +## Starting from a template + +So what just happened? The template project is based on CMake. CMake is the build system that most KDE projects use. A template in CMake is an example of how to add Rust code to KDE programs. It is possible to use another build system. + +CMake performs four steps as instructed by the `CMakeLists.txt` file. It + +1) generates Rust and C++ from `bindings.json` by calling `rust_qt_binding_generator`, +2) compiles the Rust code in `rust/` into a static library by calling `cargo`, +3) compiles the C++ code, +4) links the C++ objects, the QML files, and the Rust library into an executable. + +If you prefer to use only `cargo`, you'll have to tell it to perform steps 1, 3 and 4 in a `build.js` file. + + +## Adding some Rust + +Now let's turn this clock into the [Antikythera mechanism](https://en.wikipedia.org/wiki/Antikythera_mechanism) by adding some Rust. + +We want the Rust code to have a Time object that indicates the hour, the minute and the second. We write this interface into `bindings.json`. + +```json +{ + "cppFile": "src/Bindings.cpp", + "rust": { + "dir": "rust", + "interfaceModule": "interface", + "implementationModule": "implementation" + }, + "objects": { + "Time": { + "type": "Object", + "properties": { + "hour": { + "type": "quint32" + }, + "minute": { + "type": "quint32" + }, + "second": { + "type": "quint32" + } + } + } + } +} +``` + +Now if we run `make` again, three files will be updated: `src/Bindings.h`, `src/Bindings.cpp`, and `rust/src/interface.rs`. And then we'll get a compile error from `cargo`. + +That is because we have to adapt `rust/src/implementation.rs` to the new `interface.rs`. `interface.rs` specifies a trait that must be implemented in `implementation.rs`. + +This is the generated trait: + +```rust +// rust/src/interface.rs + +pub trait TimeTrait { + fn new(emit: TimeEmitter) -> Self; + fn emit(&self) -> &TimeEmitter; + fn hour(&self) -> u32; + fn minute(&self) -> u32; + fn second(&self) -> u32; +} +``` + +Note that the trait has getters, but no setters. With `"write": true`, you can add setters on properties. + +For now, we implement a fixed time in our new `implementation.rs`. + +```rust +// rust/src/implementation.rs + +use interface::*; + +pub struct Time { + emit: TimeEmitter +} + +impl TimeTrait for Time { + fn new(emit: TimeEmitter) -> Self { + Time { + emit + } + } + fn emit(&self) -> &TimeEmitter { + &self.emit + } + fn hour(&self) -> u32 { + 1 + } + fn minute(&self) -> u32 { + 52 + } + fn second(&self) -> u32 { + 0 + } +} +``` + +Now whenever the QML application wants to know the time, it can ask the Rust code. Well, almost. We have to change three more files and one of them is a C++ file. It is a very simple change and it is needed to tell the QML code about the Rust QObject. In `src/main.cpp`, change this line: + +```c++ + // src/main.cpp + + qmlRegisterType("RustCode", 1, 0, "Simple"); +``` + +to this + +```c++ + // src/main.cpp + + qmlRegisterType