Skip to main content

Crate iced_nodegraph

Crate iced_nodegraph 

Source
Expand description

§iced_nodegraph

A high-performance node graph editor widget for the Iced GUI framework, featuring SDF-based rendering and type-safe coordinate transformations.

§Features

  • Nodes - Draggable containers for your custom widgets
  • Pins - Connection points on nodes with type checking and visual feedback
  • Edges - Connect pins to build data flow graphs with type-safe PinRef
  • Interactive Connections - Drag to connect, click edges to re-route (cable-like unplugging)
  • Selection - Multi-select with box selection, clone (Ctrl+D), delete (Delete key)
  • Zoom & Pan - Smooth infinite canvas navigation with Camera2D
  • SDF Rendering - High-performance visualization via signed-distance fields (iced_nodegraph_sdf)
  • Spatial Index - A GPU tile index culls geometry per pixel, scaling to large graphs
  • Pin Feedback - Valid drop targets pulse while dragging an edge
  • Theme Support - Integrates with Iced’s theming system

§Quick Start

use iced_nodegraph::{NodeGraph, PinRef, edge, node, node_graph};
use iced::{Element, Theme, Point, Vector};
use iced::widget::text;
use iced_wgpu::Renderer;

#[derive(Debug, Clone)]
enum Message {
    EdgeConnected { from: PinRef<usize, usize>, to: PinRef<usize, usize> },
    NodesMoved { delta: Vector, node_ids: Vec<usize> },
}

fn view(edges: &[(PinRef<usize, usize>, PinRef<usize, usize>)]) -> Element<'_, Message, Theme, Renderer> {
    let mut ng = node_graph()
        .on_connect(|from, to| Message::EdgeConnected { from, to })
        .on_move(|delta, node_ids| Message::NodesMoved { delta, node_ids });

    // Add nodes with IDs
    ng.push_node(node(0, Point::new(100.0, 100.0), text("Node A")));
    ng.push_node(node(1, Point::new(300.0, 100.0), text("Node B")));

    // Add edges with type-safe PinRef; edge! defaults the id to ()
    for (from, to) in edges {
        ng.push_edge(edge!(*from, *to));
    }

    ng.into()
}

§Core Types

§PinRef

Type-safe identifier for a pin connection, generic over your node/pin id types:

use iced_nodegraph::PinRef;

let pin = PinRef::new(0, 1); // node 0, pin 1
assert_eq!(pin.node_id, 0);
assert_eq!(pin.pin_id, 1);

§Camera2D

Programmatic access to zoom and pan state.

§Styling

Node, edge and pin styles are concrete flat structs. Override fields with struct-update over a theme-derived default inside a .style() closure:

node(0, pos, body).style(|theme, status| NodeStyle {
    fill_color: ColorQuad::solid(Color::from_rgb(0.2, 0.3, 0.5)),
    ..default_node_style(theme, status)
});

§Ready-made presets

Reach for these before hand-rolling a look:

edge!(from, to).style(|_theme, _status, _from, _to| EdgeStyle::error());

§Stroke patterns

Pattern (re-exported from iced_nodegraph_sdf) controls every stroke: Pattern::solid(width), Pattern::dashed(width, dash, gap), Pattern::dotted(spacing, radius), plus .flow(speed) to animate it along the stroke. An animated pattern self-drives redraws - no host frame loop needed.

§Per-node status

The style closure intentionally does not receive the node id: your view loop already has it (and any per-node status). Derive the status there and pass it in; a shared function keeps it DRY across nodes:

for n in &self.nodes {
    let working = self.is_working(n.id);
    ng.push_node(node(n.id, n.pos, body).style(move |theme, status| {
        let base = default_node_style(theme, status);
        if working {
            NodeStyle { border_pattern: Pattern::dashed(2.0, 6.0, 4.0).flow(40.0), ..base }
        } else {
            base
        }
    }));
}

§Demonstration Projects

§hello_world

Basic node graph with command palette:

  • Node creation and positioning
  • Pin connections with type colors
  • Camera controls (pan/zoom)
  • Theme switching with live preview
  • Email processing workflow example
cargo run -p demo_hello_world

§styling

Visual customization showcase:

  • Custom node styles (colors, borders, opacity)
  • Live style controls with sliders
  • Preset styles (Input, Process, Output, Comment)
  • Theme switching
cargo run -p demo_styling

§500_nodes

Performance benchmark with 500+ nodes:

  • Procedural shader graph generation
  • Stress tests GPU rendering pipeline
  • Multiple node types and connection patterns
cargo run -p demo_500_nodes

§shader_editor

Visual WGSL shader editor:

  • Math, Vector, Color, Texture nodes
  • Real-time shader compilation
  • Command palette for node spawning
cargo run -p demo_shader_editor

§Platform Support

§Native (Windows, macOS, Linux)

Full WGPU rendering via signed-distance fields (iced_nodegraph_sdf).

§WebAssembly (Browser)

WebGPU only - there is no WebGL fallback. Chrome/Chromium is recommended.

§Architecture

§Coordinate System

The widget uses two distinct coordinate spaces with compile-time type safety via the euclid crate:

  • Screen Space - Pixel coordinates from user input (mouse, viewport)
  • World Space - Virtual infinite canvas where nodes exist

Transformations use mathematically consistent formulas:

  • Screen -> World: world = screen / zoom - position
  • World -> Screen: screen = (world + position) * zoom
  • Zoom at Cursor: new_pos = old_pos + cursor_screen * (1/new_zoom - 1/old_zoom)

See Camera2D for implementation details and comprehensive test coverage.

§SDF Rendering

