Groups

A group (or comment box) is a node with no pins and an explicit content size — the editor’s tool for visually organizing a large graph. Membership is purely spatial: drag a node inside the group’s bounds and it travels with the group from then on; drag it back out and the group leaves it behind. There is no “add to group” call.

node_group(GROUP_ID, (size = float2(300.0, 280.0))) {   // pin-less node; body = label
    text(GROUP_TITLE, (text = "Inputs"))
}
group_hint(GROUP_ID) $(var fg; var _bg) {               // off-screen label when zoomed out
    let mn = imgui_node_editor::GetGroupMin()
    fg |> add_text(float2(mn.x + 4.0, mn.y - 18.0), rgba(232u, 226u, 210u, 255u), "Inputs")
}

Source: examples/tutorial/groups.das.

Walkthrough

The recording is voiced and self-verifying: Texture starts inside the group, Tint outside. It drags Tint in (a group move then carries both nodes), then drags Tint back out (a group move then leaves it behind) — asserting at each step that the right nodes moved and, crucially, that the outside node stayed put (a no-op aborts at teardown).

  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: groups — a comment box that corrals the nodes dragged onto it.
 10//
 11//   node_group(id, (size = float2(w, h))) {     -- a pin-less node; its body is the label
 12//       text(...)
 13//   }
 14//   group_hint(id) $(var fg; var bg) {           -- off-screen label when zoomed out
 15//       fg |> add_text(GetGroupMin(), color, title)
 16//   }
 17//
 18// A GROUP is just a node with no pins and an explicit content `size`. Drag nodes
 19// over it and they move WITH the group — the editor handles that for free. Draw
 20// groups FIRST so regular nodes paint on top of them. The group is editor-owned
 21// geometry after its first frame, exactly like a node position: seed `size` once,
 22// then `set_group_size` to resize it later (mutating your own struct won't).
 23//
 24// group_hint draws a floating label when the canvas is zoomed out far enough that
 25// the group's own title is too small to read; it self-gates (a no-op at normal
 26// zoom), so the loop is unconditional.
 27//
 28// STANDALONE: daslang.exe modules/dasImguiNodeEditor/examples/tutorial/groups.das
 29// LIVE:       daslang-live modules/dasImguiNodeEditor/examples/tutorial/groups.das
 30// =============================================================================
 31
 32var g_ed : imgui_node_editor::EditorContext? = null
 33var g_seeded : bool = false
 34
 35// node_group(id) and node(id) live in the SAME id space — keep them disjoint.
 36let GROUP_ID = 1
 37let NODE_A   = 10
 38let NODE_B   = 20
 39
 40def draw_editor() {
 41    node_editor("graph", (editor = g_ed)) {
 42        if (!g_seeded) {
 43            imgui_node_editor::SetNodePosition(GROUP_ID, float2(60.0, 80.0))
 44            imgui_node_editor::SetNodePosition(NODE_A, float2(130.0, 170.0))   // INSIDE the group
 45            imgui_node_editor::SetNodePosition(NODE_B, float2(560.0, 200.0))   // OUTSIDE the group
 46            g_seeded = true
 47        }
 48        // The group, drawn first so the two nodes sit on top of it. `size` is the
 49        // INITIAL content box — once placed, the editor owns it.
 50        node_group(GROUP_ID, (size = float2(300.0, 280.0))) {
 51            text(GROUP_TITLE, (text = "Inputs"))
 52        }
 53        // Zoomed-out label: floats the title above the group bounds in the hint draw
 54        // list. GetGroupMin is the group's top-left (screen space inside a hint). The
 55        // hint lists swap R/B for asymmetric colors, so a near-grey label is safest.
 56        group_hint(GROUP_ID) $(var fg; var _bg) {
 57            let mn = imgui_node_editor::GetGroupMin()
 58            fg |> add_text(float2(mn.x + 4.0, mn.y - 18.0), rgba(232u, 226u, 210u, 255u), "Inputs")
 59        }
 60        // Two ordinary nodes: Texture starts INSIDE the group, Tint OUTSIDE. Membership is
 61        // spatial - dragging the group carries whatever sits within its bounds at that moment
 62        // (the editor's FindNodesInRect), so drag Tint in and it joins, drag it out and it leaves.
 63        node(NODE_A) {
 64            text("Texture")
 65            pin(11, (kind = PinKind.Output, pivot_alignment = float2(1.0, 0.5))) {
 66                text("RGBA ->")
 67            }
 68        }
 69        node(NODE_B) {
 70            text("Tint")
 71            pin(21, (kind = PinKind.Output, pivot_alignment = float2(1.0, 0.5))) {
 72                text("Color ->")
 73            }
 74        }
 75    }
 76}
 77
 78[export]
 79def init() {
 80    harness_init("Groups", 1000, 600)
 81    g_ed = create_node_editor()
 82}
 83
 84[export]
 85def update() {
 86    if (!harness_begin_frame()) return
 87    harness_new_frame()
 88    let io & = unsafe(GetIO())
 89    SetNextWindowPos(float2(0.0, 0.0), ImGuiCond.Always)
 90    SetNextWindowSize(io.DisplaySize, ImGuiCond.Always)
 91    let flags = (ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoResize |
 92                 ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoScrollbar |
 93                 ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoSavedSettings |
 94                 ImGuiWindowFlags.NoBringToFrontOnFocus)
 95    window(MAIN_WIN, (text = "Groups", closable = false, flags = flags)) {
 96        draw_editor()
 97    }
 98    harness_end_frame()
 99}
100
101[export]
102def shutdown() {
103    destroy_node_editor(g_ed)
104    harness_shutdown()
105}
106
107[export]
108def main() {
109    init()
110    while (!exit_requested()) {
111        update()
112    }
113    shutdown()
114}

A group is a node

node_group(id, (size = float2(w, h))) { ... } is BeginNode + Group(size): a pin-less node whose body is its label. It shares the same id space as node(id) — keep the ids disjoint (the tutorial uses 1 for the group, 10 / 20 for the nodes). Draw groups first so the regular nodes paint on top of them.

Membership is spatial

The editor decides what a group carries at the moment you drag it: it moves every node whose bounds fall inside the group’s content box (FindNodesInRect over m_GroupBounds in the C++). There is no membership list and no join API — drop a node inside the bounds and the next group move takes it along; drag it back outside and the next move leaves it.

To move the group itself, grab its header — the title strip above the content box. Pressing the content box instead starts a rubber-band selection (it moves nothing), which is why the recording aims its group-drag at the title.

Editor-owned geometry

size is the initial content box only. Once the group is placed, the editor owns its bounds — exactly like a node position. SetNodePosition seeds the group’s canvas position once; to resize it later call set_group_size (mutating your own struct’s size field won’t move it). Dragging a node onto the group, or dragging the group itself, is all handled by the editor — your draw code never changes.

The zoomed-out label

When the canvas is zoomed out far enough that the group’s own title is too small to read, group_hint(id) $(var fg; var bg) { ... } draws a floating label instead. It self-gates — a no-op at normal zoom — so the loop over groups is unconditional. GetGroupMin returns the group’s top-left (screen space inside a hint); fg is the foreground hint draw list, bg the background one. The hint lists swap red/blue for asymmetric colors, so a near-grey label is the safe choice. See navigation for zooming the view out far enough to trigger it.