facett-graphview 0.1.12

facett — a vello-backed, domain-agnostic scalable 2D graph render engine. Runtime-selects vello (GPU/wgpu) when a usable GPU exists, vello_cpu (multithreaded SIMD) as the no-GPU fallback. The eventual home for every graph surface (dep/arch/release dashboards, korp, graph-DB browsing).
Documentation
//! Headless demo: probe → pick a [`Backend`] → render a sample decorated graph
//! to `graphview-demo.png`. Proves the engine draws a real graph. The RGBA frame
//! it writes is exactly the buffer a host uploads as an egui texture and shows
//! via an `egui_wgpu`/`egui` paint callback — so PNG here == egui-embedding
//! there, minus the windowing.
//!
//! Run: `cargo run -p facett-graphview --example render_png`

use facett_graphview::{
    Backend, Camera, Color, Decorations, GraphEdge, GraphModel, GraphNode, NodeDecoration, Pos,
    decide, render_to_rgba, GpuProbe,
};

fn main() {
    // Probe → pick. In a GUI host, `usable_gpu` comes from `gpu::probe_usable_gpu`
    // (feature `gpu`); headless we ask for CPU.
    let backend: Backend = decide(GpuProbe::from_env(false));
    eprintln!("[graphview] backend = {}", backend.label());

    // A small three-tier dependency-ish graph.
    let mut nodes = Vec::new();
    let mut edges = Vec::new();
    let tiers = [("core", 0.0), ("mid", 320.0), ("app", 640.0)];
    for (ti, (name, x)) in tiers.iter().enumerate() {
        for r in 0..4 {
            nodes.push(GraphNode {
                id: format!("{name}{r}"),
                label: format!("{name}-{r}"),
                fill: Color::rgb(40 + (ti * 40) as u8, 90, 150 - (ti * 30) as u8),
                stroke: Color::rgb(210, 210, 230),
                pos: Pos::new(*x, r as f32 * 80.0 - 120.0),
            });
        }
    }
    for r in 0..4 {
        edges.push(edge(&format!("core{r}"), &format!("mid{r}"), false));
        edges.push(edge(&format!("mid{r}"), &format!("app{}", (r + 1) % 4), r == 2));
    }
    let model = GraphModel { nodes, edges };

    // Caller-supplied decorations (domain-agnostic): a ring + a badge on one node.
    let mut deco = Decorations::default();
    deco.nodes.insert(
        "app1".into(),
        NodeDecoration { scale: None, 
            ring: Some(Color::rgb(240, 200, 60)),
            badge: Some("!".into()),
            badge_color: Some(Color::rgb(240, 200, 60)),
        },
    );
    deco.edges.push(GraphEdge {
        from: "core0".into(),
        to: "app1".into(),
        color: Color::rgb(240, 120, 120),
        dashed: true,
        label: Some("suggested cut".into()),
    });

    let (w, h) = (1280u32, 720u32);
    let cam = Camera::fit(&model, w as f32, h as f32, 60.0);
    let frame = render_to_rgba(backend, &model, &deco, &cam, w, h, Color::rgb(16, 18, 26));

    let img = image::RgbaImage::from_raw(frame.width, frame.height, frame.rgba)
        .expect("frame buffer matches dimensions");
    let out = "graphview-demo.png";
    img.save(out).expect("write PNG");
    eprintln!("[graphview] wrote {out} ({w}x{h})");
}

fn edge(from: &str, to: &str, dashed: bool) -> GraphEdge {
    GraphEdge {
        from: from.into(),
        to: to.into(),
        color: Color::rgb(170, 180, 210),
        dashed,
        label: None,
    }
}