8.18.1.9. 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 three macros: dockspace (the full-viewport
dockable region), the sibling dockspace_in_window (an explicit
DockSpace nested inside a host window(HOST, …)), and
dock_window (each dockable panel inside either dockspace). A small
DockBuilder helper seeds an initial layout so first-run users see
something meaningful before they start dragging tabs around.
Source: modules/dasImgui/examples/tutorial/docking.das.
8.18.1.9.1. Walkthrough
The recording is voiced and self-verifying, and it docks the way a user does
— no programmatic shortcuts. It drives two REAL synthetic mouse drags: it grabs
the node splitter between Explorer and Source and drags it to widen the left
pane, then grabs Output’s tab and drags it onto Source to stack the two as tabs.
The synthetic mouse drives ImGui’s SplitterBehavior and its
window-move + dock-preview + drop path exactly like a hand on the mouse would.
Each drag asserts the layout actually moved (the resized pane’s size changed;
the re-docked panel’s dock_id changed); a no-op drag aborts the recording at
teardown rather than shipping a clip where nothing happened.
1options gen2
2options _comment_hygiene = true
3
4require imgui
5require imgui_app
6require opengl/opengl_boost
7require live/glfw_live
8require live/live_api
9require live/live_commands
10require live/live_vars
11require live_host
12require imgui/imgui_live
13require imgui/imgui_boost_runtime
14require imgui/imgui_boost_v2
15require imgui/imgui_widgets_builtin
16require imgui/imgui_docking_builtin
17require imgui/imgui_visual_aids
18
19// =============================================================================
20// TUTORIAL: docking — full-viewport dockspace + draggable Begin/End windows.
21//
22// Three things compose to make dockable UI:
23// 1. `io.ConfigFlags |= ImGuiConfigFlags.DockingEnable` in `init()` — turns
24// on ImGui's docking machinery for the whole session.
25// 2. `dockspace(DOCK_ROOT, (flags=...))` in the frame loop — wraps
26// DockSpaceOverViewport so the whole window becomes a dock target, and
27// captures the returned `state.dock_id` for the layout helper to use.
28// 3. `dock_window(NAME, (text=..., closable=..., flags=...))` per panel —
29// Begin/End-wrapped windows that ImGui dynamically docks/undocks based
30// on the user dragging their tab.
31//
32// The DockBuilder helper (`setup_default_layout`) seeds a 3-pane layout on
33// the first frame so the user sees an arranged UI without having to drag
34// tabs manually. After the first run, dock state persists via ImGui's
35// `imgui.ini` — drag a tab anywhere and the layout sticks.
36//
37// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/docking.das
38// LIVE: daslang-live modules/dasImgui/examples/tutorial/docking.das
39//
40// DRIVE (when running live):
41// curl -X POST -d '{"name":"imgui_undock","args":{"target":"DOCK_ROOT/OUTPUT"}}' localhost:9090/command
42// 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
43// curl -X POST -d '{"name":"imgui_dock_reset","args":{"target":"DOCK_ROOT"}}' localhost:9090/command
44// curl -X POST -d '{"name":"imgui_close","args":{"target":"DOCK_ROOT/OUTPUT"}}' localhost:9090/command
45//
46// Note: dockspace is a [container] that pushes its identifier onto the registry
47// path, so dock_window targets are path-qualified (``DOCK_ROOT/<name>``).
48// =============================================================================
49
50def setup_default_layout(dock_id : uint) {
51 //! Seed a 3-pane layout on the first frame: Explorer (left, full height) |
52 //! Source over Output (right column, split). Each pane's window-title string
53 //! MUST match its dock_window's `text=` — that's how ImGui ties windows to nodes.
54 DockBuilderRemoveNode(dock_id)
55 DockBuilderAddDockSpaceNode(dock_id, ImGuiDockNodeFlags.None)
56 let vp = GetMainViewport()
57 DockBuilderSetNodeSize(dock_id, ImVec2(vp.Size.x, vp.Size.y))
58 var left_id : uint = 0u
59 var right_id : uint = 0u
60 DockBuilderSplitNode(dock_id, ImGuiDir.Left, 0.25f, left_id, right_id)
61 var top_id : uint = 0u
62 var bottom_id : uint = 0u
63 DockBuilderSplitNode(right_id, ImGuiDir.Up, 0.6f, top_id, bottom_id)
64 DockBuilderDockWindow("Explorer", left_id)
65 DockBuilderDockWindow("Source", top_id)
66 DockBuilderDockWindow("Output", bottom_id)
67 DockBuilderFinish(dock_id)
68}
69
70[export]
71def init() {
72 live_create_window("dasImgui docking tutorial", 1024, 720)
73 live_imgui_init(live_window)
74 // Docking layout persists to imgui.ini; disable that so the recording always
75 // starts from setup_default_layout's seeded 3-pane arrangement (a leftover
76 // ini from a prior run would otherwise override the seed non-deterministically).
77 DisableIniPersistence()
78 var io & = unsafe(GetIO())
79 GetStyle().FontScaleMain = 1.5
80 // The single flag that turns docking on for the whole session. Without
81 // this, DockSpace() / DockSpaceOverViewport() render nothing.
82 io.ConfigFlags |= ImGuiConfigFlags.DockingEnable
83}
84
85[export]
86def update() {
87 if (!live_begin_frame()) return
88 begin_frame()
89
90 ImGui_ImplGlfw_NewFrame()
91 apply_synth_io_override()
92 NewFrame()
93
94 // Full-viewport dockspace. PassthruCentralNode lets the OS-window
95 // background show through the unsplit center (irrelevant here — we
96 // split everything — but it's the conventional default).
97 dockspace(DOCK_ROOT, (flags = ImGuiDockNodeFlags.PassthruCentralNode)) {
98 // Seed the layout once per session. has_initial_layout flips false on
99 // imgui_dock_reset; the renderer then re-runs setup. dock_id is
100 // captured by the dockspace wrapper before this block runs.
101 if (!DOCK_ROOT.has_initial_layout && DOCK_ROOT.dock_id != 0u) {
102 setup_default_layout(DOCK_ROOT.dock_id)
103 DOCK_ROOT.has_initial_layout = true
104 }
105 dock_window(EXPLORER, (text = "Explorer", closable = false,
106 flags = ImGuiWindowFlags.None)) {
107 text("Files")
108 button(REFRESH_BTN, (text = "Refresh"))
109 }
110 dock_window(SOURCE, (text = "Source", closable = false,
111 flags = ImGuiWindowFlags.None)) {
112 text("// drag tabs to rearrange")
113 text("// - layout sticks via imgui.ini")
114 button(SAVE_BTN, (text = "Save"))
115 }
116 dock_window(OUTPUT, (text = "Output", closable = true,
117 flags = ImGuiWindowFlags.None)) {
118 text("> ready.")
119 button(CLEAR_BTN, (text = "Clear"))
120 }
121 }
122
123 end_of_frame()
124 Render()
125 var w, h : int
126 live_get_framebuffer_size(w, h)
127 glViewport(0, 0, w, h)
128 glClearColor(0.10f, 0.10f, 0.12f, 1.0f)
129 glClear(GL_COLOR_BUFFER_BIT)
130 live_imgui_render()
131
132 live_end_frame()
133}
134
135[export]
136def shutdown() {
137 live_imgui_shutdown()
138 live_destroy_window()
139}
140
141[export]
142def main() {
143 init()
144 while (!exit_requested()) {
145 update()
146 }
147 shutdown()
148}
8.18.1.9.1.1. Requires
Same backend + boost layer as Layout, but layout helpers are replaced by the docking module:
imgui/imgui_docking_builtin— thedockspace,dockspace_in_window, anddock_windowmacros, plus theDockBuilder*bindings cherry-picked from ImGui’s internal API.
8.18.1.9.1.2. 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.
8.18.1.9.1.3. 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.
8.18.1.9.1.4. Variant: dockspace inside a host window
dockspace wraps DockSpaceOverViewport — the dock region claims the
entire OS window. The sibling dockspace_in_window wraps the explicit
DockSpace(id, size, flags, null) call so the dock node lives INSIDE
an enclosing window(HOST, ...) rather than over the viewport. Use
this when the host window needs its own menu bar, decorations, or a
floating / moveable frame around the dockable area.
SetNextWindowSize(ImVec2(680.0f, 440.0f), ImGuiCond.FirstUseEver)
window(HOST, (text = "Editor",
closable = false,
flags = ImGuiWindowFlags.MenuBar |
ImGuiWindowFlags.NoDocking)) {
menu_bar(HOST_MENU) {
menu(FILE_MENU, (text = "File", enabled = true)) {
menu_item(SAVE_ITEM, (text = "Save"))
}
}
dockspace_in_window(DS, (size = float2(0.0f, 0.0f),
flags = ImGuiDockNodeFlags.None)) {
dock_window(FILES, (text = "Files", closable = false,
flags = ImGuiWindowFlags.None)) { ... }
dock_window(OUTPUT, (text = "Output", closable = false,
flags = ImGuiWindowFlags.None)) { ... }
}
}
The host’s ImGuiWindowFlags.MenuBar / NoDocking live on the
window call, not on dockspace_in_window — the dockspace container
only manages the dock node. size = (0,0) (typical) fills the host’s
available content region after the menu bar.
DS.dock_id is captured for DockBuilder* layout calls the same way
as dockspace — the choice between the two is purely about whether
you want the host frame around the dock area.
See modules/dasImgui/examples/features/dockspace_in_window.das for the full
scene.
8.18.1.9.1.5. 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.
8.18.1.9.1.6. 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).
8.18.1.9.1.7. Driving from outside
Several 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
# Raise a panel to the front of its dock node — selects its tab when stacked
curl -X POST -d '{"name":"imgui_raise","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). imgui_raise brings a panel to the front
of its dock node — when several panels share a node (so they render as a tab
strip) it selects that panel’s tab; it’s the automation counterpart of
clicking a tab, which a real click can’t reach (the dock node’s tab bar is
ImGui-internal, not a registered widget).
8.18.1.9.1.8. 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: modules/dasImgui/examples/tutorial/docking.das
Richer reference: modules/dasImgui/examples/features/dock_basic.das — same boost
surface with a 4-panel initial layout and a wider widget set.
Integration test: modules/dasImgui/tests/test_docking_basic.das —
registration, initial-layout geometry, and live-command round-trips.
Previous tutorial: Layout
Boost macros — the macro layer.