iced_nodegraph 0.5.0

High-performance node graph editor widget for Iced with SDF-based rendering
Documentation
<p align="center">
  <img src="https://raw.githubusercontent.com/tuco86/iced_nodegraph/main/assets/logo/logo.svg" alt="iced_nodegraph logo" width="120" height="120">
</p>

<h1 align="center">iced_nodegraph</h1>

<p align="center">A node graph editor widget for <a href="https://github.com/iced-rs/iced">iced</a>.</p>

<p align="center">
  <a href="https://crates.io/crates/iced_nodegraph"><img src="https://img.shields.io/crates/v/iced_nodegraph.svg" alt="crates.io"></a>
  <a href="https://docs.rs/iced_nodegraph"><img src="https://img.shields.io/docsrs/iced_nodegraph" alt="docs.rs"></a>
  <a href="https://github.com/tuco86/iced_nodegraph/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT license"></a>
</p>

<p align="center">
  <a href="https://tuco86.github.io/iced_nodegraph/demo_hello_world/index.html">
    <img src="https://raw.githubusercontent.com/tuco86/iced_nodegraph/main/assets/hero.png" alt="The hello_world demo: an email workflow graph with four connected nodes" width="800">
  </a>
  <br>
  <em><a href="https://tuco86.github.io/iced_nodegraph/demo_hello_world/index.html">Run this demo in your browser</a> (WebGPU required, Chrome recommended)</em>
</p>

<p align="center">Documentation with live demos: <a href="https://tuco86.github.io/iced_nodegraph/">https://tuco86.github.io/iced_nodegraph/</a></p>

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

```bash
cargo add iced_nodegraph
cargo add iced --features wgpu
```

```rust
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`](https://docs.rs/iced_nodegraph/latest/iced_nodegraph/trait.Ids.html)
marker (`usize` everywhere by default). The
[crate docs](https://docs.rs/iced_nodegraph) 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`](https://github.com/tuco86/iced_nodegraph/blob/main/iced_nodegraph/examples/basic.rs)
(`cargo run -p iced_nodegraph --example basic`).

## Demos

Every demo runs natively and in the browser.

| Demo | Live | Shows |
|------|------|-------|
| [`hello_world`]https://github.com/tuco86/iced_nodegraph/tree/main/demos/hello_world | [run]https://tuco86.github.io/iced_nodegraph/demo_hello_world/index.html | Command palette, theme switching, selection, clone/delete, persistence |
| [`styling`]https://github.com/tuco86/iced_nodegraph/tree/main/demos/styling | [run]https://tuco86.github.io/iced_nodegraph/demo_styling/index.html | Style presets and live node styling controls |
| [`interaction`]https://github.com/tuco86/iced_nodegraph/tree/main/demos/interaction | [run]https://tuco86.github.io/iced_nodegraph/demo_interaction/index.html | Pin direction and type rules, connection validation, snap feedback |
| [`500_nodes`]https://github.com/tuco86/iced_nodegraph/tree/main/demos/500_nodes | [run]https://tuco86.github.io/iced_nodegraph/demo_500_nodes/index.html | 500 nodes / 640 edges stress test with a runtime stats overlay |
| [`shader_editor`]https://github.com/tuco86/iced_nodegraph/tree/main/demos/shader_editor | [run]https://tuco86.github.io/iced_nodegraph/demo_shader_editor/index.html | Visual WGSL editor: the graph compiles to a running shader |

```bash
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`](https://github.com/tuco86/iced_nodegraph/tree/main/iced_nodegraph_sdf)
crate. A compute-shader tile index culls per pixel, and animated stroke
patterns drive their own redraws. Details in
[ARCHITECTURE.md](https://github.com/tuco86/iced_nodegraph/blob/main/iced_nodegraph_sdf/ARCHITECTURE.md).

**Typed coordinates.** Screen space and world space are distinct
[`euclid`](https://docs.rs/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]https://github.com/tuco86/iced_nodegraph/blob/main/CHANGELOG.md.

## Development

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

```bash
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](https://github.com/tuco86/iced_nodegraph/blob/main/AGENTS.md) has the
full pre-push list, including the ignored orbit sweep and the workspace, bench
and wasm checks.

## License

[MIT](https://github.com/tuco86/iced_nodegraph/blob/main/LICENSE)