Writing a QML Application in Rust (Part 1)
Tags: rust, qml
Published: 2021-10-22 04:17:27

## 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](https://github.com/Ayush1325/weebtk). 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
1. Assess and improve the state of writing QML applications in Rust. 
2. Create general purpose rust bindings for the KDE frameworks as needed.
3. Learn more about QT and KDE development.
4. 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:
```sh
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:
```toml
[dependencies]
qmetaobject = "0.2"
cstr = "0.2"
```

### Add Starter Code
This is taken directly from qtmetaobject-rs Readme:
```rust
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.
1. First create the file `ui/main.qml` at the project root (same level as src) with the qml contents we were loading earlier:
```qml
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")
    }
}
```
2. Modify `main.rs`:
```rust
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.