Getting started with Rust + QT

Getting started with Rust + QT

Being new to Rust, I'm starting with various simple examples to understand some of the dynamics. I came across CxxQT in a good deal of searches and decided to try this out as well.

Note that I had some trouble with this one, so the instructions may be spotty.

I originally started with some examples from KDAB (https://github.com/KDAB/cxx-qt/blob/main/examples/qml_minimal/CMakeLists.txt), but was unable to resolve dependency issues for Windows.

I stumbled upon this article https://blog.logrocket.com/build-desktop-app-qt-rust/ which has several cargo-only examples.

Some additional notes:

  • I'm using the following:

    • Windows 11

    • Visual Studio 2022 Community Edition

    • QT Community 4.6.0 (qt-unified-windows-x64-4.6.0-online.exe)

  • The initial build failed with an error finding qtcore.lib

    • I believe that this is because the default install used mingw, but I was using MSVC.
  • I ended up cloning and building QT6 per the following instructions: https://wiki.qt.io/Building_Qt_6_from_Git and then adding the output bin and lib directories to my PATH.

QT Build Instruction Summary

The following steps were performed on Windows 11. I installed dependencies as needed.

  • New installs: Raspberry Perl, Ninja.

  • I did not install Bison or Flex so the QtWebEngine and QtPdf was not built

# clone the repo... yes, that is the right command. I thought it was a typo at first also.
git clone https://code.qt.io/qt/qt5.git qt6
cd qt6
git switch dev
perl init-repository

# From "x64 Native Tools Command Prompt for VS 2022"
mkdir qt6-build
cd qt6-build
..\qt6\configure.bat -prefix C:\Projects\qt\output
cmake --build .
cmake --install .

Example 1

Source

main.rs

// src/main.rs

mod cxxqt_object;

use cxx_qt_lib::{QGuiApplication, QQmlApplicationEngine, QUrl};

fn main() {
    // Create the application and engine
    let mut app = QGuiApplication::new();
    let mut engine = QQmlApplicationEngine::new();

    // Load the QML path into the engine
    if let Some(engine) = engine.as_mut() {
        engine.load(&QUrl::from("qrc:/main.qml"));
    }

    // Start the app
    if let Some(app) = app.as_mut() {
        app.exec();
    }
}

qml/main.qml

// qml/main.qml

import QtQuick.Controls 2.12
import QtQuick.Window 2.12

// This must match the qml_uri and qml_version
// specified with the #[cxx_qt::qobject] macro in Rust.
import hellorustqtcargo 1.0

Window {
    title: qsTr("Hello App")
    visible: true
    height: 480
    width: 640
    color: "#e4af79"

    Hello {
        id: hello
    }

    Column {
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.verticalCenter: parent.verticalCenter
        /* space between widget */
        spacing: 10

        Button {
            text: "Say Hello!"
            onClicked: hello.sayHello()
        }
    }
}

Output

Example 2

I modified the QML only per the layout example (https://doc.qt.io/qt-6/qtquick-layouts-example.html#running-the-example).

  • original content found here from QT install: C:\Qt\Examples\Qt-6.5.1\quick\layouts

  • I modified to work with the existing cxx-qt project.

// qml/main.qml
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

import QtQuick.Controls 2.12
import QtQuick.Window 2.12
import QtQuick.Layouts 2.12

// This must match the qml_uri and qml_version
// specified with the #[cxx_qt::qobject] macro in Rust.
import hellorustqtcargo 1.0

ApplicationWindow {
    id: appWindow

    visible: true
    title: "Basic layouts"
    property int margin: 11

    Hello {
        id: hello
    }

    Component.onCompleted: {
        width = mainLayout.implicitWidth + 2 * margin
        height = mainLayout.implicitHeight + 2 * margin
    }

    minimumWidth: mainLayout.Layout.minimumWidth + 2 * margin
    minimumHeight: mainLayout.Layout.minimumHeight + 2 * margin

    ColumnLayout {
        id: mainLayout
        anchors.fill: parent
        anchors.margins: appWindow.margin
        GroupBox {
            id: rowBox
            title: "Row layout"
            Layout.fillWidth: true
            Layout.minimumWidth: rowLayout.Layout.minimumWidth + 30

            RowLayout {
                id: rowLayout
                anchors.fill: parent
                TextField {
                    placeholderText: "This wants to grow horizontally"
                    Layout.fillWidth: true
                }
                Button {
                    text: "Button"
                    onClicked: hello.sayHello()
                }
            }
        }

        GroupBox {
            id: gridBox
            title: "Grid layout"
            Layout.fillWidth: true
            Layout.minimumWidth: gridLayout.Layout.minimumWidth + 30

            GridLayout {
                id: gridLayout
                rows: 3
                flow: GridLayout.TopToBottom
                anchors.fill: parent

                Label { text: "Line 1" }
                Label { text: "Line 2" }
                Label { text: "Line 3" }

                TextField { }
                TextField { }
                TextField { }

                TextArea {
                    text: "This widget spans over three rows in the GridLayout.\n"
                        + "All items in the GridLayout are implicitly positioned from top to bottom."
                    wrapMode: TextArea.WordWrap
                    Layout.rowSpan: 3
                    Layout.fillHeight: true
                    Layout.fillWidth: true
                    Layout.minimumHeight: implicitHeight
                    Layout.minimumWidth: 100     // guesstimate, should be size of largest word
                }
            }
        }
        TextArea {
            id: t3
            text: "This fills the whole cell"
            Layout.minimumHeight: 30
            Layout.fillHeight: true
            Layout.fillWidth: true
        }
        GroupBox {
            id: stackBox
            title: "Stack layout"
            implicitWidth: 200
            implicitHeight: 60
            Layout.minimumHeight: 60
            Layout.fillWidth: true
            Layout.fillHeight: true
            StackLayout {
                id: stackLayout
                anchors.fill: parent

                Repeater {
                    id: stackRepeater
                    model: 5
                    Rectangle {
                        required property int index
                        color: Qt.hsla((0.5 + index) / stackRepeater.count, 0.3, 0.7, 1)
                        Button {
                            anchors.centerIn: parent
                            text: "Page " + (parent.index + 1)
                            onClicked: hello.sayHello()
                        }
                    }
                }
            }
        }
    }
}

Conclusion

The QT front-end married with Rust on the back end seems to have some real potential.

Source: github.com/ericjameszimmerman/rustbasics