facett-card/examples/card_demo.rs
//! **card_demo** — a YouTrack/Jira-style surveillance **issue card**, live. A
//! parent case with a status enum, an assignee (avatar initial), priority, opened
//! / due dates, tags, a progress bar, and a ticket hyperlink — plus three nested
//! **sub-task** cards, each in its own collapsible header (recursively a card).
//!
//! A read/edit toggle swaps the parent's field grid into a bound facett-form (the
//! card *is* the form model). A theme switcher proves every chip / avatar / bar is
//! palette-driven, and Ctrl-C copies the whole card (incl. children) as JSON,
//! Ctrl-V fills it — routed exactly the way a [`facett_core::FacetDeck`] does.
//!
//! Run: `cargo run -p facett-card --example card_demo`
use eframe::egui;
use facett_card::{Card, CardField, CardView, FieldValue};
use facett_core::{Facet, Theme, clipboard, set_theme};
fn demo_card() -> Card {
let subtask = |id: &str, title: &str, state: &str, who: &str, prog: f32| {
Card::new(title)
.with_id(id)
.field(CardField::new(
"Status",
FieldValue::Enum {
state: state.into(),
options: vec!["open".into(), "in_progress".into(), "done".into()],
},
))
.field(CardField::new("Assignee", FieldValue::User(who.into())))
.field(CardField::new("Progress", FieldValue::Progress(prog)))
};
Card::new("Investigate vessel Nordkapp")
.with_id("CASE-42")
.field(CardField::new(
"Status",
FieldValue::Enum {
state: "escalated".into(),
options: vec!["open".into(), "escalated".into(), "closed".into()],
},
))
.field(CardField::new("Assignee", FieldValue::User("hugin".into())))
.field(CardField::new("Priority", FieldValue::Number(2.0)))
.field(CardField::new("Opened", FieldValue::Date("2026-06-01".into())))
.field(CardField::new("Due", FieldValue::Date("2026-06-30".into())))
.field(CardField::new(
"Tags",
FieldValue::Tags(vec!["ais-gap".into(), "port-call".into(), "nordkapp".into()]),
))
.field(CardField::new("Progress", FieldValue::Progress(0.4)))
.field(CardField::new(
"Ticket",
FieldValue::Link { label: "open in tracker".into(), url: "https://example.test/CASE-42".into() },
))
.child(subtask("CASE-42-1", "Pull AIS track", "done", "munin", 1.0))
.child(subtask("CASE-42-2", "Cross-ref port calls", "in_progress", "hugin", 0.5))
.child(subtask("CASE-42-3", "Draft surveillance report", "open", "hugin", 0.0))
}
struct Demo {
theme_idx: usize,
view: CardView,
}
impl Default for Demo {
fn default() -> Self {
Self { theme_idx: 2, view: CardView::new("CASE-42", demo_card()) }
}
}
impl eframe::App for Demo {
fn ui(&mut self, root: &mut egui::Ui, _frame: &mut eframe::Frame) {
let ctx = root.ctx().clone();
set_theme(&ctx, Theme::ALL[self.theme_idx]());
egui::Panel::top("controls").show(root, |ui| {
ui.horizontal_wrapped(|ui| {
ui.label("Theme:");
for (i, ctor) in Theme::ALL.iter().enumerate() {
ui.selectable_value(&mut self.theme_idx, i, ctor().name);
}
});
ui.label("Toggle Edit to form-bind the parent's fields · expand a sub-task · Ctrl-C copies the card as JSON · Ctrl-V fills it");
});
// Deck-style clipboard routing, gated by the facet's caps.
let caps = self.view.caps();
for action in clipboard::poll(&ctx) {
match action {
clipboard::ClipAction::Copy if caps.copyable => {
if let Some(t) = self.view.copy() {
clipboard::put(&ctx, t);
}
}
clipboard::ClipAction::Paste(s) if caps.pasteable => {
self.view.paste(&s);
}
_ => {}
}
}
egui::CentralPanel::default().show(root, |ui| {
egui::ScrollArea::vertical().show(ui, |ui| {
self.view.ui(ui);
});
});
}
}
fn main() -> eframe::Result {
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default().with_inner_size(egui::vec2(560.0, 720.0)),
..Default::default()
};
eframe::run_native("facett · card_demo", options, Box::new(|_cc| Ok(Box::<Demo>::default())))
}