Added TodoMVC example (Rust mock version) (#5396)

* Added TodoMVC example (Rust mock version)

* TodoMVC: use visible-width instead of width for selection items

and format

* TodoMVC: layout fix for qt checkbox

* TdodoMVC: fix license issues in the example

* Update examples/todo_mvc/ui/views/task_list_view.slint

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* TdodoMVC: fix license issues in the example

* TodoMVC: code review changes

* TodoMVC: code review changes

* Update .reuse/dep5

Co-authored-by: Simon Hausmann <simon.hausmann@slint.dev>

* Update examples/todo_mvc/rust/src/adapters/navigation_adapter.rs

Co-authored-by: Simon Hausmann <simon.hausmann@slint.dev>

* Update examples/todo_mvc/rust/src/adapters/navigation_adapter.rs

Co-authored-by: Simon Hausmann <simon.hausmann@slint.dev>

* TodoMVC: refactor task list model (code review feedback)

* TodoMVC: code review feedback

* Update examples/todo-mvc/rust/src/mvc/controllers/task_list_controller.rs

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* TodoMVC: add missing link in dep5

* dep5 fix

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Simon Hausmann <simon.hausmann@slint.dev>
This commit is contained in:
Florian Blasius 2024-06-13 11:05:44 +00:00 committed by GitHub
parent a2e10f8c78
commit 0870585c32
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
41 changed files with 1651 additions and 1 deletions

View file

@ -0,0 +1,36 @@
# Copyright © SixtyFPS GmbH <info@slint.dev>
# SPDX-License-Identifier: MIT
[package]
name = "todo-mvc"
version = "1.7.0"
authors = ["Slint Developers <info@slint.dev>"]
edition = "2021"
build = "build.rs"
publish = false
license = "MIT"
[lib]
crate-type = ["lib", "cdylib"]
path = "src/lib.rs"
name = "todo_lib_mvc"
[[bin]]
path = "src/main.rs"
name = "todo-mvc"
[dependencies]
slint = { path = "../../../api/rs/slint", features = ["serde", "backend-android-activity-06"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4" }
[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen = { version = "0.2" }
console_error_panic_hook = "0.1.5"
[build-dependencies]
slint-build = { path = "../../../api/rs/build" }
[dev-dependencies]
i-slint-backend-testing = { workspace = true }

View file

@ -0,0 +1,6 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
fn main() {
slint_build::compile("../ui/index.slint").unwrap();
}

View file

@ -0,0 +1,41 @@
<!DOCTYPE html>
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> -->
<!-- SPDX-License-Identifier: MIT -->
<html>
<!--
This is a static html file used to display the wasm build.
In order to generate the build
- Run `wasm-pack build --release --target web` in this directory.
-->
<head>
<meta charset="UTF-8">
<title>Slint Todo MVC Demo (Web Assembly version)</title>
<link rel="stylesheet" href="https://slint.dev/css/demos-v1.css">
</head>
<body>
<p>This is the <a href="https://slint.dev">Slint</a> Todo Demo compiled to WebAssembly.</p>
<div id="spinner" style="position: relative;">
<div class="spinner">Loading...</div>
</div>
<canvas id="canvas" unselectable="on" data-slint-auto-resize-to-preferred="true"></canvas>
<p class="links">
<a href="https://github.com/slint-ui/slint/blob/master/examples/todo-mvc/">
View Source Code on GitHub</a> -
<a href="https://slint.dev/editor?load_demo=examples/todo-mvc/ui/index.slint">
Open in SlintPad
</a>
</p>
<script type="module">
import init from './pkg/todo_lib_mvc.js';
init().finally(() => {
document.getElementById("spinner").remove();
});
</script>
</body>
</html>

View file

@ -0,0 +1,46 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
use std::cell::Cell;
type CallbackWrapper<Arguments, Result = ()> =
Cell<Option<Box<dyn FnMut(&Arguments, &mut Result)>>>;
pub struct Callback<Arguments: ?Sized, Result = ()> {
callback: CallbackWrapper<Arguments, Result>,
}
impl<Arguments: ?Sized, Res> Default for Callback<Arguments, Res> {
fn default() -> Self {
Self { callback: Default::default() }
}
}
impl<Arguments: ?Sized, Result: Default> Callback<Arguments, Result> {
pub fn on(&self, mut f: impl FnMut(&Arguments) -> Result + 'static) {
self.callback.set(Some(Box::new(move |a: &Arguments, r: &mut Result| *r = f(a))));
}
pub fn invoke(&self, a: &Arguments) -> Result {
let mut result = Result::default();
if let Some(mut callback) = self.callback.take() {
callback(a, &mut result);
self.callback.set(Some(callback));
}
result
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_invoke() {
let callback: Callback<(i32, i32), i32> = Callback::default();
callback.on(|(a, b)| a + b);
assert_eq!(callback.invoke(&(3, 2)), 5);
}
}

View file

@ -0,0 +1,39 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
pub mod mvc;
pub mod ui;
mod callback;
pub use callback::*;
pub use slint::*;
#[cfg_attr(target_arch = "wasm32", wasm_bindgen(start))]
pub fn main() {
let main_window = init();
main_window.run().unwrap();
}
fn init() -> ui::MainWindow {
let view_handle = ui::MainWindow::new().unwrap();
let task_list_controller = mvc::TaskListController::new(mvc::task_repo());
ui::task_list_adapter::connect(&view_handle, task_list_controller.clone());
ui::navigation_adapter::connect_task_list_controller(
&view_handle,
task_list_controller.clone(),
);
let create_task_controller = mvc::CreateTaskController::new(mvc::date_time_repo());
ui::create_task_adapter::connect(&view_handle, create_task_controller.clone());
ui::navigation_adapter::connect_create_task_controller(&view_handle, create_task_controller);
ui::create_task_adapter::connect_task_list_controller(&view_handle, task_list_controller);
view_handle
}
// FIXME: android example

View file

@ -0,0 +1,6 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
fn main() {
todo_lib_mvc::main();
}

View file

@ -0,0 +1,11 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
mod controllers;
pub use controllers::*;
mod models;
pub use models::*;
mod repositories;
pub use repositories::*;

View file

@ -0,0 +1,8 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
mod create_task_controller;
pub use create_task_controller::*;
mod task_list_controller;
pub use task_list_controller::*;

View file

@ -0,0 +1,120 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
use std::rc::Rc;
use crate::mvc::{traits::DateTimeRepository, DateModel, TimeModel};
use crate::{mvc, Callback};
#[derive(Clone)]
pub struct CreateTaskController {
repo: Rc<dyn mvc::traits::DateTimeRepository>,
back_callback: Rc<Callback<(), ()>>,
}
impl CreateTaskController {
pub fn new(repo: impl DateTimeRepository + 'static) -> Self {
Self { repo: Rc::new(repo), back_callback: Rc::new(Callback::default()) }
}
pub fn current_date(&self) -> DateModel {
self.repo.current_date()
}
pub fn current_time(&self) -> TimeModel {
self.repo.current_time()
}
pub fn date_string(&self, date_model: DateModel) -> String {
self.repo.date_to_string(date_model)
}
pub fn time_string(&self, time_model: TimeModel) -> String {
self.repo.time_to_string(time_model)
}
pub fn back(&self) {
self.back_callback.invoke(&());
}
pub fn on_back(&self, mut callback: impl FnMut() + 'static) {
self.back_callback.on(move |()| {
callback();
});
}
pub fn time_stamp(&self, date_model: DateModel, time_model: TimeModel) -> i32 {
self.repo.time_stamp(date_model, time_model)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mvc::MockDateTimeRepository;
use std::cell::Cell;
fn test_controller() -> CreateTaskController {
CreateTaskController::new(MockDateTimeRepository::new(
DateModel { year: 2024, month: 6, day: 12 },
TimeModel { hour: 13, minute: 30, second: 29 },
15,
))
}
#[test]
fn test_current_date() {
let controller = test_controller();
assert_eq!(controller.current_date(), DateModel { year: 2024, month: 6, day: 12 });
}
#[test]
fn test_current_time() {
let controller = test_controller();
assert_eq!(controller.current_time(), TimeModel { hour: 13, minute: 30, second: 29 });
}
#[test]
fn test_date_string() {
let controller = test_controller();
assert_eq!(
controller.date_string(DateModel { year: 2020, month: 10, day: 5 }).as_str(),
"2020/10/5"
);
}
#[test]
fn test_time_string() {
let controller = test_controller();
assert_eq!(
controller.time_string(TimeModel { hour: 10, minute: 12, second: 55 }).as_str(),
"10:12"
);
}
#[test]
fn test_back() {
let controller = test_controller();
let callback_invoked = Rc::new(Cell::new(false));
controller.on_back({
let callback_invoked = callback_invoked.clone();
move || {
callback_invoked.set(true);
}
});
controller.back();
assert!(callback_invoked.get());
}
#[test]
fn test_time_stamp() {
let controller = test_controller();
assert_eq!(controller.time_stamp(DateModel::default(), TimeModel::default()), 15);
}
}

View file

@ -0,0 +1,198 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
use std::rc::Rc;
use slint::Model;
use slint::ModelNotify;
use slint::ModelRc;
use slint::ModelTracker;
use crate::mvc;
use crate::Callback;
#[derive(Clone)]
pub struct TaskListController {
task_model: TaskModel,
show_create_task_callback: Rc<Callback<(), ()>>,
}
impl TaskListController {
pub fn new(repo: impl mvc::traits::TaskRepository + 'static) -> Self {
Self {
task_model: TaskModel::new(repo),
show_create_task_callback: Rc::new(Callback::default()),
}
}
pub fn task_model(&self) -> ModelRc<mvc::TaskModel> {
ModelRc::new(self.task_model.clone())
}
pub fn toggle_done(&self, index: usize) {
self.task_model.toggle_done(index)
}
pub fn remove_task(&self, index: usize) {
self.task_model.remove_task(index)
}
pub fn create_task(&self, title: &str, due_date: i64) {
self.task_model.push_task(mvc::TaskModel {
title: title.into(),
due_date,
..Default::default()
})
}
pub fn show_create_task(&self) {
self.show_create_task_callback.invoke(&());
}
pub fn on_show_create_task(&self, mut callback: impl FnMut() + 'static) {
self.show_create_task_callback.on(move |()| {
callback();
});
}
}
#[derive(Clone)]
struct TaskModel {
repo: Rc<dyn mvc::traits::TaskRepository>,
notify: Rc<ModelNotify>,
}
impl TaskModel {
fn new(repo: impl mvc::traits::TaskRepository + 'static) -> Self {
Self { repo: Rc::new(repo), notify: Rc::new(Default::default()) }
}
fn toggle_done(&self, index: usize) {
if !self.repo.toggle_done(index) {
return;
}
self.notify.row_changed(index)
}
fn remove_task(&self, index: usize) {
if !self.repo.remove_task(index) {
return;
}
self.notify.row_removed(index, 1)
}
fn push_task(&self, task: mvc::TaskModel) {
if !self.repo.push_task(task) {
return;
}
self.notify.row_added(self.row_count() - 1, 1);
}
}
impl Model for TaskModel {
type Data = mvc::TaskModel;
fn row_count(&self) -> usize {
self.repo.task_count()
}
fn row_data(&self, row: usize) -> Option<Self::Data> {
self.repo.get_task(row)
}
fn model_tracker(&self) -> &dyn ModelTracker {
self.notify.as_ref()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mvc;
use std::cell::Cell;
fn test_controller() -> TaskListController {
TaskListController::new(mvc::MockTaskRepository::new(vec![
mvc::TaskModel { title: "Item 1".into(), due_date: 1, done: true },
mvc::TaskModel { title: "Item 2".into(), due_date: 1, done: false },
]))
}
#[test]
fn test_tasks() {
let controller = test_controller();
let task_model = controller.task_model();
assert_eq!(task_model.row_count(), 2);
assert_eq!(
task_model.row_data(0),
Some(mvc::TaskModel { title: "Item 1".into(), due_date: 1, done: true },)
);
assert_eq!(
task_model.row_data(1),
Some(mvc::TaskModel { title: "Item 2".into(), due_date: 1, done: false },)
);
}
#[test]
fn test_toggle_task_checked() {
let controller = test_controller();
let task_model = controller.task_model();
assert!(task_model.row_data(0).unwrap().done);
controller.toggle_done(0);
assert!(!task_model.row_data(0).unwrap().done);
}
#[test]
fn test_remove_task() {
let controller = test_controller();
let task_model = controller.task_model();
assert_eq!(task_model.row_count(), 2);
controller.remove_task(0);
assert_eq!(task_model.row_count(), 1);
assert_eq!(
task_model.row_data(0),
Some(mvc::TaskModel { title: "Item 2".into(), due_date: 1, done: false },)
);
}
#[test]
fn test_show_create_task() {
let controller = test_controller();
let callback_invoked = Rc::new(Cell::new(false));
controller.on_show_create_task({
let callback_invoked = callback_invoked.clone();
move || {
callback_invoked.set(true);
}
});
controller.show_create_task();
assert!(callback_invoked.get());
}
#[test]
fn test_add_task() {
let controller = test_controller();
let task_model = controller.task_model();
assert_eq!(task_model.row_count(), 2);
controller.create_task("Item 3", 3);
assert_eq!(task_model.row_count(), 3);
assert_eq!(
task_model.row_data(2),
Some(mvc::TaskModel { title: "Item 3".into(), due_date: 3, done: false },)
);
}
}

View file

@ -0,0 +1,11 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
mod date_model;
pub use date_model::*;
mod task_model;
pub use task_model::*;
mod time_model;
pub use time_model::*;

View file

@ -0,0 +1,9 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
#[derive(Copy, Clone, Default, Debug, PartialEq)]
pub struct DateModel {
pub year: i32,
pub month: u32,
pub day: u32,
}

View file

@ -0,0 +1,11 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
#[derive(Clone, Default, Debug, PartialEq)]
pub struct TaskModel {
pub title: String,
// due date in milliseconds
pub due_date: i64,
pub done: bool,
}

View file

@ -0,0 +1,9 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
#[derive(Copy, Clone, Default, Debug, PartialEq)]
pub struct TimeModel {
pub hour: u32,
pub minute: u32,
pub second: u32,
}

View file

@ -0,0 +1,32 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
mod mock_date_time_repository;
pub use mock_date_time_repository::*;
mod mock_task_repository;
pub use mock_task_repository::*;
use crate::mvc::models::{DateModel, TaskModel, TimeModel};
pub mod traits;
pub fn date_time_repo() -> impl traits::DateTimeRepository + Clone {
MockDateTimeRepository::new(
DateModel { year: 2024, month: 6, day: 11 },
TimeModel { hour: 16, minute: 43, second: 0 },
1718183634,
)
}
pub fn task_repo() -> impl traits::TaskRepository + Clone {
MockTaskRepository::new(vec![
TaskModel { title: "Learn Rust".into(), done: true, due_date: 1717686537151 },
TaskModel { title: "Learn Slint".into(), done: true, due_date: 1717686537151 },
TaskModel {
title: "Create project with Rust and Slint".into(),
done: true,
due_date: 1717686537151,
},
])
}

View file

@ -0,0 +1,45 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
use crate::mvc;
use super::traits;
#[derive(Clone)]
pub struct MockDateTimeRepository {
current_date: mvc::DateModel,
current_time: mvc::TimeModel,
time_stamp: i32,
}
impl MockDateTimeRepository {
pub fn new(
current_date: mvc::DateModel,
current_time: mvc::TimeModel,
time_stamp: i32,
) -> Self {
Self { current_date, current_time, time_stamp }
}
}
impl traits::DateTimeRepository for MockDateTimeRepository {
fn current_date(&self) -> mvc::DateModel {
self.current_date
}
fn current_time(&self) -> mvc::TimeModel {
self.current_time
}
fn date_to_string(&self, date: mvc::DateModel) -> String {
format!("{}/{}/{}", date.year, date.month, date.day)
}
fn time_to_string(&self, time: mvc::TimeModel) -> String {
format!("{}:{}", time.hour, time.minute)
}
fn time_stamp(&self, _date: mvc::DateModel, _time: mvc::TimeModel) -> i32 {
self.time_stamp
}
}

View file

@ -0,0 +1,51 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
use std::{cell::RefCell, rc::Rc};
use super::traits;
use crate::mvc;
#[derive(Clone)]
pub struct MockTaskRepository {
tasks: Rc<RefCell<Vec<mvc::TaskModel>>>,
}
impl MockTaskRepository {
pub fn new(tasks: Vec<mvc::TaskModel>) -> Self {
Self { tasks: Rc::new(RefCell::new(tasks)) }
}
}
impl traits::TaskRepository for MockTaskRepository {
fn task_count(&self) -> usize {
self.tasks.borrow().len()
}
fn get_task(&self, index: usize) -> Option<mvc::TaskModel> {
self.tasks.borrow().get(index).cloned()
}
fn toggle_done(&self, index: usize) -> bool {
if let Some(task) = self.tasks.borrow_mut().get_mut(index) {
task.done = !task.done;
return true;
}
false
}
fn remove_task(&self, index: usize) -> bool {
if index < self.tasks.borrow().len() {
self.tasks.borrow_mut().remove(index);
return true;
}
false
}
fn push_task(&self, task: mvc::TaskModel) -> bool {
self.tasks.borrow_mut().push(task);
true
}
}

View file

@ -0,0 +1,8 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
mod date_time_repository;
pub use date_time_repository::*;
mod task_repository;
pub use task_repository::*;

View file

@ -0,0 +1,12 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
use crate::mvc;
pub trait DateTimeRepository {
fn current_date(&self) -> mvc::DateModel;
fn current_time(&self) -> mvc::TimeModel;
fn date_to_string(&self, date: mvc::DateModel) -> String;
fn time_to_string(&self, time: mvc::TimeModel) -> String;
fn time_stamp(&self, date: mvc::DateModel, time: mvc::TimeModel) -> i32;
}

View file

@ -0,0 +1,12 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
use crate::mvc;
pub trait TaskRepository {
fn task_count(&self) -> usize;
fn get_task(&self, index: usize) -> Option<mvc::TaskModel>;
fn toggle_done(&self, index: usize) -> bool;
fn remove_task(&self, index: usize) -> bool;
fn push_task(&self, task: mvc::TaskModel) -> bool;
}

View file

@ -0,0 +1,8 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
slint::include_modules!();
pub mod create_task_adapter;
pub mod navigation_adapter;
pub mod task_list_adapter;

View file

@ -0,0 +1,108 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
use slint::*;
use crate::{
mvc::{
{CreateTaskController, TaskListController}, {DateModel, TimeModel},
},
ui,
};
// a helper function to make adapter and controller connection a little bit easier
fn connect_with_controller(
view_handle: &ui::MainWindow,
controller: &CreateTaskController,
connect_adapter_controller: impl FnOnce(ui::CreateTaskAdapter, CreateTaskController) + 'static,
) {
connect_adapter_controller(view_handle.global::<ui::CreateTaskAdapter>(), controller.clone());
}
// a helper function to make adapter and controller connection a little bit easier
fn connect_with_task_list_controller(
view_handle: &ui::MainWindow,
controller: &TaskListController,
connect_adapter_controller: impl FnOnce(ui::CreateTaskAdapter, TaskListController) + 'static,
) {
connect_adapter_controller(view_handle.global::<ui::CreateTaskAdapter>(), controller.clone());
}
// one place to implement connection between adapter (view) and controller
pub fn connect(view_handle: &ui::MainWindow, controller: CreateTaskController) {
connect_with_controller(view_handle, &controller, {
move |adapter, controller| {
adapter.on_back(move || {
controller.back();
})
}
});
connect_with_controller(view_handle, &controller, {
move |adapter, controller| {
adapter.on_current_date(move || map_date_model_to_date(controller.current_date()))
}
});
connect_with_controller(view_handle, &controller, {
move |adapter, controller| {
adapter.on_current_time(move || map_time_model_to_time(controller.current_time()))
}
});
connect_with_controller(view_handle, &controller, {
move |adapter, controller| {
adapter.on_date_string(move |date| {
controller.date_string(map_date_to_date_model(date)).into()
})
}
});
connect_with_controller(view_handle, &controller, {
move |adapter, controller| {
adapter.on_time_string(move |time| {
controller.time_string(map_time_to_time_model(time)).into()
})
}
});
connect_with_controller(view_handle, &controller, {
move |adapter, controller| {
adapter.on_time_stamp(move |date, time| {
controller
.time_stamp(map_date_to_date_model(date), map_time_to_time_model(time))
.into()
})
}
});
}
pub fn connect_task_list_controller(view_handle: &ui::MainWindow, controller: TaskListController) {
connect_with_task_list_controller(view_handle, &controller, {
move |adapter, controller| {
adapter.on_create(move |title, time_stamp| {
controller.create_task(title.as_str(), time_stamp as i64)
})
}
});
}
fn map_time_model_to_time(time_model: TimeModel) -> ui::Time {
ui::Time {
hour: time_model.hour as i32,
minute: time_model.minute as i32,
second: time_model.second as i32,
}
}
fn map_time_to_time_model(time: ui::Time) -> TimeModel {
TimeModel { hour: time.hour as u32, minute: time.minute as u32, second: time.second as u32 }
}
fn map_date_model_to_date(date_model: DateModel) -> ui::Date {
ui::Date { year: date_model.year, month: date_model.month as i32, day: date_model.day as i32 }
}
fn map_date_to_date_model(date: ui::Date) -> DateModel {
DateModel { year: date.year, month: date.month as u32, day: date.day as u32 }
}

View file

@ -0,0 +1,34 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
use slint::*;
use crate::{
mvc::{CreateTaskController, TaskListController},
ui,
};
// one place to implement connection between adapter (view) and controller
pub fn connect_create_task_controller(
view_handle: &ui::MainWindow,
controller: CreateTaskController,
) {
controller.on_back({
let view_handle = view_handle.as_weak();
move || {
view_handle.unwrap().global::<ui::NavigationAdapter>().invoke_previous_page();
}
});
}
// one place to implement connection between adapter (view) and controller
pub fn connect_task_list_controller(view_handle: &ui::MainWindow, controller: TaskListController) {
controller.on_show_create_task({
let view_handle = view_handle.as_weak();
move || {
view_handle.unwrap().global::<ui::NavigationAdapter>().invoke_next_page();
}
});
}

View file

@ -0,0 +1,66 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
use chrono::DateTime;
use slint::*;
use std::rc::Rc;
use crate::{
mvc::{TaskListController, TaskModel},
ui,
};
// a helper function to make adapter and controller connection a little bit easier
pub fn connect_with_controller(
view_handle: &ui::MainWindow,
controller: &TaskListController,
connect_adapter_controller: impl FnOnce(ui::TaskListAdapter, TaskListController) + 'static,
) {
connect_adapter_controller(view_handle.global::<ui::TaskListAdapter>(), controller.clone());
}
// one place to implement connection between adapter (view) and controller
pub fn connect(view_handle: &ui::MainWindow, controller: TaskListController) {
// sets a mapped list of the task items to the ui
view_handle
.global::<ui::TaskListAdapter>()
.set_tasks(Rc::new(MapModel::new(controller.task_model(), map_task_to_item)).into());
connect_with_controller(view_handle, &controller, {
move |adapter, controller| {
adapter.on_toggle_task_checked(move |index| {
controller.toggle_done(index as usize);
})
}
});
connect_with_controller(view_handle, &controller, {
move |adapter, controller| {
adapter.on_remove_task(move |index| {
controller.remove_task(index as usize);
})
}
});
connect_with_controller(view_handle, &controller, {
move |adapter: ui::TaskListAdapter, controller| {
adapter.on_show_create_task(move || {
controller.show_create_task();
})
}
});
}
// maps a TaskModel (data) to a SelectionItem (ui)
fn map_task_to_item(task: TaskModel) -> ui::SelectionListViewItem {
ui::SelectionListViewItem {
text: task.title.into(),
checked: task.done,
description: DateTime::from_timestamp_millis(task.due_date)
.unwrap()
// example: Thu, Jun 6, 2024 16:29
.format("%a, %b %d, %Y %H:%M")
.to_string()
.into(),
}
}