#![cfg(not(target_arch = "wasm32"))]
mod common;
use iced::advanced::widget::{Tree, Widget};
use iced::advanced::{Layout, layout, mouse, renderer};
use iced::{Color, Element, Length, Point, Rectangle, Size, Theme};
use iced_wgpu::core::clipboard;
use common::shared;
use iced_wgpu::Renderer;
use iced_wgpu::graphics::Viewport;
const GW: u32 = 600;
const GH: u32 = 400;
fn render_edge_grid() -> Option<Vec<[u8; 4]>> {
use iced::widget::{Column, container, text};
use iced_nodegraph::{
ColorQuad, EdgeStyle, PinDirection, PinRef, PinSide, default_edge_style, edge, node,
node_pin,
};
let mut guard = shared()?;
let renderer = &mut *guard;
let (cols, rows) = (25usize, 20usize);
let n = cols * rows;
let (sx, sy) = (90.0f32, 80.0f32);
let zoom = (GW as f32 / (cols as f32 * sx)).min(GH as f32 / (rows as f32 * sy)) * 0.92;
let mut graph: iced_nodegraph::NodeGraph<
'static,
iced_nodegraph::Indexed,
(),
Theme,
Renderer,
> = iced_nodegraph::NodeGraph::default()
.width(Length::Fixed(GW as f32))
.height(Length::Fixed(GH as f32))
.camera(
Point::new(
GW as f32 * 0.5 / zoom - cols as f32 * sx * 0.5,
GH as f32 * 0.5 / zoom - rows as f32 * sy * 0.5,
),
zoom,
);
for i in 0..n {
let (c, r) = ((i % cols) as f32, (i / cols) as f32);
let pins = Column::with_children(vec![
Element::from(
node_pin(PinSide::Right, 0usize, text("o")).direction(PinDirection::Output),
),
Element::from(
node_pin(PinSide::Left, 1usize, text("i")).direction(PinDirection::Input),
),
]);
let content: Element<'static, (), Theme, Renderer> = container(pins)
.width(Length::Fixed(70.0))
.height(Length::Fixed(60.0))
.into();
graph = graph.push_node(node(i, Point::new(c * sx, r * sy), content));
}
let edge_count = 640usize;
let sources = 16usize; for e in 0..edge_count {
let from = e % sources;
let to = sources + (e * 7 + 3) % (n - sources);
if to == from {
continue;
}
graph = graph.push_edge(
edge((), PinRef::new(from, 0usize), PinRef::new(to, 1usize)).style(
|theme, status, _from, _to| EdgeStyle {
stroke_color: ColorQuad::solid(Color::from_rgb(0.0, 1.0, 0.0)),
..default_edge_style(theme, status)
},
),
);
}
let mut tree = Tree::new(&graph as &dyn Widget<(), Theme, Renderer>);
let layout_node = graph.layout(
&mut tree,
&*renderer,
&layout::Limits::new(Size::ZERO, Size::new(GW as f32, GH as f32)),
);
let layout = Layout::new(&layout_node);
let viewport_rect = Rectangle::new(Point::ORIGIN, Size::new(GW as f32, GH as f32));
let mut msgs: Vec<()> = Vec::new();
let mut shell = iced_wgpu::core::Shell::new(&mut msgs);
let mut clipboard = clipboard::Null;
graph.update(
&mut tree,
&iced::Event::Mouse(mouse::Event::CursorMoved {
position: Point::new(-1.0, -1.0),
}),
layout,
mouse::Cursor::Unavailable,
&*renderer,
&mut clipboard,
&mut shell,
&viewport_rect,
);
renderer::Renderer::reset(renderer, viewport_rect);
graph.draw(
&tree,
renderer,
&Theme::Dark,
&renderer::Style {
text_color: Color::WHITE,
},
layout,
mouse::Cursor::Unavailable,
&viewport_rect,
);
let bytes = renderer.screenshot(
&Viewport::with_physical_size(Size::new(GW, GH), 1.0),
Color::TRANSPARENT,
);
Some(bytes.as_chunks::<4>().0.to_vec())
}
fn green_count(px: &[[u8; 4]]) -> usize {
px.iter()
.filter(|p| {
(p[1] as i32) > (p[0] as i32) + 40 && (p[1] as i32) > (p[2] as i32) + 40 && p[1] > 80
})
.count()
}
#[test]
fn edge_grid_edges_are_visible() {
let Some(px) = render_edge_grid() else {
eprintln!("no GPU adapter - skipping edge_grid_edges_are_visible");
return;
};
let green = green_count(&px);
eprintln!("edge_grid GREEN edge pixels: {green} / {}", px.len());
assert!(
green > 50,
"almost no edges rendered: only {green} green pixels"
);
}
#[test]
#[ignore = "diagnostic: prints GPU buffer sizes"]
fn report_buffer_sizes() {
if render_edge_grid().is_none() {
return;
}
let s = iced_nodegraph_sdf::sdf_stats();
let tiles = s.tile_count as u64;
let kib = |b: u64| b / 1024;
eprintln!(
"tiles={tiles} entries={} unique_shapes={} segments={}",
s.entry_count, s.unique_shapes, s.segment_count
);
eprintln!(" tile_counts: {} KiB ({tiles} x 4)", kib(tiles * 4));
eprintln!(
" spatial index: {} KiB | all GPU buffers: {} KiB",
kib(s.index_bytes),
kib(s.gpu_bytes)
);
eprintln!(
" segments: {} KiB ({} x 64) | entries: {} KiB ({} x 80)",
kib(s.segment_count as u64 * 64),
s.segment_count,
kib(s.entry_count as u64 * 80),
s.entry_count,
);
}
#[test]
fn edge_grid_stable_across_frames() {
let mut counts = Vec::new();
for _ in 0..6 {
let Some(px) = render_edge_grid() else {
eprintln!("no GPU adapter - skipping edge_grid_stable_across_frames");
return;
};
counts.push(green_count(&px));
}
eprintln!("edge_grid GREEN per frame: {counts:?}");
let first = counts[0];
for (i, &c) in counts.iter().enumerate() {
assert!(
(c as i64 - first as i64).abs() < (first as i64) / 20 + 200,
"edge coverage drifted on frame {i}: {c} vs frame0 {first} (cross-frame state bug?)",
);
}
}
#[test]
#[ignore = "visual probe: writes widget_edge_grid_render.png"]
fn dump_edge_grid_png() {
let Some(px) = render_edge_grid() else {
eprintln!("no GPU adapter - skipping dump_edge_grid_png");
return;
};
let path = concat!(
env!("CARGO_MANIFEST_DIR"),
"/../widget_edge_grid_render.png"
);
let file = std::fs::File::create(path).unwrap();
let mut enc = png::Encoder::new(std::io::BufWriter::new(file), GW, GH);
enc.set_color(png::ColorType::Rgba);
enc.set_depth(png::BitDepth::Eight);
let mut writer = enc.write_header().unwrap();
let flat: Vec<u8> = px.iter().flat_map(|p| p.iter().copied()).collect();
writer.write_image_data(&flat).unwrap();
}
fn render_minimal_edges() -> Option<Vec<[u8; 4]>> {
use iced::widget::{Column, container, text};
use iced_nodegraph::{
ColorQuad, EdgeStyle, PinDirection, PinRef, PinSide, default_edge_style, edge, node,
node_pin,
};
let mut guard = shared()?;
let renderer = &mut *guard;
let positions = [
Point::new(40.0, 40.0),
Point::new(420.0, 60.0),
Point::new(80.0, 300.0),
Point::new(440.0, 320.0),
];
let mut graph: iced_nodegraph::NodeGraph<
'static,
iced_nodegraph::Indexed,
(),
Theme,
Renderer,
> = iced_nodegraph::NodeGraph::default()
.width(Length::Fixed(GW as f32))
.height(Length::Fixed(GH as f32))
.camera(Point::new(0.0, 0.0), 1.0);
for (i, p) in positions.iter().enumerate() {
let pins = Column::with_children(vec![
Element::from(
node_pin(PinSide::Right, 0usize, text("o")).direction(PinDirection::Output),
),
Element::from(
node_pin(PinSide::Left, 1usize, text("i")).direction(PinDirection::Input),
),
]);
let content: Element<'static, (), Theme, Renderer> = container(pins)
.width(Length::Fixed(70.0))
.height(Length::Fixed(60.0))
.into();
graph = graph.push_node(node(i, *p, content));
}
for &(f, t) in &[(0usize, 1usize), (0, 3), (2, 1)] {
graph = graph.push_edge(
edge((), PinRef::new(f, 0usize), PinRef::new(t, 1usize)).style(
|theme, status, _from, _to| EdgeStyle {
stroke_color: ColorQuad::solid(Color::from_rgb(0.0, 1.0, 0.0)),
..default_edge_style(theme, status)
},
),
);
}
let mut tree = Tree::new(&graph as &dyn Widget<(), Theme, Renderer>);
let layout_node = graph.layout(
&mut tree,
&*renderer,
&layout::Limits::new(Size::ZERO, Size::new(GW as f32, GH as f32)),
);
let layout = Layout::new(&layout_node);
let viewport_rect = Rectangle::new(Point::ORIGIN, Size::new(GW as f32, GH as f32));
let mut msgs: Vec<()> = Vec::new();
let mut shell = iced_wgpu::core::Shell::new(&mut msgs);
let mut clipboard = clipboard::Null;
graph.update(
&mut tree,
&iced::Event::Mouse(mouse::Event::CursorMoved {
position: Point::new(-1.0, -1.0),
}),
layout,
mouse::Cursor::Unavailable,
&*renderer,
&mut clipboard,
&mut shell,
&viewport_rect,
);
graph.draw(
&tree,
renderer,
&Theme::Dark,
&renderer::Style {
text_color: Color::WHITE,
},
layout,
mouse::Cursor::Unavailable,
&viewport_rect,
);
let bytes = renderer.screenshot(
&Viewport::with_physical_size(Size::new(GW, GH), 1.0),
Color::TRANSPARENT,
);
Some(bytes.as_chunks::<4>().0.to_vec())
}
#[test]
#[ignore = "visual probe: writes widget_minimal_edges.png"]
fn dump_minimal_edges_png() {
let Some(px) = render_minimal_edges() else {
eprintln!("no GPU adapter - skipping dump_minimal_edges_png");
return;
};
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../widget_minimal_edges.png");
let file = std::fs::File::create(path).unwrap();
let mut enc = png::Encoder::new(std::io::BufWriter::new(file), GW, GH);
enc.set_color(png::ColorType::Rgba);
enc.set_depth(png::BitDepth::Eight);
let mut writer = enc.write_header().unwrap();
let flat: Vec<u8> = px.iter().flat_map(|p| p.iter().copied()).collect();
writer.write_image_data(&flat).unwrap();
}