Skip to content

Commit

Permalink
Update Getting Started Guide for 0.6
Browse files Browse the repository at this point in the history
  • Loading branch information
LeonMatthesKDAB authored and ahayzen-kdab committed Nov 1, 2023
1 parent edf0b73 commit d4b6199
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 86 deletions.
29 changes: 13 additions & 16 deletions book/src/getting-started/1-qobjects-in-rust.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,33 +44,30 @@ These concepts include:
- Enums

As with CXX, to use these features you mark a Rust module with an attribute macro ([`#[cxx_qt::bridge]`](../bridge/index.md)).

Then you can use the afformentioned features with the help of extern blocks and macros to express them in a [Rust bridge](../bridge/index.md).
Inside this bridge, you then describe the bi-directional interface between your C++/Qt and Rust code.

CXX-Qt will then expand this Rust bridge into two separate parts:
- C++ files that define a QObject, enums etc to Qt.
- The Rust code which provides the implementation of the features
- C++ files that define QObjects, enums etc. and expose them to [Qts meta-object-system](https://doc.qt.io/qt-6/metaobjects.html).
- The Rust code which provides the Rust implementation of the described structures, as well as interfaces to any C++/Qt constructs you declared.

<div style="background-color: white; padding: 1rem; text-align: center;">

![Overview of CXX-Qt module generation](../images/overview_abstract.svg)

</div>

CXX-Qt also generates the code needed for interaction of the C++ QObject subclass and the `#[qobject]` marked struct using the [CXX library](https://cxx.rs/).
For more details, see the [Bridge](../bridge/index.md) page.
## Rust structs as QObjects

The important take away here is the duality of any subclass generated by CXX-Qt.
These classes are made up of the actual QObject subclass instance that C++ interacts with, as well as an instance of the Rust struct on the Rust side.
When such a QObject is instantiated, it will always also construct an instance of the Rust struct as well.
The lifetime of the Rust struct will be bound to that of the QObject.
If the QObject is deleted, the Rust struct will be deleted as well.
Typically this will be instantiated by QML and the lifetime will be directly associated with the corresponding QML item.
Similar to CXX, CXX-Qt allows you to expose Rust structs as a new type to C++.
However, CXX-Qt expands this feature to allow you to create a new QObject subclass that is backed by a Rust struct.
In comparison to a normal opaque CXX class, the mapping between the QObject subclass and the Rust struct is **not 1:1**!

The generated QObject subclass will then defer to the Rust struct for any behavior, which is then defined in Rust.
For example, using the [`#[qinvokable]`](../bridge/extern_rustqt.md#invokables) attribute, we can define functions that will be exposed to C++, but will execute Rust code.
Also, any fields in the Rust struct can be exposed to Qt as `Q_PROPERTY` fields by using the [`#[qproperty(T, NAME)]`](../bridge/extern_rustqt.md#properties) attribute.
Therefore allowing you to assign them from QML as well.
The QObject subclass it its own type in Rust, as well as in C++.
When such a QObject is instantiated, it will always also construct an instance of the Rust struct.
The QObject can then refer to the underlying Rust struct for property access.
Any behavior of this QObject subclass will also be defined in Rust, e.g. using the [`#[qinvokable]`](../bridge/extern_rustqt.html#invokables) attribute.
The Rust implementation also has access to the underlying Rust struct to modify any Rust data.
In comparison to most CXX types, the outer QObject class and the inner Rust struct will remain two distinct types!

But enough theory for now, lets jump in and write [our first CXX-Qt module](./2-our-first-cxx-qt-module.md).

110 changes: 73 additions & 37 deletions book/src/getting-started/2-our-first-cxx-qt-module.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,30 @@ tutorial
- rust
```

As with all things Rust, we'll want to create a cargo project, run the following command inside the `tutorial` folder to initialise the Rust part of the project.
As with all things Rust, we'll want to create a cargo project, run the following command inside the `tutorial` folder to initialize the Rust part of the project.
```bash
cargo init --lib rust
```
Note the `--lib` option here. For this example, we will create a static library in Rust and use CMake to
link this into a C++ executable. We'll discuss details of this later, when we [integrate our Rust project with CMake](./4-cmake-integration.md).

As outlined in the previous section, to define a new QObject subclass, we'll create a Rust module within this library crate.
As outlined in the previous section, to use CXX-Qt, we'll create a Rust module within this library crate.
This Rust module will then serve as our interface between Qt and Rust.
First, in the `rust/src/lib.rs`, we tell Cargo about the module we're about to create:

```rust,ignore
{{#include ../../../examples/qml_minimal/rust/src/lib.rs:book_mod_statement}}
```

Now, we need to create a file `rust/src/cxxqt_object.rs` for that module.
It will include our `#[cxx_qt::bridge]` that allows us to create our own qobjects in Rust:
It will include our `#[cxx_qt::bridge]` that allows us to interact with Qt concepts.

This is a lot to take in, so let's go one step at a time.

```rust,ignore
{{#include ../../../examples/qml_minimal/rust/src/cxxqt_object.rs:book_cxx_qt_module}}
```

This is a lot to take in, so let's go one step at a time.

## CXX-Qt bridge module
Starting with the module definition:
```rust,ignore
Expand All @@ -48,75 +49,110 @@ Starting with the module definition:
```

A `#[cxx_qt::bridge]` is the same as a `#[cxx::bridge]` and you can use all features of CXX in it.
Additionally, a `#[cxx_qt::bridge]` gives you a few more features that allow you to create QObjects.
Additionally, a `#[cxx_qt::bridge]` gives you a few more features that allow you to create QObjects from Rust or declare existing QObjects for access from Rust.

## `extern "RustQt"`

Like `extern "Rust"` and `extern "C++"` in CXX, CXX-Qt provides `extern "RustQt"` and `extern "C++Qt"`.

These `extern` blocks instruct CXX-Qt to where the implementation of our interface lives.
Anything that is marked as `extern "RustQt"` is implemented in Rust and will be exposed to C++.
Conversely anything inside `extern "C++Qt"` is implemented in C++ and will be exposed to Rust.

## QObject struct

To create a new QObject subclass, we can define a type alias within a `extern "RustQt"` block in our module and mark it with `#[qobject]`.
First we will create a new QObject subclass.
As we want to implement it in Rust, we need to place our interface inside `extern "RustQt"`.

To create a new QObject subclass that will be defined in Rust, use a type-alias and mark it with `#[qobject]`.
In our case the new class will be named `MyObject` and will be backed by a Rust struct named `MyObjectRust`.

```rust,ignore
{{#include ../../../examples/qml_minimal/rust/src/cxxqt_object.rs:book_rustobj_struct_signature}}
```

The Rust struct can be defined outside the module just like a normal Rust struct and can contain any kind of field, even Rust-only types.
The Rust struct must be defined **outside** the bridge module and is therefore referred to using `super::`.
This just needs to be a normal Rust struct and can contain any kind of field, even Rust-only types that are not compatible with CXX.

Unless we want to use CXX-Qt's [Constructor feature](../traits/constructor.md) we just need to ensure that this struct implements Rusts `Default` trait
In this case we just use `#[derive(Default)]` on the struct.

```rust,ignore
{{#include ../../../examples/qml_minimal/rust/src/cxxqt_object.rs:book_rustobj_struct}}
```

Optionally, add `qml_element` inside `#[qobject]` to tell the Rust build script to generate a QML_ELEMENT
that will register the QObject with QML engine at startup. If you want the name of the QML type and the Rust type to be different,
you can also add `qml_name = "OtherName"`. This takes the place of the
[qt_add_qml_module CMake function](https://doc.qt.io/qt-6/qt-add-qml-module.html) (because that doesn't work with CXX-Qt's build system).
Now the `#[qobject]` macro will take care of creating a new QObject subclass named `MyObject`.
Every instance of that class will also include an instance of the `MyObjectRust` struct that the `MyObject` class will defer to for any data or behavior.

Additionally, we need to either `impl Default` or `#[derive(Default)]` for our struct.
```rust,ignore
{{#include ../../../examples/qml_minimal/rust/src/cxxqt_object.rs:book_rustobj_default}}
```
To automatically export this new class to QML, we mark it with the `#[qml_element]` attribute.
This is the same as specifying [`QML_ELEMENT`](https://doc.qt.io/qt-6/qqmlengine.html#QML_ELEMENT) in C++.
This takes the place of the [qt_add_qml_module CMake function](https://doc.qt.io/qt-6/qt-add-qml-module.html)
(because that doesn't work with CXX-Qt's build system).

The `#[qproperty]` attribute on the type alias will create properties to the fields matching the name and be exposed to the C++ side as a `Q_PROPERTY`.
The `#[qproperty]` attribute will create a [`Q_PROPERTY`](https://doc.qt.io/qt-6/properties.html) for the given type and field name.
CXX-Qt will then:
1. Create the `Q_PROPERTY` on the QObject type.
2. Create a `NOTIFY` signal for when the property changes.
3. Generate getters and setters that use the underlying Rust fields and emit the NOTIFY signal on changes.

That means the newly created QObject subclass will have two properties as members: `number` and `string`. For names that contain multiple words, like `my_number`, CXX-Qt will automatically rename the field from snake_case to camelCase to fit with C++/QML naming conventions (e.g. `myNumber`).
In this case the newly created QObject subclass will have two properties: `number` and `string`.
CXX-Qt expects a field for each property to exist in the underlying Rust struct.
For names that contain multiple words, like `my_number`, CXX-Qt will automatically rename the field from snake_case to camelCase to fit with C++/QML naming conventions (e.g. `myNumber`).

### Types

Do note though that any fields exposed as `#[qproperty]` must be types that CXX can translate to C++ types.
Please note that any fields exposed as `#[qproperty]` must have types that CXX can translate to C++ types.
In our case that means:
- `number: i32` -> `::std::int32_t number`
- `string: QString` -> `QString string`
- `#[qpoperty(i32, number)]` -> `Q_PROPERTY(::std::int32_t number ...)`
- `#[qproperty(QString, string)` -> `Q_PROPERTY(QString string ...)`

For `i32`, CXX-Qt already knows how to translate it.
A `QString` however is unknown to CXX.
Luckily, the [`cxx_qt_lib`](https://docs.rs/cxx-qt-lib/latest/cxx_qt_lib/) crate already wraps many Qt types for us.
We can just define them in the bridge like any other CXX type:
We can just include them in the bridge like any other CXX type:
``` rust, ignore
{{#include ../../../examples/qml_minimal/rust/src/cxxqt_object.rs:book_qstring_import}}
```
For more details on the available types, see the [Qt types page](../concepts/types.md).

## Invokables
-------

CXX-Qt will then automatically generate a new QObject subclass for our `MyObjectRust` struct and expose it as an [`extern "C++"` opaque type](https://cxx.rs/extern-c++.html#opaque-c-types) to Rust.
For any type alias `type T = super::S` marked with `#[qobject]`, CXX-Qt will expose the corresponding C++ QObject under `T`.
In our case, this means we can refer to the C++ QObject for our `MyObjectRust` struct, as `MyObject`.
CXX-Qt will then automatically generate a new QObject subclass called `MyObject` and expose it as an [`extern "C++"` opaque type](https://cxx.rs/extern-c++.html#opaque-c-types) back to Rust.
In our case, this means we can refer to the C++ QObject as `qobject::MyObject`, as it is defined inside the `mod qobject`.

This type can be used like any other CXX opaque type.
Additionally, CXX-Qt allows us to add functionality to this QObject by referring to the type as the self type of functions in an `extern "RustQt"` block in together with `#[qinvokable]`.

## Invokables

Additionally, CXX-Qt allows us to add functionality to this QObject by referring to the type as the self type of functions in an `extern "RustQt"` block.
```rust,ignore
{{#include ../../../examples/qml_minimal/rust/src/cxxqt_object.rs:book_rustobj_invokable_signature}}
```

And then implementing the invokables outside the bridge using `impl qobject::MyObject`.
This works the same as exposing any other [member function with CXX](https://cxx.rs/extern-rust.html#methods) in an `extern "Rust"` block.
Additionally CXX-Qt understands the `#[qinvokable]` attribute and marks the member function as [`Q_INVOKABLE`](https://doc.qt.io/qt-6/qtqml-cppintegration-exposecppattributes.html#exposing-methods-including-qt-slots).
This means they can be called from QML.



These functions then need to be implemented outside the bridge using `impl qobject::MyObject`.

```rust,ignore
{{#include ../../../examples/qml_minimal/rust/src/cxxqt_object.rs:book_rustobj_invokable_impl}}
```

> Note that by using `qobject` instead of `ffi` as the module name for the bridge,
> referring to the C++ QObject outside the bridge becomes a natural `qobject::MyObject`
This setup is a bit unusual, as the type `qobject::MyObject` is actually defined in C++.
However, it is still possible to add member functions to it in Rust and then expose them back to C++.
This is the usual workflow for QObjects in CXX-Qt.
CXX-Qt will define the QObject class itself in C++, but defer to Rust for any behavior.

> Note that we recommend calling the bridge module `qobject` instead of the CXX-typical `ffi`.
> This way accessing the C++ QObject outside the bridge becomes a natural `qobject::MyObject`
> instead of `ffi::MyObject`.
>
> Feel free to choose any module name you like though.
Also do not forget to use the relevant imports that are required for the invokable implementation.
Also do not forget to import everything required for the invokable implementation.

```rust,ignore
{{#include ../../../examples/qml_minimal/rust/src/cxxqt_object.rs:book_use}}
Expand All @@ -132,12 +168,12 @@ In our case, we define two new functions:

Because we are implementing on the `qobject::MyObject` type instead of the `MyObject` type, `self` here is the C++ QObject that is generated from our `MyObject` struct.
As this type is a CXX Opaque type, we can't actually instantiate it.
Qt/C++ takes care of this.
However, we can still define Rust functions on this type.
They will just be normal Rust functions, but will be executed on a C++ type.
CXX-Qt will already generate getters and setters for all properties of your struct this way.
If you additionally define these in the `extern "RustQt"` block of the bridge, they will also be callable from C++ and QML.
In this case, the types of the arguments also need to convertable to C++, like with any `#[qproperty]`.
Our Qt code will take care of this.
Also, we can't move the QObject, which is why it is behind a Rust [`Pin`](https://doc.rust-lang.org/std/pin/struct.Pin.html).

CXX-Qt will generate getters and setters for all properties of our struct.
That's where the `number()` and `set_number()` functions used by `increment_number()` come from.
For more details on what you can do with the QObject from Rust and what functions CXX-Qt will generate for you, take a look at the [QObject page](../concepts/generated_qobject.md).

And that's it. We've defined our first QObject subclass in Rust. That wasn't so hard, was it?

Expand Down
21 changes: 2 additions & 19 deletions book/src/getting-started/3-qml-gui.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ So let's add a `main.qml` file in a `qml` folder:
```

If you're not familiar with QML, take a look at the [Qt QML intro](https://doc.qt.io/qt-6/qmlapplications.html).
We of course also recommend our [QML Intro Training](https://www.kdab.com/software-services/on-site-training/qt-onsite/programming-qtqml-onsite-training/).

This code will create a pretty simple GUI that consists of two Labels and two Buttons.
The important part here is the use of the `MyObject` type.
Expand All @@ -30,25 +31,7 @@ As you can see here, CXX-Qt has converted the snake_case of the function names t
This way the `MyObject` doesn't seem at all out of place in QML.

It is again important to emphasize here that `MyObject` is just another QObject subclass and can be used just like any other `QObject` subclass.
The only difference being that any invokable functions that are defined are defined in Rust, instead of C++.
The only difference being that any invokable functions are defined in Rust, instead of C++.
For QML, this doesn't make a difference though.

# Qt resources

To include the `main.qml` file inside the application, use the [Qt resource system](https://doc.qt.io/qt-6/resources.html) by listing it in the `qml_files` part of our QML module in the `build.rs` file:

<!--
TODO: this step magically comes before the build chapters?
-->

```rust,ignore
{{#include ../../../examples/qml_minimal/rust/build.rs:book_qml_module}}
```

In the `main.cpp` we then use the URL of the `main.qml` file inside the QML module.

``` cpp, ignore
{{#include ../../../examples/qml_minimal/cpp/main.cpp:book_qml_url}}
```

Now that we have some application code, let's get this project [building and running](./4-cmake-integration.md).
2 changes: 1 addition & 1 deletion book/src/getting-started/4-cmake-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ That's the power of CXX-Qt.

## Cargo setup
Before we can get started on building Qt with CMake, we first need to make our Cargo build ready for it.
If you've generated your project with the `cargo new --lib` or `cargo init --lib folder` command, your `Cargo.toml` likely looks something like this:
If you've generated your project with the `cargo new --lib` or `cargo init --lib folder` command, your `Cargo.toml` should look something like this:
```toml,ignore
[package]
name = "qml-minimal"
Expand Down
3 changes: 2 additions & 1 deletion book/src/getting-started/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ In this guide we'll go through a minimal example that uses CXX-Qt to create your

### Prerequisites
This guide won't be able to explain everything to you, but it will try its best to make sure everyone can follow along.
However, a few things you should make sure you're familiar with before attempting to follow this guide, as it may be confusing otherwise.
However, there are a few things you should be familiar with before reading this guide.
It may be confusing otherwise!

First of all, you should be familiar with Rust. There are many great resources for learning Rust, like [the book](https://doc.rust-lang.org/book/).

Expand Down
18 changes: 6 additions & 12 deletions examples/qml_minimal/rust/src/cxxqt_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ pub mod qobject {

// ANCHOR: book_rustobj_struct_signature
unsafe extern "RustQt" {
// The QObject definition
// We tell CXX-Qt that we want a QObject class with the name MyObject
// based on the Rust struct MyObjectRust.
#[qobject]
#[qml_element]
#[qproperty(i32, number)]
Expand All @@ -33,6 +36,7 @@ pub mod qobject {

// ANCHOR: book_rustobj_invokable_signature
unsafe extern "RustQt" {
// Declare the invokable methods we want to expose on the QObject
#[qinvokable]
fn increment_number(self: Pin<&mut MyObject>);

Expand All @@ -49,28 +53,18 @@ use cxx_qt_lib::QString;

/// The Rust struct for the QObject
// ANCHOR: book_rustobj_struct
#[derive(Default)]
pub struct MyObjectRust {
number: i32,
string: QString,
}
// ANCHOR_END: book_rustobj_struct

// ANCHOR: book_rustobj_default
impl Default for MyObjectRust {
fn default() -> Self {
Self {
number: 0,
string: QString::from(""),
}
}
}
// ANCHOR_END: book_rustobj_default

// ANCHOR: book_rustobj_invokable_impl
impl qobject::MyObject {
/// Increment the number Q_PROPERTY
pub fn increment_number(self: Pin<&mut Self>) {
let previous = *self.as_ref().number();
let previous = *self.number();
self.set_number(previous + 1);
}

Expand Down

0 comments on commit d4b6199

Please sign in to comment.