A high-performance node graph editor widget for the Iced GUI framework, featuring SDF-based GPU rendering via a custom WGPU pipeline and type-safe coordinate transformations.
This is a Cargo workspace containing:
iced_nodegraph/- Core node graph widget libraryiced_nodegraph_sdf/- SDF rendering engine (signed distance fields on GPU)demos/- Demonstration applicationshello_world- Basic usage and command palettestyling- Theming and visual customizationinteraction- Pin rules and connection validation500_nodes- Performance benchmark (500 nodes, 640 edges)shader_editor- Visual WGSL shader editor
0.1.0 - Initial release. Pre-1.0, so the API may still change between minor versions.
Target: Iced 0.14 | Platforms: Windows, macOS, Linux, WebAssembly (Chrome)
- Nodes - Draggable containers for custom widgets with configurable styling
- Pins - Connection points with type checking, directional flow, and visual feedback
- Edges - Bezier curve connections with patterns (solid, dashed, arrowed, dotted)
- Plug Behavior - Cable-like connections that snap on hover, not on release
- Zoom and Pan - Smooth infinite canvas with zoom-at-cursor
- Box Selection - Drag to select multiple nodes, Ctrl+click to toggle
- SDF Rendering - All shapes rendered via signed distance fields for crisp edges at any zoom
- Spatial Index - Compute shader builds a per-tile index for efficient culling
- Theme Support - Integrates with Iced's 22 built-in themes
- Generic IDs - Type-safe node, pin, and edge identifiers
Add to your Cargo.toml:
[dependencies]
iced_nodegraph = { git = "https://github.com/tuco86/iced_nodegraph" }
iced = { version = "0.14", features = ["advanced", "wgpu"] }Basic example:
use iced_nodegraph::prelude::*;
use iced_nodegraph::{edge, node};
use iced::{Element, Point};
fn view(&self) -> Element<Message> {
let mut ng = node_graph()
.on_connect(|from, to| Message::Connected(from, to))
.on_move(|delta, node_ids| Message::Moved(delta, node_ids));
// Nodes are built with node(id, position, widget) and pushed onto the graph.
ng.push_node(node(0, Point::new(200.0, 150.0), my_node_widget()));
ng.push_node(node(1, Point::new(525.0, 175.0), another_node()));
// An edge connects two pins, addressed by PinRef::new(node_id, pin_id).
// edge! defaults the edge id to (); use edge(from, to, id) for a custom id.
ng.push_edge(edge!(PinRef::new(0, 0), PinRef::new(1, 0)));
ng.into()
}node(..) and edge!(..) return builders, so per-node and per-edge styling can be
chained before pushing: node(id, pos, w).style(..).pin_style(..) and
edge!(from, to).style(..).
Ready-made looks save reinventing them: NodeStyle::input() / process() /
output() and EdgeStyle::error() / disabled() / highlighted() (plus
data_flow() and debug()). Strokes use Pattern (solid / dashed / dotted,
with .flow(speed) to animate). A full cookbook - presets, the struct-update
override idiom, and per-node status styling - is in the
crate docs.
See demos/hello_world/ for a complete working example.
git clone https://github.com/tuco86/iced_nodegraph
cd iced_nodegraph
cargo run -p demo_hello_world
cargo run -p demo_styling
cargo run -p demo_interaction
cargo run --release -p demo_500_nodes
cargo run -p demo_shader_editor
cargo run -p sdf_basic # iced_nodegraph_sdf examplecargo build -p iced_nodegraph # Core library
cargo build --workspace # Everything
cargo test -p iced_nodegraph # Core library tests
cargo test -p iced_nodegraph_sdf # SDF engine tests
cargo clippy --workspace -- -D warnings# Windows
.\build_demo_wasm.ps1
# Linux/macOS
./build_demo_wasm.shRequires wasm-pack and a WebGPU-capable browser (Chrome/Chromium recommended).
The CPU-side per-frame cost (building node silhouettes, layering, and stroking
edges into one SDF primitive - the work the on_info() callback times) is measured
with criterion:
cargo bench -p iced_nodegraph # frame_prep: 100 / 500 / 2000 nodesThe cost scales roughly linearly with element count. Indicative figures on an
Apple Silicon dev machine: ~1.2 ms at 100 nodes, ~6 ms at 500 nodes (the
demo_500_nodes scale), ~22 ms at 2000 nodes. Per-pixel culling runs separately
on the GPU (a compute-shader tile index), so it is not part of this CPU figure.
iced_nodegraph/ # Workspace root
├── iced_nodegraph/ # Core widget library
│ └── src/
│ ├── node_graph/ # Main widget + camera + state
│ ├── node_pin/ # Pin widget
│ ├── style/ # Styling and config types
│ ├── content.rs # Layout helpers (node_header, node_footer)
│ ├── ids.rs # Generic ID system
│ └── prelude.rs # Convenience re-exports
├── iced_nodegraph_sdf/ # SDF rendering engine
│ └── src/
│ ├── drawable.rs # Segment-based shapes (lines, arcs, beziers)
│ ├── curve.rs # Shape builders (rect, rounded_rect, circle)
│ ├── boolean.rs # Boolean ops (union, difference) on contours
│ ├── style.rs # Distance-stop style chains
│ ├── pattern.rs # Stroke patterns (dashed, arrowed, dotted)
│ ├── tiling.rs # Tiling backgrounds (grid, dots, ...)
│ ├── compile.rs # Drawable + Style -> GPU buffers
│ ├── primitive.rs # Iced rendering primitive
│ └── pipeline/ # WGPU pipeline, buffers, shader
└── demos/ # Demo applications
The renderer uses signed distance fields evaluated on the GPU:
- Compile - shape contours (segments) and styles are compiled to GPU data
- Upload - segments, draw entries, and styles are written to GPU storage buffers
- Spatial Index - a compute shader builds a per-tile segment list for culling
- Render - the fragment shader evaluates only the segments in each pixel's tile
All geometry (nodes, edges, pins, shadows, outlines) is rendered through this single pipeline. A style is a distance-stop chain controlling appearance: fill, gradient, stroke pattern, border, and shadow.
Type-safe coordinate spaces using the euclid crate:
- Screen Space (
ScreenPoint) - Physical pixel coordinates - World Space (
WorldPoint) - Virtual canvas coordinates
Transformations are compile-time checked. See camera.rs for formulas and tests.
| Action | Input |
|---|---|
| Pan | Right mouse drag |
| Zoom | Scroll wheel (at cursor) |
| Connect | Drag from pin to pin |
| Disconnect | Click connected pin to unplug |
| Move node | Drag node header |
| Box select | Left drag on background |
| Toggle select | Ctrl+click |
| Clone | Ctrl+D |
| Delete | Delete key |
| Cut edges | Alt+drag across edges |
- iced 0.14 - GUI framework
- iced_wgpu 0.14 - WebGPU renderer
- euclid - Type-safe coordinate math
- glam - Vector math for SDF evaluation
- encase - WGSL buffer layout
- bytemuck - Safe transmutation for GPU buffers
See LICENSE file for details.