Add example Todos application from FOSDEM presentation

https://fosdem.org/2018/schedule/event/rust_qt_binding_generator/
master
Jos van den Oever 2018-05-18 18:37:38 +02:00
parent f4ffeeda91
commit 574440ba58
15 changed files with 1250 additions and 0 deletions

View File

@ -0,0 +1,73 @@
project (my_rust_qt_quick_project)
cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR)
cmake_policy(SET CMP0046 NEW)
cmake_policy(SET CMP0063 NEW)
LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
string(TOUPPER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_UPPER)
if(CMAKE_BUILD_TYPE_UPPER STREQUAL DEBUG)
set(RUST_TARGET_DIR target/debug/)
set(RUST_BUILD_FLAG)
else()
set(RUST_TARGET_DIR target/release/)
set(RUST_BUILD_FLAG --release)
endif()
### find dependencies ###
include(FeatureSummary)
find_package(Cargo REQUIRED)
find_package(Rust REQUIRED)
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
find_package(Threads REQUIRED)
set(QT_MIN_VERSION "5.6.0")
find_package(Qt5 ${QT_MIN_VERSION} CONFIG
REQUIRED COMPONENTS Core Quick Widgets
)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
find_package(RustQtBindingGenerator REQUIRED)
feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
### build commands ###
SET(RUST_DIR "${CMAKE_CURRENT_SOURCE_DIR}/rust")
SET(RUST_LIB "${RUST_DIR}/${RUST_TARGET_DIR}/librust.a")
# generate c++ and rust code from bindings.json
add_custom_command(
OUTPUT "${RUST_DIR}/src/interface.rs"
"${CMAKE_CURRENT_SOURCE_DIR}/src/Bindings.h"
# if the cpp file is marked GENERATED, CMake will not check it for moc
# "${CMAKE_CURRENT_SOURCE_DIR}/src/Bindings.cpp"
COMMAND "${RustQtBindingGenerator_EXECUTABLE}" #--overwrite-implementation
"${CMAKE_CURRENT_SOURCE_DIR}/bindings.json"
DEPENDS bindings.json
)
# compile the rust code into a static library
add_custom_command(
OUTPUT "${RUST_LIB}"
COMMAND ${Cargo_EXECUTABLE} build ${RUST_BUILD_FLAG}
DEPENDS rust/src/lib.rs
rust/src/implementation.rs
rust/src/interface.rs
WORKING_DIRECTORY "${RUST_DIR}"
)
add_custom_target(rust_target DEPENDS "${RUST_LIB}")
list(APPEND Libs "${RUST_LIB}")
list(APPEND Libs Qt5::Core Qt5::Quick Qt5::Widgets Threads::Threads ${CMAKE_DL_LIBS})
set(SRCS src/main.cpp src/Bindings.cpp "qml.qrc")
add_executable(todos ${SRCS})
add_dependencies(todos rust_target)
target_link_libraries(todos ${Libs})
set_target_properties(todos PROPERTIES
CXX_STANDARD 11
CXX_STANDARD_REQUIRED ON
)

13
examples/todos/README.md Normal file
View File

@ -0,0 +1,13 @@
Source code for https://fosdem.org/2018/schedule/event/rust_qt_binding_generator/
Make sure `rust_qt_binding_generator` is in the `PATH`.
```bash
mkdir build
cd build
cmake -GNinja ..
ninja
export QT_QUICK_CONTROLS_MATERIAL_THEME=Dark
export QT_QUICK_CONTROLS_STYLE=Material
./todos
```

View File

@ -0,0 +1,64 @@
{
"cppFile": "src/Bindings.cpp",
"rust": {
"dir": "rust",
"interfaceModule": "interface",
"implementationModule": "implementation"
},
"objects": {
"Todos": {
"type": "List",
"properties": {
"count": {
"type": "quint64"
},
"activeCount": {
"type": "quint64"
}
},
"itemProperties": {
"completed": {
"type": "bool",
"write": true,
"roles": [ [ "display" ] ]
},
"description": {
"type": "QString",
"write": true,
"roles": [ [], [ "display" ] ]
}
},
"functions": {
"add": {
"return": "void",
"mut": true,
"arguments": [{
"name": "description",
"type": "QString"
}]
},
"remove": {
"return": "bool",
"mut": true,
"arguments": [{
"name": "index",
"type": "quint64"
}]
},
"setAll": {
"return": "void",
"mut": true,
"arguments": [{
"name": "completed",
"type": "bool"
}]
},
"clearCompleted": {
"return": "void",
"mut": true,
"arguments": []
}
}
}
}
}

