iced_nodegraph 0.5.0

High-performance node graph editor widget for Iced with SDF-based rendering
Documentation

Nodes are ordinary iced widgets - sliders, text inputs, whatever your view builds - placed on an infinite zoom/pan canvas and wired together through typed pins. The widget holds no graph state: your application owns the data model, and connections, moves, and deletes arrive as messages, like any other iced widget. Everything on the canvas is drawn by a single WGPU pipeline as signed distance fields, so it stays sharp at every zoom level and handles graphs with hundreds of nodes.

Getting started

cargo add iced_nodegraph
cargo add iced --features wgpu
use iced_nodegraph::prelude::*;
use iced::{Element, Point};

fn view(&self) -> Element<'_, Message> {
    node_graph()
        .on_connect(|from, to| Message::Connected(from, to))
        .on_move(|delta, node_ids| Message::Moved(delta, node_ids))
        // A node is an id, a position, and any iced widget as content.
        .push_node(node(0, Point::new(200.0, 150.0), my_node_widget()))
        .push_node(node(1, Point::new(525.0, 175.0), another_node()))
        // An edge connects two pins, addressed as (node id, pin id).
        .push_edge(edge((), PinRef::new(0, 0), PinRef::new(1, 0)))
        .into()
}

node(..) and edge(..) are builders: chain .style(..) for per-node and per-edge looks, starting from presets like NodeStyle::input or EdgeStyle::error that derive from the theme like iced's button::success. Ids are yours: name the node, pin, edge and anchor id types once on an Ids marker (usize everywhere by default). The crate docs cover styling, connection validation, and the callback contract in detail. A complete runnable graph with typed pin payloads and live evaluation is examples/basic.rs (cargo run -p iced_nodegraph --example basic).

Demos

Every demo runs natively and in the browser.

Demo Live Shows
hello_world run Command palette, theme switching, selection, clone/delete, persistence
styling run Style presets and live node styling controls
interaction run Pin direction and type rules, connection validation, snap feedback
500_nodes run 500 nodes / 640 edges stress test with a runtime stats overlay
shader_editor run Visual WGSL editor: the graph compiles to a running shader
cargo run -p demo_hello_world             # also: demo_styling, demo_interaction, demo_shader_editor
cargo run --release -p demo_500_nodes

Controls

Action Mouse / Keyboard Touch
Pan Right mouse drag One-finger drag on empty canvas, or two-finger drag
Zoom Scroll wheel (zooms at cursor) Two-finger pinch
Connect Drag from pin to pin Drag from pin to pin
Disconnect Click a connected pin to unplug Tap a connected pin to unplug
Fork edge Shift+drag from a connected pin -
Move node Drag node (hold Alt to ignore the snap grid) Drag node
Resize node Drag the bottom-right grip (needs Node::resizable; hold Alt to ignore the snap grid) Drag the bottom-right grip
Move frame with contents Drag a frame's empty area Same
Box select Left drag on empty canvas - (empty-canvas drag pans)
Add to selection Shift+click -
Clear selection Escape -
Select all Ctrl+A -
Clone selection Ctrl+D (web: Alt+D) -
Delete selection Delete / Backspace (web: Delete) -
Frame everything Home -
Frame selection F -
Cut edges Ctrl+click an edge, or Ctrl+drag across edges -
Add routing anchor Drag a cable mid-run Same
Re-route Drag a cable where it wraps an anchor Same
Attach to anchor Drop either drag onto an anchor Same
Detach from anchor Right-click a cable's wrap -
Delete anchor Right-click an anchor's core -
Move anchor Drag an anchor's core (hold Alt to ignore the snap grid) Same
Jump the camera Click in the minimap Tap in the minimap
Pan via minimap Drag in the minimap Drag in the minimap

Ctrl is Cmd on macOS. On the web, clone avoids Ctrl/Cmd+D (the browser's bookmark shortcut) and delete drops the Backspace alternative (legacy back-navigation). Every binding is host-rebindable through NodeGraph::keymap - see the Keymap type. Connections snap while dragging near a compatible pin - like plugging in a cable - rather than on mouse release, and compatible targets pulse during the drag. A route attaches and detaches the same way, during the drag rather than on release.

Anchor delete and detach ride the pan button as a travel-free click, so a right-DRAG starting on an anchor still pans; they follow a rebound pan_button. The routing gestures need on_anchor_create, on_route_attach and on_route_detach wired, anchor delete needs on_anchor_delete, and moving an anchor needs on_anchor_move - which also gates the core's hover highlight and the grab cursor, so a host wiring delete but not move gets a working delete on a core that never lights up. An unwired gesture leaves its zone inert rather than reporting into the void.

The minimap is opt-in through NodeGraph::minimap; without it the two map rows do not apply and the corner stays empty canvas. It takes a press before any node under it does, and reports the camera it steers through on_camera.

How it works

Your app owns the graph. The widget is stateless between frames: it renders the nodes and edges you pass in and reports intent (on_connect, on_move, on_delete, ...) through callbacks. Your update logic applies the change and the next view reflects it - the plain iced loop, no hidden state to sync.

One GPU pipeline. Node bodies, edges, pins, shadows, and the background grid are compiled into a single signed-distance-field scene and rendered by the in-tree iced_nodegraph_sdf crate. A compute-shader tile index culls per pixel, and animated stroke patterns drive their own redraws. Details in ARCHITECTURE.md.

Typed coordinates. Screen space and world space are distinct euclid types, so mixing them up is a compile error.

Compatibility

  • Targets iced 0.14 with the wgpu renderer (the SDF pipeline has no tiny-skia fallback).
  • Native: Windows, macOS, Linux. Web: WebAssembly on WebGPU-capable browsers - there is no WebGL fallback, so Chrome/Chromium is recommended.
  • Pre-1.0: minor releases may change the API. See the CHANGELOG.

Development

The workspace contains the widget (iced_nodegraph), the SDF renderer (iced_nodegraph_sdf), and the demos.

cargo fmt --all -- --check
cargo clippy -p iced_nodegraph -p iced_nodegraph_sdf --all-targets -- -D warnings
ICED_TEST_BACKEND=tiny-skia cargo test -p iced_nodegraph
cargo test -p iced_nodegraph_sdf -- --test-threads=1
cargo bench -p iced_nodegraph_bench  # CPU shape-evaluation cost
./build_docs.sh  # rustdoc + all demos as WASM (needs wasm-pack)

AGENTS.md has the full pre-push list, including the ignored orbit sweep and the workspace, bench and wasm checks.

License

MIT