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 (virtualization-ready)
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
- Edge rendering improvements
- Connection (drag-to-connect)
- Spatial indexing (Quadtree/Grid)
- Interaction priority & conflict resolution
- Collaboration support
Contributions are welcome!
Feel free to open issues or PRs for:
- New plugins
- Performance improvements
- API design suggestions
Apache2.0
