Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

GraphView · GraphCtx · GraphResponse

Layer: graph · Path: src/graph/canvas.rs · Exports: GraphView, GraphCtx<'a>, GraphResponse

The single public entry point to the node-editor. GraphView is a builder; its show method allocates the canvas, runs an [egui::Scene] (which owns pan/zoom and scales the real DS widgets uniformly, n8n-style), opens a per-frame emit scope, and returns a GraphResponse of intents. The caller describes its nodes and edges inside the show closure every frame using GraphCtx; the library owns only the view-state (GraphViewState), the caller owns the data. Everything is drawn in scene (world) coordinates inside Scene’s transformed sublayer.

Design

  • Purpose / when to use. Any reactflow-style node graph: pipeline editors, behaviour trees, dataflow. Use it whenever the caller has node + edge data it owns and wants pan/zoom, selection, drag-move, resize, connect-by-drag, delete, fit, minimap, and node-search for free.
  • Frame lifecycle. show runs once per frame: resolve GraphTokens → allocate rect + paint surface/border → load GraphViewState from egui memory (keyed by the builder id) → Scene::show against &mut state.scene_rect → paint grid (culled when on-screen dot spacing < grid::MIN_DOT_SPACING) → reserve an under-node edge layer via Painter::add(Shape::Noop) → build the GraphCtx and run the caller’s closure → resolve any completed connect-drag against recorded handle positions → flush accumulated edge shapes into the reserved slot (always under nodes, regardless of caller interleaving) → draw the pending connect-wire on top → apply selection / delete / controls / minimap → persist state → return GraphResponse.
  • Coordinate convention. A node emitted at world pos lands there; Scene scales it. GraphCtx::scale is the live scene→screen factor; to_global is the TSTransform.
  • Zoom range. Canvas Scene zoom_range is MIN_ZOOM = 0.2 .. MAX_ZOOM = 4.0 (private consts). This is the live range — note it is not the Viewport helper’s 0.25..2.5; the canvas does not use Viewport.
  • Pan binding. Scene pan is bound to DragPanButtons::MIDDLE | SECONDARY only; primary drag is reserved for node move / marquee / connect so it never double-moves against Scene’s background pan.

GraphResponse fields (every field)

FieldTypeMeaning
responseegui::ResponseScene background interaction (pan response). Use for focus / context-menu hooks.
connectionOption<Connection>A connect-drag completed onto a valid target port. Always oriented Out → In.
delete_edgeOption<(Port, Port)>Selected edge + Delete/Backspace. Deleted before nodes.
delete_nodesVec<NodeId>Selected nodes + Delete/Backspace (only when no edge was selected).
edge_clickedOption<(Port, Port)>An edge was clicked this frame.
node_movedVec<(NodeId, Vec2)>World-space move deltas to apply (caller owns positions).
node_resizedVec<(NodeId, Vec2)>World-space size deltas from the node resizer.
create_requestOption<(NodeKindId, Pos2)>“Create a node of this kind at this world position” (from node search).
selectionHashSet<NodeId>Current selection, mirrored out (e.g. to drive per-node toolbars next frame).
fit_requestedboolThe user hit the controls’ fit-to-content button this frame.

Tokens consumed

show resolves a GraphTokens and threads it through the paint helpers (grid, edge, handle). Canvas chrome uses Theme directly: theme.background fill, theme.border stroke at core::BORDER_THIN, corner radius core::RADIUS_LG. Fit padding is core::SPACE_8.

API

GraphView (builder)

MethodSignatureEffect
newfn new(id_source: impl Hash) -> SelfConstruct with a stable id; the view-state is keyed by it.
sizefn size(self, size: Vec2) -> SelfExplicit canvas size. Default: full available width × 420.0.
gridfn grid(self, on: bool) -> SelfToggle the dot grid (default true).
controlsfn controls(self, on: bool) -> SelfToggle the floating zoom/fit overlay (default false).
minimapfn minimap(self, on: bool) -> SelfToggle the minimap overlay (default false).
showfn show(self, ui: &mut egui::Ui, build: impl FnOnce(&mut GraphCtx)) -> GraphResponseRun the canvas and return intents.

GraphCtx<'a> (per-frame emit surface)

