Ayush
Posted on October 22, 2021
Introduction
I have been a KDE user for a long time and have been getting into contributing to KDE. I also love Rust Programming Language. With the release of GTK4, the Rust bindings for GTK have become official and are pretty great to use. However, Qt bindings for Rust are still relatively limited or not practical. So I decided to improve the situation as much as I could instead of just complaining since I have a lot of time.
I also have a few applications I would like to create, so this works out perfectly. I am not sure how long this blog series will be, but it might be a long one (unless I give up).
Application
The application can be found here. It is supposed to be a tracker for things like Visual Novels, Anime and Manga. Maybe it will start including other regular trackers at some point, but that is not the goal. Also, the application should look native in KDE.
Goals
- Assess and improve the state of writing QML applications in Rust.
- Create general purpose rust bindings for the KDE frameworks as needed.
- Learn more about QT and KDE development.
- Package as Flatpak
Initial Setup
Setup QMAKE in Fedora
Qtmetaobject.rs use Qmake for searching for qt install and other things. qmake is available as qmake-qt5 in Fedora, so we need to set the environment variable. I just put it in my .zshrc
as:
export QMAKE=/usr/bin/qmake-qt5
System Dependencies
Since I already have most qt5 dependencies in my system, I am unsure what needs to be installed on a new system. I will update this section if I start fresh again.
Rust Setup
Create Project
This step is as simple as running:
cargo new weebtk
Adding Dependencies
Add the following in the Cargo.toml
file:
[dependencies]
qmetaobject = "0.2"
cstr = "0.2"
Add Starter Code
This is taken directly from qtmetaobject-rs Readme:
use cstr::cstr;
use qmetaobject::prelude::*;
// The `QObject` custom derive macro allows to expose a class to Qt and QML
#[derive(QObject, Default)]
struct Greeter {
// Specify the base class with the qt_base_class macro
base: qt_base_class!(trait QObject),
// Declare `name` as a property usable from Qt
name: qt_property!(QString; NOTIFY name_changed),
// Declare a signal
name_changed: qt_signal!(),
// And even a slot
compute_greetings: qt_method!(fn compute_greetings(&self, verb: String) -> QString {
format!("{} {}", verb, self.name.to_string()).into()
})
}
fn main() {
// Register the `Greeter` struct to QML
qml_register_type::<Greeter>(cstr!("Greeter"), 1, 0, cstr!("Greeter"));
// Create a QML engine from rust
let mut engine = QmlEngine::new();
// (Here the QML code is inline, but one can also load from a file)
engine.load_data(r#"
import QtQuick 2.6
import QtQuick.Window 2.0
// Import our Rust classes
import Greeter 1.0
Window {
visible: true
// Instantiate the rust struct
Greeter {
id: greeter;
// Set a property
name: "World"
}
Text {
anchors.centerIn: parent
// Call a method
text: greeter.compute_greetings("hello")
}
}
"#.into());
engine.exec();
}
We can run the project now but I would like to add one more step to this post.
Extract Qml to ui/main.qml
Qml files can be rendered separately and have imperative programming capabilities. So it stands to reason that almost every code will separate the qml and rust code. While it is possible to directly load file from path, I decided to add instructions on how to use the Qt resource system.
- First create the file
ui/main.qml
at the project root (same level as src) with the qml contents we were loading earlier:
import QtQuick 2.6
import QtQuick.Window 2.0
// Import our Rust classes
import Greeter 1.0
Window {
visible: true
// Instantiate the rust struct
Greeter {
id: greeter;
// Set a property
name: "World"
}
Text {
anchors.centerIn: parent
// Call a method
text: greeter.compute_greetings("hello")
}
}
- Modify
main.rs
:
use cstr::cstr;
use qmetaobject::prelude::*;
use qmetaobject::QUrl;
// The `QObject` custom derive macro allows to expose a class to Qt and QML
#[derive(QObject, Default)]
struct Greeter {
// Specify the base class with the qt_base_class macro
base: qt_base_class!(trait QObject),
// Declare `name` as a property usable from Qt
name: qt_property!(QString; NOTIFY name_changed),
// Declare a signal
name_changed: qt_signal!(),
// And even a slot
compute_greetings: qt_method!(fn compute_greetings(&self, verb: String) -> QString {
format!("{} {}", verb, self.name.to_string()).into()
})
}
qrc!(root_qml,
"" {
"ui/main.qml" as "main.qml",
}
);
fn main() {
// Register the `Greeter` struct to QML
qml_register_type::<Greeter>(cstr!("Greeter"), 1, 0, cstr!("Greeter"));
// Create a QML engine from rust
let mut engine = QmlEngine::new();
// Load Resource
root_qml();
engine.load_url(QUrl::from(QString::from("qrc:///main.qml")));
engine.exec();
}
Run the Project
Finally, we can run the project using:
cargo run
If everything goes well, you should see a sample application with "hello World".
Conclusion
This should be enough to get everything setup for actually starting with creating the application. In the next section, I will probably take a look at setting up the logging side of things since I find that logging is extremely important but can be forgotten since it is mostly already setup.
Posted on October 22, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.