Skip to content
CyanideX edited this page Aug 5, 2026 · 1 revision

Two-Panel Splits

Horizontal and vertical two-panel splits with draggable grab bars, double-click collapse, and programmatic control.

Quick Start

local wu = GetMod("WindowUtils")
local split = wu.Splitter

-- Horizontal split: left sidebar | right content
split.horizontal("sidebar", function()
    for i, item in ipairs(menuItems) do
        if ImGui.Selectable(item.label, selected == i) then
            selected = i
        end
    end
end, function()
    menuItems[selected].draw()
end, { defaultPct = 0.25, minPct = 0.15, maxPct = 0.4 })

horizontal(id, leftFn, rightFn, opts?)

Create a horizontal split (left | right) with a draggable grab bar.

Parameter Type Default Description
id string - Unique splitter ID
leftFn function|nil - Renders left panel content
rightFn function|nil - Renders right panel content
opts.defaultPct number 0.5 Default split position (0-1)
opts.minPct number 0.1 Minimum left panel fraction
opts.maxPct number 0.9 Maximum left panel fraction
opts.grabWidth number ItemSpacing.x Grab bar width in pixels

Returns: number - current split fraction (0-1)

Alias: split.h(...)

vertical(id, topFn, bottomFn, opts?)

Create a vertical split (top / bottom) with a draggable grab bar.

Parameter Type Default Description
id string - Unique splitter ID
topFn function|nil - Renders top panel content
bottomFn function|nil - Renders bottom panel content
opts.defaultPct number 0.5 Default split position (0-1)
opts.minPct number 0.1 Minimum top panel fraction
opts.maxPct number 0.9 Maximum top panel fraction
opts.grabWidth number ItemSpacing.x Grab bar height in pixels

Returns: number - current split fraction (0-1)

Alias: split.v(...)

Option Lifetime

Every opts field on horizontal and vertical is read only on the first call for a given id. defaultPct, minPct, maxPct, and grabWidth are copied into the splitter's state when that state is created; passing different values on later frames is silently ignored. There are no every-frame opts fields on these two functions.

To change the position after creation, call setSplitPct(id, pct). To change minPct, maxPct, or grabWidth, call splitter.destroy(id) and let the next frame re-create the state with the new opts.

Two other consequences of this:

  • opts.elementId is not read by two-panel splits, so their grab bars cannot be targeted by the tutorial system. Only multi-splitter dividers support elementId.
  • Two-panel splits are not persisted. Unlike multi() and toggle(), there is no layout-cache restore, so the position starts at defaultPct every session.

State Functions

getSplitPct(id)

Get current split fraction for a two-panel splitter.

Returns: number|nil - fraction (0-1), or nil if not initialized

setSplitPct(id, pct)

Set split fraction programmatically. Value is clamped to the splitter's min/max range.

reset(id)

Reset splitter to its default fraction. Cancels any collapse animation.

Examples

Sidebar + Content

split.horizontal("sidebar", function()
    for i, item in ipairs(menuItems) do
        if ImGui.Selectable(item.label, selected == i) then
            selected = i
        end
    end
end, function()
    menuItems[selected].draw()
end, { defaultPct = 0.25, minPct = 0.15, maxPct = 0.4 })

Nested Splitters

-- Vertical outer split with horizontal inner split
split.vertical("outer", function()
    split.horizontal("inner", function()
        ImGui.Text("Top-Left")
    end, function()
        ImGui.Text("Top-Right")
    end)
end, function()
    ImGui.Text("Bottom")
end, { defaultPct = 0.6 })

Clone this wiki locally