Warning, /sdk/rust-qt-binding-generator/tutorial/time_for_rust_and_qml.md is written in an unsupported language. File is not indexed.
0001 # Time for QML
0002
0003 If you are new to QML, I can recommend the [QML book](https://qmlbook.github.io/). It walks you through many wonderful examples. In addition, Qt Creator comes with [many more](https://doc.qt.io/qt-5/qtexamples.html).
0004
0005 It is a tradition in KDE to use clocks as examples. I will follow this tradition and create a widget that shows the time.
0006
0007 We'll start without any Rust at all. The first code is only QML. It uses the Rust logo as SVG image for the background, [`rust-logo-blk.svg`](https://www.rust-lang.org/logos/rust-logo-blk.svg). So download this file (or pick another SVG image and store it with that name).
0008
0009 The syntax of QML is declarative. The rotation of the Rust logo is given by the statement `angle: time.second * 6`. This is a binding. The value of `angle` updates automatically whenever `time.second` changes. The rotation of the logo changes every second because of this declarative binding.
0010
0011 Another example is `anchors.fill: parent` on the `Image` item. This means that the image takes up the same rectangular space as the parent item. If that item is resized, the image will scale along with it.
0012
0013 In this file, we added a temporary `QtObject` with properties `hour`, `minute` and `second`. The values in this object are updated every second by the `Timer` item. The JavaScript code between `{}` runs every second and updates the values in the `QtObject`. This object has `id: time` and the logo and hands are bound to this object.
0014
0015 The `QtObject` is a functional placeholder for the Rust code that we are going to write later.
0016
0017 ```qml
0018 // just_qml.qml
0019
0020 import QtQuick 2.5
0021 import QtQuick.Window 2.2
0022
0023 Window {
0024 width: 512
0025 height: 512
0026 visible: true
0027
0028 // A mock-up of the time object that we will
0029 // implement in Rust
0030 QtObject {
0031 id: time
0032 property int hour
0033 property int minute
0034 property int second
0035 }
0036 // This timer will also become Rust code
0037 Timer {
0038 interval: 1000; running: true; repeat: true
0039 onTriggered: {
0040 var date = new Date()
0041 time.hour = date.getHours()
0042 time.minute = date.getMinutes()
0043 time.second = date.getSeconds()
0044 }
0045 }
0046
0047 // the clock face
0048 Image {
0049 anchors.fill: parent
0050 source: "rust-logo-blk.svg"
0051 sourceSize: Qt.size(width, height) // ensure rendered SVG canvas matches used size
0052 fillMode: Image.PreserveAspectFit
0053 transform: Rotation {
0054 origin.x: width / 2
0055 origin.y: height / 2
0056 angle: time.second * 6 // convert seconds to degrees
0057 }
0058 }
0059 // the minute hand
0060 Rectangle {
0061 id: minute
0062 x: (parent.width - width) / 2
0063 y: 0
0064 width: parent.width / 100
0065 height: parent.height / 1.8
0066 radius: width
0067 color: "#3daefd"
0068 transform: Rotation {
0069 origin.x: hour.width / 2
0070 origin.y: height / 2
0071 // convert minutes to degrees
0072 angle: time.minute * 6
0073 }
0074 }
0075 // the hour hand
0076 Rectangle {
0077 id: hour
0078 x: (parent.width - width) / 2
0079 y: parent.height / 6
0080 width: parent.width / 50
0081 height: parent.height / 2.8
0082 radius: width
0083 color: "#3daefd"
0084 transform: Rotation {
0085 origin.x: hour.width / 2
0086 origin.y: height / 3
0087 // convert hours to degrees
0088 angle: time.hour * 30 + time.minute / 2
0089 }
0090 }
0091 }
0092 ```
0093
0094 You can run the plain QML file with the tool `qmlscene`.
0095
0096 ```
0097 $ qmlscene just_qml.qml
0098 ```
0099
0100
0101 <figure>
0102 <img src="time.png" alt="Time for Rust and QML"/>
0103 <figcaption>Time for Rust and QML</figcaption>
0104 </figure>
0105
0106 `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.
0107
0108
0109 ## Set up a QML project with Rust
0110
0111 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).
0112
0113 You can get set up like so. You will need to have Qt, Rust and CMake installed.
0114
0115 First build `rust_qt_binding_generator`.
0116
0117 ```
0118 $ git clone git://anongit.kde.org/rust-qt-binding-generator
0119 $ mkdir build
0120 $ cd rust-qt-binding-generator/build
0121 $ cmake ..
0122 $ make rust_qt_binding_generator
0123 $ export PATH=$PATH:$PWD/src
0124 ```
0125
0126 `cmake ..` uses `make` by default, but you can use another build tool, for example Ninja, like this: `cmake -GNinja ..`.
0127
0128 Now build and run the template project.
0129
0130 ```
0131 $ mkdir ../templates/qt_quick/build
0132 $ cd ../templates/qt_quick/build
0133 $ cmake ..
0134 $ make
0135 $ ./MyExe
0136 ```
0137
0138 You will be greeted with a 'Hello World' application.
0139
0140
0141 ## Starting from a template
0142
0143 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.
0144
0145 CMake performs four steps as instructed by the `CMakeLists.txt` file. It
0146
0147 1) generates Rust and C++ from `bindings.json` by calling `rust_qt_binding_generator`,
0148 2) compiles the Rust code in `rust/` into a static library by calling `cargo`,
0149 3) compiles the C++ code,
0150 4) links the C++ objects, the QML files, and the Rust library into an executable.
0151
0152 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.
0153
0154
0155 ## Adding some Rust
0156
0157 Now let's turn this clock into the [Antikythera mechanism](https://en.wikipedia.org/wiki/Antikythera_mechanism) by adding some Rust.
0158
0159 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`.
0160
0161 ```json
0162 {
0163 "cppFile": "src/Bindings.cpp",
0164 "rust": {
0165 "dir": "rust",
0166 "interfaceModule": "interface",
0167 "implementationModule": "implementation"
0168 },
0169 "objects": {
0170 "Time": {
0171 "type": "Object",
0172 "properties": {
0173 "hour": {
0174 "type": "quint32"
0175 },
0176 "minute": {
0177 "type": "quint32"
0178 },
0179 "second": {
0180 "type": "quint32"
0181 }
0182 }
0183 }
0184 }
0185 }
0186 ```
0187
0188 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`.
0189
0190 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`.
0191
0192 This is the generated trait:
0193
0194 ```rust
0195 // rust/src/interface.rs
0196
0197 pub trait TimeTrait {
0198 fn new(emit: TimeEmitter) -> Self;
0199 fn emit(&self) -> &TimeEmitter;
0200 fn hour(&self) -> u32;
0201 fn minute(&self) -> u32;
0202 fn second(&self) -> u32;
0203 }
0204 ```
0205
0206 Note that the trait has getters, but no setters. With `"write": true`, you can add setters on properties.
0207
0208 For now, we implement a fixed time in our new `implementation.rs`.
0209
0210 ```rust
0211 // rust/src/implementation.rs
0212
0213 use interface::*;
0214
0215 pub struct Time {
0216 emit: TimeEmitter
0217 }
0218
0219 impl TimeTrait for Time {
0220 fn new(emit: TimeEmitter) -> Self {
0221 Time {
0222 emit
0223 }
0224 }
0225 fn emit(&self) -> &TimeEmitter {
0226 &self.emit
0227 }
0228 fn hour(&self) -> u32 {
0229 1
0230 }
0231 fn minute(&self) -> u32 {
0232 52
0233 }
0234 fn second(&self) -> u32 {
0235 0
0236 }
0237 }
0238 ```
0239
0240 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:
0241
0242 ```c++
0243 // src/main.cpp
0244
0245 qmlRegisterType<Simple>("RustCode", 1, 0, "Simple");
0246 ```
0247
0248 to this
0249
0250 ```c++
0251 // src/main.cpp
0252
0253 qmlRegisterType<Time>("RustCode", 1, 0, "Time");
0254 ```
0255
0256 Next we add the Rust logo to the program, by copying the file `rust-logo-blk.svg` into the toplevel dir of the template and noting it as resource in `qml.qrc`. That file lists files that should be compiled into the executable.
0257
0258 ```xml
0259 <RCC>
0260 <qresource prefix="/">
0261 <file>main.qml</file>
0262 <file>rust-logo-blk.svg</file>
0263 </qresource>
0264 </RCC>
0265 ```
0266
0267 Now create the file `main.qml`. The line `import RustCode 1.0` imports our Rust object into the application. Our mockup `QtObject` and the `Timer` have been replaced with `Time { id: time }`.
0268
0269 This `Time` still has the properties `hour`, `minute`, and `second`. Whenever these change, the user interface is updated.
0270
0271 ```qml
0272 // main.qml
0273
0274 import QtQuick 2.5
0275 import QtQuick.Window 2.2
0276 import RustCode 1.0
0277
0278 Window {
0279 width: 512
0280 height: 512
0281 visible: true
0282
0283 // here is our Rust time
0284 Time {
0285 id: time
0286 }
0287
0288 // the clock face
0289 Image {
0290 anchors.fill: parent
0291 source: "rust-logo-blk.svg"
0292 sourceSize: Qt.size(width, height) // ensure rendered SVG canvas matches used size
0293 fillMode: Image.PreserveAspectFit
0294 transform: Rotation {
0295 origin.x: width / 2
0296 origin.y: height / 2
0297 angle: time.second * 6 // convert seconds to degrees
0298 }
0299 }
0300 // the minute hand
0301 Rectangle {
0302 id: minute
0303 x: (parent.width - width) / 2
0304 y: 0
0305 width: parent.width / 100
0306 height: parent.height / 1.8
0307 radius: width
0308 color: "#3daefd"
0309 transform: Rotation {
0310 origin.x: hour.width / 2
0311 origin.y: height / 2
0312 // convert minutes to degrees
0313 angle: time.minute * 6
0314 }
0315 }
0316 // the hour hand
0317 Rectangle {
0318 id: hour
0319 x: (parent.width - width) / 2
0320 y: parent.height / 6
0321 width: parent.width / 50
0322 height: parent.height / 2.8
0323 radius: width
0324 color: "#3daefd"
0325 transform: Rotation {
0326 origin.x: hour.width / 2
0327 origin.y: height / 3
0328 // convert hours to degrees
0329 angle: time.hour * 30 + time.minute / 2
0330 }
0331 }
0332 }
0333 ```
0334
0335
0336 ## Start the time
0337
0338 <figure>
0339 <img src="happy_time.png" alt="The time is now"/>
0340 <figcaption>A happy clock</figcaption>
0341 </figure>
0342
0343 Are you still here? That was quite a few instructions to follow for a simple example. The good news is that this setup does not get harder when you add more interfaces.
0344
0345 Anyway, now the part you've been waiting for. We will let Rust update the time and send it to the user interface. The crate `chrono` is used to get the time. Add it to `lib.rs` and `Cargo.toml`.
0346
0347 ```toml
0348 # rust/Cargo.toml
0349 ...
0350
0351 [dependencies]
0352 chrono = "*"
0353 ...
0354 ```
0355
0356 ```rust
0357 // rust/src/lib.rs
0358 ...
0359 extern crate chrono;
0360 ...
0361 ```
0362
0363 This code goes in `implementation.rs`. A thread wakes up every second and sends a signal to the user interface whenever a property changes.
0364
0365 ```rust
0366 // rust/src/implementation.rs
0367
0368 use interface::*;
0369 use chrono::{Local, Timelike};
0370 use std::thread;
0371 use std::time::Duration;
0372
0373 pub struct Time {
0374 emit: TimeEmitter,
0375 }
0376
0377 fn emit_time(emit: TimeEmitter) {
0378 thread::spawn(move || {
0379 loop {
0380 thread::sleep(Duration::from_secs(1));
0381 emit.second_changed();
0382 if Local::now().second() == 0 {
0383 emit.minute_changed();
0384 if Local::now().minute() == 0 {
0385 emit.hour_changed();
0386 }
0387 }
0388 }
0389 });
0390 }
0391
0392 impl TimeTrait for Time {
0393 fn new(emit: TimeEmitter) -> Self {
0394 emit_time(emit.clone());
0395 Time {
0396 emit
0397 }
0398 }
0399 fn emit(&self) -> &TimeEmitter {
0400 &self.emit
0401 }
0402 fn hour(&self) -> u32 {
0403 Local::now().hour()
0404 }
0405 fn minute(&self) -> u32 {
0406 Local::now().minute()
0407 }
0408 fn second(&self) -> u32 {
0409 Local::now().second()
0410 }
0411 }
0412 ```
0413
0414
0415 ## Closing remarks
0416
0417 This was a pretty long tutorial with quite a few different parts. That was the point of the tutorial: to learn the parts that make up a binding between Qt and Rust.