with one click
preferred imgui layout patterns
npx skills add https://github.com/meow-sci/ksa-mod-experiments --skill imgui-designCopy and paste this command into Claude Code to install the skill
preferred imgui layout patterns
npx skills add https://github.com/meow-sci/ksa-mod-experiments --skill imgui-designCopy and paste this command into Claude Code to install the skill
ImGui is an immediate mode UI library. KSA uses ImGui for UI
details about the ksa game code and behavior
how to use fd cli tool
how to use ripgrep `rg` cli tool
how to use genhttp effectively for http server
details about the rpc mechanism in unladen swallow
| name | imgui-design |
| description | preferred imgui layout patterns |
These patterns are extracted from the KSA mod codebase and represent the preferred style for building ImGui UIs in submods.
Every submod's RenderContent() wraps its body with SubmodUI.BeginContentArea / EndContentArea from MeowSci.KsaAbstractions. This applies a consistent WindowPadding = float2(20f, 20f) and extra bottom padding. Always call this — never render content directly.
SubmodUI.BeginContentArea("##my_content");
// ... content ...
SubmodUI.EndContentArea();
All layout tables use NoPadOuterX so content stretches edge-to-edge with the window border (the window padding from BeginContentArea handles outer inset).
Apply CellPadding = float2(6f, 6f) before every layout table and pop after:
ImGui.PushStyleVar(ImGuiStyleVar.CellPadding, new float2(6f, 6f));
if (ImGui.BeginTable("##id", columns, flags))
{
// ...
ImGui.EndTable();
}
ImGui.PopStyleVar(); // CellPadding
Use for pairs of label+widget side by side (e.g. Columns/Rows, Spacing/Scale):
var flags = ImGuiTableFlags.SizingStretchSame | ImGuiTableFlags.NoPadOuterX;
if (ImGui.BeginTable("##params", 4, flags))
{
ImGui.TableNextRow();
ImGui.TableNextColumn(); ImGui.AlignTextToFramePadding(); ImGui.Text("Label A");
ImGui.TableNextColumn(); ImGui.SetNextItemWidth(-1); ImGui.DragFloat("##a", ref valA, ...);
ImGui.TableNextColumn(); ImGui.AlignTextToFramePadding(); ImGui.Text("Label B");
ImGui.TableNextColumn(); ImGui.SetNextItemWidth(-1); ImGui.DragFloat("##b", ref valB, ...);
ImGui.EndTable();
}
Use for rows where the label gets ¼ width and the widget gets ¾, matching the 4-column table's visual alignment:
var flags = ImGuiTableFlags.SizingStretchProp | ImGuiTableFlags.NoPadOuterX;
if (ImGui.BeginTable("##select", 2, flags))
{
ImGui.TableSetupColumn("##lbl", ImGuiTableColumnFlags.WidthStretch, 1f);
ImGui.TableSetupColumn("##widget", ImGuiTableColumnFlags.WidthStretch, 3f);
ImGui.TableNextRow();
ImGui.TableNextColumn(); ImGui.AlignTextToFramePadding(); ImGui.Text("Label");
ImGui.TableNextColumn(); ImGui.SetNextItemWidth(-1); /* widget */
ImGui.EndTable();
}
Use when rows need a label, a fill widget, and a fixed-width button group (e.g. con-man layout):
var flags = ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoPadOuterX;
if (ImGui.BeginTable("##form", 3, flags))
{
ImGui.TableSetupColumn("##lbl", ImGuiTableColumnFlags.WidthFixed, labelW);
ImGui.TableSetupColumn("##widget", ImGuiTableColumnFlags.WidthStretch);
ImGui.TableSetupColumn("##btns", ImGuiTableColumnFlags.WidthFixed, btnColW);
// ...
ImGui.EndTable();
}
Always call ImGui.AlignTextToFramePadding() immediately before any label text that sits in the same row as a widget, to vertically center it.
Use ImGui.Spacing() between logical groups. Use ImGui.SeparatorText("label") for named section dividers (e.g. between a creation form and a list of items).
(?) to the label when a tooltip is needed, then call ImGui.SetItemTooltip(...) immediately after.ImGuiTreeNodeFlags.DefaultOpen for primary/important sections.if (!ImGui.CollapsingHeader("My Section (?)", ImGuiTreeNodeFlags.DefaultOpen))
return;
ImGui.SetItemTooltip("Explanation of this section.");
When rendering a list of items where each has its own CollapsingHeader, put the content inside a bordered child window. The CollapsingHeader renders flush to the window edges (ignoring WindowPadding), so the child must be manually expanded to match and its cursor pulled left:
if (!ImGui.CollapsingHeader($"Item: {name}##id"))
return;
var wpadX = ImGui.GetStyle().WindowPadding.X;
float childW = ImGui.GetContentRegionAvail().X + wpadX * 2;
ImGui.SetCursorPosX(ImGui.GetCursorPosX() - wpadX);
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new float2(20f, 10f)); // inner padding
ImGui.BeginChild($"child_{id}", new float2(childW, 0),
ImGuiChildFlags.Borders | ImGuiChildFlags.AutoResizeY | ImGuiChildFlags.AlwaysUseWindowPadding,
ImGuiWindowFlags.NoScrollbar);
ImGui.PopStyleVar(); // WindowPadding
// ... content ...
ImGui.Spacing();
ImGui.EndChild();
The WindowPadding push/pop must happen before BeginChild — ImGui captures it at Begin time.
" Apply ", " Delete ", " Create ", " Off ", " Rows ", etc.ImGui.PushStyleColor(ImGuiCol.Button, ImGui.GetColorU32(KSAColor.Xkcd.Scarlet));
ImGui.PushStyleColor(ImGuiCol.Text, ImGui.GetColorU32(KSAColor.Xkcd.PaleGrey));
if (ImGui.Button($" Destroy ##{id}"))
DoDestroy();
ImGui.PopStyleColor();
ImGui.PopStyleColor();
ImGui.SameLine(0, 8).ImGui.SameLine(0, 12) then ImGui.AlignTextToFramePadding() before the text.All combo dropdowns that may have many items follow this pattern:
if (ImGui.BeginCombo("##id", preview))
{
if (ImGui.IsWindowAppearing())
{
ImGui.SetKeyboardFocusHere();
_filter.Clear();
}
_filter.Draw("##filter", -1f);
for (int i = 0; i < items.Length; i++)
{
if (!_filter.PassFilter(items[i])) continue;
bool sel = _selectedIndex == i;
if (ImGui.Selectable(items[i], sel))
_selectedIndex = i;
if (sel) ImGui.SetItemDefaultFocus();
}
ImGui.EndCombo();
}
ImGui.TextColored(new float4(1f, 0.3f, 0.3f, 1f), message)ImGui.TextColored(new float4(0.4f, 1f, 0.4f, 1f), message)ImGui.TextDisabled(message)if (!string.IsNullOrEmpty(_message)) and add ImGui.Spacing() before.Wrap unavailable controls with the standard ImGui disable guard:
if (!canAct) ImGui.BeginDisabled();
if (ImGui.Button(" Act ##id")) DoAct();
if (!canAct) ImGui.EndDisabled();