View File

@ -0,0 +1,10 @@
include(FindPackageHandleStandardArgs)
find_program(Cargo_EXECUTABLE cargo)
execute_process(COMMAND "${Cargo_EXECUTABLE}" --version
OUTPUT_VARIABLE Cargo_VERSION_OUTPUT)
STRING(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+"
Cargo_VERSION "${Cargo_VERSION_OUTPUT}")
find_package_handle_standard_args(Cargo
REQUIRED_VARS Cargo_EXECUTABLE
VERSION_VAR Cargo_VERSION)
mark_as_advanced(Cargo_EXECUTABLE)

View File

@ -0,0 +1,10 @@
include(FindPackageHandleStandardArgs)
find_program(Rust_EXECUTABLE rustc)
execute_process(COMMAND "${Rust_EXECUTABLE}" --version
OUTPUT_VARIABLE Rust_VERSION_OUTPUT)
STRING(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+"
Rust_VERSION "${Rust_VERSION_OUTPUT}")
find_package_handle_standard_args(Rust
REQUIRED_VARS Rust_EXECUTABLE
VERSION_VAR Rust_VERSION)
mark_as_advanced(Rust_EXECUTABLE)

View File

@ -0,0 +1,5 @@
include(FindPackageHandleStandardArgs)
find_program(RustQtBindingGenerator_EXECUTABLE rust_qt_binding_generator)
find_package_handle_standard_args(RustQtBindingGenerator
REQUIRED_VARS RustQtBindingGenerator_EXECUTABLE)
mark_as_advanced(RustQtBindingGenerator_EXECUTABLE)

208
examples/todos/main.qml Normal file
View File

