Docking

ImGui’s native docking lets the user grab a window’s tab and drop it into a side pane, the bottom pane, or even pop it out to a free-floating window — all preserved across runs through imgui.ini. The dasImgui boost layer wraps the C++ surface in two macros: dockspace (the full-viewport dockable region) and dock_window (each dockable panel inside it). A small DockBuilder helper seeds a 3-pane initial layout so first-run users see something meaningful before they start dragging tabs around.

Source: examples/tutorial/docking.das.

Walkthrough

docking recording
  1options gen2
  2
  3require imgui
  4require imgui_app
  5require glfw/glfw_boost
  6require opengl/opengl_boost
  7require live/glfw_live
  8require live/live_api
  9require live/live_commands
 10require live/live_vars
 11require live/opengl_live
 12require live_host
 13require imgui/imgui_live
 14require imgui/imgui_boost_runtime
 15require imgui/imgui_boost_v2
 16require imgui/imgui_widgets_builtin
 17require imgui/imgui_docking_builtin
 18require imgui/imgui_visual_aids
 19
 20// =============================================================================
 21// TUTORIAL: docking — full-viewport dockspace + draggable Begin/End windows.
 22//
 23// Three things compose to make dockable UI:
 24//   1. `io.ConfigFlags |= ImGuiConfigFlags.DockingEnable` in `init()` — turns
 25//      on ImGui's docking machinery for the whole session.
 26//   2. `dockspace(DOCK_ROOT, (flags=...))` in the frame loop — wraps
 27//      DockSpaceOverViewport so the whole window becomes a dock target, and
 28//      captures the returned `state.dock_id` for the layout helper to use.
 29//   3. `dock_window(NAME, (text=..., closable=..., flags=...))` per panel —
 30//      Begin/End-wrapped windows that ImGui dynamically docks/undocks based
 31//      on the user dragging their tab.
 32//
 33// The DockBuilder helper (`setup_default_layout`) seeds a 3-pane layout on
 34// the first frame so the user sees an arranged UI without having to drag
 35// tabs manually. After the first run, dock state persists via ImGui's
 36// `imgui.ini` — drag a tab anywhere and the layout sticks.
 37//
 38// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/docking.das
 39// LIVE:       daslang-live modules/dasImgui/examples/tutorial/docking.das
 40//
 41// DRIVE (when running live):
 42//   curl -X POST -d '{"name":"imgui_undock","args":{"target":"DOCK_ROOT/OUTPUT"}}'    localhost:9090/command
 43//   curl -X POST -d '{"name":"imgui_set_window_pos","args":{"target":"DOCK_ROOT/OUTPUT","value":{"x":580,"y":220,"w":360,"h":220}}}' localhost:9090/command
 44//   curl -X POST -d '{"name":"imgui_dock_reset","args":{"target":"DOCK_ROOT"}}'        localhost:9090/command
 45//   curl -X POST -d '{"name":"imgui_close","args":{"target":"DOCK_ROOT/OUTPUT"}}'      localhost:9090/command
 46//
 47// Note: dockspace is a [container] that pushes its identifier onto the registry
 48// path, so dock_window targets are path-qualified (``DOCK_ROOT/<name>``).
 49// =============================================================================
 50
 51def setup_default_layout(dock_id : uint) {
 52    //! Seed a 3-pane layout on the first frame:
 53    //!     +-----------+--------------------+
 54    //!     |           |                    |
 55    //!     |           |       Source       |
 56    //!     | Explorer  |                    |
 57    //!     |           +--------------------+
 58    //!     |           |                    |
 59    //!     |           |       Output       |
 60    //!     +-----------+--------------------+
 61    //! Each pane's window-title string MUST match the corresponding
 62    //! `dock_window`'s `text=` field; that's how ImGui ties dockable windows
 63    //! to dock nodes.
 64    DockBuilderRemoveNode(dock_id)
 65    DockBuilderAddDockSpaceNode(dock_id, ImGuiDockNodeFlags.None)
 66    let vp = GetMainViewport()
 67    DockBuilderSetNodeSize(dock_id, ImVec2(vp.Size.x, vp.Size.y))
 68    var left_id : uint = 0u
 69    var right_id : uint = 0u
 70    DockBuilderSplitNode(dock_id, ImGuiDir.Left, 0.25f, left_id, right_id)
 71    var top_id : uint = 0u
 72    var bottom_id : uint = 0u
 73    DockBuilderSplitNode(right_id, ImGuiDir.Up, 0.6f, top_id, bottom_id)
 74    DockBuilderDockWindow("Explorer", left_id)
 75    DockBuilderDockWindow("Source",   top_id)
 76    DockBuilderDockWindow("Output",   bottom_id)
 77    DockBuilderFinish(dock_id)
 78}
 79
 80[export]
 81def init() {
 82    live_create_window("dasImgui docking tutorial", 1024, 720)
 83    live_imgui_init(live_window)
 84    var io & = unsafe(GetIO())
 85    io.FontGlobalScale = 1.5
 86    // The single flag that turns docking on for the whole session. Without
 87    // this, DockSpace() / DockSpaceOverViewport() render nothing.
 88    io.ConfigFlags |= ImGuiConfigFlags.DockingEnable
 89}
 90
 91[export]
 92def update() {
 93    if (!live_begin_frame()) return
 94    begin_frame()
 95
 96    ImGui_ImplOpenGL3_NewFrame()
 97    ImGui_ImplGlfw_NewFrame()
 98    apply_synth_io_override()
 99    NewFrame()
100
101    // Full-viewport dockspace. PassthruCentralNode lets the OS-window
102    // background show through the unsplit center (irrelevant here — we
103    // split everything — but it's the conventional default).
104    dockspace(DOCK_ROOT, (flags = ImGuiDockNodeFlags.PassthruCentralNode)) {
105        // Seed the layout once per session. has_initial_layout flips false on
106        // imgui_dock_reset; the renderer then re-runs setup. dock_id is
107        // captured by the dockspace wrapper before this block runs.
108        if (!DOCK_ROOT.has_initial_layout && DOCK_ROOT.dock_id != 0u) {
109            setup_default_layout(DOCK_ROOT.dock_id)
110            DOCK_ROOT.has_initial_layout = true
111        }
112        dock_window(EXPLORER, (text = "Explorer", closable = false,
113                               flags = ImGuiWindowFlags.None)) {
114            text("Files")
115            button(REFRESH_BTN, (text = "Refresh"))
116        }
117        dock_window(SOURCE, (text = "Source", closable = false,
118                             flags = ImGuiWindowFlags.None)) {
119            text("// drag tabs to rearrange")
120            text("// — layout sticks via imgui.ini")
121            button(SAVE_BTN, (text = "Save"))
122        }
123        dock_window(OUTPUT, (text = "Output", closable = true,
124                             flags = ImGuiWindowFlags.None)) {
125            text("> ready.")
126            button(CLEAR_BTN, (text = "Clear"))
127        }
128    }
129
130    end_of_frame()
131    Render()
132    var w, h : int
133    live_get_framebuffer_size(w, h)
134    glViewport(0, 0, w, h)
135    glClearColor(0.10f, 0.10f, 0.12f, 1.0f)
136    glClear(GL_COLOR_BUFFER_BIT)
137    ImGui_ImplOpenGL3_RenderDrawData(GetDrawData())
138
139    live_end_frame()
140}
141
142[export]
143def shutdown() {
144    live_imgui_shutdown()
145    live_destroy_window()
146}
147
148[export]
149def main() {
150    init()
151    while (!exit_requested()) {
152        update()
153    }
154    shutdown()
155}

