woboq / qmetaobject-rs

Integrate Qml and Rust by building the QMetaObject at compile time.
MIT License
648 stars 89 forks source link

Example how to create a global application interface object available as QmlEngine root context property #306

Open antis81 opened 1 year ago

antis81 commented 1 year ago

I am pretty new to Rust and simply found the idea intriguing and adapt to an existing QML frontend (this one more precisely). The first steps went pretty smooth starting from the qmetaobject example and I was quickly able to get some visuals rendered on screen. :+1:

However I got a little stuck with how to register a QObject as a root context property to interface with the application backend. A little (functional C++) snippet should illustrate this.

int main(int argc, char *argv[]) {
  QGuiApplication app(argc, argv);

  MyObject_1 obj_1; // some QObject instance
  MyObject_2 obj_2; // another QObject instance

  QQmlApplicationEngine engine;
  engine.rootContext()->setContextProperty(u"Obj1"_qs, &obj_1);
  engine.rootContext()->setContextProperty(u"Obj2"_qs, &obj_2);
  // engine.load(…qml page…);
  return app.exec();
}

In the C++ application those (global) objects are then accessible in QML.

Text { text: "Obj1 name:" + Obj1.name }

I could not find an example on how to formulate something like this with qmetaobject-rs. Something like qml_register_singleton_instance(…) would also be fine. The question is more like how to correctly bind a QObject value in rust?

use qmetaobject::prelude::*

#[derive(QObject, Default)]
struct MyObject1 {
    base: qt_base_class!(trait QObject),
    my_method: qt_method!(
        fn my_method(&self) -> QString {
            "Hello QML!".into()
        }
    ),
}

fn main() {
    let mut obj_1 = /* ??? what to put here ??? */;
}

:information_source: UPDATE: The following example works. May be useful for an example on the website.

fn main() {
    let obj_1 = MyObject1::default();
    // FIXME: Does compile; produces runtime error in QML -> Unable to determine callable overload.
    // qml_register_singleton_instance(cstr!("Obj1"), 1, 0, cstr!("Obj1"), obj_1);

    let mut engine = QmlEngine::new();
    let app_interface = engine.new_qobject(obj_1);

    engine.set_property("Obj1".into(), obj_1.to_variant()); // TODO: Couldn't get this to work with set_object_property
    engine.add_import_path("qml".into());
    engine.load_file("qml/Main.qml".into());
    engine.exec();
}

However I am not completely happy with this and think a singleton instance is preferable for this (or maybe QJSValue property?). Is the FIXME mentioned actually an issue with QmlEngine's internal initalization sequence? (no other code changes involved). :lady_beetle: :question: