Skip to content

Dropdowns

donniedice edited this page May 1, 2026 · 3 revisions

Dropdowns Module

The Dropdowns module (RGXDropdowns) provides a nested dropdown widget system built on top of WoW's UIDropDownMenu with auto-width, inline buttons, and dual-schema item compatibility.


Overview

RGX Dropdowns wrap WoW's UIDropDownMenu with:

  • Nested submenus — groups with arrows expand into child lists
  • Auto-width — dropdown button width adjusts to match the longest item text
  • Inline buttons — extra clickable icons within menu items
  • Dual-schema items — every item works with both MenuUtil (Dragonflight+) and legacy UIDropDownMenu
  • Lazy rebuild — items can be a function that regenerates the list on every open

CreateNestedDropdown(parent, opts)table

Creates a nested dropdown widget.

Parameters

Parameter Type Required Description
parent Frame Yes Parent frame
opts.title string No Label shown above the dropdown
opts.items table|function Yes Menu items array, or a function returning items (lazy rebuild)
opts.width number No Override width. If nil, auto-calculated from items.
opts.onChange function No Callback when selection changes: onChange(value, item)
opts.initializer function No Custom UIDropDownMenu_Initialize function
opts.keepShownOnClick bool No Keep menu open after clicking an item (default false)

Returns

A holder table with these fields:

Field Type Description
holder.dropdown Frame The UIDropDownMenu widget
holder.button Button The dropdown button
holder.text FontString The current selection text
holder.onChange function Change callback (settable after creation)
holder.value any Current selected value

Example

local Dropdowns = RGX:GetDropdowns()

local holder = Dropdowns:CreateNestedDropdown(parent, {
    title = "Select Option",
    items = {
        {
            text = "Group 1",
            children = {
                { text = "Option A", value = "a" },
                { text = "Option B", value = "b" },
            },
        },
        { text = "Option C", value = "c" },
    },
    onChange = function(value, item)
        print("Selected:", value, item.text)
    end,
})

Lazy Rebuild (Items as Function)

When items is a function, it is called each time the dropdown opens. This ensures the list always reflects current state:

local holder = Dropdowns:CreateNestedDropdown(parent, {
    title = "Dynamic List",
    items = function()
        local list = {}
        for name in pairs(myDynamicData) do
            tinsert(list, { text = name, value = name })
        end
        sort(list, function(a, b) return a.text < b.text end)
        return list
    end,
})

Item Schema

Leaf Items

Field Type RGX Schema Legacy Schema Description
text string Display text
value any Selected value (RGX)
arg1 any Selected value (legacy)
onClick function Click handler (RGX)
func function Click handler (legacy)
checked bool|function Check mark state
disabled bool Grayed out / unclickable
keepShownOnClick bool Keep menu open after click
tooltipTitle string Tooltip title
tooltipText string Tooltip body
icon string Icon texture path
iconSize number Icon dimensions
margin number Left margin/indent

Group Items (Submenus)

Field Type RGX Schema Legacy Schema Description
text string Display text
children table Child items (RGX)
menuList table Child items (legacy)
hasArrow bool Show expand arrow
notCheckable bool No check mark for group

Dual-Schema Convention

RGX produces items that have both schemas populated:

{
    text = "Inter-Regular",
    value = "Inter-Regular",       -- RGX: value
    arg1 = "Inter-Regular",        -- legacy: arg1
    onClick = function() end,      -- RGX: handler
    func = <same function>,        -- legacy: handler
    checked = function() ... end,
    keepShownOnClick = true,
}

Group items:

{
    text = "Inter",
    children = { ... },            -- RGX: children
    menuList = <same table ref>,   -- legacy: menuList (same reference!)
    hasArrow = true,
    notCheckable = true,
}

children and menuList are the same table reference — not a copy. This ensures both systems see the same data.


CopyItem(item)table

Deep-copy and normalize a single menu item. Used by NormalizeItems and BuildGroupedFontItems.

Normalization Rules

