Skip to content

Commit

Permalink
New example: ToDo app
Browse files Browse the repository at this point in the history
This is the example code developed during the tutorial videos.
  • Loading branch information
LeonMatthesKDAB committed Dec 18, 2024
1 parent f5afefe commit 7d50e2e
Show file tree
Hide file tree
Showing 7 changed files with 237 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
12 changes: 12 additions & 0 deletions examples/todo_app/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"] }
12 changes: 12 additions & 0 deletions examples/todo_app/build.rs
Original file line number Diff line number Diff line change
@@ -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();
}
57 changes: 57 additions & 0 deletions examples/todo_app/qml/main.qml
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
}
16 changes: 16 additions & 0 deletions examples/todo_app/src/main.rs
Original file line number Diff line number Diff line change
@@ -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();
}
}
138 changes: 138 additions & 0 deletions examples/todo_app/src/todo_list.rs
Original file line number Diff line number Diff line change
@@ -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 <QAbstractListModel>
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<cxx_qt_lib::QHashPair_i32_QByteArray>;
}

#[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();
}
}

0 comments on commit 7d50e2e

Please sign in to comment.