qmetaobject-rs

repository·master·Indexed 20 days ago

https://github.com/woboq/qmetaobject-rs

A framework for creating Qt/QML applications using Rust. It generates QMetaObject at compile time via procedural macros, allowing Rust structs to be exposed as QML types with properties, signals, and methods. The project includes the qmetaobject crate for QML bindings and the qttypes crate for idiomatic Rust bindings to basic Qt value types. It provides tools for nesting QObjects using RefCell, integrating asynchronous Rust futures into the Qt event loop, and calling missing C++ wrappers via the cpp! macro.

Tokens
18.5K
Snippets
50
Records
69
Agent score
72%

What's inside qmetaobject-rs

  1. Use qttypes for idiomatic Qt value type bindings

    master

    The qttypes crate provides manually generated, idiomatic Rust bindings to basic Qt value types. These types are exposed on the stack and are designed to be used by other crates (like qmetaobject) or directly by developers who need direct equivalents to Qt's C++ value types.

    Unlike the qmetaobject crate which focuses on QObject and widgets, qttypes concentrates specifically on core value types. The API is designed to be 'Rust-like' while maintaining similarity to the original Qt C++ API.

  2. Compare qmetaobject with other Rust-Qt solutions

    master

    When choosing a library for Rust and Qt integration, consider the following distinctions:

    • qmetaobject: Focuses exclusively on providing idiomatic Rust bindings for QML. It aims to eliminate the need for C++ knowledge or external build systems. It is currently in passive maintenance.
    • CXX-Qt: Best for incorporating Rust into an existing C++ project. It uses modern Rust features like attribute macros.
    • Rust Qt Binding Generator: Designed for integrating Rust logic into existing C++/Qt projects using an external JSON file for code generation.
    • Slint: A separate project (created by the same author) that is not a QML/Qt binding. It is a new UI language implemented entirely in Rust, inspired by QML, intended as a modern alternative to Qt for Rust developers.
  3. How to nest QObjects using RefCell

    master

    In qmetaobject-rs, you can nest one QObject inside another by wrapping the child QObject in a RefCell. This allows the parent QObject to own the child while still providing the interior mutability required to interact with the child through the Qt meta-object system.

    This pattern is useful when you need to build complex object hierarchies where components are managed by a parent object but must remain accessible to Qt's property and signal/slot systems.

  4. Create Qt/QML applications with Rust using QMetaObject

    master

    The qmetaobject crate allows you to create Qt/QML applications by defining QObjects in Rust. It uses procedural macros to generate QMetaObjects at compile time, enabling you to expose Rust structs as QML types with properties, signals, and methods.

    Key Workflow:

    1. Use #[derive(QObject)] on a struct to make it a Qt object.
    2. Use qt_base_class! to specify the Qt base class (e.g., trait QObject).
    3. Use qt_property! to define properties accessible from QML.
    4. Use qt_signal! to define signals.
    5. Use qt_method! to define slots (methods) callable from QML.
    6. Register the type with qml_register_type::<T>(...).
    7. Load your QML code using QmlEngine.
    use cstr::cstr;
    use qmetaobject::prelude::*;
    
    #[derive(QObject, Default)]
    struct Greeter {
        base: qt_base_class!(trait QObject),
        name: qt_property!(QString; NOTIFY name_changed),
        name_changed: qt_signal!(),
        compute_greetings: qt_method!(fn compute_greetings(&self, verb: String) -> QString {
            format!("{} {}", verb, self.name.to_string()).into()
        })
    }
    
    fn main() {
        qml_register_type::<Greeter>(cstr!("Greeter"), 1, 0, cstr!("Greeter"));
        let mut engine = QmlEngine::new();
        engine.load_data(r#"
            import QtQuick 2.6
            import QtQuick.Window 2.0
            import Greeter 1.0
    
            Window {
                visible: true
                Greeter {
                    id: greeter;
                    name: "World"
                }
                Text {
                    anchors.centerIn: parent
                    text: greeter.compute_greetings("hello")
                }
            }
        "#.into());
        engine.exec();
    }
  5. Add missing Qt C++ wrappers using the `cpp!` macro

    master

    If a specific Qt C++ function is not wrapped by qmetaobject-rs, you can call it directly from Rust using the cpp! macro from the cpp crate.

    Setup Requirements

    1. Dependencies: Add qttypes (with required features like qtquick), cpp, and cpp_build to your Cargo.toml.
    2. Build Script: Copy the build.rs from the qmetaobject repository to your project. This ensures cpp_build runs correctly with the qttypes environment.

    Using the cpp! macro

    • Verbatim Content: Use {{ ... }} to append content (like #include directives or C++ class definitions) directly to the generated C++ file.
    • Runtime Expressions: Use ( ... ) to call C++ expressions. This requires an unsafe block or keyword and allows passing Rust variables as C++ arguments using the [arg as "Type"] syntax.

    Example: Calling a method on a QObject

    To call a method on a wrapped object, use .get_cpp_object() to get the raw pointer, then pass it into the cpp! macro.

    // Example: Calling QQuickItem::setFlag
    let obj = self.get_cpp_object();
    cpp!(unsafe [obj as "QQuickItem *"] {
        obj->setFlag(QQuickItem::ItemHasContents);
    });
    // 1. Include headers
    cpp! {{
        #include <QtQuick/QQuickItem>
    }}
    
    // 2. Call C++ methods
    impl Graph {
        fn set_flag(&mut self, flag: QQuickItemFlag) {
            let obj = self.get_cpp_object();
            assert!(!obj.is_null());
            cpp!(unsafe [obj as "QQuickItem *", flag as "QQuickItem::Flag"] {
                obj->setFlag(flag);
            });
        }
    }
  6. Porting examples from rust-qt-binding-generator to qmetaobject-rs

    master
    This directory provides patches to demonstrate how to port examples originally written using rust-qt-binding-generator to qmetaobject-rs. The primary goal is to show that qmetaobject-rs simplifies the process by removing the requirement to write C++ code, even for implementations.
  7. Run the qrep example port

    master

    The qrep example is a port of the tool described in this blog post. To run this port, you must clone the original repository, checkout the specific branch, and apply the provided patches from the qmetaobject-rs repository.

    git clone https://invent.kde.org/vandenoever/qrep
    cd qrep
    git checkout bdbde040e74819351609581c0d98a59bbfeecbf9 -b qmetaobject-rs
    git am ../qmetaobject-rs/examples/rqbg/0001-Port-to-qmetaobject-rs.patch
    git am ../qmetaobject-rs/examples/rqbg/0002-Fix-build.patch
    cargo run
  8. Run the mailmodel example port

    master

    The mailmodel example is a port of the application described in this blog post. To run it, clone the repository, checkout the specific branch, apply the mailmodel.patch, and provide a configuration file (e.g., config.json) when running via cargo run.

    Troubleshooting OpenSSL errors: If you encounter compilation errors related to OpenSSL, try setting the following environment variables: OPENSSL_LIB_DIR=/usr/lib/openssl-1.0 OPENSSL_INCLUDE_DIR=/usr/include/openssl-1.0

    git clone https://anongit.kde.org/scratch/vandenoever/mailmodel
    cd mailmodel
    git checkout 87991f1090b57706f5c713c842568eba144cec2 -b qmetaobject-rs
    git am ../qmetaobject-rs/examples/rqbg/mailmodel.patch
    # create a configuration file as explained
    cargo run config.json
  9. Implement a custom data model with QAbstractItemModel

    master

    To create a custom data model in Rust that can be used with Qt, implement the QAbstractItemModel trait on a type that also implements QObject. This trait allows you to override the core methods of Qt's QAbstractItemModel to provide your own data structure to Qt views.

    Required Methods

    You must implement the following methods to satisfy the core requirements of a model:

    • index(&self, row: i32, column: i32, parent: QModelIndex) -> QModelIndex: Returns the model index for a given position.
    • parent(&self, index: QModelIndex) -> QModelIndex: Returns the parent index for a given index.
    • row_count(&self, parent: QModelIndex) -> i32: Returns the number of rows under the given parent.
    • column_count(&self, parent: QModelIndex) -> i32: Returns the number of columns under the given parent.
    • data(&self, index: QModelIndex, role: i32) -> QVariant: Returns the data for a specific index and role.

    Optional Methods

    • set_data(&mut self, index: QModelIndex, value: &QVariant, role: i32) -> bool: Allows modifying data. Defaults to returning false.
    • role_names(&self) -> HashMap<i32, QByteArray>: Defines the mapping of integer roles to byte arrays (useful for QML integration). Defaults to an empty map.
    use qmetaobject::*;
    use std::collections::HashMap;
    
    #[derive(QObject, Default)]
    struct MyModel {
        base: qt_base_class!(trait QAbstractItemModel),
    }
    
    impl QAbstractItemModel for MyModel {
        fn row_count(&self, _parent: QModelIndex) -> i32 {
            10
        }
        fn column_count(&self, _parent: QModelIndex) -> i32 {
            1
        }
        fn data(&self, index: QModelIndex, _role: i32) -> QVariant {
            // Return data for the row
            QVariant::from(format!("Row {}", index.row()))
        }
        fn index(&self, row: i32, column: i32, parent: QModelIndex) -> QModelIndex {
            self.create_index(row, column, 0)
        }
        fn parent(&self, _index: QModelIndex) -> QModelIndex {
            QModelIndex::new()
        }
    }
  10. Configure Cargo features for qmetaobject

    master

    The crate provides several optional features to extend its functionality:

    • log (Enabled by default): Integrates Qt's logging system (like console.log in QML) with the Rust log crate. To use it, call qmetaobject::log::init_qt_to_rust(); as early as possible in your main() function.
    • chrono_qdatetime (Disabled by default): Enables interoperability between Qt's QDate/QTime and the Rust chrono crate.
    • webengine (Disabled by default): Enables QtWebEngine functionality.
  11. Manage QObject lifetime with `QPointer` and `QObjectPinned`

    master

    Because QObjects are exposed to C++, they cannot be moved in memory once they are registered.

    • QObjectPinned<'pin, T>: A reference to a QObject inside a RefCell that is guaranteed not to move. Use borrow() or borrow_mut() to access the underlying Rust data.
    • QPointer<T>: A weak pointer to a QObject. It tracks the C++ object and returns None if the object has been deleted by the Qt engine. Use .as_ref() or .as_pinned() to access the object safely.
    • QObjectBox<T>: A wrapper around Box<RefCell<T>> that ensures the object is pinned in memory, making it safe for exposure to Qt.