Skip to content

Commit 7a3c019

Browse files
committed
Add Rust setup for Kirigami tutorial
This adds a Rust setup for the Kirigami tutorial. The idea is that we'd have an introductory setup in the Kirigami tutorial and a more complete tutorial in a new section "Rust with Kirigami". This MR is fairly complete regardless, but I intend to simplify it and move the more complex things to the new section later. Unfortunately there are two problems, although I don't consider them blockers: * the current cxx-qt QmlModule doesn't quite match what we do in this tutorial (where we only have an entrypoint and load a QML file, without exposing anything from business logic to QML), so it required a dummy struct and QObject, which is a workaround * engine.load() is, expectedly, very very very long and I can't wait for KDAB/cxx-qt#1141 tbh This tutorial **doesn't** use the full cxx-qt CMake API mentioned in https://kdab.github.io/cxx-qt/book/getting-started/5-cmake-integration.html, opting instead for just basic CMake to keep things simple and focused on the necessary install / ECM stuff. New devs wanting to make a Qt Rust app won't really care about using C++ stuff (they'll use cxx-qt for their Qt needs and cxx-kde-frameworks for their KDE Frameworks needs once we make a tutorial for it), and they'll certainly find it weird to initialize the app with a C++ main.cpp for a Rust-only app. We can use the cxx-qt CMake API if it somehow allows to fix the first problem listed above, though.
1 parent 81e0567 commit 7a3c019

File tree

8 files changed

+307
-0
lines changed

8 files changed

