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

Drag Controls

Drag controls use ImGui.DragFloat/ImGui.DragInt instead of sliders. The value changes by dragging left/right with configurable speed. Hold Shift for precision mode (reduced drag speed).

Quick Start

local wu = GetMod("WindowUtils")
local c = wu.Controls
local styles = wu.Styles

-- Simple float drag
local val, changed = c.DragFloat(
    IconGlyphs.Speedometer, "speed", settings.speed,
    0.0, 10.0, { speed = 0.05, default = 1.0, tooltip = "Drag speed" }
)

-- Three colored float drags in a row
local values, changed = c.DragFloatRow(nil, "pos", {
    { value = pos.x, color = styles.dragColors.x, label = "X", min = -2000, max = 2000 },
    { value = pos.y, color = styles.dragColors.y, label = "Y", min = -2000, max = 2000 },
    { value = pos.z, color = styles.dragColors.z, label = "Z", min = -2000, max = 2000 },
}, { speed = 0.1 })

DragFloat(icon, id, value, min, max, opts?)

Float drag with optional icon prefix, column width, right-click reset, tooltip, and Shift precision.

Parameter Type Default Description
icon string|nil - IconGlyph (nil = no icon)
id string - Unique drag ID
value number - Current value
min number - Minimum
max number - Maximum
opts.speed number (max-min)/200 Base drag speed
opts.format string "%.2f" Display format
opts.cols number|nil nil Grid columns (nil = fill remaining)
opts.default number|nil nil Right-click reset value
opts.tooltip string|nil nil Icon tooltip (attaches to the icon, so needs icon set)
opts.alwaysShowTooltip boolean true Show the icon tooltip even when tooltips are disabled globally. Set false to respect the global setting
opts.elementId string|nil nil Tutorial/bounds target ID
opts.precisionMultiplier number 0.1 Shift precision factor (lower = slower)
opts.noPrecision boolean false Disable Shift precision for this control

Returns: number, boolean - new value, whether changed

Shift precision: ImGui natively multiplies drag speed by 10x when Shift is held. DragFloat counters that and applies precisionMultiplier, giving an effective speed of baseSpeed * 0.1 * precisionMultiplier when Shift is held.

local val, changed = c.DragFloat(
    IconGlyphs.Speedometer, "myDrag", settings.speed,
    0.0, 10.0, { speed = 0.05, default = 1.0, tooltip = "Drag speed" }
)

DragInt(icon, id, value, min, max, opts?)

Same as DragFloat but for integers. Default format: "%d", default speed: 0.5.

local val, changed = c.DragInt(
    IconGlyphs.Counter, "quality", settings.quality,
    1, 100, { tooltip = "Quality level" }
)

DragFloatRow(icon, id, drags, opts?)

Compound control that renders multiple float drag inputs (and optionally buttons) on a single horizontal line. Supports per-drag color theming, width weighting, label-to-value display, and delta mode.

Parameter Type Description
icon string|nil IconGlyph prefix (nil = no icon)
id string Base ImGui ID (each drag appends its index)
drags table Array of Drag_Definitions and/or Button_Definitions
opts table|nil Row-level options

opts fields:

Field Type Default Description
cols number|nil nil Grid columns for row width
speed number|nil (max-min)/200 Default base drag speed
min number|nil nil Default min for all drags
max number|nil nil Default max for all drags
precisionMultiplier number|nil 0.1 Shift precision factor
noPrecision boolean|nil false Disable Shift precision
tooltip string|nil nil Icon tooltip. Always shown regardless of the global tooltip setting; row icons ignore alwaysShowTooltip
spacing number|nil itemSpacingX Gap between elements
suppressCtrlReveal boolean|nil false Stop Ctrl from revealing values on every labelled element in the row
mode string|nil "absolute" "absolute" (value in/out) or "delta" (always 0 in, delta out)
onChange function|nil nil (index, newValue, key) callback when a drag changes
onReset function|nil nil (index, key) callback on right-click in delta mode
disabled boolean|string|nil nil true = fully faded, "dimmed" = faded with outlined on hover, "dimmedColor" = faded with color border on hover
state table|nil nil Caller-owned state table for unbound rows (enables hover tracking and delta accumulation across frames)
id string|nil nil Unique suffix for bound rows sharing the same first key on the same bind context

