diff --git a/examples/todos/CMakeLists.txt b/examples/todos/CMakeLists.txt new file mode 100644 index 0000000..c2642b0 --- /dev/null +++ b/examples/todos/CMakeLists.txt @@ -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 +) diff --git a/examples/todos/README.md b/examples/todos/README.md new file mode 100644 index 0000000..cafcf59 --- /dev/null +++ b/examples/todos/README.md @@ -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 +``` diff --git a/examples/todos/bindings.json b/examples/todos/bindings.json new file mode 100644 index 0000000..10ebc6d --- /dev/null +++ b/examples/todos/bindings.json @@ -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": [] + } + } + } + } +} diff --git a/examples/todos/cmake/FindCargo.cmake b/examples/todos/cmake/FindCargo.cmake new file mode 100644 index 0000000..6627d86 --- /dev/null +++ b/examples/todos/cmake/FindCargo.cmake @@ -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) diff --git a/examples/todos/cmake/FindRust.cmake b/examples/todos/cmake/FindRust.cmake new file mode 100644 index 0000000..1b67045 --- /dev/null +++ b/examples/todos/cmake/FindRust.cmake @@ -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) diff --git a/examples/todos/cmake/FindRustQtBindingGenerator.cmake b/examples/todos/cmake/FindRustQtBindingGenerator.cmake new file mode 100644 index 0000000..4be2426 --- /dev/null +++ b/examples/todos/cmake/FindRustQtBindingGenerator.cmake @@ -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) diff --git a/examples/todos/main.qml b/examples/todos/main.qml new file mode 100644 index 0000000..749bc04 --- /dev/null +++ b/examples/todos/main.qml @@ -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() + } + } + } + } +} diff --git a/examples/todos/qml.qrc b/examples/todos/qml.qrc new file mode 100644 index 0000000..5f6483a --- /dev/null +++ b/examples/todos/qml.qrc @@ -0,0 +1,5 @@ + + + main.qml + + diff --git a/examples/todos/rust/Cargo.toml b/examples/todos/rust/Cargo.toml new file mode 100644 index 0000000..7013c67 --- /dev/null +++ b/examples/todos/rust/Cargo.toml @@ -0,0 +1,11 @@ + +[package] +name = "rust" +version = "1.0.0" + +[dependencies] +libc = "*" + +[lib] +name = "rust" +crate-type = ["staticlib"] diff --git a/examples/todos/rust/src/implementation.rs b/examples/todos/rust/src/implementation.rs new file mode 100644 index 0000000..0b93095 --- /dev/null +++ b/examples/todos/rust/src/implementation.rs @@ -0,0 +1,127 @@ +use interface::*; + +#[derive(Default, Clone)] +struct TodosItem { + completed: bool, + description: String, +} + +pub struct Todos { + emit: TodosEmitter, + model: TodosList, + list: Vec, + 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(); + } +} diff --git a/examples/todos/rust/src/interface.rs b/examples/todos/rust/src/interface.rs new file mode 100644 index 0000000..a5135ad --- /dev/null +++ b/examples/todos/rust/src/interface.rs @@ -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 { + data: T, + some: bool, +} + +impl From> for COption +where + T: Default, +{ + fn from(t: Option) -> COption { + 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>, + 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) +} diff --git a/examples/todos/rust/src/lib.rs b/examples/todos/rust/src/lib.rs new file mode 100644 index 0000000..d17196f --- /dev/null +++ b/examples/todos/rust/src/lib.rs @@ -0,0 +1,5 @@ + +extern crate libc; + +pub mod interface; +mod implementation; diff --git a/examples/todos/src/Bindings.cpp b/examples/todos/src/Bindings.cpp new file mode 100644 index 0000000..75f8bae --- /dev/null +++ b/examples/todos/src/Bindings.cpp @@ -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::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 Todos::roleNames() const { + QHash 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())) { + return setCompleted(index.row(), value.value()); + } + } + if (role == Qt::UserRole + 1) { + if (value.canConvert(qMetaTypeId())) { + return setDescription(index.row(), value.value()); + } + } + } + if (index.column() == 1) { + if (role == Qt::DisplayRole || role == Qt::UserRole + 1) { + if (value.canConvert(qMetaTypeId())) { + return setDescription(index.row(), value.value()); + } + } + } + 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); +} diff --git a/examples/todos/src/Bindings.h b/examples/todos/src/Bindings.h new file mode 100644 index 0000000..6f2cab9 --- /dev/null +++ b/examples/todos/src/Bindings.h @@ -0,0 +1,62 @@ +/* generated by rust_qt_binding_generator */ +#ifndef BINDINGS_H +#define BINDINGS_H + +#include +#include + +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 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, QVariant> m_headerData; + void initHeaderData(); +signals: + void activeCountChanged(); + void countChanged(); +}; +#endif // BINDINGS_H diff --git a/examples/todos/src/main.cpp b/examples/todos/src/main.cpp new file mode 100644 index 0000000..5b50148 --- /dev/null +++ b/examples/todos/src/main.cpp @@ -0,0 +1,18 @@ +#include "Bindings.h" + +#include +#include +#include + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + qmlRegisterType("RustCode", 1, 0, "Todos"); + + QQmlApplicationEngine engine; + engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); + if (engine.rootObjects().isEmpty()) + return -1; + + return app.exec(); +}