Skip to main content

qtbridge_runtime/
qmlelement.rs

1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only
3
4use std::rc::Rc;
5use std::cell::RefCell;
6
7use crate::QObjectHolder;
8use crate::qobjectholder::CppProxyOf;
9use crate::qqmllistproperty::{list_append, list_count, list_at, list_clear};
10use crate::qproxies::QCppProxy;
11use crate::registry::Owner;
12use qtbridge_type_lib::QObject;
13use qtbridge_type_lib::QMetaType;
14use qtbridge_type_lib::QMetaTypeInterface;
15use qtbridge_type_lib::{QVariant, list_property_to_qvariant};
16
17pub trait QmlElement : QObjectHolder + Default
18{
19    const URI: &str;
20    const ELEMENT_NAME: &str;
21    const MINOR_VERSION: u8;
22    const MAJOR_VERSION: u8;
23
24    /// Returns the [`QMetaType`] identifying this type, unique per concrete
25    /// type.
26    ///
27    /// The `#[qobject]` macro overrides this with a per-type `OnceLock` body; the
28    /// default serves generic types and hand-written impls.
29    fn get_qmetatype() -> QMetaType {
30        let iface = crate::qmetatypeforqobject::interface_for_generic::<Self>();
31        QMetaType::new_with_interface(iface as *const _)
32    }
33    const IS_SINGLETON: bool;
34
35    fn get_list_qmetatype() -> QMetaType {
36        // TODO: The HashMap can be replaced with OnceCell in a per-type generated implementation
37        // of this function in qtbridge-gen. Might improve performance.
38        use std::collections::HashMap;
39        thread_local!(static LIST_IFACE_MAP: RefCell<HashMap<i32, *const QMetaTypeInterface>>
40            = RefCell::new(HashMap::new()));
41
42        let element = Self::get_qmetatype();
43        let key = element.id();
44
45        let existing = LIST_IFACE_MAP.with_borrow(|m| m.get(&key).copied().unwrap_or_default());
46        let iface = if existing.is_null() {
47            let leaked = std::ptr::from_ref(Box::leak(Box::new(
48                QMetaTypeInterface::qqml_list_property_for(&element)
49            )));
50            LIST_IFACE_MAP.with_borrow_mut(|m| m.insert(key, leaked));
51            leaked
52        } else {
53            existing
54        };
55        QMetaType::new_with_interface(iface)
56    }
57
58    fn list_to_qvariant<Owner, Notify>(owner: &Owner, store: &Vec<Rc<RefCell<Self>>>, _notify: Notify) -> QVariant
59    where
60        Owner: QObjectHolder,
61        Notify: Fn(&mut Owner) + 'static,
62    {
63        debug_assert_eq!(std::mem::size_of::<Notify>(), 0, "Notify must be a zero-sized type");
64        let qobject = owner.get_qobject_ptr();
65        let base = owner as *const Owner as *const u8;
66        let field = store as *const _ as *const u8;
67        // SAFETY: `store` is a field within `owner`, so both pointers are in the same allocation.
68        // The `owner` can be reconstructed safely, even when objects are moved since we store a
69        // shared reference to it. With the known offset we can also reconstruct `store`. The callbacks
70        // in crate::qqmllistproperty expect this exact format.
71        let store_offset = unsafe { field.offset_from(base) } as usize;
72        unsafe { list_property_to_qvariant(
73            &Self::get_list_qmetatype(),
74            qobject,
75            store_offset as *mut u8,
76            (list_append::<Owner, Self, Notify> as *const ()).addr(),
77            (list_count::<Owner, Self> as *const ()).addr(),
78            (list_at::<Owner, Self> as *const ()).addr(),
79            (list_clear::<Owner, Self, Notify> as *const ()).addr(),
80        ) }
81    }
82
83    fn register() {
84        let meta_obj_data = <Self as QObjectHolder>::get_shared_dynamic_meta_object_data();
85        let meta_obj = unsafe {
86            meta_obj_data
87                .get_meta_object()
88                .as_ref()
89                .expect("Failed to get QMetaObject")
90        };
91
92        if Self::IS_SINGLETON {
93            crate::qmlprivate::qml_register_singleton(
94                <Self as QObjectHolder>::get_qobject_ptr_qmetatype(),
95                monomorphize_singleton_ctor::<Self>(),
96                Self::URI.as_bytes(),
97                Self::MAJOR_VERSION,
98                Self::MINOR_VERSION,
99                Self::ELEMENT_NAME.as_bytes(),
100                meta_obj,
101            )
102        } else {
103            let list_metatype = Self::get_list_qmetatype();
104            list_metatype.register_type();
105
106            crate::qmlprivate::qml_register_element(
107                <Self as QObjectHolder>::get_qobject_ptr_qmetatype(),
108                list_metatype,
109                <CppProxyOf<Self> as QCppProxy>::get_size() as u32,
110                <CppProxyOf<Self> as QCppProxy>::parser_status_cast(),
111                monomorphize_element_ctor::<Self>(),
112                Self::URI.as_bytes(),
113                Self::MAJOR_VERSION,
114                Self::MINOR_VERSION,
115                Self::ELEMENT_NAME.as_bytes(),
116                meta_obj,
117            );
118        }
119    }
120}
121
122fn element_ctor<T: QmlElement>(addr: *mut u8, _userdata: *mut u8) {
123    let instance = std::rc::Rc::new(std::cell::RefCell::new(T::default()));
124    T::register_instance(instance.clone(), Owner::Engine, Some(addr));
125}
126
127fn singleton_ctor<T: QmlElement>() -> *mut QObject {
128    let instance = std::rc::Rc::new(std::cell::RefCell::new(T::default()));
129    T::register_instance(instance.clone(), Owner::Engine, None);
130    instance.borrow().get_qobject_ptr()
131}
132
133fn monomorphize_element_ctor<T: QmlElement>() -> usize {
134    extern "C" fn default_ctor<T: QmlElement>(addr: *mut u8, userdata: *mut u8) {
135        element_ctor::<T>(addr, userdata)
136    }
137    default_ctor::<T> as *const () as usize
138}
139
140fn monomorphize_singleton_ctor<T: QmlElement>() -> usize {
141    extern "C" fn default_ctor<T: QmlElement>() -> *mut QObject {
142        singleton_ctor::<T>()
143    }
144    default_ctor::<T> as *const () as usize
145}