Returns: table, boolean - values array (one per drag, buttons excluded), whether any drag changed

Drag_Definition fields

Field Type Default Description
type string|nil nil Omit or "drag" for the row's native type, "int" to force ImGui.DragInt for this element
value number required Current value (ignored in delta mode)
min number|nil opts.min Minimum (falls back to -math.huge)
max number|nil opts.max Maximum (falls back to math.huge)
color table|nil nil Base {r,g,b,a} color theme
format string|nil "%.2f" Display format. type = "int" elements default to "%d"
label string|nil nil Idle display text, switches to value on hover, active drag, or Ctrl
tooltip string|nil nil Hover tooltip
default number|nil nil Right-click reset value. In delta mode, right-click calls onReset instead
speed number|nil opts.speed Per-drag speed override
key any|nil nil Passed as the second argument to onChange/onReset. Bound rows set this to the data key
width number|nil nil Fixed pixel width
widthPercent number|nil nil Fixed width as percentage of display width (e.g., 5 = 5% of screen)
widthFrom string|nil nil Use cached width from a button group (see getButtonGroupWidth)
fitLabel boolean|nil nil Auto-size width to fit the label text with padding. Needs label set, otherwise ignored
weight number|nil 1 Flex weight for remaining space (ignored when width/widthPercent/widthFrom/fitLabel is set)
disabled boolean|string|nil nil Per-drag disabled style override (same values as opts.disabled)
onChange function|nil nil Per-drag (newValue, key) callback. Takes precedence over opts.onChange
groupId string|nil nil Group ID for width caching (elements with same groupId have widths summed)
progressFrom string|nil nil Replace with progress bar when the named hold button is active
progressStyle string|nil "danger" Style for cross-element progress bar
mode string|nil nil Per-element "delta" override
elementId string|nil nil Tutorial/bounds target ID. Only reported while bounds reporting is active

Width precedence is width, widthFrom, widthPercent, fitLabel, then flex weight. The first one present wins.

Consecutive elements sharing the same progressFrom are merged into a single progress bar spanning their combined width. A type = "button" element ends the run.

Button_Definition fields

Field Type Default Description
type string required Must be "button"
icon string|nil nil Icon name (resolved via utils.resolveIcon). Takes precedence over label
label string|nil nil Button text
hoverLabel string|nil nil Text shown on hover or Ctrl (label-to-value pattern). Needs a state table
style string "inactive" Button style. "transparent" also opts the button out of row-level disabled styling, so invisible spacers stay invisible
tooltip string|nil nil Hover tooltip
onClick function|nil nil Click callback (index). On a hold button, fires when the hold completes, not on click
holdDuration number|nil nil Hold-to-confirm duration. Setting it turns the slot into a HoldButton
progressDisplay string|nil nil Hold progress display mode passed to HoldButton (defaults to "overlay" there)
progressFrom string|nil nil Show progress bar from another hold button
progressStyle string|nil "danger" Progress bar style
width number|nil auto Fixed pixel width. Default auto-sizes to the icon or label text
widthPercent number|nil nil Fixed width as percentage of display width
widthFrom string|nil nil Use cached width from a button group
fitLabel boolean|nil nil Auto-size width to fit label with extra padding
weight number|nil nil Flex weight (participates in remaining space distribution)
groupId string|nil nil Group ID for width caching, read back via widthFrom or getButtonGroupWidth
disabled boolean|string|nil nil Disable the button. Falls back to opts.disabled
id string|nil nil Custom button ID (for hold buttons)
elementId string|nil nil Tutorial/bounds target ID. Only reported while bounds reporting is active

A disabled button is drawn as a raw ImGui.Button under the faded style so it still highlights on hover, but onClick is never wired up, so clicks do nothing.

Time_Definition fields (type = "time")

