Skip to content

Commit

Permalink
cxx-qt-gen: use impl cxx_qt::QObject<T> block for invokables
Browse files Browse the repository at this point in the history
This helps later as the invokables are implemented onto a different
struct, so this clearly defines the split between C++ and Rust
methods etc.
  • Loading branch information
ahayzen-kdab committed Aug 3, 2022
1 parent 14594b5 commit a7b8db8
Show file tree
Hide file tree
Showing 25 changed files with 144 additions and 50 deletions.
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
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 to 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" =>
{
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 {
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
5 changes: 3 additions & 2 deletions 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 @@ -37,9 +37,10 @@ pub mod rust_obj_invokables {
#[invokable]
pub fn invokable_multiply(&mut self, factor: i32) -> i32 {
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
2 changes: 1 addition & 1 deletion examples/qml_features/src/serialisation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ mod serialisation {
#[derive(Default)]
pub struct RustObj;

impl RustObj {
impl cxx_qt::QObject<RustObj> {
#[invokable]
pub fn as_json_str(&self, cpp: &mut CppObj) -> UniquePtr<QString> {
let data = Data::from(cpp);
Expand Down
2 changes: 1 addition & 1 deletion examples/qml_features/src/signals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ pub mod signals {
pub struct RustObj;

// ANCHOR: book_rust_obj_impl
impl RustObj {
impl cxx_qt::QObject<RustObj> {
#[invokable]
pub fn invokable(&self, cpp: &mut CppObj) {
unsafe {
Expand Down
2 changes: 1 addition & 1 deletion examples/qml_features/src/sub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ pub mod sub_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();
Expand Down
Loading

0 comments on commit a7b8db8

Please sign in to comment.