@ -0,0 +1,208 @@
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import RustCode 1.0;
ApplicationWindow {
visible: true
width: 450
height: 580
header: ToolBar {
Label {
anchors.fill: parent
text: qsTr("todos")
font.pixelSize: 30
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
Component.onCompleted: {
input.forceActiveFocus()
}
Todos {
id: todoModel
Component.onCompleted: {
add("write bindings.json")
add("run rust_qt_binding_generator")
add("check bindings.h")
add("check bindings.cpp")
add("check interface.rs")
add("write implementation.rs")
add("write main.qml")
}
}
Component {
id: todoDelegate
RowLayout {
// the active tab determines if this item should be shown
// 0: all, 1: active, 2: completed
property bool show: filter.currentIndex === 0
|| (filter.currentIndex === 1 && !completed)
|| (filter.currentIndex === 2 && completed)
visible: show
width: parent.width
height: show ? implicitHeight : 0
CheckBox {
checked: completed
onToggled: todoModel.setCompleted(index, checked)
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
Label {
id: label
visible: !editInput.visible
text: description
anchors.fill: parent
verticalAlignment: Text.AlignVCenter
font.strikeout: completed
font.pixelSize: 20
}
MouseArea {
id: mouse
anchors.fill: parent
hoverEnabled: true
onDoubleClicked: {
editInput.text = label.text
editInput.visible = true
editInput.forceActiveFocus()
}
}
Button {
text: 'X'
visible: (mouse.containsMouse && !editInput.visible)
|| closeMouse.containsMouse
anchors.right: parent.right
MouseArea {
id: closeMouse
anchors.fill: parent
hoverEnabled: true
onClicked: todoModel.remove(index)
}
}
TextField {
id: editInput
visible: false
anchors.fill: parent
text: description
font.pixelSize: label.font.pixelSize
onAccepted: {
todoModel.setDescription(index, text)
visible = false
}
onActiveFocusChanged: {
// hide when focus is lost
if (!activeFocus) {
visible = false
}
}
Keys.onPressed: {
// on escape, set value, hide (and lose focus)
if (event.key === Qt.Key_Escape) {
todoModel.setDescription(index, text)
visible = false
event.accepted = true
}
}
}
}
}
}
Pane {
anchors.fill: parent
leftPadding: 0
Page {
anchors.fill: parent
header: RowLayout {
CheckBox {
tristate: true
// if there are no todos, do not show this checkbox
// but let it take up the same space
enabled: todoModel.count > 0
opacity: todoModel.count === 0 ? 0 : 1
checkState: {
if (todoModel.activeCount === 0) {
return Qt.Checked
} else if (todoModel.activeCount >= todoModel.count) {
return Qt.Unchecked
}
return Qt.PartiallyChecked
}
onCheckStateChanged: {
// if the change is triggered by a user action on this
// checkbox, check or uncheck all todos
// otherwise, do nothing
// (onToggle does not emit for tristate buttons)
if (activeFocus) {
var checked = checkState !== Qt.Unchecked
todoModel.setAll(checked)
}
}
}
TextField {
id: input
Layout.fillWidth: true
placeholderText: qsTr("What needs to be done?")
onAccepted: {
const todo = text.trim()
if (todo) {
todoModel.add(todo)
}
input.clear()
}
}
}
Flickable {
anchors.fill: parent
ListView {
anchors.fill: parent
model: todoModel
delegate: todoDelegate
}
}
}
}
footer: Pane {
padding: 0
ColumnLayout {
width: parent.width
TabBar {
id: filter
Layout.fillWidth: true
visible: todoModel.count > 0
TabButton {
text: qsTr("All")
checked: true
}
TabButton {
text: qsTr("Active")
}
TabButton {
text: qsTr("Completed")
}
}
RowLayout {
visible: todoModel.count > 0
width: parent.width
Label {
Layout.fillWidth: true
text: (todoModel.activeCount === 1)
? qsTr("1 item left")
: todoModel.activeCount + qsTr(" items left")
}
Button {
enabled: todoModel.count > todoModel.activeCount
opacity: enabled
text: qsTr("Clear completed")
onClicked: todoModel.clearCompleted()
}
}
}
}
}

5
examples/todos/qml.qrc Normal file
View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/">
<file>main.qml</file>
</qresource>
</RCC>

View File

@ -0,0 +1,11 @@
[package]
name = "rust"
version = "1.0.0"
[dependencies]
libc = "*"
[lib]
name = "rust"
crate-type = ["staticlib"]

View File

@ -0,0 +1,127 @@
use interface::*;
#[derive(Default, Clone)]
struct TodosItem {
completed: bool,
description: String,
}
pub struct Todos {
emit: TodosEmitter,
model: TodosList,
list: Vec<TodosItem>,
active_count: usize,
}
impl Todos {
fn update_active_count(&mut self) {
let ac = self.list.iter().filter(|i| !i.completed).count();
if self.active_count != ac {
self.active_count = ac;
self.emit.active_count_changed();
}
}
}
impl TodosTrait for Todos {
fn new(emit: TodosEmitter, model: TodosList) -> Todos {
Todos {
emit: emit,
model: model,
list: vec![TodosItem::default(); 0],
active_count: 0,
}
}
fn emit(&self) -> &TodosEmitter {
&self.emit
}
fn active_count(&self) -> u64 {
self.active_count as u64
}
fn count(&self) -> u64 {
self.list.len() as u64
}
fn row_count(&self) -> usize {
self.list.len()
}
fn completed(&self, item: usize) -> bool {
if item >= self.list.len() {
return false;
}
self.list[item].completed
}
fn set_completed(&mut self, item: usize, v: bool) -> bool {
if item >= self.list.len() {
return false;
}
self.list[item].completed = v;
self.update_active_count();
true
}
fn description(&self, item: usize) -> &str {
if item < self.list.len() {
&self.list[item].description
} else {
""
}
}
fn set_description(&mut self, item: usize, v: String) -> bool {
if item >= self.list.len() {
return false;
}
self.list[item].description = v;
true
}
fn insert_rows(&mut self, row: usize, count: usize) -> bool {
if count == 0 || row > self.list.len() {
return false;
}
self.model.begin_insert_rows(row, row + count - 1);
for i in 0..count {
self.list.insert(row + i, TodosItem::default());
}
self.model.end_insert_rows();
self.active_count += count;
self.emit.active_count_changed();
self.emit.count_changed();
true
}
fn remove_rows(&mut self, row: usize, count: usize) -> bool {
if count == 0 || row + count > self.list.len() {
return false;
}
self.model.begin_remove_rows(row, row + count - 1);
self.list.drain(row..row + count);
self.model.end_remove_rows();
self.emit.count_changed();
self.update_active_count();
true
}
fn clear_completed(&mut self) -> () {
self.model.begin_reset_model();
self.list.retain(|i| !i.completed);
self.model.end_reset_model();
self.emit.count_changed();
}
fn add(&mut self, description: String) {
let end = self.list.len();
self.model.begin_insert_rows(end, end);
self.list.insert(end, TodosItem { completed: false, description });
self.model.end_insert_rows();
self.active_count += 1;
self.emit.active_count_changed();
self.emit.count_changed();
self.model.begin_reset_model();
self.model.end_reset_model();
}
fn remove(&mut self, index: u64) -> bool {
self.remove_rows(index as usize, 1)
}
fn set_all(&mut self, completed: bool) {
for i in &mut self.list {
i.completed = completed;
}
self.model.data_changed(0, self.list.len() - 1);
self.update_active_count();
}
}

View File

@ -0,0 +1,319 @@
/* generated by rust_qt_binding_generator */
#![allow(unknown_lints)]
#![allow(mutex_atomic, needless_pass_by_value)]
use libc::{c_char, c_ushort, c_int};
use std::slice;
use std::char::decode_utf16;
use std::sync::{Arc, Mutex};
use std::ptr::null;
use implementation::*;
#[repr(C)]
pub struct COption<T> {
data: T,
some: bool,
}
impl<T> From<Option<T>> for COption<T>
where
T: Default,
{
fn from(t: Option<T>) -> COption<T> {
if let Some(v) = t {
COption {
data: v,
some: true,
}
} else {
COption {
data: T::default(),
some: false,
}
}
}
}
pub enum QString {}
fn set_string_from_utf16(s: &mut String, str: *const c_ushort, len: c_int) {
let utf16 = unsafe { slice::from_raw_parts(str, to_usize(len)) };
let characters = decode_utf16(utf16.iter().cloned())
.into_iter()
.map(|r| r.unwrap());
s.clear();
s.extend(characters);
}
#[repr(C)]
pub enum SortOrder {
Ascending = 0,
Descending = 1,
}
#[repr(C)]
pub struct QModelIndex {
row: c_int,
internal_id: usize,
}
fn to_usize(n: c_int) -> usize {
if n < 0 {
panic!("Cannot cast {} to usize", n);
}
n as usize
}
fn to_c_int(n: usize) -> c_int {
if n > c_int::max_value() as usize {
panic!("Cannot cast {} to c_int", n);
}
n as c_int
}
pub struct TodosQObject {}
#[derive(Clone)]
pub struct TodosEmitter {
qobject: Arc<Mutex<*const TodosQObject>>,
active_count_changed: fn(*const TodosQObject),
count_changed: fn(*const TodosQObject),
new_data_ready: fn(*const TodosQObject),
}
unsafe impl Send for TodosEmitter {}
impl TodosEmitter {
fn clear(&self) {
*self.qobject.lock().unwrap() = null();
}
pub fn active_count_changed(&self) {
let ptr = *self.qobject.lock().unwrap();
if !ptr.is_null() {
(self.active_count_changed)(ptr);
}
}
pub fn count_changed(&self) {
let ptr = *self.qobject.lock().unwrap();
if !ptr.is_null() {
(self.count_changed)(ptr);
}
}
pub fn new_data_ready(&self) {
let ptr = *self.qobject.lock().unwrap();
if !ptr.is_null() {
(self.new_data_ready)(ptr);
}
}
}
pub struct TodosList {
qobject: *const TodosQObject,
data_changed: fn(*const TodosQObject, usize, usize),
begin_reset_model: fn(*const TodosQObject),
end_reset_model: fn(*const TodosQObject),
begin_insert_rows: fn(*const TodosQObject, usize, usize),
end_insert_rows: fn(*const TodosQObject),
begin_remove_rows: fn(*const TodosQObject, usize, usize),
end_remove_rows: fn(*const TodosQObject),
}
impl TodosList {
pub fn data_changed(&self, first: usize, last: usize) {
(self.data_changed)(self.qobject, first, last);
}
pub fn begin_reset_model(&self) {
(self.begin_reset_model)(self.qobject);
}
pub fn end_reset_model(&self) {
(self.end_reset_model)(self.qobject);
}
pub fn begin_insert_rows(&self, first: usize, last: usize) {
(self.begin_insert_rows)(self.qobject, first, last);
}
pub fn end_insert_rows(&self) {
(self.end_insert_rows)(self.qobject);
}
pub fn begin_remove_rows(&self, first: usize, last: usize) {
(self.begin_remove_rows)(self.qobject, first, last);
}
pub fn end_remove_rows(&self) {
(self.end_remove_rows)(self.qobject);
}
}
pub trait TodosTrait {
fn new(emit: TodosEmitter, model: TodosList) -> Self;
fn emit(&self) -> &TodosEmitter;
fn active_count(&self) -> u64;
fn count(&self) -> u64;
fn add(&mut self, description: String) -> ();
fn clear_completed(&mut self) -> ();
fn remove(&mut self, index: u64) -> bool;
fn set_all(&mut self, completed: bool) -> ();
fn row_count(&self) -> usize;
fn insert_rows(&mut self, _row: usize, _count: usize) -> bool { false }
fn remove_rows(&mut self, _row: usize, _count: usize) -> bool { false }
fn can_fetch_more(&self) -> bool {
false
}
fn fetch_more(&mut self) {}
fn sort(&mut self, u8, SortOrder) {}
fn completed(&self, item: usize) -> bool;
fn set_completed(&mut self, item: usize, bool) -> bool;
fn description(&self, item: usize) -> &str;
fn set_description(&mut self, item: usize, String) -> bool;
}
#[no_mangle]
pub extern "C" fn todos_new(
todos: *mut TodosQObject,
active_count_changed: fn(*const TodosQObject),
count_changed: fn(*const TodosQObject),
todos_new_data_ready: fn(*const TodosQObject),
todos_data_changed: fn(*const TodosQObject, usize, usize),
todos_begin_reset_model: fn(*const TodosQObject),
todos_end_reset_model: fn(*const TodosQObject),
todos_begin_insert_rows: fn(*const TodosQObject, usize, usize),
todos_end_insert_rows: fn(*const TodosQObject),
todos_begin_remove_rows: fn(*const TodosQObject, usize, usize),
todos_end_remove_rows: fn(*const TodosQObject),
) -> *mut Todos {
let todos_emit = TodosEmitter {
qobject: Arc::new(Mutex::new(todos)),
active_count_changed: active_count_changed,
count_changed: count_changed,
new_data_ready: todos_new_data_ready,
};
let model = TodosList {
qobject: todos,
data_changed: todos_data_changed,
begin_reset_model: todos_begin_reset_model,
end_reset_model: todos_end_reset_model,
begin_insert_rows: todos_begin_insert_rows,
end_insert_rows: todos_end_insert_rows,
begin_remove_rows: todos_begin_remove_rows,
end_remove_rows: todos_end_remove_rows,
};
let d_todos = Todos::new(todos_emit, model);
Box::into_raw(Box::new(d_todos))
}
#[no_mangle]
pub unsafe extern "C" fn todos_free(ptr: *mut Todos) {
Box::from_raw(ptr).emit().clear();
}
#[no_mangle]
pub unsafe extern "C" fn todos_active_count_get(ptr: *const Todos) -> u64 {
(&*ptr).active_count()
}
#[no_mangle]
pub unsafe extern "C" fn todos_count_get(ptr: *const Todos) -> u64 {
(&*ptr).count()
}
#[no_mangle]
pub extern "C" fn todos_add(ptr: *mut Todos, description_str: *const c_ushort, description_len: c_int) -> () {
let mut description = String::new();
set_string_from_utf16(&mut description, description_str, description_len);
let o = unsafe { &mut *ptr };
let r = o.add(description);
r
}
#[no_mangle]
pub extern "C" fn todos_clear_completed(ptr: *mut Todos) -> () {
let o = unsafe { &mut *ptr };
let r = o.clear_completed();
r
}
#[no_mangle]
pub extern "C" fn todos_remove(ptr: *mut Todos, index: u64) -> bool {
let o = unsafe { &mut *ptr };
let r = o.remove(index);
r
}
#[no_mangle]
pub extern "C" fn todos_set_all(ptr: *mut Todos, completed: bool) -> () {
let o = unsafe { &mut *ptr };
let r = o.set_all(completed);
r
}
#[no_mangle]
pub unsafe extern "C" fn todos_row_count(ptr: *const Todos) -> c_int {
to_c_int((&*ptr).row_count())
}
#[no_mangle]
pub unsafe extern "C" fn todos_insert_rows(ptr: *mut Todos, row: c_int, count: c_int) -> bool {
(&mut *ptr).insert_rows(to_usize(row), to_usize(count))
}
#[no_mangle]
pub unsafe extern "C" fn todos_remove_rows(ptr: *mut Todos, row: c_int, count: c_int) -> bool {
(&mut *ptr).remove_rows(to_usize(row), to_usize(count))
}
#[no_mangle]
pub unsafe extern "C" fn todos_can_fetch_more(ptr: *const Todos) -> bool {
(&*ptr).can_fetch_more()
}
#[no_mangle]
pub unsafe extern "C" fn todos_fetch_more(ptr: *mut Todos) {
(&mut *ptr).fetch_more()
}
#[no_mangle]
pub unsafe extern "C" fn todos_sort(
ptr: *mut Todos,
column: u8,
order: SortOrder,
) {
(&mut *ptr).sort(column, order)
}
#[no_mangle]
pub extern "C" fn todos_data_completed(ptr: *const Todos, row: c_int) -> bool {
let o = unsafe { &*ptr };
o.completed(to_usize(row)).into()
}
#[no_mangle]
pub unsafe extern "C" fn todos_set_data_completed(
ptr: *mut Todos, row: c_int,
v: bool,
) -> bool {
(&mut *ptr).set_completed(to_usize(row), v)
}
#[no_mangle]
pub extern "C" fn todos_data_description(
ptr: *const Todos, row: c_int,
d: *mut QString,
set: fn(*mut QString, *const c_char, len: c_int),
) {
let o = unsafe { &*ptr };
let data = o.description(to_usize(row));
let s: *const c_char = data.as_ptr() as (*const c_char);
set(d, s, to_c_int(data.len()));
}
#[no_mangle]
pub extern "C" fn todos_set_data_description(
ptr: *mut Todos, row: c_int,
s: *const c_ushort, len: c_int,
) -> bool {
let o = unsafe { &mut *ptr };
let mut v = String::new();
set_string_from_utf16(&mut v, s, len);
o.set_description(to_usize(row), v)
}

View File

@ -0,0 +1,5 @@
extern crate libc;
pub mod interface;
mod implementation;

View File

@ -0,0 +1,320 @@
/* generated by rust_qt_binding_generator */
#include "Bindings.h"
namespace {
struct option_quintptr {
public:
quintptr value;
bool some;
operator QVariant() const {
if (some) {
return QVariant::fromValue(value);
}
return QVariant();
}
};
static_assert(std::is_pod<option_quintptr>::value, "option_quintptr must be a POD type.");
typedef void (*qstring_set)(QString* val, const char* utf8, int nbytes);
void set_qstring(QString* val, const char* utf8, int nbytes) {
*val = QString::fromUtf8(utf8, nbytes);
}
struct qmodelindex_t {
int row;
quintptr id;
};
inline QVariant cleanNullQVariant(const QVariant& v) {
return (v.isNull()) ?QVariant() :v;
}
inline void todosActiveCountChanged(Todos* o)
{
emit o->activeCountChanged();
}
inline void todosCountChanged(Todos* o)
{
emit o->countChanged();
}
}
extern "C" {
bool todos_data_completed(const Todos::Private*, int);
bool todos_set_data_completed(Todos::Private*, int, bool);
void todos_data_description(const Todos::Private*, int, QString*, qstring_set);
bool todos_set_data_description(Todos::Private*, int, const ushort* s, int len);
void todos_sort(Todos::Private*, unsigned char column, Qt::SortOrder order = Qt::AscendingOrder);
int todos_row_count(const Todos::Private*);
bool todos_insert_rows(Todos::Private*, int, int);
bool todos_remove_rows(Todos::Private*, int, int);
bool todos_can_fetch_more(const Todos::Private*);
void todos_fetch_more(Todos::Private*);
}
int Todos::columnCount(const QModelIndex &parent) const
{
return (parent.isValid()) ? 0 : 2;
}
bool Todos::hasChildren(const QModelIndex &parent) const
{
return rowCount(parent) > 0;
}
int Todos::rowCount(const QModelIndex &parent) const
{
return (parent.isValid()) ? 0 : todos_row_count(m_d);
}
bool Todos::insertRows(int row, int count, const QModelIndex &)
{
return todos_insert_rows(m_d, row, count);
}
bool Todos::removeRows(int row, int count, const QModelIndex &)
{
return todos_remove_rows(m_d, row, count);
}
QModelIndex Todos::index(int row, int column, const QModelIndex &parent) const
{
if (!parent.isValid() && row >= 0 && row < rowCount(parent) && column >= 0 && column < 2) {
return createIndex(row, column, (quintptr)row);
}
return QModelIndex();
}
QModelIndex Todos::parent(const QModelIndex &) const
{
return QModelIndex();
}
bool Todos::canFetchMore(const QModelIndex &parent) const
{
return (parent.isValid()) ? 0 : todos_can_fetch_more(m_d);
}
void Todos::fetchMore(const QModelIndex &parent)
{
if (!parent.isValid()) {
todos_fetch_more(m_d);
}
}
void Todos::sort(int column, Qt::SortOrder order)
{
todos_sort(m_d, column, order);
}
Qt::ItemFlags Todos::flags(const QModelIndex &i) const
{
auto flags = QAbstractItemModel::flags(i);
if (i.column() == 0) {
flags |= Qt::ItemIsEditable;
}
if (i.column() == 1) {
flags |= Qt::ItemIsEditable;
}
return flags;
}
bool Todos::completed(int row) const
{
return todos_data_completed(m_d, row);
}
bool Todos::setCompleted(int row, bool value)
{
bool set = false;
set = todos_set_data_completed(m_d, row, value);
if (set) {
QModelIndex index = createIndex(row, 0, row);
emit dataChanged(index, index);
}
return set;
}
QString Todos::description(int row) const
{
QString s;
todos_data_description(m_d, row, &s, set_qstring);
return s;
}
bool Todos::setDescription(int row, const QString& value)
{
bool set = false;
set = todos_set_data_description(m_d, row, value.utf16(), value.length());
if (set) {
QModelIndex index = createIndex(row, 0, row);
emit dataChanged(index, index);
}
return set;
}
QVariant Todos::data(const QModelIndex &index, int role) const
{
Q_ASSERT(rowCount(index.parent()) > index.row());
switch (index.column()) {
case 0:
switch (role) {
case Qt::DisplayRole:
case Qt::UserRole + 0:
return QVariant::fromValue(completed(index.row()));
case Qt::UserRole + 1:
return QVariant::fromValue(description(index.row()));
}
case 1:
switch (role) {
case Qt::DisplayRole:
case Qt::UserRole + 1:
return QVariant::fromValue(description(index.row()));
}
}
return QVariant();
}
QHash<int, QByteArray> Todos::roleNames() const {
QHash<int, QByteArray> names = QAbstractItemModel::roleNames();
names.insert(Qt::UserRole + 0, "completed");
names.insert(Qt::UserRole + 1, "description");
return names;
}
QVariant Todos::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation != Qt::Horizontal) {
return QVariant();
}
return m_headerData.value(qMakePair(section, (Qt::ItemDataRole)role), role == Qt::DisplayRole ?QString::number(section + 1) :QVariant());
}
bool Todos::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role)
{
if (orientation != Qt::Horizontal) {
return false;
}
m_headerData.insert(qMakePair(section, (Qt::ItemDataRole)role), value);
return true;
}
bool Todos::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.column() == 0) {
if (role == Qt::DisplayRole || role == Qt::UserRole + 0) {
if (value.canConvert(qMetaTypeId<bool>())) {
return setCompleted(index.row(), value.value<bool>());
}
}
if (role == Qt::UserRole + 1) {
if (value.canConvert(qMetaTypeId<QString>())) {
return setDescription(index.row(), value.value<QString>());
}
}
}
if (index.column() == 1) {
if (role == Qt::DisplayRole || role == Qt::UserRole + 1) {
if (value.canConvert(qMetaTypeId<QString>())) {
return setDescription(index.row(), value.value<QString>());
}
}
}
return false;
}
extern "C" {
Todos::Private* todos_new(Todos*, void (*)(Todos*), void (*)(Todos*),
void (*)(const Todos*),
void (*)(Todos*, quintptr, quintptr),
void (*)(Todos*),
void (*)(Todos*),
void (*)(Todos*, int, int),
void (*)(Todos*),
void (*)(Todos*, int, int),
void (*)(Todos*));
void todos_free(Todos::Private*);
quint64 todos_active_count_get(const Todos::Private*);
quint64 todos_count_get(const Todos::Private*);
void todos_add(Todos::Private*, const ushort*, int);
void todos_clear_completed(Todos::Private*);
bool todos_remove(Todos::Private*, quint64);
void todos_set_all(Todos::Private*, bool);
};
Todos::Todos(bool /*owned*/, QObject *parent):
QAbstractItemModel(parent),
m_d(0),
m_ownsPrivate(false)
{
initHeaderData();
}
Todos::Todos(QObject *parent):
QAbstractItemModel(parent),
m_d(todos_new(this,
todosActiveCountChanged,
todosCountChanged,
[](const Todos* o) {
emit o->newDataReady(QModelIndex());
},
[](Todos* o, quintptr first, quintptr last) {
o->dataChanged(o->createIndex(first, 0, first),
o->createIndex(last, 1, last));
},
[](Todos* o) {
o->beginResetModel();
},
[](Todos* o) {
o->endResetModel();
},
[](Todos* o, int first, int last) {
o->beginInsertRows(QModelIndex(), first, last);
},
[](Todos* o) {
o->endInsertRows();
},
[](Todos* o, int first, int last) {
o->beginRemoveRows(QModelIndex(), first, last);
},
[](Todos* o) {
o->endRemoveRows();
}
)),
m_ownsPrivate(true)
{
connect(this, &Todos::newDataReady, this, [this](const QModelIndex& i) {
this->fetchMore(i);
}, Qt::QueuedConnection);
initHeaderData();
}
Todos::~Todos() {
if (m_ownsPrivate) {
todos_free(m_d);
}
}
void Todos::initHeaderData() {
m_headerData.insert(qMakePair(0, Qt::DisplayRole), QVariant("completed"));
m_headerData.insert(qMakePair(1, Qt::DisplayRole), QVariant("description"));
}
quint64 Todos::activeCount() const
{
return todos_active_count_get(m_d);
}
quint64 Todos::count() const
{
return todos_count_get(m_d);
}
void Todos::add(const QString& description)
{
return todos_add(m_d, description.utf16(), description.size());
}
void Todos::clearCompleted()
{
return todos_clear_completed(m_d);
}
bool Todos::remove(quint64 index)
{
return todos_remove(m_d, index);
}
void Todos::setAll(bool completed)
{
return todos_set_all(m_d, completed);
}