Requires

Same backend + boost layer as Layout, but layout helpers are replaced by the docking module:

  • imgui/imgui_docking_builtin — the dockspace and dock_window macros, plus the DockBuilder* bindings cherry-picked from ImGui’s internal API.

The flag that lights it up

ImGui docking is gated by a single io flag. Without it, DockSpace calls render nothing and dock_window panels behave like ordinary windows:

io.ConfigFlags |= ImGuiConfigFlags.DockingEnable

This goes in init() once per session.

Seeding the layout

ImGui will happily start with every dockable window stacked in a single tab-bar — the user is expected to drag tabs into place. For a tutorial we ship a default arrangement via DockBuilder:

DockBuilderRemoveNode(dock_id)                          // clear any prior state
DockBuilderAddDockSpaceNode(dock_id, flags)             // fresh dockspace root
DockBuilderSetNodeSize(dock_id, ImVec2(vp.Size.x, vp.Size.y))
var left_id, right_id : uint
DockBuilderSplitNode(dock_id, ImGuiDir.Left, 0.25f, left_id, right_id)
var top_id, bottom_id : uint
DockBuilderSplitNode(right_id, ImGuiDir.Up, 0.6f, top_id, bottom_id)
DockBuilderDockWindow("Explorer", left_id)
DockBuilderDockWindow("Source",   top_id)
DockBuilderDockWindow("Output",   bottom_id)
DockBuilderFinish(dock_id)

