Repository navigation
dragdrop
Drag-and-drop reordering for ImGui lists. Items stay in place during drag; a line indicator shows where the drop will land.
local wu = GetMod("WindowUtils")
local dd = wu.DragDrop
local items = { "First", "Second", "Third" }
dd.list("myList", items, function(item, index)
ImGui.Button(item, ImGui.GetContentRegionAvail(), 0)
end)Render a reorderable list with all state and mutation handled internally.
dd.list("myList", items, function(item, index)
ImGui.Button(item.name, ImGui.GetContentRegionAvail(), 0)
end, function(from, to)
print("Moved " .. from .. " -> " .. to)
end)| Parameter | Type | Description |
|---|---|---|
| id | string | Unique list ID |
| items | table | Array to reorder in-place on drop |
| renderFn | function |
function(item, index) - render each item |
| onReorder | function|nil |
function(fromIndex, toIndex) - called after reorder |
| opts | table|nil | Configuration (see below) |
Returns nothing. Renders nothing when items is nil or empty. All opts are re-read every frame.
| Option | Type | Default | Description |
|---|---|---|---|
| direction | string | "vertical" |
"horizontal" or "vertical"
|
| color | table | {0.0, 1.0, 0.7, 1.0} | Indicator line color {r,g,b,a}
|
| thickness | number | 8 | Line thickness in pixels |
| rounding | number | 16 | Corner rounding in pixels (uses rounded rect when > 0) |
| spacing | number | ItemSpacing.y (horiz) / 4 (vert) | Gap between items in pixels. Horizontal mode defaults to ImGui's ItemSpacing.y for tight layout. |
| padding | number | 0 | Extends indicator line beyond item bounds in pixels |
| dragAlpha | number | 0.4 | Opacity of the item being dragged (0=invisible, 1=no dim) |
| showHandle | boolean | false | Show a drag handle icon before each item |
| handleIcon | string | "DragVertical" | IconGlyphs key for the drag handle, or a literal glyph string if the key is not found |
| handleColor | table | {0.5, 0.5, 0.5, 1.0} | Handle icon color {r,g,b,a}
|
dd.list("handleList", items, function(item, index)
ImGui.Button(item.name, ImGui.GetContentRegionAvail(), 0)
end, onReorder, {
showHandle = true,
handleColor = { 0.2, 0.8, 0.5, 0.7 },
color = { 0.0, 1.0, 0.7, 1.0 },
thickness = 3,
rounding = 2,
})dd.list("buttons", items, function(item, index)
controls.Button(" " .. item .. " ", "inactive")
end, onReorder, {
direction = "horizontal",
})The module handles SameLine() between items in horizontal mode. Do not add SameLine() inside renderFn.
For full manual control over the drag-drop lifecycle.
local state = dd.createState()
local items = { "Alpha", "Beta", "Gamma" }
-- Each frame:
dd.beginFrame(state)
for i = 1, #items do
ImGui.BeginGroup()
ImGui.Button(items[i])
dd.checkDragSource(state, i)
ImGui.EndGroup()
dd.collectRect(state, i)
end
dd.update(state, items, {
direction = "vertical",
color = { 1.0, 0.5, 0.0, 1.0 },
thickness = 3,
rounding = 4,
onReorder = function(from, to)
print("Reordered!")
end,
})Create a new drag-drop state object.
Reset rect collection for a new frame. Call before rendering items.
Record the last ImGui item's screen rect. Call after each item (or group) is rendered. Does not initiate drags - use checkDragSource for that.
Call it for every index from 1 to your item count, every frame, with no gaps. update walks the whole range up to the highest index it has seen and reads a rect for each one, so a skipped index means either a stale position or an error on a rect that was never collected. That rules out clipping and conditional rendering in a reorderable list.
Check if the last ImGui item initiates a drag. Call on the interactive element that should be the drag handle (a Button, Selectable, etc.). Must be called before collectRect for the same index.
Compute drop position, draw the indicator, and apply reorder on mouse release. Mutates items in place. Cheap to call every frame: it returns immediately when no drag is active.
No indicator is drawn when the drop would land the item back where it started (either side of the source), and in that case the release is swallowed without calling onReorder.
Returns: boolean - true if a reorder was applied this frame.
Options are the same as list() plus:
| Option | Type | Description |
|---|---|---|
| onReorder | function|nil | Callback after reorder (replaces the separate parameter in list()) |
Whether a drag is active or just completed (suppresses click-through for one frame after release).
Returns: boolean
Move an element in an array from one index to another (mutates in-place). Returns nothing, and silently does nothing when either index is out of range or the two are equal.
Remove internal state for a list ID. Call when dynamically created lists are removed.
The indicator is drawn on the foreground draw list, so it renders above all content. When rounding > 0, the indicator is drawn as a rounded rectangle instead of a line, giving it capsule-shaped ends.
The renderFn must include at least one interactive ImGui element (Button, InvisibleButton) as the last item for drag detection. Selectable does not hold active state during drag in CET's ImGui and will not work as a drag source. Use showHandle = true if your content uses non-button widgets.