Repository navigation
buttons
Styled buttons with automatic style push/pop, toggle states, adaptive sizing, and row layouts.
local wu = GetMod("WindowUtils")
local c = wu.Controls
-- Simple styled button
if c.Button("Save", "active", c.ColWidth(6)) then save() end
-- Toggle button bound to state
c.ToggleButton("Grid", settings.showGrid, c.ColWidth(4))
-- Row of buttons with auto width
c.ButtonRow({
{ label = "Save", style = "active", onClick = save },
{ label = "Reset", style = "warning", onClick = reset },
{ icon = IconGlyphs.TrashCanOutline, style = "danger", onHold = deleteAll },
})Styled button with automatic push/pop.
| Parameter | Type | Default | Description |
|---|---|---|---|
| label | string | - | Button label |
| styleName | string | "inactive" | Style name (see Styles module) |
| width | number | 0 | Width (0 = auto, negative = fill available width) |
| height | number | 0 | Height (0 = auto, negative = fill available height) |
| opts.color | string|table|nil | nil | Text color override (preset name or {r,g,b,a}) |
| opts.elementId | string|nil | nil | Tutorial/bounds target ID |
Returns: boolean - true if clicked
Switches between "active" and "inactive" styles based on state. Same opts as Button.
Returns: boolean - true if clicked
Button that fills available width. Same opts as Button.
Returns: boolean - true if clicked
Non-interactive greyed-out button. Returns nothing.
Icon glyph drawn on a transparent button background. Used internally as the icon prefix for sliders, drags, inputs, and combos.
| Parameter | Type | Default | Description |
|---|---|---|---|
| icon | string | - | Icon glyph or IconGlyphs key |
| clickable | boolean | false | When false, the click result is discarded and false is returned |
Returns: boolean - true only when clickable is true and the button was clicked
The button is always rendered and always hoverable. clickable controls the return value, not interactivity.
Non-interactive status bar with a label and optional value.
| Parameter | Type | Default | Description |
|---|---|---|---|
| label | string | - | Left-side label (rendered inside button) |
| value | any|nil | nil | Right-side value (rendered via SameLine()) |
| opts.widthFraction | number | 1 | Divides available width (2 = half width) |
| opts.style | string | "statusbar" | Button style name |
| opts.elementId | string|nil | nil | Tutorial/bounds target ID |
c.StatusBar("FPS", tostring(fps))
c.StatusBar("Mode", "Fixed", { widthFraction = 2 })Button that adapts content based on available width. Full text at normal widths, truncated with "..." when narrow, icon-only when the button is too narrow for meaningful text.
The icon-only threshold is content-aware: it uses the smaller of frameHeight * 1.5 or the label's natural width. This means short labels (like "Kilo") stay as text even at narrow widths since the full label still fits.
Icon-only mode uses custom draw-list rendering with a vertical glyph nudge for better visual centering of icon font glyphs.
| Parameter | Type | Default | Description |
|---|---|---|---|
| label | string | - | Full button label |
| icon | string|nil | - | Icon glyph or IconGlyphs key for narrow display |
| opts.style | string | "inactive" | Button style |
| opts.width | number | available | Button width |
| opts.height | number | 0 | Button height |
| opts.iconThreshold | number|nil | min(frameHeight*1.5, labelWidth) | Pixel width below which icon-only mode triggers |
| opts.iconFallback | string | "?" | Fallback if icon resolves to nil |
| opts.tooltip | string|nil | label | Tooltip when truncated or icon mode |
| opts.color | string|table|nil | nil | Text color in text mode (preset name or {r,g,b,a}) |
| opts.iconColor | string|table|nil | nil | Glyph color in icon-only mode (preset name or {r,g,b,a}) |
| opts.displayMode | string | "text" | Display mode: "text" (adaptive text/icon), "iconText" (icon + label side by side), "icon" (always icon-only). Unrecognized values default to "text"
|
| opts.linkGroup | string|nil | nil | Non-empty group name. All DynamicButton instances sharing the same linkGroup switch to icon-only together the frame any one of them goes below its iconThreshold
|
| opts.elementId | string|nil | nil | Tutorial/bounds target ID |
Display modes:
-
"text"- the existing adaptive behavior: full label at normal widths, truncated with "..." when narrower, icon-only when belowiconThreshold -
"iconText"- shows the icon glyph and label text side by side; falls back to icon-only when width drops belowiconThreshold; the label is truncated to fit and shown in full via tooltip when clipped -
"icon"- always icon-only regardless of available width
Link groups: All buttons sharing the same non-empty linkGroup string switch to icon-only simultaneously the frame any one of them is narrow enough to trigger icon-only mode. nil, empty string, or non-string values disable the group behavior.
Returns: boolean - true if clicked
-- Adapts from "Save Changes" -> "Sav..." -> icon (at square size)
c.DynamicButton("Save Changes", IconGlyphs.ContentSave, {
style = "active", width = c.ColWidth(4),
})
-- Custom icon threshold override
c.DynamicButton("Export", IconGlyphs.Export, {
iconThreshold = 40,
})
-- Yellow icon when shrunk, white text otherwise
c.DynamicButton("Favorites", IconGlyphs.Star, {
style = "inactive",
iconColor = { 1.0, 0.749, 0.086, 1.0 },
width = -1,
})Row of buttons with automatic width distribution. Icon-only buttons auto-size; text buttons share remaining space by weight. Uses tight horizontal spacing by default.
| Parameter | Type | Default | Description |
|---|---|---|---|
| defs | table | - | Array of button definitions |
| opts.gap | number | ItemSpacing.y | Gap between buttons |
| opts.normalSpacing | boolean | false | Use standard ItemSpacing.x |
| opts.id | string|nil | nil | ID for min-width caching |
| opts.elementId | string|nil | nil | Tutorial/bounds target for the row group |
Button definition fields:
| Field | Type | Description |
|---|---|---|
| label | string|nil | Button text (falls back to def[1]) |
| icon | string|nil | Icon glyph (if no label/weight, auto-sizes to icon width) |
| style | string | Style name (falls back to def[2], then "inactive") |
| weight | number | Flex weight for text buttons (default 1) |
| width | number|nil | Fixed width override |
| height | number|nil | Button height |
| color | string|table|nil | Text/icon color override (preset name or {r,g,b,a}) |
| hoverIcon | string|nil | Icon glyph swapped in on hover (icon-only slots) |
| hoverColor | string|table|nil | Glyph color on hover (icon-only slots) |
| disabled | boolean|string|nil |
true = soft disable, "hard" = full ImGui disable |
| tooltip | string|nil | Tooltip text (shown on hover) |
| truncatedTooltip | string|false|nil | Tooltip for truncated labels. false = suppress auto-tooltip |
| onClick | function|nil | Click callback |
| onHold | function|nil | Hold callback (turns into HoldButton) |
| holdDuration | number | Hold duration (default 2.0) |
| progressFrom | string|string[]|nil | Show hold progress from another button's hold state. Merged bar: when consecutive slots share the same string source ID and that source is held, they collapse into a single progress bar spanning their combined width. Array form: a string[] renders one bar for the first actively-held source in the list; non-contiguous or foreign-row IDs fall back to the first ID with a log warning |
| progressStyle | string|table | Progress bar style name (default "danger"). A table keyed by source button ID selects a per-source style; unmapped sources fall back to "danger". Only the table form is meaningful with array progressFrom
|
| progressDisplay | string | Progress display mode passed to HoldButton |
| linkGroup | string|nil | Non-empty group name passed to DynamicButton for synchronized icon mode switching across the row |
| displayMode | string|nil | Display mode passed to DynamicButton: "text", "iconText", or "icon"
|
| id | string|nil | Custom button ID (for hold buttons) |
| elementId | string|nil | Tutorial/bounds target ID |
| type | string|nil | Optional explicit slot type. When set, overrides shape-based inference. Accepted values: "button", "label", "icon", "hold", "dynamic", "progress"
|
Slot type is inferred from the def, and each slot type reads a different subset of the fields above:
When
def.typeis set to a recognised value, it overrides all inference below. Omittingtyperuns the existing inference unchanged.
| Slot | Condition | Notes |
|---|---|---|
| HoldButton |
onHold set and not soft-disabled |
Reads id, holdDuration, style, progressDisplay. Ignores color, height, elementId
|
| DynamicButton | both label and icon set |
Reads style, height, tooltip, elementId, linkGroup, displayMode. Ignores color and truncatedTooltip
|
| Icon-only |
icon set, no label, no weight
|
Reads color, hoverIcon, hoverColor, height
|
| Plain label | anything else | Reads color, height, truncatedTooltip. Label is truncated to the allocated width |
ButtonRow also passes def.warningMessage through to HoldButton for onHold slots, but HoldButton never reads it, so setting it has no effect. See holdbuttons.
-- Star toggle + LUT name button
c.ButtonRow({
{ icon = "Star", style = "label", color = { 1, 0.75, 0.09, 1 }, onClick = toggleFav },
{ label = lut.name, style = "active", weight = 1, onClick = select },
})
-- Status label + action button + info icon
c.ButtonRow({
{ type = "label", label = "My Preset", weight = 2 },
{ label = "Apply", style = "active", onClick = apply },
{ type = "icon", icon = IconGlyphs.InformationOutline, tooltip = "Read-only preset" },
})
-- Merged progress bar: hold button collapses two adjacent slots into one bar
c.ButtonRow({
{ label = "Delete", style = "danger", holdDuration = 1.5,
id = "del_src", progressDisplay = "external",
onHold = function() deleteItem() end,
onClick = function() notify("Hold to confirm") end },
{ type = "label", label = "Item Name", style = "label", weight = 1,
progressFrom = "del_src", progressStyle = "danger" },
{ label = "Edit", style = "inactive", onClick = editItem,
progressFrom = "del_src", progressStyle = "danger" },
})Get the cached minimum width recorded by the last ButtonRow/ButtonBlock render that had opts.id set. ButtonRow caches the summed minimum width of its defs; ButtonBlock caches frameCache.frameHeight (square icon size) instead.
Returns: number|nil - minimum width in pixels, or nil if that ID has not rendered yet
Nothing inside WindowUtils calls this; it is here for consumers that want to feed splitter minimum widths from a row they render themselves.
Get cached combined width of a button group from a previous DragFloatRow/DragIntRow. Buttons with the same groupId have their widths + spacing summed and cached.
Returns: number|nil - combined pixel width, or nil if that group has not rendered yet
Also unused inside WindowUtils. Drag rows consume the same cache directly through the widthFrom field, so reach for widthFrom first and only call this if you need the number yourself.
Multi-button layout with two modes: flow (natural-width wrapping) and grid (even-width columns).
- Flow mode (default): natural text width, wrapping on overflow
-
Grid mode (when
opts.columnsoropts.minWidthis set): even-width columns
Button definition fields: Same format as ButtonRow (label, icon, style, height, disabled, tooltip, color, hoverIcon, hoverColor, onClick, onHold, holdDuration, progressFrom, progressStyle, progressDisplay, id, elementId, linkGroup, displayMode). Buttons with both label and icon render as DynamicButtons with adaptive content based on cell width.
Two ButtonRow fields behave differently here:
-
widthis ignored. Grid mode gives every button the same computed column width; flow mode uses the label's natural text width. -
weightis ignored for sizing. It only affects slot detection: anicon-only def with aweightrenders as a plain label button rather than an icon button.
truncatedTooltip is not supported in ButtonBlock. Truncated labels fall back to showing the raw label, and only when def.tooltip is unset.
| Parameter | Type | Default | Description |
|---|---|---|---|
| defs | table | - | Array of button definitions (same as ButtonRow) |
| opts.columns | number|nil | nil | Fixed column count (grid mode) |
| opts.minWidth | number|string|nil | nil | Min button width or "auto" (grid mode) |
| opts.maxWidth | number|nil | nil | Max button width cap (grid mode) |
| opts.gap | number | ItemSpacing.y | Gap between buttons |
| opts.justify | boolean | false | Stretch full rows to fill width (flow mode) |
| opts.normalSpacing | boolean | false | Use standard ItemSpacing.x |
| opts.id | string|nil | nil | Records frameHeight as the min width for getButtonRowMinWidth(id)
|
opts.justify only stretches rows that are followed by another row and hold more than one button, so the final (partial) row keeps natural widths.
Flow mode measures each button from def.label (or def[1]) only. Icon-only defs measure as an empty string and collapse to frame padding, so use grid mode when the row contains icon-only buttons.
-- Auto columns: at least 100px per button, columns calculated from available width
c.ButtonBlock({
{ label = "Cut", icon = IconGlyphs.ContentCut, onClick = cut },
{ label = "Copy", icon = IconGlyphs.ContentCopy, onClick = copy },
{ label = "Paste", icon = IconGlyphs.ContentPaste, onClick = paste },
{ label = "Delete", icon = IconGlyphs.TrashCanOutline, style = "danger", onClick = delete },
}, { minWidth = 100 })
-- Fixed 3 columns with max width cap
c.ButtonBlock({
{ label = "Save", style = "active", onClick = save },
{ label = "Load", onClick = load },
{ label = "Reset", style = "warning", onClick = reset },
{ label = "Export", onClick = export },
{ label = "Import", onClick = import },
{ label = "Delete All", style = "danger", onHold = deleteAll },
}, { columns = 3, maxWidth = 200 })
-- Auto minWidth: columns sized to fit the longest label
c.ButtonBlock({
{ label = "Button One" },
{ label = "Button Twenty-Three" },
{ label = "Button Six" },
}, { minWidth = "auto" })-- Wrapping tag cloud
c.ButtonBlock({
{ label = "Show", style = "inactive", tooltip = "tooltips.Show(text)" },
{ label = "ShowAlways", style = "inactive", tooltip = "tooltips.ShowAlways(text)" },
{ label = "ShowWrapped", style = "inactive", tooltip = "tooltips.ShowWrapped(text, maxWidth)" },
{ label = "ShowTitled", style = "inactive", tooltip = "tooltips.ShowTitled(title, desc)" },
{ label = "ShowHelp", style = "inactive", tooltip = "tooltips.ShowHelp(text)" },
})
-- Justified: full rows stretch buttons to fill width
c.ButtonBlock({
{ label = "Option A", style = "active" },
{ label = "Option B", style = "inactive" },
{ label = "Option C", style = "inactive" },
{ label = "Option D", style = "inactive" },
}, { justify = true })All button types support color overrides via preset names or direct RGBA tables:
-- Preset name (from styles.colors)
{ color = "yellow" }
{ color = "green" }
-- Direct RGBA
{ color = { 1.0, 0.5, 0.0, 1.0 } }
-- DynamicButton: separate icon vs text colors
c.DynamicButton("Favorites", "Star", {
color = { 1, 1, 1, 1 }, -- text mode: white
iconColor = { 1, 0.75, 0, 1 }, -- icon mode: gold
})Common color presets: green, blue, red, yellow, orange, grey, textBlack, textWhite, transparent. Each of the first six also has *Hover and *Active variants, and grey adds greyText, greyLight, greyDim. styles.colors additionally holds internal frame, outline, splitter, and scrollbar entries. Any key in styles.colors is accepted; an unknown name resolves to nil and the override is skipped.
Available button styles: active, inactive, danger, warning, update, disabled, statusbar, label, labelOutlined, transparent, frameless
An unrecognised style name falls back to inactive colors.