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:
- Nodes:
NodeStyle::input(blue),NodeStyle::process(green),NodeStyle::output(orange). - Edges:
EdgeStyle::error(red animated marching-ants with a border ring),EdgeStyle::disabled(gray dashed),EdgeStyle::highlighted(yellow with a soft ring),EdgeStyle::data_flow(blue),EdgeStyle::debug(dotted cyan straight line).
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
| Action | Input |
|---|---|
| Pan canvas | Right mouse drag |
| Zoom | Mouse wheel (maintains cursor position) |
| Connect pins | Left-click source pin, drag to target |
| Re-route edge | Click on edge endpoint to unplug |
| Move node | Left-click and drag node |
| Box select | Left-click on empty space, drag |
| Clone selection | Ctrl+D |
| Delete selection | Delete key |
| Add to selection | Shift+click |
§Plug Behavior
Edge connections behave like physical plugs:
- Snap: When dragging an edge close to a compatible pin, it “snaps” and
EdgeConnectedfires 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_connectfires on every snap during a drag (not on release), so one drag can report several connections. The defaultcan_connectalready rejects a second edge into an occupied input; for replace-on-drop instead, drop that rule (seeconnection) and remove the prior edge whose input matches in youron_connecthandler -tois 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_clonereport intent; your model applies it and feeds the result back on the nextview.
§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
- Color
Quad - 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 viaNodeGraph::push_edge. The id is the user’s own (e.g. a DB key); it travels with the edge, symmetric tonode. - Graph
Info - 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 viaNodeGraph::push_node. Looks like its own widget even though the body and pins are drawn by the graph. - Node
Graph - 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_styleclosure 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_idpaired with apin_id, generic over your id types.
Enums§
- Combo
Key - The logical key half of a
KeyCombo. - Drag
Info - Identifies what an in-progress drag is moving. Delivered to the
on_drag_startcallback 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.
- SdfPattern
Type - Pattern type for stroke rendering.