Aspirational examples for app developer contract.
This commit is contained in:
148
aspirational_examples/03_fine_grained_list.rs
Normal file
148
aspirational_examples/03_fine_grained_list.rs
Normal file
@@ -0,0 +1,148 @@
|
||||
//! Aspirational RUIN app API for fine-grained reactivity and keyed layout islands.
|
||||
//!
|
||||
//! Intentionally non-compiling; this is a design sketch.
|
||||
|
||||
use ruin::prelude::*;
|
||||
|
||||
#[ruin_runtime::async_main]
|
||||
async fn main() -> ruin::Result<()> {
|
||||
App::new()
|
||||
.window(Window::new().title("RUIN Tasks").size(1180.0, 780.0))
|
||||
.mount::<TaskBoard>()
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn TaskBoard() -> impl View {
|
||||
let filter = use_signal(|| TaskFilter::All);
|
||||
let tasks = use_signal_vec(seed_tasks());
|
||||
|
||||
let visible_ids = use_memo(move || {
|
||||
tasks.iter()
|
||||
.filter(|task| filter.get().matches(task.status))
|
||||
.map(|task| task.id)
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
view! {
|
||||
column(fill = true, gap = 16, padding = 20) {
|
||||
text(role = TextRole::Heading(1), size = 28, weight = FontWeight::Semibold) {
|
||||
"Task board"
|
||||
}
|
||||
|
||||
row(gap = 8) {
|
||||
button(
|
||||
kind = if matches!(filter.get(), TaskFilter::All) {
|
||||
ButtonKind::Primary
|
||||
} else {
|
||||
ButtonKind::Secondary
|
||||
},
|
||||
on_press = move |_| filter.set(TaskFilter::All),
|
||||
) { "All" }
|
||||
|
||||
button(
|
||||
kind = if matches!(filter.get(), TaskFilter::OpenOnly) {
|
||||
ButtonKind::Primary
|
||||
} else {
|
||||
ButtonKind::Secondary
|
||||
},
|
||||
on_press = move |_| filter.set(TaskFilter::OpenOnly),
|
||||
) { "Open" }
|
||||
|
||||
button(
|
||||
kind = if matches!(filter.get(), TaskFilter::CompletedOnly) {
|
||||
ButtonKind::Primary
|
||||
} else {
|
||||
ButtonKind::Secondary
|
||||
},
|
||||
on_press = move |_| filter.set(TaskFilter::CompletedOnly),
|
||||
) { "Completed" }
|
||||
}
|
||||
|
||||
text(color = colors::muted()) {
|
||||
"The goal here is fine-grained change tracking: reordering, toggling, and editing \
|
||||
a single row should not force unrelated rows to rebuild or the entire layout tree \
|
||||
to be recomputed."
|
||||
}
|
||||
|
||||
column(fill = true, gap = 10) {
|
||||
for task_id in visible_ids keyed by *task_id {
|
||||
layout_boundary(key = task_id) {
|
||||
TaskRow(task = tasks.item(task_id)) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn TaskRow(task: ItemSignal<Task>) -> impl View {
|
||||
let expanded = use_signal(|| false);
|
||||
let title = use_memo(move || task.with(|task| task.title.clone()));
|
||||
let done = use_memo(move || task.with(|task| task.done));
|
||||
|
||||
view! {
|
||||
row(gap = 12, padding = 12, align = Align::Center) {
|
||||
checkblock(
|
||||
checked = done,
|
||||
on_toggle = move |checked| task.update(|task| task.done = checked),
|
||||
) {}
|
||||
|
||||
column(fill = true, gap = 6) {
|
||||
text(weight = if done.get() { FontWeight::Medium } else { FontWeight::Semibold }) {
|
||||
title
|
||||
}
|
||||
|
||||
if expanded.get() {
|
||||
text(color = colors::muted()) {
|
||||
task.with(|task| task.notes.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
icon_button(
|
||||
icon = if expanded.get() { Icon::ChevronUp } else { Icon::ChevronDown },
|
||||
on_press = move |_| expanded.toggle(),
|
||||
) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Task {
|
||||
id: u64,
|
||||
title: String,
|
||||
notes: String,
|
||||
status: TaskStatus,
|
||||
done: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum TaskStatus {
|
||||
Backlog,
|
||||
Doing,
|
||||
Done,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum TaskFilter {
|
||||
All,
|
||||
OpenOnly,
|
||||
CompletedOnly,
|
||||
}
|
||||
|
||||
impl TaskFilter {
|
||||
fn matches(self, status: TaskStatus) -> bool {
|
||||
match self {
|
||||
TaskFilter::All => true,
|
||||
TaskFilter::OpenOnly => !matches!(status, TaskStatus::Done),
|
||||
TaskFilter::CompletedOnly => matches!(status, TaskStatus::Done),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn seed_tasks() -> Vec<Task> {
|
||||
Vec::new()
|
||||
}
|
||||
Reference in New Issue
Block a user