dasImguiNodeEditor documentation Logo
2.0

Contents

  • dasImguiNodeEditor tutorials
    • First graph
      • Walkthrough
        • EditorContext
        • Nodes and pins
        • Links
    • Connecting by drag
      • Walkthrough
        • The create scope
        • Driving it from a test
    • Deleting & selection
      • Walkthrough
        • The delete scope
        • The cascade
        • Driving the delete
    • Context menus
      • Walkthrough
        • The suspend island
        • Which menu fired
        • Driving it from a test
    • Create by drag
      • Walkthrough
        • The create scope
        • The auto-connect
        • Driving it from a test
    • Clipboard
      • Walkthrough
        • The app owns the clipboard
        • Flag now, act later
        • Driving it from a test
    • Styling
      • Walkthrough
        • Canvas theme
        • Per-node tint
        • Scoped style var + color
        • Pin pivots
        • Background draw list
    • Groups
      • Walkthrough
        • A group is a node
        • Membership is spatial
        • Editor-owned geometry
        • The zoomed-out label
    • Navigation
      • Walkthrough
        • Two view ops and one node move
        • Placement: inside vs outside the block
        • Driving it from a test
  • dasImguiNodeEditor v2.0
    • 1. Boost v2 — the node-editor DSL
      • 1.1. Boost v2 — the node-editor DSL: graph entities, link/delete queues, selection, context menus, shortcuts, styling
        • 1.1.1. Type aliases
        • 1.1.2. Call macros
        • 1.1.3. Editor lifecycle
        • 1.1.4. Node geometry & view
        • 1.1.5. Selection
        • 1.1.6. Link creation
        • 1.1.7. Item deletion
        • 1.1.8. Context menus
        • 1.1.9. Clipboard & shortcuts
        • 1.1.10. Styling
        • 1.1.11. Drawing & hints
        • 1.1.12. Flow animation
    • 2. Canvas theme
      • 2.1. Canvas theme — daslang-styled node-editor colors and style vars
        • 2.1.1. Canvas theme
    • 3. Testing harness
      • 3.1. App harness — with_node_editor_app + with_node_editor_recording_app, thin node-editor delegates over imgui_playwright’s with_imgui_app / with_recording_app
        • 3.1.1. App harness
      • 3.2. Editor playwright — node-editor-aware test layer over imgui_playwright (EditorSession + ne_* helpers)
        • 3.2.1. Structures
        • 3.2.2. Session
        • 3.2.3. Actions
        • 3.2.4. Snapshots & queries
        • 3.2.5. Polling / await
    • 4. Live commands
    • 5. Consumer lint
    • 6. External types
      • 6.1. Node / pin / link ids
      • 6.2. imgui_node_editor::EditorContext
      • 6.3. imgui_node_editor::PinKind
      • 6.4. imgui_node_editor::FlowDirection
      • 6.5. imgui_node_editor::StyleColor
      • 6.6. imgui_node_editor::StyleVar
      • 6.7. imgui::ImDrawList
      • 6.8. imgui_playwright::ImguiApp
dasImguiNodeEditor documentation
  • dasImguiNodeEditor tutorials
  • Navigation
  • Edit on GitHub

Navigation

The editor separates the graph (node positions, in canvas space) from the view (the pan + zoom that maps canvas to screen). Two of these ops change only the view — fit the whole graph, or frame the selection. The third, center_node_on_screen, is a footgun: despite the name it moves the node to the view center.

if (button(FIT_ALL, (text = "Fit All"))) {
    g_fit = true                                  // serviced inside the editor block
}
if (button(FRAME_SEL, (text = "Frame Selection"))) {
    navigate_to_selection(g_ed, false, -1.0)      // bracketed wrapper → safe out here
}
if (button(CENTER_FIRST, (text = "Center #1"))) {
    center_node_on_screen(g_ed, 1)
}

node_editor("graph", (editor = g_ed)) {
    ...
    if (g_fit) {
        imgui_node_editor::NavigateToContent(0.0)  // must run with the editor current
        g_fit = false
    }
}

Source: examples/tutorial/navigation.das.

Walkthrough

