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

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.