Skip to content

buttons

CyanideX edited this page Aug 18, 2026 · 4 revisions

Buttons

Styled buttons with automatic style push/pop, toggle states, adaptive sizing, and row layouts.

Quick Start

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 },
})

Button(label, styleName?, width?, height?, opts?)

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

ToggleButton(label, isActive, width?, height?, opts?)

Switches between "active" and "inactive" styles based on state. Same opts as Button.

Returns: boolean - true if clicked

FullWidthButton(label, styleName?, opts?)

Button that fills available width. Same opts as Button.

Returns: boolean - true if clicked

DisabledButton(label, width?, height?)

Non-interactive greyed-out button. Returns nothing.

IconButton(icon, clickable?)

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.

StatusBar(label, value?, opts?)

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 })

DynamicButton(label, icon, opts?)

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 below iconThreshold
  • "iconText" - shows the icon glyph and label text side by side; falls back to icon-only when width drops below iconThreshold; 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,
})

ButtonRow(defs, opts?)

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.type is set to a recognised value, it overrides all inference below. Omitting type runs 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" },
})

getButtonRowMinWidth(id)

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.

getButtonGroupWidth(groupId)

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.


ButtonBlock(defs, opts?)

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.columns or opts.minWidth is 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:

  • width is ignored. Grid mode gives every button the same computed column width; flow mode uses the label's natural text width.
  • weight is ignored for sizing. It only affects slot detection: an icon-only def with a weight renders 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.

Grid Mode Examples

-- 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" })

Flow Mode Examples

-- 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 })

Color Overrides

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.


Style Names

Available button styles: active, inactive, danger, warning, update, disabled, statusbar, label, labelOutlined, transparent, frameless

An unrecognised style name falls back to inactive colors.

Clone this wiki locally