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

cxx-qt-gen: use impl cxx_qt::QObject<T> block for invokables #178

Merged
merged 2 commits into from
Aug 3, 2022
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ mod my_object {
#[derive(Default)]
pub struct RustObj;

impl RustObj {
impl cxx_qt::QObject<RustObj> {
#[invokable]
pub fn increment(&self, cpp: &mut CppObj) {
cpp.set_number(cpp.number() + 1);
Expand Down
4 changes: 3 additions & 1 deletion book/src/getting-started/1-qobjects-in-rust.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ These CXX-Qt modules consist of multiple parts:
- The `impl` of the `RustObj` struct (optional):
- Contains any Rust code.
- Functions marked with `#[invokable]` will be callable from QML and C++.
<!-- TODO: Add Signals enum, once #67 lands -->
- The `Signal` enum
- A normal Rust enum.
- Defines signals that are added to the QObject class

CXX-Qt will then expand this Rust module into two separate parts:
- A C++ subclass of QObject with the same name as the module
Expand Down
9 changes: 1 addition & 8 deletions book/src/qobject/cpp_object.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,7 @@ If there is a [`Signals` enum](./signals_enum.md) then you can call `emit_queued
Note that `emit_immediate` is unsafe as it can cause deadlocks if the `Q_EMIT` is `Qt::DirectConnection` connected to a Rust invokable on the same QObject that has caused the `Q_EMIT`, as this would then try to lock the `RustObj` which is already locked.

```rust,ignore,noplayground
impl RustObj {
#[invokable]
fn invokable(&self, cpp: &mut CppObj) {
unsafe { cpp.emit_immediate(Signal::Ready); }

cpp.emit_queued(Signal::DataChanged { data: 1 });
}
}
{{#include ../../../examples/qml_features/src/signals.rs:book_rust_obj_impl}}
```

## Threading
Expand Down
9 changes: 7 additions & 2 deletions book/src/qobject/rustobj_struct.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,17 @@ The RustObj struct allows you to define the following items

## Invokables

To define a method which is exposed to QML and C++, add a method on the `RustObj` struct and add the attribute `#[invokable]`. The parameters and return type are then matched to the Qt side. Also CXX-Qt automatically adds wrapper code around your invokable to automatically perform any conversion between the [C++ and Rust types](../concepts/types.md).
A `impl cxx_qt::QObject<RustObj>` is used to define invokables, the `impl cxx_qt::QObject<RustObj>` defines that the methods are implemented onto the C++ QObject.
Therefore they have access to both C++ and Rust methods. Also CXX-Qt adds wrapper code around your invokables to automatically perform any conversion between the [C++ and Rust types](../concepts/types.md).

To mark a method as invokable simply add the `#[invokable]` attribute to the Rust method. This then causes `Q_INVOKABLE` to be set on the C++ definition of the method, allowing QML to call the invokable.

Note to access properties on the C++ object use [Cpp Object](./cpp_object.md).

## Private Methods and Fields

Unlike the [Data Struct](./data_struct.md) fields which are defined on the `RustObj` struct are not exposed as properties to Qt. These can be considered as "private to Rust" fields, and are useful for storing channels for threading or internal information for the QObject.

Methods implemented on the `RustObj` that do not have an `#[invokable]` attribute are not exposed to C++ and are considered "private to Rust" methods. Similar to fields these are useful for threading and internal information.
Methods implemented using `impl RustObj` (and not `impl cxx_qt::QObject<RustObj>`) are just normal Rust member methods.
Therefore they do not have access to any C++ or QObject functionality (e.g. emitting Signals, changing properties, etc.)
You will usually only need to use `impl RustObj` if you want to also use your RustObj struct as a normal Rust struct, that is not wrapped in a QObject.
41 changes: 28 additions & 13 deletions cxx-qt-gen/src/extract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,10 @@ pub struct QObject {
pub ident: Ident,
/// All the methods that can also be invoked from QML
pub(crate) invokables: Vec<Invokable>,
/// All the methods that cannot be invoked from QML
/// All the methods that cannot be invoked from QML (but are on the C++ object)
pub(crate) normal_methods: Vec<ImplItemMethod>,
/// All the rust only methods
pub(crate) rust_methods: Vec<ImplItem>,
/// All the properties that can be used from QML
pub(crate) properties: Vec<Property>,
/// All the signals that can be emitted/connected from QML
Expand Down Expand Up @@ -466,7 +468,7 @@ fn extract_invokables(
.cloned()
.collect();

// Skip non-invokable members
// Non invokable methods we just pass through as normal methods
if filtered_attrs.len() == method.attrs.len() {
normal_methods.push(method);
continue;
Expand Down Expand Up @@ -847,6 +849,8 @@ pub fn extract_qobject(
let mut object_invokables = vec![];
// A list of the normal methods (i.e. not invokables) for the struct
let mut object_normal_methods = vec![];
// A list of the rust only methods for the struct
let mut object_rust_methods = vec![];
// A list of original trait impls for the struct (eg `impl Default for Struct`)
let mut original_trait_impls = vec![];
// A list of insignificant declarations for the mod that will be directly passed through (eg `use crate::thing`)
Expand Down Expand Up @@ -943,7 +947,7 @@ pub fn extract_qobject(
// Extract the path from the type (this leads to the struct name)
if let Type::Path(TypePath { path, .. }) = &mut *original_impl.self_ty {
// Check that the path contains segments
if path.segments.len() != 1 {
if path.segments.is_empty() {
return Err(Error::new(
original_impl.span(),
"Invalid path on impl block.",
Expand Down Expand Up @@ -1008,16 +1012,24 @@ pub fn extract_qobject(
_others => original_trait_impls.push(original_impl.to_owned()),
}
} else {
let mut extracted = extract_invokables(
&original_impl.items,
cpp_namespace_prefix,
&qt_ident,
)?;

object_invokables.append(&mut extracted.invokables);
object_normal_methods.append(&mut extracted.normal_methods);
// Rust only methods are on the impl T block
object_rust_methods.append(&mut original_impl.items);
}
}
// Invokables are defined in the impl cxx_qt::QObject<T> block
"cxx_qt"
if path.segments.len() > 1
&& path.segments[1].ident.to_string().as_str() == "QObject" =>
LeonMatthesKDAB marked this conversation as resolved.
Show resolved Hide resolved
{
let mut extracted = extract_invokables(
&original_impl.items,
cpp_namespace_prefix,
&qt_ident,
)?;

object_invokables.append(&mut extracted.invokables);
object_normal_methods.append(&mut extracted.normal_methods);
}
_others => {
return Err(Error::new(
path.span(),
Expand Down Expand Up @@ -1130,6 +1142,7 @@ pub fn extract_qobject(
ident: qt_ident,
invokables: object_invokables,
normal_methods: object_normal_methods,
rust_methods: object_rust_methods,
properties: object_properties,
signals: object_signals,
signal_ident,
Expand Down Expand Up @@ -1394,7 +1407,8 @@ mod tests {
assert!(invokable.mutable);

// Check that the normal method was also detected
assert_eq!(qobject.normal_methods.len(), 1);
assert_eq!(qobject.normal_methods.len(), 4);
assert_eq!(qobject.rust_methods.len(), 1);
}

#[test]
Expand Down Expand Up @@ -1458,7 +1472,8 @@ mod tests {
// Check that it got the inovkables and properties
assert_eq!(qobject.invokables.len(), 0);
assert_eq!(qobject.properties.len(), 1);
assert_eq!(qobject.normal_methods.len(), 2);
assert_eq!(qobject.normal_methods.len(), 0);
assert_eq!(qobject.rust_methods.len(), 2);

// Check that there is a use, enum and fn declaration
assert_eq!(qobject.original_passthrough_decls.len(), 13);
Expand Down
5 changes: 3 additions & 2 deletions cxx-qt-gen/src/gen_rs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -979,14 +979,14 @@ pub fn generate_qobject_rs(
.map(|ident_wrapper| invokable_generate_wrapper(i, &ident_wrapper.rust_ident))
})
.collect::<Result<Vec<TokenStream>, TokenStream>>()?;

let invokable_methods = obj
.invokables
.iter()
.map(|m| m.original_method.clone())
.collect::<Vec<syn::ImplItemMethod>>();
let normal_methods = &obj.normal_methods;

let normal_methods = &obj.normal_methods;
let rust_methods = &obj.rust_methods;
let original_trait_impls = &obj.original_trait_impls;
let original_passthrough_decls = &obj.original_passthrough_decls;

Expand Down Expand Up @@ -1019,6 +1019,7 @@ pub fn generate_qobject_rs(
#(#invokable_method_wrappers)*
#(#invokable_methods)*
#(#normal_methods)*
#(#rust_methods)*

#handle_update_request
}
Expand Down
22 changes: 20 additions & 2 deletions cxx-qt-gen/test_inputs/invokables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ mod my_object {
#[derive(Default)]
pub struct RustObj;

impl RustObj {
impl cxx_qt::QObject<RustObj> {
#[invokable]
pub fn invokable(&self) {
println!("invokable");
Expand Down Expand Up @@ -66,8 +66,26 @@ mod my_object {
QString::from_str("static")
}

pub fn cpp_context_method(&self) {
println!("C++ context method");
}

pub fn cpp_context_method_mutable(&mut self) {
println!("mutable method");
}

pub fn cpp_context_method_cpp_obj(&mut self, cpp: &mut CppObj) {
println!("cppobj");
}

pub fn cpp_context_method_return_opaque(&self) -> UniquePtr<QColor> {
cxx_qt_lib::QColor::from_rgba(255, 0, 0, 0)
}
}

impl RustObj {
pub fn rust_only_method(&self) {
println!("QML can't call this :)");
println!("QML or C++ can't call this :)");
}
}
}
2 changes: 1 addition & 1 deletion cxx-qt-gen/test_inputs/naming.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ mod my_object {
#[derive(Default)]
pub struct RustObj;

impl RustObj {
impl cxx_qt::QObject<RustObj> {
#[invokable]
pub fn invokable_name(&self) {
println!("Bye from Rust!");
Expand Down
2 changes: 1 addition & 1 deletion cxx-qt-gen/test_inputs/signals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ mod my_object {
#[derive(Default)]
pub struct RustObj;

impl RustObj {
impl cxx_qt::QObject<RustObj> {
#[invokable]
pub fn invokable(&self, cpp: &mut CppObj) {
unsafe {
Expand Down
2 changes: 1 addition & 1 deletion cxx-qt-gen/test_inputs/types_qt_invokable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ mod my_object {
#[derive(Default)]
pub struct RustObj;

impl RustObj {
impl cxx_qt::QObject<RustObj> {
#[invokable]
pub fn test_color(&self, _cpp: &mut CppObj, color: &QColor) -> UniquePtr<QColor> {
color
Expand Down
18 changes: 17 additions & 1 deletion cxx-qt-gen/test_outputs/invokables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,24 @@ mod cxx_qt_my_object {
QString::from_str("static")
}

pub fn cpp_context_method(&self) {
println!("C++ context method");
}

pub fn cpp_context_method_mutable(&mut self) {
println!("mutable method");
}

pub fn cpp_context_method_cpp_obj(&mut self, cpp: &mut CppObj) {
println!("cppobj");
}

pub fn cpp_context_method_return_opaque(&self) -> UniquePtr<QColor> {
cxx_qt_lib::QColor::from_rgba(255, 0, 0, 0)
}

pub fn rust_only_method(&self) {
println!("QML can't call this :)");
println!("QML or C++ can't call this :)");
}
}

Expand Down
40 changes: 38 additions & 2 deletions cxx-qt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@ use cxx_qt_gen::{extract_qobject, generate_qobject_rs};
/// #[cxx_qt::bridge]
/// mod my_object {
/// #[derive(Default)]
/// struct MyObject {
/// struct Data {
/// property: i32,
/// }
///
/// impl MyObject {
/// #[derive(Default)]
/// struct RustObj;
///
/// impl cxx_qt::QObject<RustObj> {
/// #[invokable]
/// fn invokable(&self, a: i32, b: i32) -> i32 {
/// a + b
/// }
Expand Down Expand Up @@ -60,6 +64,38 @@ pub fn signals(_args: TokenStream, _input: TokenStream) -> TokenStream {
unreachable!("cxx_qt::signals should not be used as a macro by itself. Instead it should be used within a cxx_qt::bridge definition")
}

/// A macro which describes that the inner methods should be implemented on the C++ QObject.
/// This allows for defining C++ methods which are Q_INVOKABLE for QML in Rust.
///
/// It should not be used by itself and instead should be used inside a cxx_qt::bridge definition.
///
/// # Example
///
/// ```ignore
/// #[cxx_qt::bridge]
/// mod my_object {
/// #[derive(Default)]
/// struct Data {
/// property: i32,
/// }
///
/// #[derive(Default)]
/// struct RustObj;
///
/// impl cxx_qt::QObject<RustObj> {
/// #[invokable]
/// fn invokable(&self, a: i32, b: i32) -> i32 {
/// a + b
/// }
/// }
/// }
/// ```
#[proc_macro]
#[allow(non_snake_case)]
pub fn QObject(_input: TokenStream) -> TokenStream {
LeonMatthesKDAB marked this conversation as resolved.
Show resolved Hide resolved
unreachable!("cxx_qt::QObject should not be used as a macro by itself. Instead it should be used within a cxx_qt::bridge definition")
}

// Take the module and C++ namespace and generate the rust code
//
// Note that wee need a separate function here, as we need to annotate the lifetimes to allow
Expand Down
2 changes: 2 additions & 0 deletions examples/demo_threading/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,9 @@ mod energy_usage {
.ok();
stream.flush().await.unwrap();
}
}

impl cxx_qt::QObject<RustObj> {
#[invokable]
pub fn start_server(&mut self, cpp: &mut CppObj) {
if self.join_handles.is_some() {
Expand Down
2 changes: 1 addition & 1 deletion examples/qml_extension_plugin/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ mod my_object {
#[derive(Default)]
pub struct RustObj;

impl RustObj {
impl cxx_qt::QObject<RustObj> {
#[invokable]
pub fn increment(&self, cpp: &mut CppObj) {
cpp.set_number(cpp.number() + 1);
Expand Down
2 changes: 1 addition & 1 deletion examples/qml_features/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ mod my_object {
#[derive(Default)]
pub struct RustObj;

impl RustObj {
impl cxx_qt::QObject<RustObj> {
#[invokable]
pub fn increment_number_self(&self, cpp: &mut CppObj) {
let value = cpp.number() + 1;
Expand Down
2 changes: 1 addition & 1 deletion examples/qml_features/src/mock_qt_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ mod mock_qt_types {
#[derive(Default)]
pub struct RustObj;

impl RustObj {
impl cxx_qt::QObject<RustObj> {
#[invokable]
pub fn test_signal(&self, cpp: &mut CppObj) {
cpp.emit_queued(Signal::Ready);
Expand Down
2 changes: 1 addition & 1 deletion examples/qml_features/src/nested.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ mod nested {
#[derive(Default)]
pub struct RustObj;

impl RustObj {
impl cxx_qt::QObject<RustObj> {
#[invokable]
pub fn nested_parameter(
&self,
Expand Down
4 changes: 3 additions & 1 deletion examples/qml_features/src/rust_obj_invokables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub mod rust_obj_invokables {
}
}

impl RustObj {
impl cxx_qt::QObject<RustObj> {
// ANCHOR: book_cpp_obj
#[invokable]
pub fn invokable_mutate_cpp(&self, cpp: &mut CppObj) {
Expand All @@ -39,7 +39,9 @@ pub mod rust_obj_invokables {
self.rust_only_method(factor);
self.rust_only_field
}
}

impl RustObj {
fn rust_only_method(&mut self, factor: i32) {
self.rust_only_field *= factor;
}
Expand Down
Loading