Source Field Target Field Condition
menuList children If children is nil, set children = menuList
children menuList If menuList is nil, set menuList = children
arg1 value If value is nil, set value = arg1
value arg1 If arg1 is nil, set arg1 = value
font value If value is nil, set value = font (font dropdown compatibility)
name value If value is nil and name looks like a path, set value = name
func onClick If onClick is nil, set onClick = func
onClick func If func is nil, set func = onClick

This ensures every item is usable by both MenuUtil and legacy UIDropDownMenu systems.


NormalizeItems(items)table

Applies CopyItem to every item in the array. Returns a new array of normalized items.

local normalized = Dropdowns:NormalizeItems(rawItems)

AddInlineButton(item, btnOpts)item

Adds a clickable icon button to the right side of a menu item. Returns the modified item.

Parameters

Parameter Type Required Description
item table Yes Menu item to modify (in-place)
btnOpts.texture string Yes Icon texture path
btnOpts.onClick function Yes Button click handler
btnOpts.width number No Button width (default 16)
btnOpts.height number No Button height (default 16)
btnOpts.margin number No Right margin (default 4)

Example

local item = { text = "My Font", value = "MyFont" }
Dropdowns:AddInlineButton(item, {
    texture = "Interface\\Buttons\\UI-RefreshButton",
    onClick = function()
        print("Reset clicked for", item.value)
    end,
})

Inline buttons are rendered during the dropdown's initialize function by creating a Button inside the menu item's frame.


Auto-Width Calculation

When opts.width is not provided, CreateNestedDropdown calculates the width from the longest item text:

  1. Iterates all top-level items
  2. For group items, also iterates children
  3. Measures text width using the dropdown's font
  4. Adds padding for arrow/icon space
  5. Clamps to a minimum of 150px

You can override this by setting opts.width explicitly.


Utility Methods

ForceWidth(dropdown, width)

Force a dropdown's text region and button to a specific width:

Dropdowns:ForceWidth(myDropdown, 200)

GetListFrame(level)Frame|nil

Get the UIDropDownMenu_ListFrame at a given nesting level. Returns nil if the list frame doesn't exist at that level.

local listFrame = Dropdowns:GetListFrame(1)

ShortenLabel(label, maxChars)string

Truncate a label string with "..." if it exceeds maxChars:

local short = Dropdowns:ShortenLabel("Very Long Font Name That Goes On And On", 20)
-- → "Very Long Font Na..."

Integration with Fonts Module

The Fonts module is the primary consumer of the Dropdowns module:

  • Fonts:CreateFontDropdown()Dropdowns:CreateNestedDropdown() with font items
  • Fonts:BuildGroupedFontItems()Dropdowns:CopyItem() for normalization
  • Fonts:CreateSimpleFontSelector() → simplified variant

See Fonts for font-specific dropdown usage.


Integration with Textures Module

  • Textures:CreateBarDropdown()Dropdowns:CreateNestedDropdown() with statusbar items

Integration with UI Module

The UI module's CreateFontDropdown and CreateStatusBarDropdown delegate to their respective module dropdowns, which in turn use the Dropdowns module.


Common Patterns

Cascading Dropdowns

local mainHolder = Dropdowns:CreateNestedDropdown(parent, {
    title = "Category",
    items = categoryItems,
    onChange = function(value)
        subHolder.items = getSubItems(value)
    end,
})

local subHolder = Dropdowns:CreateNestedDropdown(parent, {
    title = "Subcategory",
    items = {}, -- populated by main selection
})

Dynamic Items with Inline Reset

local items = {}
for name, data in pairs(myData) do
    local item = { text = name, value = name, onClick = function() applySelection(name) end }
    Dropdowns:AddInlineButton(item, {
        texture = "Interface\\Buttons\\UI-StopButton",
        onClick = function() resetItem(name) end,
    })
    tinsert(items, item)
end

Combat-Safe Dropdown Updates

RGX:RegisterEvent("PLAYER_REGEN_ENABLED", function()
    -- Safe to reinitialize dropdowns out of combat
    RGX:SafeUIDropDownMenu_Initialize(myDropdown, myInitFunc)
end, "myAddon_regenDropdown")

Clone this wiki locally