diff --git a/CHANGELOG.md b/CHANGELOG.md index 01c066438..3c53daf75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- New example: ToDo app - `#[auto_cxx_name]` and `#[auto_rust_name]` attributes for `extern` blocks, which will convert the case of names, automatically camelCase for cxx, and snake_case for rust - Support for further types: `QLine`, `QLineF`, `QImage`, `QPainter`, `QFont`, `QPen`, `QPolygon`, `QPolygonF`, `QRegion`, `QAnyStringView` - `internal_pointer_mut()` function on `QModelIndex` diff --git a/Cargo.toml b/Cargo.toml index 6663dbdd1..7db2a9ecb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ members = [ "crates/cxx-qt-lib-extras", "examples/cargo_without_cmake", + "examples/todo_app", "examples/demo_threading/rust", "examples/qml_features/rust", "examples/qml_minimal/rust", diff --git a/examples/todo_app/Cargo.toml b/examples/todo_app/Cargo.toml new file mode 100644 index 000000000..326a64cc9 --- /dev/null +++ b/examples/todo_app/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "cxx-qt-todo-app" +version = "0.1.0" +edition = "2021" + +[dependencies] +cxx.workspace = true +cxx-qt.workspace = true +cxx-qt-lib = { workspace = true, features = ["full"] } + +[build-dependencies] +cxx-qt-build = { workspace = true, features = ["link_qt_object_files"] } diff --git a/examples/todo_app/build.rs b/examples/todo_app/build.rs new file mode 100644 index 000000000..ae43c719e --- /dev/null +++ b/examples/todo_app/build.rs @@ -0,0 +1,12 @@ +use cxx_qt_build::{CxxQtBuilder, QmlModule}; +fn main() { + CxxQtBuilder::new() + .qml_module(QmlModule { + uri: "com.kdab.todo", + qml_files: &["qml/main.qml"], + rust_files: &["src/todo_list.rs"], + ..Default::default() + }) + .qt_module("Network") + .build(); +} diff --git a/examples/todo_app/qml/main.qml b/examples/todo_app/qml/main.qml new file mode 100644 index 000000000..4880ef7b5 --- /dev/null +++ b/examples/todo_app/qml/main.qml @@ -0,0 +1,57 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.11 +import QtQuick.Window 2.12 + +import com.kdab.todo 1.0 + +ApplicationWindow { + width: 640 + height: 480 + visible: true + + title: qsTr("Todo List") + + TodoList { + id: todoList + } + + + Component { + id: todoDelegate + + CheckBox { + width: ListView.view.width + checked: model.done + + text: model.todo + font.strikeout: model.done + onCheckedChanged: { + if (checked !== model.done) { + todoList.setChecked(model.index, checked); + } + } + } + } + + ListView { + anchors.fill: parent + model: todoList + delegate: todoDelegate + spacing: 10 + } + + footer: RowLayout { + TextField { + id: newTodo + Layout.fillWidth: true + placeholderText: qsTr("Add new Todo") + } + Button { + text: qsTr("Add") + onClicked: { + todoList.addTodo(newTodo.text) + } + } + } +} diff --git a/examples/todo_app/src/main.rs b/examples/todo_app/src/main.rs new file mode 100644 index 000000000..4eebd1889 --- /dev/null +++ b/examples/todo_app/src/main.rs @@ -0,0 +1,16 @@ +mod todo_list; + +use cxx_qt_lib::{QGuiApplication, QQmlApplicationEngine, QUrl}; + +fn main() { + let mut app = QGuiApplication::new(); + let mut engine = QQmlApplicationEngine::new(); + + if let Some(engine) = engine.as_mut() { + engine.load(&QUrl::from("qrc:/qt/qml/com/kdab/todo/qml/main.qml")); + } + + if let Some(app) = app.as_mut() { + app.exec(); + } +} diff --git a/examples/todo_app/src/todo_list.rs b/examples/todo_app/src/todo_list.rs new file mode 100644 index 000000000..5ae1745cd --- /dev/null +++ b/examples/todo_app/src/todo_list.rs @@ -0,0 +1,138 @@ +use std::pin::Pin; + +use cxx_qt_lib::QString; +use qobject::TodoRoles; + +#[cxx_qt::bridge] +mod qobject { + unsafe extern "C++" { + // #include + include!(< QAbstractListModel >); + type QAbstractListModel; + + include!("cxx-qt-lib/qmodelindex.h"); + type QModelIndex = cxx_qt_lib::QModelIndex; + + include!("cxx-qt-lib/qvariant.h"); + type QVariant = cxx_qt_lib::QVariant; + + include!("cxx-qt-lib/qstring.h"); + type QString = cxx_qt_lib::QString; + + include!("cxx-qt-lib/qhash.h"); + type QHash_i32_QByteArray = cxx_qt_lib::QHash; + } + + #[qenum(TodoList)] + enum TodoRoles { + Todo, + Done, + } + + unsafe extern "RustQt" { + #[qobject] + #[qml_element] + #[base = QAbstractListModel] + type TodoList = super::TodoListRust; + + #[cxx_override] + #[rust_name = "row_count"] + fn rowCount(self: &TodoList, parent: &QModelIndex) -> i32; + + #[cxx_override] + fn data(self: &TodoList, index: &QModelIndex, role: i32) -> QVariant; + + #[cxx_override] + #[rust_name = "role_names"] + fn roleNames(self: &TodoList) -> QHash_i32_QByteArray; + } + + unsafe extern "RustQt" { + #[qinvokable] + #[rust_name = "set_checked"] + fn setChecked(self: Pin<&mut TodoList>, row: i32, checked: bool); + + #[inherit] + #[rust_name = "begin_reset_model"] + fn beginResetModel(self: Pin<&mut TodoList>); + + #[inherit] + #[rust_name = "end_reset_model"] + fn endResetModel(self: Pin<&mut TodoList>); + + #[qinvokable] + #[rust_name = "add_todo"] + fn addTodo(self: Pin<&mut TodoList>, todo: &QString); + } +} + +pub struct TodoListRust { + todos: Vec<(bool, QString)>, +} + +impl Default for TodoListRust { + fn default() -> Self { + Self { + todos: vec![ + (true, "Build ToDo Example".into()), + (false, "Modify ToDo Example".into()), + ], + } + } +} + +use cxx_qt::CxxQtType; +use qobject::*; + +impl qobject::TodoList { + fn row_count(&self, _parent: &QModelIndex) -> i32 { + self.todos.len() as i32 + } + + fn data(&self, index: &QModelIndex, role: i32) -> QVariant { + let role = TodoRoles { repr: role }; + + if let Some((done, ref todo)) = self.todos.get(index.row() as usize) { + match role { + TodoRoles::Todo => { + return todo.into(); + } + TodoRoles::Done => { + return done.into(); + } + _ => {} + } + } + QVariant::default() + } + + fn role_names(&self) -> QHash_i32_QByteArray { + let mut hash = QHash_i32_QByteArray::default(); + hash.insert(TodoRoles::Todo.repr, "todo".into()); + hash.insert(TodoRoles::Done.repr, "done".into()); + hash + } + + fn set_checked(mut self: Pin<&mut Self>, row: i32, checked: bool) { + if let Some((done, _todo)) = self.as_mut().rust_mut().todos.get_mut(row as usize) { + if *done != checked { + *done = checked; + self.sort(); + } + } + } + + fn sort(mut self: Pin<&mut Self>) { + self.as_mut().begin_reset_model(); + self.as_mut() + .rust_mut() + .todos + .sort_by_key(|(done, _todo)| *done); + self.as_mut().end_reset_model(); + } + + fn add_todo(mut self: Pin<&mut Self>, todo: &QString) { + self.as_mut().rust_mut().todos.push((false, todo.clone())); + self.sort(); + } +}