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.