Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

register QML elements at build time #428

Merged
merged 5 commits into from
Feb 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions .github/workflows/github-cxx-qt-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ jobs:
- name: Ubuntu 22.04 (gcc) Qt5
os: ubuntu-22.04
qt_version: 5
# FIXME: valgrind complains about invalid debuginfo. Might be fixed when Ubuntu updates to valgrind 3.20
# https://bugs.kde.org/show_bug.cgi?id=452758
ctest_args: --exclude-regex '^(example_qml_features_test_valgrind|example_qml_minimal_myobject_test_valgrind)$'
qt_qpa_platform: offscreen
compiler_cache_path: /home/runner/.cache/sccache
cargo_dir: ~/.cargo
Expand All @@ -33,6 +36,9 @@ jobs:
- name: Ubuntu 22.04 (gcc) Qt6
os: ubuntu-22.04
qt_version: 6
# FIXME: valgrind complains about invalid debuginfo. Might be fixed when Ubuntu updates to valgrind 3.20
# https://bugs.kde.org/show_bug.cgi?id=452758
ctest_args: --exclude-regex '^(example_qml_features_test_valgrind|example_qml_minimal_myobject_test_valgrind)$'
qt_qpa_platform: offscreen
compiler_cache_path: /home/runner/.cache/sccache
cargo_dir: ~/.cargo
Expand All @@ -59,7 +65,7 @@ jobs:
# once Ubuntu 22.04 is we can move to clang-format-14 everywhere
# for now we need at least clang-format-12 otherwise include reordering fails after clang-format off
# https://github.com/KDAB/cxx-qt/issues/121
ctest_args: --exclude-regex '^(example_qml_extension_plugin_test|reuse_lint|cpp_clang_format|.*valgrind)$'
ctest_args: --exclude-regex '^(example_qml_extension_plugin_test|example_qml_features_test|example_qml_minimal_myobject_test|reuse_lint|cpp_clang_format|.*valgrind)$'
qt_qpa_platform: cocoa
compiler_cache_path: /Users/runner/Library/Caches/Mozilla.sccache
cargo_dir: ~/.cargo
Expand All @@ -69,7 +75,7 @@ jobs:
qt_version: 6
# FIXME: qmltestrunner fails to import QtQuick module
# https://github.com/KDAB/cxx-qt/issues/110
ctest_args: --exclude-regex '^(example_qml_extension_plugin_test|reuse_lint|cpp_clang_format|.*valgrind)$'
ctest_args: --exclude-regex '^(example_qml_extension_plugin_test|example_qml_features_test|example_qml_minimal_myobject_test|reuse_lint|cpp_clang_format|.*valgrind)$'
qt_qpa_platform: cocoa
compiler_cache_path: /Users/runner/Library/Caches/Mozilla.sccache
cargo_dir: ~/.cargo
Expand Down
7 changes: 3 additions & 4 deletions book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,9 @@ SPDX-License-Identifier: MIT OR Apache-2.0
- [Getting Started](./getting-started/index.md)
- [QObjects in Rust](./getting-started/1-qobjects-in-rust.md)
- [Our first CXX-Qt module](./getting-started/2-our-first-cxx-qt-module.md)
- [Exposing to QML](./getting-started/3-exposing-to-qml.md)
- [Creating the QML GUI](./getting-started/4-qml-gui.md)
- [Building with CMake](./getting-started/5-cmake-integration.md)
- [Building with Cargo](./getting-started/6-cargo-executable.md)
- [Creating the QML GUI](./getting-started/3-qml-gui.md)
- [Building with CMake](./getting-started/4-cmake-integration.md)
- [Building with Cargo](./getting-started/5-cargo-executable.md)
- [QObject](./qobject/index.md)
- [`#[cxx_qt::bridge]` - Bridge Macro](./qobject/bridge-macro.md)
- [`#[cxx_qt::qobject]` - Defining QObjects](./qobject/qobject_struct.md)
Expand Down
6 changes: 3 additions & 3 deletions book/src/concepts/build_systems.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ SPDX-License-Identifier: MIT OR Apache-2.0

CXX-Qt can be integrated into existing CMake projects or built with only cargo. The getting started guide provides documentation on how to setup your project:

* [CMake Integration](../getting-started/5-cmake-integration.md)
* [Cargo Integration](../getting-started/6-cargo-executable.md)
* [CMake Integration](../getting-started/4-cmake-integration.md)
* [Cargo Integration](../getting-started/5-cargo-executable.md)

CXX-Qt could work with any C++ build system so long as the `QMAKE` and `CXXQT_EXPORT_DIR` environment variables are set before calling Cargo,
as documented in [CMake integration](../getting-started/5-cmake-integration.md). However, using C++ build systems besides CMake with CXX-Qt is untested.
as documented in [CMake integration](../getting-started/4-cmake-integration.md). However, using C++ build systems besides CMake with CXX-Qt is untested.
38 changes: 0 additions & 38 deletions book/src/concepts/register_types.md

This file was deleted.