Nodes, edges, pins and overlays are drawn with signed-distance fields via the in-tree iced_nodegraph_sdf crate - there are no hand-written vertex or fragment shaders in this crate:

  • Layered compositing for correct draw order (shadows, edges, node bodies)
  • A GPU tile index culls geometry per pixel, scaling to large graphs
  • Cross-platform (native WGPU + WebGPU)

§Interaction

ActionInput
Pan canvasRight mouse drag
ZoomMouse wheel (maintains cursor position)
Connect pinsLeft-click source pin, drag to target
Re-route edgeClick on edge endpoint to unplug
Move nodeLeft-click and drag node
Box selectLeft-click on empty space, drag
Clone selectionCtrl+D
Delete selectionDelete key
Add to selectionShift+click

§Plug Behavior

Edge connections behave like physical plugs:

  • Snap: When dragging an edge close to a compatible pin, it “snaps” and EdgeConnected fires immediately (not on mouse release)
  • Unsnap: Moving away from the snapped pin fires EdgeDisconnected
  • Release: Releasing the mouse while snapped keeps the connection; releasing while not snapped discards the drag

§What the host owns

The widget is stateless between frames and never mutates your data model, so a few invariants are yours to enforce:

  • Edge dedupe. on_connect fires on every snap during a drag (not on release), so one drag can report several connections. The default can_connect already rejects a second edge into an occupied input; for replace-on-drop instead, drop that rule (see connection) and remove the prior edge whose input matches in your on_connect handler - to is always the input pin.
  • Unique node ids. Lookups resolve to the first match, so reuse renders a node doubled. Prefer a stable id from your data - a database key, uuid::Uuid, or a typed newtype - over a hand-managed counter (collision-proof, and it survives multi-client collaboration); debug builds assert uniqueness.
  • Applying moves/deletes/clones. on_move / on_delete / on_clone report intent; your model applies it and feeds the result back on the next view.

§Diagnostics

The widget is stateless between frames - the host owns nodes, edges and selection - so there are no node_count() / edges() query methods. For per-frame metrics (element counts total/in-view/culled and CPU op timings), register a callback with NodeGraph::on_info; it delivers a GraphInfo each redraw.

Re-exports§

pub use connection::default_can_connect;
pub use connection::direction_ok;
pub use connection::input_not_occupied;
pub use connection::not_same_node;
pub use content::EdgeRadii;
pub use content::node_header;
pub use ids::EdgeId;
pub use ids::NodeId;
pub use ids::PinId;
pub use style::EdgeCurve;
pub use style::EdgeStatus;
pub use style::EdgeStyle;
pub use style::GraphStyle;
pub use style::NodeStatus;
pub use style::NodeStyle;
pub use style::PinShape;
pub use style::PinStatus;
pub use style::PinStyle;
pub use style::SelectionStyle;
pub use style::TilingBackground;
pub use style::TilingKind;
pub use style::default_edge_style;
pub use style::default_node_style;
pub use style::default_pin_style;
pub use iced;

Modules§

connection
Composable predicates for NodeGraph::can_connect.
content
Rounded header/footer helpers for node interiors.
ids
Generic ID types for user-defined node, pin, and edge identification.
prelude
Common imports for building a node graph view.
style
Style definitions for NodeGraph visual customization.

Macros§

edge
Builds an Edge, defaulting the id to () when omitted.
pin
Macro for creating pins with concise syntax.

Structs§

Camera2D
ColorQuad
Four corner colors: arc-length (start/end) crossed with distance (near/far).
Counts
Counts for one element kind in a frame: how many exist, how many are in view, and how many were culled (off-screen). total == in_view + culled.
Edge
An edge to push onto the graph: a user id, endpoint pin references, and an optional per-edge status-driven style closure. Build with edge + Edge::style, then add via NodeGraph::push_edge. The id is the user’s own (e.g. a DB key); it travels with the edge, symmetric to node.
GraphInfo
Per-frame diagnostics for the graph, delivered to NodeGraph::on_info.
KeyCombo
A key plus the exact modifier state required to trigger it.
Keymap
Host-configurable bindings for graph-level keyboard and pointer actions.
Node
A node to push onto the graph: id, position, content element, an optional per-node style closure, and an optional closure styling all of its pins. Build with node + Node::style/Node::pin_style, then add via NodeGraph::push_node. Looks like its own widget even though the body and pins are drawn by the graph.
NodeGraph
Node graph widget with generic ID types.
NodePin
A transparent wrapper used as a marker within NodeGraph.
OpTiming
One timed slice of the per-frame CPU work, in the order it runs.
Pattern
Pattern configuration for SDF stroke rendering.
PinEnd
Read-only view of one endpoint of a candidate connection, passed to NodeGraph::can_connect. Bundles the pin’s node id, pin id, direction and user payload.
PinInfo
Read-only view of a pin’s semantic info, passed to a node’s pin_style closure so it can style each pin by direction, user info, or id. The pin itself carries no style; the owning node decides how its pins look.
PinRef
Type-safe reference to a pin: a node_id paired with a pin_id, generic over your id types.

Enums§

ComboKey
The logical key half of a KeyCombo.
DragInfo
Identifies what an in-progress drag is moving. Delivered to the on_drag_start callback so the app can observe a drag live (e.g. to broadcast it), alongside the commit-on-drop callbacks.
KeyAction
A graph-level action triggered by a keyboard shortcut.
PinDirection
Direction of data flow for a pin.
PinSide
Which side of a node this pin attaches to. Determines the tangent direction for edge bezier curves.
SdfPatternType
Pattern type for stroke rendering.

Functions§

edge
Creates an Edge with the given id and default (theme) styling.
node
Creates a Node with default (theme) styling.
node_graph
Creates a new NodeGraph with default usize-based IDs and no pin user info.
node_pin