View File

@ -0,0 +1,62 @@
/* generated by rust_qt_binding_generator */
#ifndef BINDINGS_H
#define BINDINGS_H
#include <QObject>
#include <QAbstractItemModel>
class Todos;
class Todos : public QAbstractItemModel
{
Q_OBJECT
public:
class Private;
private:
Private * m_d;
bool m_ownsPrivate;
Q_PROPERTY(quint64 activeCount READ activeCount NOTIFY activeCountChanged FINAL)
Q_PROPERTY(quint64 count READ count NOTIFY countChanged FINAL)
explicit Todos(bool owned, QObject *parent);
public:
explicit Todos(QObject *parent = nullptr);
~Todos();
quint64 activeCount() const;
quint64 count() const;
Q_INVOKABLE void add(const QString& description);
Q_INVOKABLE void clearCompleted();
Q_INVOKABLE bool remove(quint64 index);
Q_INVOKABLE void setAll(bool completed);
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &index) const override;
bool hasChildren(const QModelIndex &parent = QModelIndex()) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
bool canFetchMore(const QModelIndex &parent) const override;
void fetchMore(const QModelIndex &parent) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
QHash<int, QByteArray> roleNames() const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole) override;
Q_INVOKABLE bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
Q_INVOKABLE bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
Q_INVOKABLE bool completed(int row) const;
Q_INVOKABLE bool setCompleted(int row, bool value);
Q_INVOKABLE QString description(int row) const;
Q_INVOKABLE bool setDescription(int row, const QString& value);
signals:
// new data is ready to be made available to the model with fetchMore()
void newDataReady(const QModelIndex &parent) const;
private:
QHash<QPair<int,Qt::ItemDataRole>, QVariant> m_headerData;
void initHeaderData();
signals:
void activeCountChanged();
void countChanged();
};
#endif // BINDINGS_H

View File

@ -0,0 +1,18 @@
#include "Bindings.h"
#include <QtQml/qqml.h>
#include <QApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
qmlRegisterType<Todos>("RustCode", 1, 0, "Todos");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}