//! 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::() .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::>() }); 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) -> 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 { Vec::new() }