From d4b6199cfdf0d49a23c50406faa03ff3d9add0da Mon Sep 17 00:00:00 2001 From: Leon Matthes Date: Fri, 13 Oct 2023 17:58:00 +0200 Subject: [PATCH] Update Getting Started Guide for 0.6 --- .../src/getting-started/1-qobjects-in-rust.md | 29 +++-- .../2-our-first-cxx-qt-module.md | 110 ++++++++++++------ book/src/getting-started/3-qml-gui.md | 21 +--- .../getting-started/4-cmake-integration.md | 2 +- book/src/getting-started/index.md | 3 +- examples/qml_minimal/rust/src/cxxqt_object.rs | 18 +-- 6 files changed, 97 insertions(+), 86 deletions(-) diff --git a/book/src/getting-started/1-qobjects-in-rust.md b/book/src/getting-started/1-qobjects-in-rust.md index 15dc549ed..92e9a7e21 100644 --- a/book/src/getting-started/1-qobjects-in-rust.md +++ b/book/src/getting-started/1-qobjects-in-rust.md @@ -44,12 +44,11 @@ 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.
@@ -57,20 +56,18 @@ CXX-Qt will then expand this Rust bridge into two separate parts:
-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). diff --git a/book/src/getting-started/2-our-first-cxx-qt-module.md b/book/src/getting-started/2-our-first-cxx-qt-module.md index 26dc53ce4..e244117fa 100644 --- a/book/src/getting-started/2-our-first-cxx-qt-module.md +++ b/book/src/getting-started/2-our-first-cxx-qt-module.md @@ -16,14 +16,15 @@ 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 @@ -31,14 +32,14 @@ First, in the `rust/src/lib.rs`, we tell Cargo about the module we're about to c ``` 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 @@ -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}} @@ -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? diff --git a/book/src/getting-started/3-qml-gui.md b/book/src/getting-started/3-qml-gui.md index c90d0e3a3..59feab04e 100644 --- a/book/src/getting-started/3-qml-gui.md +++ b/book/src/getting-started/3-qml-gui.md @@ -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. @@ -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: - - - -```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). diff --git a/book/src/getting-started/4-cmake-integration.md b/book/src/getting-started/4-cmake-integration.md index 5637fd030..aa7babe71 100644 --- a/book/src/getting-started/4-cmake-integration.md +++ b/book/src/getting-started/4-cmake-integration.md @@ -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" diff --git a/book/src/getting-started/index.md b/book/src/getting-started/index.md index e450e9a1d..144394b7d 100644 --- a/book/src/getting-started/index.md +++ b/book/src/getting-started/index.md @@ -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/). diff --git a/examples/qml_minimal/rust/src/cxxqt_object.rs b/examples/qml_minimal/rust/src/cxxqt_object.rs index 9be9646e0..60d7ec8ab 100644 --- a/examples/qml_minimal/rust/src/cxxqt_object.rs +++ b/examples/qml_minimal/rust/src/cxxqt_object.rs @@ -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)] @@ -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>); @@ -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); }