Field Type Default Description
type string required Must be "time"
value number 0 Seconds since midnight (0-86399)
default number|nil nil Right-click reset value (seconds)
tooltip string|nil nil Hover tooltip
onChange function|nil nil (newSeconds, key) callback. Takes precedence over opts.onChange
onRelease function|nil nil (seconds) called on the frame the drag stops while the element was hovered last frame. Needs a state table
key any|nil nil Passed as the second argument to onChange
color table|nil nil Base {r,g,b,a} color theme, same as a drag element
disabled boolean|string|nil nil Disable the control. Falls back to opts.disabled
weight number|nil 1 Flex weight for width distribution
width number|nil nil Fixed pixel width
widthPercent number|nil nil Fixed width as percentage of display width
widthFrom string|nil nil Use cached width from a button group
groupId string|nil nil Group ID for width caching
elementId string|nil nil Tutorial/bounds target ID. Only reported while bounds reporting is active

Displays "h:MM AM/PM" format. Drag adjusts in minute increments and wraps at midnight. Double-click to type a time string ("1:45 PM", "13:45", "1345"). Time elements do not support format, label, min, max, speed, mode, fitLabel, progressFrom, or progressStyle.

Element types

Elements without type (or type = "drag") are treated as drags. Elements with type = "button" render as buttons. Elements with type = "int" render as integer drags. Elements with type = "time" render as time-of-day drags. All elements are laid out left-to-right with SameLine(). Fixed-width elements (width, widthFrom, widthPercent, fitLabel) and unweighted buttons are subtracted from available space first; remaining space is divided proportionally by weight among weighted elements.

Return values

values holds one entry per drag or time element, in order, with buttons skipped. Callers that pass opts.state (or use a bound row) get the same values table back on every frame, reused in place. Copy out what you need rather than holding the table across frames.

Rows called without a state table allocate a fresh values table each frame, but lose hover tracking, delta accumulation, cached ImGui IDs, and onRelease.

Color theming

A single {r, g, b, a} base color derives four ImGui style colors with a shared faded blue FrameBg background and axis-colored hover, active, and border. Drags without a color use the default outlined style.

Disabled styles

disabled = true renders a fully faded blue style. "dimmed" shows faded at rest but reveals the normal outlined style on hover. "dimmedColor" shows faded at rest but reveals the full color border on hover (requires color on each drag). Can be set per-drag or at the row level via opts.

Label-to-value

When a drag has a label, it displays the label text when idle and switches to the numeric value on hover or active drag. Requires a persistent state table (automatic for bound rows, pass opts.state for unbound rows).

Holding Ctrl reveals values on every labelled element in the row at once, and on buttons that set hoverLabel. This is suppressed while any ImGui item is active, and can be turned off per row with opts.suppressCtrlReveal = true.

Fully disabled elements (disabled = true) always show their label and never reveal the value.

Examples

-- Three colored float drags.
-- posRowState lives outside onDraw so hover reveal and onRelease work.
local values, changed = c.DragFloatRow(nil, "pos", {
    { value = pos.x, color = styles.dragColors.x, label = "X", min = -2000, max = 2000 },
    { value = pos.y, color = styles.dragColors.y, label = "Y", min = -2000, max = 2000 },
    { value = pos.z, color = styles.dragColors.z, label = "Z", min = -2000, max = 2000 },
}, { speed = 0.1, state = posRowState })

-- Mixed buttons + drags
local values, changed = c.DragFloatRow(nil, "posRow", {
    { type = "button", icon = "Upload", tooltip = "Load", onClick = function() loadPos() end },
    { type = "button", icon = "Refresh", tooltip = "Update", holdDuration = 1.0, onClick = function() updatePos() end },
    { value = pos.x, color = styles.dragColors.x, label = "X", min = -2000, max = 2000 },
    { value = pos.y, color = styles.dragColors.y, label = "Y", min = -2000, max = 2000 },
    { value = pos.z, color = styles.dragColors.z, label = "Z", min = -2000, max = 2000 },
}, { speed = 0.1 })

