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
2
3require imgui/imgui_harness
4require imgui/imgui_node_editor_boost_v2
5require imgui/imgui_node_editor_live
6
7// =============================================================================
8// TUTORIAL: create_by_drag — drag from a pin into EMPTY canvas to create + connect.
9//
10// begin_create(ed) {
11// if (query_new_link(ed, a, b)) { ... accept_new_item ... } -- pin -> pin
12// if (show_new_node_drag(ed, from_pin, drop_pos)) { -- pin -> empty
13// drag_fired = true -- (remember source + pos)
14// }
15// }
16// with_suspended() { -- popups in screen space
17// if (drag_fired) open_popup("...")
18// popup_window(...) { ... menu_label(...) -> spawn + enqueue_new_link ... }
19// }
20//
21// connect_by_drag taught the pin -> pin gesture. Releasing the SAME drag in empty
22// canvas instead is the "create + connect" gesture: show_new_node_drag hands back the
23// source pin + the canvas-space drop point, the app opens its create-node menu, and on
24// a pick it spawns at the drop point and enqueue_new_link's the source to the new pin.
25// The enqueued link replays through begin_create's query_new_link a frame later — the
26// same path a hand-dragged link takes — so commit_link stores it once.
27//
28// STANDALONE: daslang.exe modules/dasImguiNodeEditor/examples/tutorial/create_by_drag.das
29// LIVE: daslang-live modules/dasImguiNodeEditor/examples/tutorial/create_by_drag.das
30// =============================================================================
31
32struct Nd {
33 id : int
34 title : string
35 in_pin : int
36 out_pin : int
37 pos : float2
38}
39
40struct Lk {
41 id : int
42 from_pin : int
43 to_pin : int
44}
45
46var g_nodes : table<int; Nd>
47var g_links : table<int; Lk>
48var g_ed : imgui_node_editor::EditorContext? = null
49var g_seeded : bool = false
50var g_next_id : int = 200 // ids for drag-created nodes (pins follow: in = id+1, out = id+2)
51var g_next_link : int = 100
52var g_drag_pin : int = 0 // source pin of a pin->empty drag (0 = none pending)
53var g_drop_pos : float2 // canvas pos where the drag released
54
55var NODE_TITLE : table<int; NarrativeState> // per-id title slot (data-driven node idiom)
56
57def seed() {
58 // One source node with a single output pin to drag from.
59 g_nodes[1] = Nd(id = 1, title = "Source", in_pin = 0, out_pin = 11, pos = float2(140.0, 230.0))
60}
61
62def commit_link(from_out : int; to_in : int) {
63 let lid = g_next_link
64 g_next_link ++
65 g_links[lid] = Lk(id = lid, from_pin = from_out, to_pin = to_in)
66}
67
68def spawn_node(kind : string; pos : float2) : int {
69 // Each drag-created node is a downstream sink: one input pin (id+1), one output (id+2).
70 let nid = g_next_id
71 g_next_id += 10
72 g_nodes[nid] = Nd(id = nid, title = kind, in_pin = nid + 1, out_pin = nid + 2, pos = pos)
73 imgui_node_editor::SetNodePosition(nid, pos) // inside the editor block -> raw SetNodePosition
74 return nid
75}
76
77def spawn_and_connect(kind : string) {
78 let nid = spawn_node(kind, g_drop_pos)
79 // Auto-connect: enqueue source -> new input. The link replays through begin_create's
80 // query_new_link next frame (commit_link stores it) — never added to g_links directly.
81 if (g_drag_pin != 0) {
82 enqueue_new_link(g_ed, g_drag_pin, g_nodes[nid].in_pin)
83 g_drag_pin = 0
84 }
85}
86
87def draw_editor() {
88 node_editor("graph", (editor = g_ed)) {
89 if (!g_seeded) {
90 for (n in values(g_nodes)) {
91 imgui_node_editor::SetNodePosition(n.id, n.pos)
92 }
93 g_seeded = true
94 }
95 for (n in values(g_nodes)) {
96 node(n.id) {
97 text(NODE_TITLE[n.id], (text = n.title))
98 if (n.in_pin != 0) {
99 pin(n.in_pin, PinKind.Input) {
100 text("-> in")
101 }
102 }
103 if (n.out_pin != 0) {
104 pin(n.out_pin, PinKind.Output) {
105 text("out ->")
106 }
107 }
108 }
109 }
110 for (l in values(g_links)) {
111 link(l.id, l.from_pin, l.to_pin)
112 }
113 // The create scope serves both gestures. Pins arrive output-first (a = output,
114 // b = input) for a live pin->pin drag AND for the enqueue_new_link replay, so the
115 // auto-link from a menu pick lands here too.
116 var drag_fired = false
117 begin_create(g_ed) {
118 var a = 0
119 var b = 0
120 if (query_new_link(g_ed, a, b)) {
121 if (a != 0 && b != 0 && a != b && accept_new_item(g_ed)) {
122 commit_link(a, b)
123 }
124 }
125 if (show_new_node_drag(g_ed, g_drag_pin, g_drop_pos)) {
126 drag_fired = true
127 }
128 }
129 // The create-node menu is plain ImGui (screen space) -> Suspend/Resume island.
130 with_suspended() {
131 if (drag_fired) {
132 open_popup("ne_create")
133 }
134 popup_window(CREATE_MENU, (str_id = "ne_create", flags = ImGuiWindowFlags.None)) {
135 text("Create + connect")
136 if (menu_label(ADD_MUL, (text = "Multiply"))) {
137 spawn_and_connect("Multiply")
138 }
139 if (menu_label(ADD_OUT, (text = "Output"))) {
140 spawn_and_connect("Output")
141 }
142 }
143 }
144 }
145}
146
147[export]
148def init() {
149 harness_init("Create by drag", 1000, 600)
150 g_ed = create_node_editor()
151 seed()
152}
153
154[export]
155def update() {
156 if (!harness_begin_frame()) return
157 harness_new_frame()
158 let io & = unsafe(GetIO())
159 SetNextWindowPos(float2(0.0, 0.0), ImGuiCond.Always)
160 SetNextWindowSize(io.DisplaySize, ImGuiCond.Always)
161 let flags = (ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoResize |
162 ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoScrollbar |
163 ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoSavedSettings |
164 ImGuiWindowFlags.NoBringToFrontOnFocus)
165 window(MAIN_WIN, (text = "Create by drag", closable = false, flags = flags)) {
166 draw_editor()
167 }
168 harness_end_frame()
169}
170
171[export]
172def shutdown() {
173 destroy_node_editor(g_ed)
174 harness_shutdown()
175}
176
177[export]
178def main() {
179 init()
180 while (!exit_requested()) {
181 update()
182 }
183 shutdown()
184}
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.