DockBuilderDockWindow matches by window title string — the same string you pass to dock_window(NAME, (text = "Explorer")). The boost macro doesn’t auto-derive the title from the identifier so the binding is explicit.

The setup is gated on state.has_initial_layout so it runs once per session — or after imgui_dock_reset flips the flag back to false.

The frame loop

The dockspace(DOCK_ROOT, ...) macro wraps DockSpaceOverViewport — the dock region is the entire OS window. Inside its block, each dock_window(NAME, ...) is a Begin/End-wrapped panel that participates in the docking system. Path-prefixing works the same as containers (window / child / tab_bar): dock_window(EXPLORER) { button(REFRESH_BTN, ...) } registers the button under DOCK_ROOT/EXPLORER/REFRESH_BTN.

The closable = true option (on OUTPUT here) wires the X-button in the tab to state.open — closing the panel without rebuilding the layout.

Standalone vs live

Same as previous tutorials — main() runs the loop standalone; daslang-live invokes init / update / shutdown directly. ImGui’s docking state is preserved across reloads because the ImGui context survives reload (imgui_live serializes the context pointer through the reload, and the dock state lives inside that context).

Driving from outside

Four live commands cover the docking surface. Targets are path-qualified — the dockspace pushes its name onto the path, so panel targets are DOCK_ROOT/<name>:

# Pop Output out into a floating window
curl -X POST -d '{"name":"imgui_undock","args":{"target":"DOCK_ROOT/OUTPUT"}}' \
     localhost:9090/command

# Reposition the floating window (also works on docked windows — ImGui ignores
# the SetNextWindowPos while a window is docked, so this is most useful after
# imgui_undock). w/h are optional.
curl -X POST -d '{"name":"imgui_set_window_pos","args":{"target":"DOCK_ROOT/OUTPUT","value":{"x":580,"y":220,"w":360,"h":220}}}' \
     localhost:9090/command

# Reset the dockspace back to the default layout
curl -X POST -d '{"name":"imgui_dock_reset","args":{"target":"DOCK_ROOT"}}' \
     localhost:9090/command

# Close the OUTPUT panel (X-button equivalent — closable=true required)
curl -X POST -d '{"name":"imgui_close","args":{"target":"DOCK_ROOT/OUTPUT"}}' \
     localhost:9090/command

imgui_dock is the inverse of imgui_undock — it takes a value of type uint (a dock-node id from a prior DockBuilder* call) and re-docks the panel into that node. imgui_set_window_pos is the companion you’ll usually pair with imgui_undock, since a freshly undocked window picks its position from imgui.ini (or (0,0) if the window has never floated).

Next steps

Style scopes are next — with_style for pushing colors and metrics across a sub-tree of widgets, balanced pop, and how nesting stacks.

See also

Full source: examples/tutorial/docking.das

Richer reference: examples/features/dock_basic.das — same boost surface with a 4-panel initial layout and a wider widget set.

Integration test: tests/integration/test_docking_basic.das — registration, initial-layout geometry, and live-command round-trips.

Previous tutorial: Layout

Boost macros — the macro layer.