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