Create by drag
The connect by drag tutorial dragged from a pin
onto another pin to make a link. Release that same drag in empty canvas instead and
the editor offers to create a node there, pre-wired to the pin you started from. The
create scope reports it through show_new_node_drag: it hands back the source pin and
the canvas-space drop point, the app opens its create-node menu, and on a pick it spawns
the node at the drop point and enqueue_new_link connects the source to it.
var drag_fired = false
begin_create(g_ed) {
var a = 0
var b = 0
if (query_new_link(g_ed, a, b)) { // pin -> pin: commit a link
if (a != 0 && b != 0 && a != b && accept_new_item(g_ed)) {
commit_link(a, b)
}
}
if (show_new_node_drag(g_ed, g_drag_pin, g_drop_pos)) { // pin -> empty
drag_fired = true // remember source + drop point
}
}
with_suspended() { // the menu is screen-space ImGui
if (drag_fired) {
open_popup("ne_create")
}
popup_window(CREATE_MENU, (str_id = "ne_create")) {
if (menu_label(ADD_MUL, (text = "Multiply"))) {
spawn_and_connect("Multiply") // spawn + enqueue_new_link
}
}
}
Source: examples/tutorial/create_by_drag.das.
Walkthrough
The recording is voiced and self-verifying: a real synthetic pin-drag released
in empty canvas must open the create menu, and the menu pick must spawn the node
and commit the auto-link (a no-op aborts at teardown). It closes by pulsing the
freshly enqueued link with flow() to show the editor-made wire is live.
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: create_by_drag — drag from a pin into EMPTY canvas to create + connect.
10//
11// begin_create(ed) {
12// if (query_new_link(ed, a, b)) { ... accept_new_item ... } -- pin -> pin
13// if (show_new_node_drag(ed, from_pin, drop_pos)) { -- pin -> empty
14// drag_fired = true -- (remember source + pos)
15// }
16// }
17// with_suspended() { -- popups in screen space
18// if (drag_fired) open_popup("...")
19// popup_window(...) { ... menu_label(...) -> spawn + enqueue_new_link ... }
20// }
21//
22// connect_by_drag taught the pin -> pin gesture. Releasing the SAME drag in empty
23// canvas instead is the "create + connect" gesture: show_new_node_drag hands back the
24// source pin + the canvas-space drop point, the app opens its create-node menu, and on
25// a pick it spawns at the drop point and enqueue_new_link's the source to the new pin.
26// The enqueued link replays through begin_create's query_new_link a frame later — the
27// same path a hand-dragged link takes — so commit_link stores it once.
28//
29// STANDALONE: daslang.exe modules/dasImguiNodeEditor/examples/tutorial/create_by_drag.das
30// LIVE: daslang-live modules/dasImguiNodeEditor/examples/tutorial/create_by_drag.das
31// =============================================================================
32
33struct Nd {
34 id : int
35 title : string
36 in_pin : int
37 out_pin : int
38 pos : float2
39}
40
41struct Lk {
42 id : int
43 from_pin : int
44 to_pin : int
45}
46
47var g_nodes : table<int; Nd>
48var g_links : table<int; Lk>
49var g_ed : imgui_node_editor::EditorContext? = null
50var g_seeded : bool = false
51var g_next_id : int = 200 // ids for drag-created nodes (pins follow: in = id+1, out = id+2)
52var g_next_link : int = 100
53var g_drag_pin : int = 0 // source pin of a pin->empty drag (0 = none pending)
54var g_drop_pos : float2 // canvas pos where the drag released
55
56var NODE_TITLE : table<int; NarrativeState> // per-id title slot (data-driven node idiom)
57
58def seed() {
59 // One source node with a single output pin to drag from.
60 g_nodes[1] = Nd(id = 1, title = "Source", in_pin = 0, out_pin = 11, pos = float2(140.0, 230.0))
61}
62
63def commit_link(from_out : int; to_in : int) {
64 let lid = g_next_link
65 g_next_link ++
66 g_links[lid] = Lk(id = lid, from_pin = from_out, to_pin = to_in)
67}
68
69def spawn_node(kind : string; pos : float2) : int {
70 // Each drag-created node is a downstream sink: one input pin (id+1), one output (id+2).
71 let nid = g_next_id
72 g_next_id += 10
73 g_nodes[nid] = Nd(id = nid, title = kind, in_pin = nid + 1, out_pin = nid + 2, pos = pos)
74 imgui_node_editor::SetNodePosition(nid, pos) // inside the editor block -> raw SetNodePosition
75 return nid
76}
77
78def spawn_and_connect(kind : string) {
79 let nid = spawn_node(kind, g_drop_pos)
80 // Auto-connect: enqueue source -> new input. The link replays through begin_create's
81 // query_new_link next frame (commit_link stores it) — never added to g_links directly.
82 if (g_drag_pin != 0) {
83 enqueue_new_link(g_ed, g_drag_pin, g_nodes[nid].in_pin)
84 g_drag_pin = 0
85 }
86}
87
88def draw_editor() {
89 node_editor("graph", (editor = g_ed)) {
90 if (!g_seeded) {
91 for (n in values(g_nodes)) {
92 imgui_node_editor::SetNodePosition(n.id, n.pos)
93 }
94 g_seeded = true
95 }
96 for (n in values(g_nodes)) {
97 node(n.id) {
98 text(NODE_TITLE[n.id], (text = n.title))
99 if (n.in_pin != 0) {
100 pin(n.in_pin, PinKind.Input) {
101 text("-> in")
102 }
103 }
104 if (n.out_pin != 0) {
105 pin(n.out_pin, PinKind.Output) {
106 text("out ->")
107 }
108 }
109 }
110 }
111 for (l in values(g_links)) {
112 link(l.id, l.from_pin, l.to_pin)
113 }
114 // The create scope serves both gestures. Pins arrive output-first (a = output,
115 // b = input) for a live pin->pin drag AND for the enqueue_new_link replay, so the
116 // auto-link from a menu pick lands here too.
117 var drag_fired = false
118 begin_create(g_ed) {
119 var a = 0
120 var b = 0
121 if (query_new_link(g_ed, a, b)) {
122 if (a != 0 && b != 0 && a != b && accept_new_item(g_ed)) {
123 commit_link(a, b)
124 }
125 }
126 if (show_new_node_drag(g_ed, g_drag_pin, g_drop_pos)) {
127 drag_fired = true
128 }
129 }
130 // The create-node menu is plain ImGui (screen space) -> Suspend/Resume island.
131 with_suspended() {
132 if (drag_fired) {
133 open_popup("ne_create")
134 }
135 popup_window(CREATE_MENU, (str_id = "ne_create", flags = ImGuiWindowFlags.None)) {
136 text("Create + connect")
137 if (menu_label(ADD_MUL, (text = "Multiply"))) {
138 spawn_and_connect("Multiply")
139 }
140 if (menu_label(ADD_OUT, (text = "Output"))) {
141 spawn_and_connect("Output")
142 }
143 }
144 }
145 }
146}
147
148[export]
149def init() {
150 harness_init("Create by drag", 1000, 600)
151 g_ed = create_node_editor()
152 seed()
153}
154
155[export]
156def update() {
157 if (!harness_begin_frame()) return
158 harness_new_frame()
159 let io & = unsafe(GetIO())
160 SetNextWindowPos(float2(0.0, 0.0), ImGuiCond.Always)
161 SetNextWindowSize(io.DisplaySize, ImGuiCond.Always)
162 let flags = (ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoResize |
163 ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoScrollbar |
164 ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoSavedSettings |
165 ImGuiWindowFlags.NoBringToFrontOnFocus)
166 window(MAIN_WIN, (text = "Create by drag", closable = false, flags = flags)) {
167 draw_editor()
168 }
169 harness_end_frame()
170}
171
172[export]
173def shutdown() {
174 destroy_node_editor(g_ed)
175 harness_shutdown()
176}
177
178[export]
179def main() {
180 init()
181 while (!exit_requested()) {
182 update()
183 }
184 shutdown()
185}
The create scope
begin_create(ed) { ... } is the same scope that commits a hand-dragged link, and it
serves both gestures of a pin-drag:
query_new_link(ed, a, b)reports the pins when the drag is released on a pin — commit a link, exactly as in connect by drag.show_new_node_drag(ed, from_pin, drop_pos)is true the frame the drag is released in empty canvas. It hands back the source pin and the canvas-space drop point, and is an event (one frame), not a scope — open the create-node UI in response.
The popup is plain ImGui, so it lives in a with_suspended island (screen space) just
like the context menus.
The auto-connect
A menu pick spawns the node at the drop point and calls
enqueue_new_link(ed, source_pin, new_input_pin). That queued link is not added to
the graph directly — it replays through begin_create’s query_new_link on the next
frame, the same path a mouse-dragged link takes, so commit_link stores it once. The
app gets one code path for “a link appeared”, whether the user dragged it or the editor
created it.
Driving it from a test
The recording is a real synthetic pin-drag (see tests/integration/record_create_by_drag.das):
press on the output pin, travel to an empty point, release. Because the tutorial’s pins
render a real screen-space bbox, the drag targets the pin center directly — a genuine
gesture, not an injected one. (shader_graph’s pins have no queryable bbox, so its
test_new_node_drag reaches for the ne_new_node_drag injection rail instead.) The
menu pick is then an ordinary click resolved from the item’s bbox.
set_user_control(false) hands IO to the synthetic timeline so the real OS cursor can’t
race the synth and eat the drag or the menu click.