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
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.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.