-- Delta mode (drags always show 0, onChange receives deltas)
c.DragFloatRow(nil, "posRelative", {
    { value = 0, color = styles.dragColors.x, label = "X", min = -10000, max = 10000 },
    { value = 0, color = styles.dragColors.y, label = "Y", min = -10000, max = 10000 },
    { value = 0, color = styles.dragColors.z, label = "Z", min = -10000, max = 10000 },
}, {
    mode = "delta",
    speed = 0.1,
    state = relRowState,
    onChange = function(index, delta)
        local axes = { right, forward, up }
        local axis = axes[index]
        pos.x = pos.x + delta * axis.x
        pos.y = pos.y + delta * axis.y
        pos.z = pos.z + delta * axis.z
    end,
})

-- Width weighting (3 equal-weight + 1 fixed-width)
c.DragFloatRow(nil, "camera", {
    { value = cam.yaw,  label = "Yaw",  min = -180, max = 180 },
    { value = cam.tilt, label = "Tilt", min = -90,  max = 90 },
    { value = cam.roll, label = "Roll", min = -360, max = 360 },
    { value = cam.fov,  label = "FOV",  width = 80, min = 20, max = 130 },
}, { speed = 0.5 })

DragIntRow(icon, id, drags, opts?)

Identical to DragFloatRow but uses ImGui.DragInt. Default format: "%d", default speed: 0.5.

local values, changed = c.DragIntRow("TuneVariant", "stats", {
    { value = stats.quality, label = "Quality" },
    { value = stats.priority, label = "Priority", width = 60 },
    { value = stats.count, label = "Count", width = 60 },
}, { min = 0, max = 100 })

TimeDrag(icon, id, value, opts?)

Standalone time-of-day drag control. Value is seconds since midnight (0-86399). Displays as "h:MM AM/PM". Drag adjusts in minute increments with wrapping. Double-click to type a time string.

Parameter Type Default Description
icon string|nil - IconGlyph (nil = no icon)
id string - Unique drag ID
value number - Seconds since midnight (0-86399)
opts.cols number|nil nil Grid columns (nil = fill remaining)
opts.default number|nil nil Right-click reset value (seconds)
opts.tooltip string|nil nil Icon tooltip
opts.alwaysShowTooltip boolean true Show the icon tooltip even when tooltips are disabled globally
opts.elementId string|nil nil Tutorial/bounds target ID

Returns: number, boolean - new value in seconds, whether changed

Text input accepts: "1:45 PM", "1:45pm", "13:45", "1345", "945", "9:45". Raw seconds are not accepted: bare digit strings of three or more characters are read as HHMM, so "43200" parses as hour 432 and is rejected. Minutes wrap at 0 and 1439.

TimeDrag and type = "time" row elements share one text-edit state table keyed by ImGui ID, so keep their IDs distinct. TimeSlider uses a separate table.

local tod, changed = c.TimeDrag(
    IconGlyphs.ClockOutline, "gameTime", settings.timeOfDay,
    { default = 43200, tooltip = "Game time" }
)

Preset Colors: styles.dragColors

Convenience color presets for common XYZ axis theming. Located in modules/styles.lua.

styles.dragColors = {
    x = { 0.8, 0.3, 0.3, 1.0 },   -- red
    y = { 0.3, 0.7, 0.3, 1.0 },   -- green
    z = { 0.3, 0.5, 0.9, 1.0 },   -- blue
}

Custom RGBA tables are also accepted anywhere a color is expected.

styles.PushDragColor(color) / styles.PopDragColor()

Push/pop colored drag style with faded blue FrameBg and axis-colored hover, active, and border. Returns false if color is nil, not a table, or not exactly four elements, in which case nothing is pushed. A three-element {r,g,b} table is rejected. Located in modules/styles.lua.

styles.PushDragDisabled() / styles.PopDragDisabled()

Push/pop the faded blue disabled drag style. Renders faded blue backgrounds, no colored border, and muted white text. Located in modules/styles.lua.

Clone this wiki locally