A high-performance, extensible node-based editor built with Rust and gpui. Designed for building visual programming tools, workflow editors, and graph-based UIs.
This project is in early stage (alpha), API may change
- Plugin-based architecture
- Interaction system (drag, pan, select, etc.)
- Undo / Redo (Command pattern)
- Viewport control (zoom & pan)
- Box selection & multi-select
- Node / Port / Edge model
- Custom node rendering system
- Built with performance in mind
- Multi-user collaboration support (by plugin)
2026-04-05.11.08.00.mov
cargo add ferrum-flowThe system is designed with clear separation of concerns:
-
Graph Stores persistent data (nodes, edges, ports)
-
Viewport Handles zooming and panning
-
Plugin System Extends behavior (rendering, input handling, etc.)
-
Interaction System Manages ongoing user interactions (dragging, selecting, etc.)
-
Command System Enables undo/redo support
Plugins are the primary extension mechanism:
pub trait Plugin {
fn name(&self) -> &'static str;
fn setup(&mut self, ctx: &mut InitPluginContext);
fn on_event(&mut self, event: &FlowEvent, ctx: &mut PluginContext) -> EventResult;
fn render(&mut self, ctx: &mut RenderContext) -> Option<AnyElement>;
fn priority(&self) -> i32 {
0
}
fn render_layer(&self) -> RenderLayer {
RenderLayer::Overlay
}
}Responsibilities
A plugin can:
- Handle input events
- Start interactions
- Render UI layers
- Modify graph state
Interactions represent ongoing user actions, such as:
- Node dragging
- Box selection
- Viewport panning
pub trait Interaction {
fn on_mouse_move(&mut self, event: &MouseMoveEvent, ctx: &mut PluginContext) -> InteractionResult;
fn on_mouse_up(&mut self, event: &MouseUpEvent, ctx: &mut PluginContext) -> InteractionResult;
fn render(&self, ctx: &mut RenderContext) -> Option<AnyElement>;
}Interaction Lifecycle
Start → Update → End / Replace
pub enum InteractionResult {
Continue,
End,
Replace(Box<dyn Interaction>),
}Implements the Command Pattern:
pub trait Command {
fn execute(&mut self, ctx: &mut CommandContext);
fn undo(&mut self, ctx: &mut CommandContext);
}Built-in Features
- Undo / Redo stacks
- Composite commands
- Easy integration via PluginContext
ctx.execute_command(MyCommand { ... });Rendering is fully customizable via a registry:
pub trait NodeRenderer {
fn render(&self, node: &Node, ctx: &mut RenderContext) -> AnyElement;
// custom render port UI
fn port_render(&self, node: &Node, port: &Port, ctx: &mut RenderContext) -> Option<AnyElement> {
// ... default implement
}
// computing the position of port relative to node
fn port_offset(&self, node: &Node, port: &Port, graph: &Graph) -> Point<Pixels> {
// ... default implement
}
}Render example:
div()
.absolute()
.left(x)
.top(y)
.w(width)
.h(height)
.bg(white())pub struct Node {
pub id: NodeId,
pub node_type: String,
pub x: Pixels,
pub y: Pixels,
pub size: Size<Pixels>,
pub inputs: Vec<PortId>,
pub outputs: Vec<PortId>,
pub data: serde_json::Value,
}Creating Nodes (Builder API)
graph.create_node("math.add")
.position(100.0, 100.0)
.input()
.output()
.build(&mut graph);Designed to scale to large graphs:
- Viewport-based rendering (virtualization)
- Layered rendering system
- Interaction-aware rendering (degraded mode during drag)
- Ready for spatial indexing
- Separation of data and interaction
- Plugins over hardcoded behavior
- Explicit state transitions
- Performance-first rendering
- Composable architecture
We want an explicit, maintained view of supported vs partial vs missing relative to React Flow’s documented capabilities (nodes, edges, handles, selection, keyboard, minimap, controls, snapping, grouping/subflows, accessibility, etc.):
- Audit — Walk the React Flow feature list and map each item to FerrumFlow (plugin, core graph, or N/A by design).
- Gap list — For every row, mark done, partial, missing, or different by design (short rationale).
- Surface in this README — Add a compact table or bullet matrix here (or link to
docs/react-flow-parity.mdif it grows large).
Contributions welcome: propose a matrix in an issue or open a PR that extends this section.
Foundation work that unlocks most other extensions:
- Separate data, logic, and rendering — Clear boundaries between graph/state, interaction and commands, and GPUI (or future) paint so features can grow without entangling layers.
- Large-graph performance — Investigate arena-style allocation for nodes/edges and ID-based references instead of pointer-heavy graphs to improve locality and scale.
No active schedule; keep these in mind when designing APIs above.
- Abstract / pluggable render backend — Allow FerrumFlow to sit inside existing render systems (similar in spirit to graph editors embedded in tools like Blender or Unreal: the host owns the surface; the library supplies model + interaction contracts).
- WASM target — When the stack allows, support Web deployment paths.
- Predictive rendering for collaboration — Reduce felt latency in multi-user editing (optimistic / speculative UI reconciled with synced state, e.g. CRDT-backed updates).
Contributions are welcome!
Feel free to open issues or PRs for:
- New plugins
- Performance improvements
- API design suggestions
Apache2.0