Public methods. Node/edge/toolbar emit methods (node, edge, node_toolbar) are added by other modules via separate impl GraphCtx blocks (node.rs, edge.rs, toolbar.rs) — see layer README. All fields are crate-private; interact only through these methods.

MethodSignatureEffect
scalefn scale(&self) -> f32The scene→screen scale (current zoom factor).
visible_rectfn visible_rect(&self) -> RectThe visible region in scene (world) coordinates.
tokensfn tokens(&self) -> GraphTokensThe resolved graph paint tokens.
screen_delta_to_worldfn screen_delta_to_world(&self, delta: Vec2) -> Vec2Convert a screen-space delta (e.g. a Response::drag_delta) to a world delta.
screen_to_worldfn screen_to_world(&self, screen: Pos2) -> Pos2Convert a global screen point to a scene (world) point.

Emit methods (documented under their own pages):

MethodSignature
nodefn node(&mut self, id: NodeId, world_pos: Pos2, frame: NodeFrame, body: impl FnOnce(&mut egui::Ui)) -> NodeResult
edgefn edge(&mut self, from: Port, to: Port, style: EdgeStyle) -> EdgeResult
node_toolbarfn node_toolbar(&mut self, node: NodeId, content: impl FnOnce(&mut egui::Ui))

GraphResponse

#[derive(Clone, Debug)]. All fields public; see the field table above. Defaults to “nothing happened” (empty/None).

Usage

#![allow(unused)]
fn main() {
use ouroboros_ui::graph::{GraphView, GraphCtx, GraphResponse};
use ouroboros_ui::graph::{NodeId, PortId, Port, PortSide, EdgeStyle, NodeFrame};

// Caller owns nodes + edges; lib owns only view-state.
let resp: GraphResponse = GraphView::new("my_graph")
    .size(egui::vec2(720.0, 420.0))
    .grid(true)
    .controls(true)
    .minimap(true)
    .show(ui, |g: &mut GraphCtx| {
        // Nodes first, so their handle positions are recorded before edges resolve.
        for (id, pos, label) in &nodes {
            let frame = NodeFrame::base().title(label.clone()).input(0).output(1);
            g.node(NodeId(*id), *pos, frame, |ui| {
                Text::new("body content").muted().show(ui);
            });
        }
        // Edges after nodes (anchored on handles, drawn under the nodes).
        for (from, to) in &edges {
            g.edge(
                Port { node: NodeId(*from), port: PortId(1), side: PortSide::Out },
                Port { node: NodeId(*to),   port: PortId(0), side: PortSide::In  },
                EdgeStyle::Default,
            );
        }
    });

// Commit the intents back into caller-owned data.
for (moved_id, delta) in &resp.node_moved {
    if let Some(pos) = positions.get_mut(moved_id) { *pos += *delta; }
}
if let Some(c) = resp.connection { edges.push((c.from.node.0, c.to.node.0)); }
if let Some((from, to)) = resp.delete_edge { /* drop edge */ }
for n in &resp.delete_nodes { /* drop node + its edges */ }
}

Composition / Notes

  • Emit order matters. Emit nodes before edges — edge anchors on handle positions recorded by node, and returns a default (no-op) EdgeResult if either endpoint hasn’t been emitted yet. Within a frame the paint order is fixed regardless of caller interleaving: edges flush into a reserved slot under the nodes.
  • Ownership. The lib persists exactly one value per GraphView id in egui temp memory — GraphViewState (camera + selection + transient drag state). All node/edge data is the caller’s; every mutation is reported as an intent in GraphResponse, never applied to caller data by the lib.
  • Connect resolution. A released connect-drag is resolved at scope end: first a precise handle hit within tokens.handle_hit_radius, else the nearest compatible (opposite-side) port of whatever node body the release landed in. Output is oriented Out → In.
  • Paint tier vs compose tier. The canvas itself is the paint shell (surface, grid, edge layer, connect-wire) plus the compose overlays (controls, minimap) it drives from GraphView flags. node / edge / node_toolbar are compose-tier and live in sibling modules.
  • Identity. NodeId, PortId, Port, PortSide, Connection, NodeKindId — see identity.
  • Foundation: architecture · tokens · theming · guards.