kdab / cxx-qt Goto Github PK
View Code? Open in Web Editor NEWSafe interop between Rust and Qt
Home Page: https://kdab.github.io/cxx-qt/book/
Safe interop between Rust and Qt
Home Page: https://kdab.github.io/cxx-qt/book/
Have the ability to declare a #[property(getter = "...", setter = "...", notify = "...")]
For example we can't currently say that a property is readonly to C++/QML, but note that this would require the setter to still be accessible from Rust. Does this simply mean which Q_PROPERTY attributes as set?
We need to support enums to allow for complex objects like QAbstractListModel etc.
https://doc.qt.io/qt-5/qtqml-typesystem-basictypes.html
impl Default
as Qt and any relevant get/set methods. (split into #53 )Original Description
Types like QPoint have a lot of member functions, as well as operators (i.e. +,-,/,*) that make them a lot more useful. We should give Rust access to these as well.
De-deduplicate all the code that converts idents to specific cases (e.g. PascalCase) into one specific place.
Instead of using #[invokable]
to mark which methods should be exposed to Qt, use the visibility.
// This method is exposed to Qt as a Q_INVOKABLE
pub fn qt_invokable() {}
// This method is not exposed to Qt but other Rust objects can use it
pub(crate) fn rust_only_public_method() {}
// This method is private to only this Rust struct
fn rust_only_private_method() {}
Later we might also want to have #[cxx_qt(skip)]
to skip pub invokables which could be related to #22 and #25
Ensure that our wrapped Qt types have defaults that are the same as Qt and relevant methods (eg getters and setters).
Move to Rust 1.56 + Rust 2021 - https://doc.rust-lang.org/edition-guide/editions/transitioning-an-existing-project-to-a-new-edition.html
Consider other modernisations
For the conversion of some types, we currently rely on the fact that sizeof(int) is 32 bit wide on most platforms.
Unfortunately, according to the C and C++ standards, this doesn't have to be the case. There might already exist platforms where an "int" is 64 bits wide.
Therefore instead of checking that an int is 32-bit wide, maybe the appropriate action is to change the data type we're using for the Rust side depending on the size of int.
We should probably also look into how CXX deals with this ๐ค
Currently nested objects can be used as properties and parameters. But they cannot be used as return types as we need return a pointer on the C++ side, but also ensure that the ownership is in a valid place (?). Eg we can't construct a sub object inside an invokable and then return it, as no one owns it.
Make the examples more logical so they have a property, parameter, return type for each type. And then have (de)serialise for each type. And then have a sub object test.
Check if cargo emits any data about what libraries should be linked, eg when using diesel with feature sqlite, you need to add sqlite3 to target_link_libraries.
Use full module path in generated source paths, so src/moda/lib.rs with the contents mod modb { mod modc { our macro around mod my_object } } would become src/moda/modb/modc/my_object.h. This helps later if we allow for generating objects into different QML namespaces.
At the moment it's possible to cause two modules to collide in generation.
There will be fun here around sub objects, might even simplfy code as sub objects use fully qualified names?
For types such as QPoint consider how the user can use these with serde on the Data struct.
Options appear to be
For certain types (String / QString/ QPointF ?) the type should be a reference and not value as the parameter in an invokable.
Ensure that this is the case and this could be related to #9
Allow for methods on the Data struct, this allows for eg serde(default = "default_filed") attribute to use a method on the Data struct. Note these are private methods and not invokables.
Add u64 and i64 support, qint64 and std::int64_t disagree on long long int vs long int so cxx becomes confused. And using just std means QML is confused (we need to register the types?).
Currently our includes are just #include <QColor>
, consider using #include <QtGui/QColor>
so that it is obvious which Qt modules we need to link to in the cmake.
As this caused compile failures when developing due to missing non-obvious modules.
Currently there is no way to perform any tasks at creation of the object that require access to the CppObj. (eg starting a background thread that takes the update_requester).
There also isn't a way to tidy up when the object is deconstructed, eg closing a thread / disconnect from db / network.
Add a HandleInit / HandleDestroy trait and method similar to handle_property_change, which is triggered by in the constructor / deconstructor of the object.
Figure out how sub objects in Data can be (de)serialised ? At the moment subobjects are removed silently from the internal Data, so then the from impl of Data doesn't consider it.
We might need to manually implement the (de)serialise traits to have custom code.
We need to come up with a better way to suppress "possibly lost" errors. Suppression file doesn't work because there is a ton of mangled names that won't remain stable. So we currently use --error-exitcode=1 --errors-for-leak-kinds=definite
.
We fail to generate a C++ from Rust File with the error explicit #[repr(...)] is required for enum without any variants
, is this due to the Property enum being empty in the CXX bridge?
Solutions are one of the following
impl From<&QPointF> for QPointF
for trivial typesQPointF::new(x, y)
impl From<&QVariant> for Variant
for opaque types and just use the to_rust()
?impl<'a> From<&CppObj<'a>> for Data
so that it uses .clone()
or .to_rust()
for the right values
Currently we have cpp: Pin<&mut CppObj>
as the type to signify the Cpp instance. This works until you try to perform multiple mutations, and doesn't allow us to perform the Rust <-> C++ conversions transparently. This also means users can perform tasks that might be considered "unsafe" - eg triggering an invokable via the C++ side rather than Rust (could cause deadlock).
Currently users need to write let wrapper = CppObjWrapper::new(cpp);
and then user this wrapper.
Instead use something like cpp: CppObj
or cpp: CppObjWrapper
as the type the user uses, then transparently generate another rust method which is called first and takes the Pin<&mut CppObj>
, then creates the wrapper and calls the users method.
The cxx-qt-gen/src/cxx_qt.cpp
file must create a C++ wrapper function for every function we want to use on an opaque type on the Rust side.
That requires a lot of boilerplate code that should be automated.
We need support for the following items
cxx_qt(QAbstractItemModel)
)// what happens for qabstractitemmodel, qabstracttablemodel, qstandardmodel etc ?
#[make_qabstractlistmodel]
mod my_model {
#[derive(Default)]
pub struct Row {
first_name: String,
last_name: String,
}
#[derive(Default)]
struct RustObj {
items: Vec<Row>,
}
// TODO: need a way to trigger dataChanged() from another thread
// TODO: need a way to trigger beginInsertRows / remove / reset / move etc
impl unsafe Model<CppObj, Role> for RustObj {
// TODO: what do we return here ? some kind of variant or an enum?
fn data(&self, index: i32, role: Role) -> Option<Value> {
if let Some(row) = self.items.get(index) {
let value = match role {
Role::FirstName => row.first_name.into(),
Role::LastName => row.last_name.into(),
}
Some(value)
} else {
None
}
}
// TODO: QAbstractItemModel takes a parent: QModelIndex
fn row_count(&self) -> i32 {
self.items.len()
}
fn set_data(&mut self, index: i32, value: Value, role: Role) -> bool {
if let Some(row) = self.items.get_mut(index) {
match role {
// TODO: how do we do the conversion here?
Role::FirstName => row.first_name = value.into(),
Role::LastName => row.last_name = value.into(),
}
// TODO: needs to emit dataChanged()
// could we have an existing channel to send signals into?
// or should developers handle this themselves
self.model.send(Model::DataChanged{ first, last });
true
} else {
false
}
}
}
impl UpdateRequestHandler<CppObj> for RustObj {
fn handle_update_request(&mut self, cpp: &mut CppObj) {
// From a custom channel we could perform signal updates here
cpp.model_data_changed(first, last);
cpp.model_rows_inserted(first, last);
cpp.model_rows_moved(first, last);
cpp.model_rows_removed(first, last);
cpp.model_reset();
}
}
}
Try to enable #![deny(missing_docs)] in all crates and ensure any ```ignore are removed from code blocks so we have real code blocks
Consider changing our macros to always have cxx_qt so we don't collide. This is similar to other crates such as serde. As currently we use macros like make_qobject
and invokable
these could collide with other crates.
Eg have cxx_qt(QObject), cxx_qt(QAbstractListModel), cxx_qt(invokable), cxx_qt(enum)
Github Actions supports using Windows as the machine, add a CI definition which supports this ( https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources )
Go through C++ code and ensure we use std::int32_t
or similar instead of int
or qint32
etc
Once we have #8 the user will always be using the CppObjWrapper. So then in the CppObjWrapper getter/setters etc change the types so that the parameters and return types are the "Rust" types and any conversion is performed inside the CppObjWrapper.
Eg instead of the user needing to change QVariant -> Variant. The CppObjWrapper can do the to_rust() conversion and return Variant.
Currently we only allow crate::module::CppObj
or CppObj
as the type. Once we are able to track where a module is relative to the root, consider adding support for super::module::CppObj
.
Currently this won't work as we need to build the namespace and other items from the module path, so we require a full module path.
We need the following to be true
use
)Rename C++ members so that more Q_PROPERTY names are available eg use m_internalRustObj, m_propertyObj, m_mutexInternalRustObj, m_mutexPropertyObj etc
So there could be the following prefixes
Also consider if there are any names we cannot avoid collisions and we should error if they are used in Rust (and if this should move to another issue).
Potentially document in the Rust Book how enabling lto in the cargo.toml of the project can reduce the plugin size (eg we went from 5MB โ 2MB)
If you have a method named a_b2 something gets confused and it looks for a_b_2. This is likely due to the convert_case going wrong somewhere.
Not all of the gen_cpp code is formatted with clang-format's mozilla style, ensure that we are following that to reduce work by clang-format on our generated C++ code. (eg in the cpp files the return type is on the same line as the method definition not the previous).
Currently we generate code the is void valueChanged();
in the C++, this works fine for QML. But when using C++ / widgets sometimes it's easier if the signal passes the new property value as well, eg void valueChanged(int value)
.
Currently we support types by value in signals but we don't support having a sub object / CppObj as an argument.
Consider if this is possible or useful ?
Currently if you use the QQmlExtensionPlugin all generated types are registered to the QML engine.
But if you don't use the plugin then you currently have to register them manually.
Provide a method somewhere that does this automatically, eg CxxQtTypes::registerTypes(uri)
.
We should change CxxQObject to be a member of the generated class rather than inherited.
This will then allow us to reuse the same object for other classes we generate (eg non-QObject ones).
This could look something like ThreadHelper m_threadHelper
and then m_threadHelper->runOnGUIThread(...)
.
If you try to put a const VAR: T = V
inside our macro module then it fails with unsupported type. We should pass these through like we do with use
and enum
etc.
Rust API would use enums, then have an emit method in the wrapper. We use named struct like enums so that the names can be used for the C++ signal arg.
enum Signal {
Ready,
DataChanged { first: i32, second: i32 }
}
impl RustObj {
#[invokable]
fn invokable(&self, cpp: &mut CppObj) {
cpp.emit_queued(Signal::Ready { 1, 2 });
cpp.emit_queued(Signal::DataChanged { 1, 2 });
}
}
Internally the emit method destructures the enum and call a relevant C++ method.
impl CppObj {
fn emit_queued(&mut self, signal: Signal) {
match signal {
Signal::Ready => self.cpp.as_mut().emit_ready(),
Signal::DataChanged(first, second) => self.cpp.as_mut().emit_data_changed(first, second),
}
}
}
The public C++ side would then appear as
Q_SIGNALS:
void ready();
void dataChanged(qint32 first, qint32 second);
Then the C++ side would have methods for each of these signals to emit them on the GUI thread (these are called from Rust).
void MyObject::emitReady() {
runOnGUIThread([&]() { Q_EMIT ready(); };
}
void MyObject::emitDataChanged(qint32 first, qint32 second) {
runOnGUIThread([&]() { Q_EMIT dataChanged(); };
}
The flow is the following for a generic "MySignal" with "number".
# A signal being emitted from Rust
CppObj::emit_queued(Signal::MySignal { number }) -> MyObject::emit_my_signal(number) -> runOnGUIThread -> Q_EMIT MyObject::mySignal(number)
Have the ability to perform borrowRustObj on a sub objects CppObjWrapper (not our own)
This would allow the QML code below, where the tablemodel needs to clone() the database pool on the project RustObj to access the database connection.
Project { id: project; path: "/my/db" }
TableModel { project: project }
Add option to mark an option as a singleton in the macro attribute, so make_object(singleton = true) or something.
Once #22 is done this might be cxx_qt(QObject, singleton = true)
or cxx_qt(QObject(singleton = true))
. Consider if we can hint the QML versions and name here as well.
Ensure that helper methods like rustStringToQString are in the CxxQt namespace (and consider CxxQt vs cxx_qt).
"rust-analyzer.checkOnSave": false,
set)Github Actions supports using macOS as the machine, add a CI definition which supports this ( https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources ).
Currently we sometimes declare extern "C"
functions in C++ that take references as arguments.
As references aren't part of the C language, it's unclear what exactly this code compiles down to. Currently, this doesn't seem to cause issues under GCC, Clang or MSVC, but it is likely compiler dependent.
This is a tracking issue to reference, should this issue come up in the future.
We will not change our code for now, as CXX also uses references in this way, so CXX would need to be changed anyway.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.