mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-31 15:47:26 +00:00
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:
parent
a2e10f8c78
commit
0870585c32
41 changed files with 1651 additions and 1 deletions
36
examples/todo-mvc/rust/Cargo.toml
Normal file
36
examples/todo-mvc/rust/Cargo.toml
Normal 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 }
|
6
examples/todo-mvc/rust/build.rs
Normal file
6
examples/todo-mvc/rust/build.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
fn main() {
|
||||
slint_build::compile("../ui/index.slint").unwrap();
|
||||
}
|
41
examples/todo-mvc/rust/index.html
Normal file
41
examples/todo-mvc/rust/index.html
Normal 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>
|
46
examples/todo-mvc/rust/src/callback.rs
Normal file
46
examples/todo-mvc/rust/src/callback.rs
Normal 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);
|
||||
}
|
||||
}
|
39
examples/todo-mvc/rust/src/lib.rs
Normal file
39
examples/todo-mvc/rust/src/lib.rs
Normal 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
|
6
examples/todo-mvc/rust/src/main.rs
Normal file
6
examples/todo-mvc/rust/src/main.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
fn main() {
|
||||
todo_lib_mvc::main();
|
||||
}
|
11
examples/todo-mvc/rust/src/mvc.rs
Normal file
11
examples/todo-mvc/rust/src/mvc.rs
Normal 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::*;
|
8
examples/todo-mvc/rust/src/mvc/controllers.rs
Normal file
8
examples/todo-mvc/rust/src/mvc/controllers.rs
Normal 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::*;
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 },)
|
||||
);
|
||||
}
|
||||
}
|
11
examples/todo-mvc/rust/src/mvc/models.rs
Normal file
11
examples/todo-mvc/rust/src/mvc/models.rs
Normal 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::*;
|
9
examples/todo-mvc/rust/src/mvc/models/date_model.rs
Normal file
9
examples/todo-mvc/rust/src/mvc/models/date_model.rs
Normal 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,
|
||||
}
|
11
examples/todo-mvc/rust/src/mvc/models/task_model.rs
Normal file
11
examples/todo-mvc/rust/src/mvc/models/task_model.rs
Normal 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,
|
||||
}
|
9
examples/todo-mvc/rust/src/mvc/models/time_model.rs
Normal file
9
examples/todo-mvc/rust/src/mvc/models/time_model.rs
Normal 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,
|
||||
}
|
32
examples/todo-mvc/rust/src/mvc/repositories.rs
Normal file
32
examples/todo-mvc/rust/src/mvc/repositories.rs
Normal 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,
|
||||
},
|
||||
])
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
8
examples/todo-mvc/rust/src/mvc/repositories/traits.rs
Normal file
8
examples/todo-mvc/rust/src/mvc/repositories/traits.rs
Normal 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::*;
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
8
examples/todo-mvc/rust/src/ui.rs
Normal file
8
examples/todo-mvc/rust/src/ui.rs
Normal 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;
|
108
examples/todo-mvc/rust/src/ui/create_task_adapter.rs
Normal file
108
examples/todo-mvc/rust/src/ui/create_task_adapter.rs
Normal 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 }
|
||||
}
|
34
examples/todo-mvc/rust/src/ui/navigation_adapter.rs
Normal file
34
examples/todo-mvc/rust/src/ui/navigation_adapter.rs
Normal 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();
|
||||
}
|
||||
});
|
||||
}
|
66
examples/todo-mvc/rust/src/ui/task_list_adapter.rs
Normal file
66
examples/todo-mvc/rust/src/ui/task_list_adapter.rs
Normal 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(),
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue