It’s been a week since I announced Rust Qt Binding Generator.
A brief recap. The new project Rust Qt Binding Generator can create bindings between Rust code and Qt code. You can use Rust code to power your Qt application. Or in other words, you can use Qt as a cross-platform user interface for your Rust application.
You describe the interface between Rust and Qt in a simple JSON file. Then you compile the Rust code into a static library that you call from Qt. The user interface is still written in QML or C++. QML in particular is a wonderful language for writing user interfaces. And with these bindings, your Rust code will feel as a natural part of that user interface.
Today, I’ll walk through a very simple application. We’ll pretend that Qt code has no way to figure out the time, so we’ll implement code in Rust to ask the time.
But first an update on the happenings since the announcement of the bindings.
The project is hosted at KDE. This is important to me. KDE is a community where software is created that gives the user control. It is a smoothly running organization that churns out releases of libraries and applications regularly.
Since this week the project is set up on the KDE infrastructure thanks to our admin team. That means bugzilla for bug reports, Phabricator for code browsing and patch review and Jenkins for continuous integration including code coverage.
The first translation commits and bot housekeeping commits have appeared already.
If you want to contribute, please create an account at the KDE community.
If you are new to QML, I can recommend the QML book. It walks you through many wonderful examples. In addition, Qt Creator comes with many more.
It is a tradition in KDE to use clocks as examples. I will follow this tradition and create a widget that shows the time.
We’ll start without any Rust at all. The initial version is only QML. It uses a simple SVG image as the background.
You can download this version and run it locally with
qmlscene
. qmlscene
can run 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.
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.
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.
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.
The QtObject
is a functional placeholder for the Rust
code that we are going to write later.
import QtQuick 2.5
import QtQuick.Window 2.2
Window {width: 512
height: 512
visible: true
// A mock-up of the time object that we will
// implement in Rust
QtObject {
id: time
property int hour
property int minute
property int second
}// This timer will also become Rust code
Timer {
interval: 1000; running: true; repeat: true
onTriggered: {
var date = new Date()
.hour = date.getHours()
time.minute = date.getMinutes()
time.second = date.getSeconds()
time
}
}
// the clock face
Image {
.fill: parent
anchorssource: "rust-logo-blk.svg"
fillMode: Image.PreserveAspectFit
transform: Rotation {
.x: width / 2
origin.y: height / 2
originangle: time.second * 6 // convert seconds to degrees
}
}// the minute hand
Rectangle {
id: minute
x: (parent.width - width) / 2
y: 0
width: parent.width / 100
height: parent.height / 1.8
radius: width
color: "#3daefd"
transform: Rotation {
.x: hour.width / 2
origin.y: height / 2
origin// convert minutes to degrees
angle: time.minute * 6
}
}// the hour hand
Rectangle {
id: hour
x: (parent.width - width) / 2
y: parent.height / 6
width: parent.width / 50
height: parent.height / 2.8
radius: width
color: "#3daefd"
transform: Rotation {
.x: hour.width / 2
origin.y: height / 3
origin// convert hours to degrees
angle: time.hour * 30 + time.minute / 2
}
} }
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
.
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/../target/release
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.
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. It
generates Rust and C++ from bindings.json by calling rust_qt_binding_generator,
compiles the Rust code in rust/ into a static library by calling cargo,
compiles the C++ code,
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.rs
file.
Now let’s turn this clock into the 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
.
{
"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:
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
.
use interface::*;
pub struct Time {
: TimeEmitter
emit}
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:
qmlRegisterType<Simple>("RustCode", 1, 0, "Simple");
to this
qmlRegisterType<Time>("RustCode", 1, 0, "Time");
And we have to add the logo to the qml.qrc
. This file
lists files that should be compiled into the executable.
RCC>
<qresource prefix="/">
<file>main.qml</file>
<file>rust-logo-blk.svg</file>
<qresource>
</RCC> </
Now write this in main.qml
. The
third line imports our Rust object into the application. Our mockup
QtObject
and the Timer
have been replaced with
Time { id: time }
.
This Time
still has the properties hour
,
minute
, and second
. Whenever these change, the
user interface is updated.
import QtQuick 2.5
import QtQuick.Window 2.2
import RustCode 1.0
Window {width: 512
height: 512
visible: true
// here is our Rust time
Time {id: time
}
// the clock face
Image {
.fill: parent
anchorssource: "rust-logo-blk.svg"
fillMode: Image.PreserveAspectFit
transform: Rotation {
.x: width / 2
origin.y: height / 2
originangle: time.second * 6 // convert seconds to degrees
}
}// the minute hand
Rectangle {
id: minute
x: (parent.width - width) / 2
y: 0
width: parent.width / 100
height: parent.height / 1.8
radius: width
color: "#3daefd"
transform: Rotation {
.x: hour.width / 2
origin.y: height / 2
origin// convert minutes to degrees
angle: time.minute * 6
}
}// the hour hand
Rectangle {
id: hour
x: (parent.width - width) / 2
y: parent.height / 6
width: parent.width / 50
height: parent.height / 2.8
radius: width
color: "#3daefd"
transform: Rotation {
.x: hour.width / 2
origin.y: height / 3
origin// convert hours to degrees
angle: time.hour * 30 + time.minute / 2
}
} }
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.
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
.
This code goes in implementation.rs
. A thread wakes up
every second and sends a signal to the user interface whenever a
property changes.
use interface::*;
use chrono::{Local, Timelike};
use std::thread;
use std::time::Duration;
pub struct Time {
: TimeEmitter,
emit}
fn emit_time(emit: TimeEmitter) {
thread::spawn(move || {
loop {
thread::sleep(Duration::from_secs(1));
.second_changed();
emitif Local::now().second() == 0 {
.minute_changed();
emitif Local::now().minute() == 0 {
.hour_changed();
emit}
}
}
});
}
impl TimeTrait for Time {
fn new(emit: TimeEmitter) -> Self {
.clone());
emit_time(emit{
Time
emit}
}
fn emit(&self) -> &TimeEmitter {
&self.emit
}
fn hour(&self) -> u32 {
Local::now().hour()
}
fn minute(&self) -> u32 {
Local::now().minute()
}
fn second(&self) -> u32 {
Local::now().second()
}
}
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.
Next time, we will go into Model/View programming. We’ll create a
list in Rust that can be used in many Qt widgets. If you cannot wait
until then, please have a look at the code in demo/
.
Comments
Post a comment