+307
-0
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
cmake_minimum_required(VERSION 3.28)
2+
3+
project(kirigami_rust)
4+
5+
find_package(ECM 6.0 REQUIRED NO_MODULE)
6+
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
7+
include(KDEInstallDirs)
8+
include(ECMUninstallTarget)
9+
10+
include(ECMFindQmlModule)
11+
ecm_find_qmlmodule(org.kde.kirigami REQUIRED)
12+
find_package(KF6 REQUIRED COMPONENTS QQC2DesktopStyle)
13+
14+
add_custom_target(kirigami_rust
15+
ALL
16+
COMMAND cargo build --target-dir ${CMAKE_CURRENT_BINARY_DIR}
17+
)
18+
19+
install(
20+
PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/debug/kirigami_hello
21+
DESTINATION ${KDE_INSTALL_BINDIR}
22+
)
23+
24+
install(FILES org.kde.kirigami_rust.desktop DESTINATION ${KDE_INSTALL_APPDIR})
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "kirigami_hello"
3+
version = "0.1.0"
4+
authors = [ "Konqi the Konqueror <[email protected]>" ]
5+
edition = "2021"
6+
license = "GPLv3"
7+
8+
[dependencies]
9+
cxx = "1.0.95"
10+
cxx-qt = "0.7"
11+
cxx-qt-lib = { version="0.7", features = ["qt_full"] }
12+
cxx-qt-lib-extras = "0.7"
13+
14+
[build-dependencies]
15+
# The link_qt_object_files feature is required for statically linking Qt 6.
16+
cxx-qt-build = { version = "0.7", features = [ "link_qt_object_files" ] }
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
use cxx_qt_build::{CxxQtBuilder, QmlModule};
2+
3+
fn main() {
4+
CxxQtBuilder::new()
5+
.qml_module(QmlModule {
6+
uri: "org.kde.tutorial",
7+
qml_files: &["src/qml/Main.qml"],
8+
rust_files: &["src/main.rs"],
9+
..Default::default()
10+
})
11+
.build();
12+
}
Binary file not shown.
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
---
2+
title: Kirigami with Rust
3+
weight: 3
4+
group: setup
5+
description: >
6+
Create your first Kirigami application with Rust
7+
aliases:
8+
- /docs/getting-started/kirigami/setup-rust/
9+
---
10+
11+
## Installing Kirigami
12+
13+
Before getting started, we will need to install Kirigami and Rust on our machine.
14+
15+
{{< installpackage
16+
arch="cargo cmake extra-cmake-modules kirigami breeze qqc2-desktop-style"
17+
opensuse="cargo cmake kf6-extra-cmake-modules kf6-kirigami-devel qt6-quickcontrols2-devel kf6-qqc2-desktop-style"
18+
fedora="cargo cmake extra-cmake-modules kf6-kirigami2-devel kf6-qqc2-desktop-style" >}}
19+
20+
Further information for other distributions can be found [here](/docs/getting-started/building/help-dependencies).
21+
22+
## Project structure
23+
24+
First we create our project folder (you can use the commands below). We are going to call ours `kirigami_rust/`. This will be the project's structure:
25+
26+
```
27+
kirigami_rust/
28+
├── CMakeLists.txt
29+
├── Cargo.toml
30+
├── build.rs
31+
├── org.kde.kirigami_rust.desktop
32+
└── src/
33+
├── main.rs
34+
└── qml/
35+
└── Main.qml
36+
```
37+
38+
This project will use CMake to call Cargo, which in turn will build the project.
39+
40+
{{< alert title="About CMake and Cargo" color="info" >}}
41+
42+
This is **not** the traditional way of building a Rust project: technically, only Cargo is needed to build it, usually with `cargo build` and `cargo run`.
43+
44+
For desktop applications however, CMake (or an equivalent like [Meson](https://mesonbuild.com/) used by GNOME or [Just](https://just.systems/man/en/introduction.html) used by COSMIC) is needed to install because Cargo lacks the necessary features to install GUI desktop applications.
45+
46+
{{< /alert >}}
47+
48+
The project will be called `kirigami_rust` and it will generate an executable called `kirigami_hello`.
49+
50+
{{< alert title="💡 Tip" color="success" >}}
51+
52+
You can quickly create this file structure with: `mkdir -p kirigami_rust/src/qml/`.
53+
54+
{{< /alert >}}
55+
56+
### org.kde.kirigami_rust.desktop {#desktop-file}
57+
58+
The primary purpose of [Desktop Entry files](https://specifications.freedesktop.org/desktop-entry-spec/latest/) is to show your app on the application launcher on Linux. Another reason to have them is to have window icons on Wayland, as they are required to tell the compositor "this window goes with this icon".
59+
60+
It must follow a [reverse-DNS naming scheme](https://en.wikipedia.org/wiki/Reverse_domain_name_notation) followed by the `.desktop` extension such as `org.kde.kirigami_rust.desktop`:
61+
62+
{{< readfile file="/content/docs/getting-started/kirigami/setup-rust/org.kde.kirigami_rust.desktop" highlight="ini" >}}
63+
64+
### CMakeLists.txt {#cmakelists}
65+
66+
The `CMakeLists.txt` file is going to be used to run Cargo and to install the necessary files together with our application. It also provides certain quality of life features, such as making sure that Kirigami is installed during compilation and to signal Linux distributions to install the necessary dependencies with the application.
67+
68+
{{< readfile file="/content/docs/getting-started/kirigami/setup-rust/CMakeLists.txt" highlight="cmake" >}}
69+
70+
The first thing we do is add KDE's [Extra CMake Modules (ECM)](https://api.kde.org/ecm/manual/ecm.7.html) to our project so we can use [ecm_find_qml_module](https://api.kde.org/ecm/module/ECMFindQmlModule.html) to check that Kirigami is installed when trying to build the application, and if it's not, fail immediately. Another useful ECM feature is [ECMUninstallTarget](https://api.kde.org/ecm/module/ECMUninstallTarget.html), which allows to easily uninstall the application with CMake if desired.
71+
72+
We also use CMake's [find_package()](https://cmake.org/cmake/help/latest/command/find_package.html) to make sure we have [qqc2-desktop-style](https://invent.kde.org/frameworks/qqc2-desktop-style), KDE's QML style for the desktop. This is one of the two reasons we use CMake in this tutorial.
73+
74+
Typically Rust projects are built with Cargo, and it won't be different here. We create a target that will simply run Cargo when run, and mark it with `ALL` so it builds by default. Cargo will build the executable inside CMake's binary directory (typically `build/`).
75+
76+
For more information about CMake, targets, and the binary directory, see [Building KDE software manually](/docs/getting-started/building/cmake-build).
77+
78+
After this, we simply install the `kirigami_rust` executable generated by Cargo in the binary directory and install it to the `BINDIR`, which is usually `/usr/bin`, `/usr/local/bin` or `~/.local/bin`. We also install the necessary desktop file to `APPDIR`, which is usually `/usr/share/applications` or `~/.local/share/applications`. This is the second reason we use CMake in this tutorial.
79+
80+
For more information about where KDE software is installed, see [Building KDE software manually: The install step](/docs/getting-started/building/cmake-build/#install).
81+
82+
Now that CMake has been taken care of, let's look at the files we are going to spend the majority of our time working with.
83+
84+
### Cargo.toml {#cargo-toml}
85+
86+
Next we have a very straightforward `Cargo.toml`:
87+
88+
{{< readfile file="/docs/getting-started/kirigami/setup-rust/Cargo.toml" highlight="toml" >}}
89+
90+
It consists of project metadata and a list of dependencies that will be pulled automatically by Cargo, namely `cxx` and `cxx-qt`, which are necessary to run Qt applications written in Rust.
91+
92+
### build.rs {#build-rs}
93+
94+
Where in C++ you'd typically register QML elements with [QML_ELEMENT](https://doc.qt.io/qt-6/qtqml-cppintegration-definetypes.html) and [ecm_add_qml_module](https://api.kde.org/ecm/module/ECMQmlModule.html) using [declarative registration](https://www.qt.io/blog/qml-type-registration-in-qt-5.15), with Rust you'll need to declare it in a [build script build.rs](https://doc.rust-lang.org/cargo/reference/build-scripts.html) file:
95+
96+
{{< readfile file="/docs/getting-started/kirigami/setup-rust/build.rs" highlight="rust" >}}
97+
98+
This is necessary to make the QML file available in the entrypoint for our application, `main.rs`.
99+
100+
### src/main.rs {#main-rs}
101+
102+
The file `kirigami_rust/src/main.rs` initializes the project and then loads the QML file, which will consist of the user interface for the application.
103+
104+
{{< readfile file="/content/docs/getting-started/kirigami/setup-rust/src/main.rs" highlight="rust" emphasize="12-13 17 28-30" >}}
105+
106+
The first part that is marked with the [#[cxx_qt::bridge]](https://kdab.github.io/cxx-qt/book/bridge/index.html) Rust macro just creates a dummy QObject out of a dummy Rust struct. This is needed just to complete the use of [QmlModule](https://docs.rs/cxx-qt-build/latest/cxx_qt_build/struct.QmlModule.html) in the previous build script `build.rs`. This will play a larger part in a future tutorial teaching how to expose Rust code to QML, but for now you can ignore it.
107+
108+
After this starts the important part:
109+
110+
Lines 12-13 import the needed Qt libraries exposed through cxx-qt.
111+
112+
We first create a new instance of a `QApplication`, then perform a few integrations in lines 20-26.
113+
114+
Then comes the part that actually creates the application window:
115+
116+
{{< readfile file="/content/docs/getting-started/kirigami/setup-rust/src/main.rs" highlight="rust" start=28 lines=3 >}}
117+
118+
The long URL `qrc:/qt/qml/org/kde/tutorial/src/qml/Main.qml` corresponds to the `Main.qml` file according to the [Qt Resource System](https://doc.qt.io/qt-6/resources.html), and it follows this scheme: `<resource_prefix><import_URI><QML_dir><file>`.
119+
120+
In other words: the default resource prefix `qrc:/qt/qml/` + the import URI `org/kde/tutorial` (set in `build.rs`, separated by slashes instead of dots) + the QML dir `src/qml/` + the QML file `Main.qml`.
121+
122+
### src/qml/Main.qml {#main-qml}
123+
124+
{{< readfile file="/content/docs/getting-started/kirigami/setup-rust/src/qml/Main.qml" highlight="qml" >}}
125+
126+
Here's where we will be handling our application's frontend.
127+
128+
If you know some Javascript, then much of QML will seem familiar to you (though it does have its own peculiarities). [Qt's documentation](https://doc.qt.io/qt-6/qtqml-index.html) has an extensive amount of material on this language if you feel like trying something on your own. Over the course of these tutorials we will be focusing much of our attention on our QML code, where we can use Kirigami to get the most out of it.
129+
130+
For now, let's focus on `Main.qml`. First we [import](https://doc.qt.io/qt-6/qtqml-syntax-imports.html) a number of important modules:
131+
132+
- [QtQuick](https://doc.qt.io/qt-6/qtquick-index.html), the standard library used in QML applications.
133+
- [QtQuick Controls](https://doc.qt.io/qt-6/qtquickcontrols-index.html), which provides a number of standard controls we can use to make our applications interactive.
134+
- [QtQuick Layouts](https://doc.qt.io/qt-6/qtquicklayouts-index.html), which provides tools for placing components within the application window.
135+
- [Kirigami](docs:kirigami2), which provides a number of components suited for creating applications that work across devices of different shapes and sizes.
136+
137+
{{< alert title="Note" color="info">}}
138+
139+
Putting the QtQuick Controls and Kirigami imports into separate namespaces using the `as` keyword is a best practice that ensures no components with the same name can conflict. You might see different names for QtQuick Controls in the wild, such as "QQC" or "QQC2". We will be using "Controls" in this tutorial for clarity.
140+
141+
{{< /alert >}}
142+
143+
We then come to our base element, [Kirigami.ApplicationWindow](docs:kirigami2;ApplicationWindow), which provides some basic features needed for all Kirigami applications. This is the window that will contain each of our pages, the main sections of our UI.
144+
145+
We then set the window's `id` property to "root". IDs are useful because they let us uniquely reference a component, even if we have several of the same type.
146+
147+
We also set the window `title` property to "Hello World".
148+
149+
We then set the first page of our page stack. Most Kirigami applications are organised as a stack of pages, each page containing related components suited to a specific task. For now, we are keeping it simple, and sticking to a single page. [pageStack](docs:kirigami2;AbstractApplicationWindow::pageStack) is an initially empty stack of pages provided by [Kirigami.ApplicationWindow](docs:kirigami2;ApplicationWindow), and with `pageStack.initialPage: Kirigami.Page {...}` we set the first page presented upon loading the application to a [Kirigami.Page](docs:kirigami2;Page). This page will contain all our content.
150+
151+
Finally, we include in our page a [Controls.Label](docs:qtquickcontrols;QtQuick.Controls.Label) that lets us place text on our page. We use `anchors.centerIn: parent` to center our label horizontally and vertically within our parent element. In this case, the parent component of our label is [Kirigami.Page](docs:kirigami2;Page). The last thing we need to do is set its text: `text: "Hello World!"`.
152+
153+
## Compiling and installing the application {#build}
154+
155+
You should find the generated executable `kirigami_hello` under `build/debug/kirigami_hello` and you may run it directly or with `cargo run`, but it will lack a Window icon. To address this, we'll install the application first.
156+
157+
Run the following:
158+
159+
```bash
160+
cmake -B build --install-prefix ~/.local
161+
cmake --build build/
162+
cmake --install build/
163+
```
164+
165+
With the first command, CMake will search for Kirigami and qqc2-desktop-style.
166+
167+
With the second command, CMake will build the `kirigami_rust` target, which just calls `cargo build --target-dir build/`. This step will take a while to complete, but the next time you repeat the second CMake command it will be faster or you will not need to compile at all.
168+
169+
In the third step, CMake will install the executable `kirigami_hello` under `~/.local/bin/kirigami_hello` and the desktop file under `~/.local/share/applications`, and a new entry named "Kirigami Tutorial in Rust" will appear on your menu.
170+
171+
Open the menu entry and voilà! Now you will see your very first Kirigami app appear before your very own eyes.
172+
173+
![Screenshot of the generated Kirigami application](hello-kworld.webp)
174+
175+
To run the new QML application in mobile mode, you can use `QT_QUICK_CONTROLS_MOBILE=1`:
176+
177+
```bash
178+
QT_QUICK_CONTROLS_MOBILE=1 kirigami_hello
179+
```
180+
181+
If you have compiled the project manually with CMake and for some reason you'd like to uninstall the project, you can run:
182+
183+
```bash
184+
cmake --build build/ --target uninstall
185+
```
186+
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[Desktop Entry]
2+
Name=Kirigami Tutorial in Rust
3+
Exec=kirigami_hello
4+
Icon=kde
5+
Type=Application
6+
Terminal=false
7+
Categories=Utility
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#[cxx_qt::bridge]
2+
mod ffi {
3+
extern "RustQt" {
4+
#[qobject]
5+
type DummyQObject = super::DummyRustStruct;
6+
}
7+
}
8+
9+
#[derive(Default)]
10+
pub struct DummyRustStruct;
11+
12+
use cxx_qt_lib::{QGuiApplication, QQmlApplicationEngine, QQuickStyle, QString, QUrl};
13+
use cxx_qt_lib_extras::QApplication;
14+
use std::env;
15+
16+
fn main() {
17+
let mut app = QApplication::new();
18+
let mut engine = QQmlApplicationEngine::new();
19+
20+
// To associate the executable to the installed desktop file
21+
QGuiApplication::set_desktop_file_name(&QString::from("org.kde.kirigami_rust"));
22+
// To ensure the style is set correctly
23+
let style = env::var("QT_QUICK_CONTROLS_STYLE");
24+
if style.is_err() {
25+
QQuickStyle::set_style(&QString::from("org.kde.desktop"));
26+
}
27+
28+
if let Some(engine) = engine.as_mut() {
29+
engine.load(&QUrl::from("qrc:/qt/qml/org/kde/tutorial/src/qml/Main.qml"));
30+
}
31+
32+
if let Some(app) = app.as_mut() {
33+
app.exec();
34+
}
35+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Includes relevant modules used by the QML
2+
import QtQuick
3+
import QtQuick.Layouts
4+
import QtQuick.Controls as Controls
5+
import org.kde.kirigami as Kirigami
6+
7+
// Provides basic features needed for all kirigami applications
8+
Kirigami.ApplicationWindow {
9+
// Unique identifier to reference this object
10+
id: root
11+
12+
width: 400
13+
height: 300
14+
15+
// Window title
16+
title: "Hello World"
17+
18+
// Set the first page that will be loaded when the app opens
19+
// This can also be set to an id of a Kirigami.Page
20+
pageStack.initialPage: Kirigami.Page {
21+
Controls.Label {
22+
// Center label horizontally and vertically within parent object
23+
anchors.centerIn: parent
24+
text: "Hello World!"
25+
}
26+
}
27+
}

0 commit comments

Comments
 (0)