facett-graphpan/tests/graph_opts.rs
//! **`draw_graph_opts` — the one-call interactive canvas with `PaintOpts`.**
//!
//! `draw_graph_opts` is the seam a scale/"universe" host mounts: it does everything
//! [`facett_graphpan::draw_graph`] does (allocate the painter, pan/drag + scroll-zoom,
//! click-to-select) but takes [`PaintOpts`] so the host can route the node/edge cloud
//! through the shared instanced GPU renderer, and RETURNS a [`LegibilityReport`] whose
//! `gpu_cloud` flag names the lane that drew.
//!
//! These run headless (a plain `egui::Context` frame — no GPU stack, no renderer
//! installed), i.e. in exactly the state that must FALL BACK: opting into the GPU here
//! draws every node on the CPU and reports `gpu_cloud == false`. It is the same
//! fail-safe `gpu_fallback.rs` pins for `GraphPanView`, asserted on the free function.
use std::cell::RefCell;
use egui::{pos2, Color32};
use facett_graphpan::{
draw_graph_opts, Decorations, GraphEdge, GraphModel, GraphNode, GraphView, LegibilityReport,
PaintOpts,
};
fn node(id: &str, x: f32, y: f32) -> GraphNode {
GraphNode {
id: id.into(),
label: id.into(),
fill: Color32::from_rgb(120, 200, 255),
stroke: Color32::from_rgb(30, 40, 60),
pos: pos2(x, y),
}
}
fn grid(n: usize) -> GraphModel {
let side = (n as f32).sqrt().ceil() as usize;
let nodes: Vec<GraphNode> = (0..n)
.map(|i| {
let (r, c) = (i / side, i % side);
node(&format!("n{i}"), c as f32 * 60.0 - 300.0, r as f32 * 40.0 - 200.0)
})
.collect();
let edges: Vec<GraphEdge> = (0..n.saturating_sub(1))
.map(|i| GraphEdge {
from: format!("n{i}"),
to: format!("n{}", i + 1),
color: Color32::WHITE,
dashed: false,
label: None,
})
.collect();
GraphModel { nodes, edges }
}
/// Run ONE headless egui frame that paints `model` through `draw_graph_opts` with `opts`,
/// and return the frame's `LegibilityReport` plus the tessellated vertex count (the "did it
/// actually draw?" measure — a fallback CPU paint dwarfs a blank pane).
fn frame(model: &GraphModel, opts: PaintOpts) -> (LegibilityReport, usize) {
let ctx = egui::Context::default();
let report = RefCell::new(LegibilityReport::default());
let mut view = GraphView {
dot_px: Some(3.0), // point-cloud lane, like the universe star-field
..Default::default()
};
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| {
egui::CentralPanel::default().show(ui, |ui| {
let (_resp, r) = draw_graph_opts(
ui,
model,
&Decorations::default(),
&mut view,
Color32::from_gray(14),
Color32::from_gray(235),
Color32::from_rgb(0x39, 0x87, 0xe5),
Color32::from_gray(120),
opts,
);
*report.borrow_mut() = r;
});
});
out.textures_delta.clear();
let verts: usize = ctx
.tessellate(out.shapes, out.pixels_per_point)
.iter()
.map(|c| match &c.primitive {
egui::epaint::Primitive::Mesh(m) => m.vertices.len(),
egui::epaint::Primitive::Callback(_) => 0,
})
.sum();
(report.into_inner(), verts)
}
/// GPU opt-in with NO renderer installed draws every node on the CPU (a real, non-blank
/// frame) and honestly reports `gpu_cloud == false` — the fail-safe, on the free function.
#[test]
fn gpu_opt_in_without_a_renderer_falls_back_and_says_so() {
// Only meaningful on a box with no cloud renderer installed (headless => none).
assert!(
!facett_graphpan::legible::gpu_cloud_available(),
"headless test: no cloud renderer must be installed"
);
let model = grid(256);
let (off, v_off) = frame(&model, PaintOpts::default());
let (gpu, v_gpu) = frame(&model, PaintOpts::default().with_gpu(true));
assert!(!off.gpu_cloud, "gpu off => CPU lane");
assert!(!gpu.gpu_cloud, "gpu opt-in with no renderer must report the CPU fallback lane");
// The fallback drew the SAME frame as the plain canvas — vertex for vertex, never blank.
assert_eq!(v_gpu, v_off, "the GPU fallback paints the CPU frame vertex for vertex ({v_gpu} vs {v_off})");
assert!(v_gpu > 256, "a 256-node point cloud is a real, non-blank frame (got {v_gpu} verts)");
}
/// The click/pan interaction survives the opts path: `draw_graph_opts` returns a
/// `GraphResponse` (here: no pointer, so nothing clicked) and never panics on an empty
/// model — the seam is a drop-in for `draw_graph`.
#[test]
fn empty_model_is_safe_and_reports_nothing_drawn_on_the_gpu() {
let (r, _v) = frame(&GraphModel::default(), PaintOpts::default().with_gpu(true));
assert!(!r.gpu_cloud, "empty model never claims a GPU paint");
}