facett-pipeline/examples/pipeline_demo.rs
//! **pipeline_demo** — the glow-up, alive. A multi-stage Spark-style pipeline DAG
//! that **animates** (marching-ants flow along edges, per-stage status glow:
//! running pulses, done holds, failed blooms) and is **fully theme-driven** —
//! every colour comes from the active [`Theme`], switchable live.
//!
//! Pure egui on the **glow** backend (no wgpu / no custom shader).
//!
//! Run: `cargo run -p facett-pipeline --example pipeline_demo`
use eframe::egui;
use egui::Vec2;
use facett_core::{Facet, Theme, set_theme};
use facett_pipeline::{JobProgress, PipelineView, Status};
/// Build a fresh 6-stage ETL pipeline (rebuilt each frame so the demo can swap
/// speed/animation without threading state through).
fn pipeline(speed: f32, animated: bool) -> PipelineView {
// raw → clean → join ↘ (join also reads dim)
// agg → out
// dim ─────────↗
let nodes = vec![
("raw.events".to_string(), Status::Done),
("clean".to_string(), Status::Done),
("dim.users".to_string(), Status::Done),
("join".to_string(), Status::Running),
("agg.daily".to_string(), Status::Running),
("gold.out".to_string(), Status::Idle),
("audit".to_string(), Status::Failed),
];
// (reads_idx, target_idx): the left node reads from the right.
let edges = vec![
(1, 0), // clean reads raw
(3, 1), // join reads clean
(3, 2), // join reads dim
(4, 3), // agg reads join
(5, 4), // out reads agg
(6, 4), // audit reads agg
];
let throughput = vec![
Some("48k rows/s".to_string()),
Some("31k/s".to_string()),
Some("dim".to_string()),
Some("12k/s".to_string()),
None,
Some("err".to_string()),
];
PipelineView::new("ETL", nodes, edges)
.with_progress(JobProgress {
fraction: 0.62,
tasks_done: 124,
tasks_total: 200,
rows: 1_240_000,
bytes_read: 48 * 1024 * 1024,
elapsed_s: 12.5,
inflight: 3,
})
.with_throughput(throughput)
.with_speed(speed)
.with_animation(animated)
}
struct App {
theme_idx: usize,
speed: f32,
animated: bool,
}
impl Default for App {
fn default() -> Self {
Self { theme_idx: 3, speed: 0.6, animated: true } // start cyberpunk-neon
}
}
impl eframe::App for App {
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("Palette:");
for (i, ctor) in Theme::ALL.iter().enumerate() {
ui.selectable_value(&mut self.theme_idx, i, ctor().name);
}
});
ui.horizontal(|ui| {
ui.checkbox(&mut self.animated, "Animate flow + glow");
ui.add(egui::Slider::new(&mut self.speed, 0.0..=3.0).text("flow speed"));
});
});
egui::CentralPanel::default().show(root, |ui| {
// Rebuild + render the pipeline (cheap; layout is deterministic).
egui::ScrollArea::both().show(ui, |ui| {
pipeline(self.speed, self.animated).ui(ui);
});
});
}
}
fn main() -> eframe::Result {
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default().with_inner_size(Vec2::new(980.0, 640.0)),
..Default::default()
};
eframe::run_native("facett · pipeline_demo", options, Box::new(|_cc| Ok(Box::<App>::default())))
}