25 changes: 22 additions & 3 deletions book/src/getting-started/2-our-first-cxx-qt-module.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ As with all things Rust, we'll want to create a cargo project, run the following
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](./5-cmake-integration.md).
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.
First, in the `rust/src/lib.rs`, we tell Cargo about the module we're about to create:
Expand All @@ -38,6 +38,8 @@ It will include our `#[cxx_qt::bridge]` that allows us to create our own qobject
```

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
{{#include ../../../examples/qml_minimal/rust/src/cxxqt_object.rs:book_bridge_macro}}
Expand All @@ -48,16 +50,31 @@ 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.

## QObject struct

To create a new QObject subclass, we can define a struct within our module and mark it with `#[cxx_qt::qobject]`.
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_struct}}
```

Optionally, add `qml_uri` and `qml_version` inside `#[cxx_qt::qobject]` to tell the Rust build script to generate a QML plugin
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).

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}}
```

The Rust struct can be defined just like a normal Rust struct and can contain any kind of field, even Rust-only types.
If a field is marked as `#[qproperty]` it will be exposed to the C++ side as a `Q_PROPERTY`.

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`).

### Types

Do note though that any fields marked as `#[qproperty]` must be types that CXX can translate to C++ types.
In our case that means:
- `number: i32` -> `::std::int32_t number`
Expand All @@ -72,6 +89,8 @@ We can just import them like any other CXX type:
```
For more details on the available types, see the [Qt types page](../concepts/types.md).

## qobject::T

CXX-Qt will then automatically generate a new QObject subclass for our `MyObject` struct and expose it as an [`extern "C++"` opaque type](https://cxx.rs/extern-c++.html#opaque-c-types) to Rust.
For any Rust struct `T` that is marked with `#[cxx_qt::qobject]`, CXX-Qt will expose the corresponding C++ QObject under `qobject::T`.
In our case, this means we can refer to the C++ QObject for our `MyObject` struct, as `qobject::MyObject`.
Expand Down Expand Up @@ -101,4 +120,4 @@ In this case, the types of the arguments also need to convertable to C++, like w

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

Now let's get to [using it in Qt](./3-exposing-to-qml.md).
Now let's get to [using it in Qt](./3-qml-gui.md).
51 changes: 0 additions & 51 deletions book/src/getting-started/3-exposing-to-qml.md

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ So let's add a `main.qml` file in a `qml` folder:
{{#include ../../../examples/qml_minimal/qml/main.qml:book_main_qml}}
```

If you're not familiar with QML, I recommend you take a look at the [Qt QML intro](https://doc.qt.io/qt-6/qmlapplications.html).
If you're not familiar with QML, take a look at the [Qt QML intro](https://doc.qt.io/qt-6/qmlapplications.html).

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 @@ -33,4 +33,16 @@ It is again important to emphasize here that `MyObject` is just another QObject
The only difference being that any invokable functions that are defined are defined in Rust, instead of C++.
For QML, this doesn't make a difference though.

But enough of that, let's get this project [building and running](./5-cmake-integration.md).
# 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 a `qml.qrc` file in the `qml` folder:
```qrc,ignore
{{#include ../../../examples/qml_minimal/qml/qml.qrc:book_rcc_block}}
```

You can omit this, but then you should change the url of the `main.qml` file, so that Qt can find it on your computer.
``` 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).
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,27 @@ SPDX-License-Identifier: MIT OR Apache-2.0
In this example, we will demonstrate how to integrate cxx-qt code into a C++ application. Cargo builds the cxx-qt code
as a static library, then CMake links it into a C++ executable.

> If you don't want to use CMake, and only want to use Cargo to build your project, you can [skip to the next chapter](./6-cargo-executable.md).
> Note that using CMake is currently still easier!
> It also allows you to integrate Rust into existing Qt projects and tooling.
> If you don't want to use CMake, and only want to use Cargo to build your project, you can [skip to the next chapter](./5-cargo-executable.md).
## C++ executable

To start our QML application, we'll need a small `main.cpp` file with an ordinary `main` function. Puts this in a `cpp` folder to clearly separate the C++ and Rust code:
```cpp,ignore
{{#include ../../../examples/qml_minimal/cpp/main.cpp:book_main_cpp}}
```

You can add as much C++ code as you want in addition to this.

## Using Rust QObjects in C++

For every `#[cxx_qt::bridge]` that we define in Rust, CXX-Qt will generate a corresponding C++ header file.
They will always be in the `cxx-qt-gen/` include path and use the snake_case naming convention.
The name of the header file will be the name of the Rust module of your `#[cxx_qt::bridge]`, followed by `.cxxqt.h`.
So in our case: `#include cxx-qt-gen/my_object.cxxqt.h`

Including the generated header allows accessing the `MyObject` C++ class, just like any other C++ class.
Inherit from it, connect signals and slots to it, put it in a QVector, do whatever you want with it.
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.
Expand Down Expand Up @@ -44,10 +62,9 @@ We'll then also need to add a script named `build.rs` next to our `Cargo.toml`:
```rust,ignore
{{#include ../../../examples/qml_minimal/rust/build.rs:book_build_rs}}
```
This is what generates the C++ code for our `MyObject` class at compile-time.
It will output the `cxx-qt-gen/include/my_object.h` file we included earlier in `main.cpp`.
This is what generates and compiles the C++ code for our `MyObject` class at build time.

Note that all Rust source files that uses the `#[cxx_qt::bridge]` macro need to be included in this script!
Every Rust source file that uses the `#[cxx_qt::bridge]` macro need to be included in this script.
In our case, this is only the `src/cxxqt_object.rs` file.

## CMake setup
Expand Down Expand Up @@ -94,12 +111,12 @@ If this fails for any reason, take a look at the [`examples/qml_minimal`](https:
This should now configure and compile our project.
If this was successful, you can now run our little project.
```shell
$ build/qml_minimal
$ build/examples/qml_minimal/example_qml_minimal
```

You should now see the two Labels that display the state of our `MyObject`, as well as the two buttons to call our two Rust functions.

## Success 🥳
## Success 🥳

For further reading, you can take a look at the [QObject chapter](../qobject/index.md) which goes into detail about all features that CXX-Qt exposes to new QObject subclasses.
As well as the [Concepts chapter](../concepts/index.md), which explains the concepts underlying CXX-Qt.
Expand Down
Loading