Your browser doesn't support HTML5 video. Download the recording.
  1options gen2
  2
  3require imgui/imgui_harness
  4require imgui/imgui_node_editor_boost_v2
  5require imgui/imgui_node_editor_live
  6
  7// =============================================================================
  8// TUTORIAL: navigation — the view ops, and the one that fools you.
  9//
 10//   NavigateToContent(duration)            -- fit the whole graph to the viewport (VIEW)
 11//   navigate_to_selection(ed, zoomIn, dur) -- frame just the selected nodes        (VIEW)
 12//   center_node_on_screen(ed, nodeId)      -- MOVES the node to the view center    (GRAPH!)
 13//
 14// The editor separates the GRAPH (node positions, in canvas space) from the VIEW
 15// (the pan + zoom that maps canvas to screen). Fit All and Frame Selection change only
 16// the view — no node moves. center_node_on_screen is the footgun: despite the name,
 17// upstream CenterNodeOnScreen MOVES the node to the view center, it does not pan.
 18// Placement also matters: NavigateToContent must run INSIDE the node_editor block with
 19// the editor current, so the app raises a flag and services it there;
 20// navigate_to_selection / center_node_on_screen are bracketed wrappers that set the
 21// editor current themselves, so the toolbar buttons call them directly.
 22//
 23// STANDALONE: daslang.exe modules/dasImguiNodeEditor/examples/tutorial/navigation.das
 24// LIVE:       daslang-live modules/dasImguiNodeEditor/examples/tutorial/navigation.das
 25// =============================================================================
 26
 27var g_ed : imgui_node_editor::EditorContext? = null
 28var g_seeded : bool = false
 29var g_fit : bool = false   // "Fit All" raises this; serviced inside the editor block
 30
 31// Spread wide so a fit visibly zooms out and recenters.
 32struct Nd {
 33    id  : int
 34    pos : float2
 35}
 36let NODES = [
 37    Nd(id = 1, pos = float2(40.0,   40.0)),
 38    Nd(id = 2, pos = float2(560.0,  300.0)),
 39    Nd(id = 3, pos = float2(1180.0, 560.0))
 40]
 41
 42def toolbar() {
 43    if (button(FIT_ALL, (text = "Fit All"))) {
 44        g_fit = true   // NavigateToContent runs inside the block (editor must be current)
 45    }
 46    same_line()
 47    if (button(FRAME_SEL, (text = "Frame Selection"))) {
 48        navigate_to_selection(g_ed, false, -1.0)   // bracketed wrapper → safe outside the block
 49    }
 50    same_line()
 51    if (button(CENTER_FIRST, (text = "Center #1"))) {
 52        center_node_on_screen(g_ed, 1)   // NB: MOVES node 1 to the view center - not a view op
 53    }
 54}
 55
 56def draw_editor() {
 57    node_editor("graph", (editor = g_ed)) {
 58        if (!g_seeded) {
 59            for (n in NODES) {
 60                imgui_node_editor::SetNodePosition(n.id, n.pos)
 61            }
 62            g_seeded = true
 63        }
 64        for (n in NODES) {
 65            node(n.id) {
 66                text("Node {n.id}")
 67                pin(n.id * 10 + 1, (kind = PinKind.Output, pivot_alignment = float2(1.0, 0.5))) {
 68                    text("out ->")
 69                }
 70            }
 71        }
 72        // Service "Fit All" here, with the editor current. NavigateToContent(0) snaps
 73        // instantly; pass a duration (seconds) for an animated fly-to instead.
 74        if (g_fit) {
 75            imgui_node_editor::NavigateToContent(0.0)
 76            g_fit = false
 77        }
 78    }
 79}
 80
 81[export]
 82def init() {
 83    harness_init("Navigation", 1000, 600)
 84    g_ed = create_node_editor()
 85}
 86
 87[export]
 88def update() {
 89    if (!harness_begin_frame()) return
 90    harness_new_frame()
 91    let io & = unsafe(GetIO())
 92    SetNextWindowPos(float2(0.0, 0.0), ImGuiCond.Always)
 93    SetNextWindowSize(io.DisplaySize, ImGuiCond.Always)
 94    let flags = (ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoResize |
 95                 ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoScrollbar |
 96                 ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoSavedSettings |
 97                 ImGuiWindowFlags.NoBringToFrontOnFocus)
 98    window(MAIN_WIN, (text = "Navigation", closable = false, flags = flags)) {
 99        toolbar()
100        draw_editor()
101    }
102    harness_end_frame()
103}
104
105[export]
106def shutdown() {
107    destroy_node_editor(g_ed)
108    harness_shutdown()
109}
110
111[export]
112def main() {
113    init()
114    while (!exit_requested()) {
115        update()
116    }
117    shutdown()
118}

Two view ops and one node move

  • NavigateToContent(duration) — view: fit the whole graph to the viewport. 0.0 snaps instantly; a positive duration (seconds) animates a fly-to.

  • navigate_to_selection(ed, zoomIn, duration) — view: frame just the selected nodes.

  • center_node_on_screen(ed, nodeId) — moves the node, not the view. Despite the name, upstream CenterNodeOnScreen translates the node’s bounds to the view center (and marks a user position change). Reach for it to recall a stray node, not to navigate.

Placement: inside vs outside the block

NavigateToContent must run with the editor current — i.e. inside the node_editor block. So the “Fit All” button raises a flag and the block services it, the same flag-then-act pattern the context menus use for deferred work. navigate_to_selection and center_node_on_screen are bracketed wrappers that set the editor current themselves, so the toolbar buttons — which run outside the block — call them directly.

Driving it from a test

A view op shifts a node’s screen-space bbox while its canvas position holds; center_node_on_screen is the mirror image — it leaves the view alone and changes the canvas position. The recording (record_navigation.das) and the headless regression (test_navigation.das) exploit both: they assert Fit All and Frame Selection move the screen bbox but leave the canvas bbox put (record_check_changed + record_check_unchanged), then assert Center #1 moves the canvas bbox — proof it relocates the node, not the view. The recording app holds set_user_control(false) so the real OS cursor can’t race the synth and eat a click.

Previous Next

© Copyright 2026-2026, Gaijin Entertainment.

Built with Sphinx using a theme provided by Read the Docs.