-
Notifications
You must be signed in to change notification settings - Fork 0
Dropdowns
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.
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 legacyUIDropDownMenu - Lazy rebuild — items can be a function that regenerates the list on every open
Creates a nested dropdown widget.
| 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) |
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 |
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,
})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,
})| 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 |
| 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 |
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.
Deep-copy and normalize a single menu item. Used by NormalizeItems and BuildGroupedFontItems.
| 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.
Applies CopyItem to every item in the array. Returns a new array of normalized items.
local normalized = Dropdowns:NormalizeItems(rawItems)Adds a clickable icon button to the right side of a menu item. Returns the modified item.
| 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) |
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.
When opts.width is not provided, CreateNestedDropdown calculates the width from the longest item text:
- Iterates all top-level items
- For group items, also iterates children
- Measures text width using the dropdown's font
- Adds padding for arrow/icon space
- Clamps to a minimum of 150px
You can override this by setting opts.width explicitly.
Force a dropdown's text region and button to a specific width:
Dropdowns:ForceWidth(myDropdown, 200)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)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..."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.
-
Textures:CreateBarDropdown()→Dropdowns:CreateNestedDropdown()with statusbar items
The UI module's CreateFontDropdown and CreateStatusBarDropdown delegate to their respective module dropdowns, which in turn use the Dropdowns module.
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
})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)
endRGX:RegisterEvent("PLAYER_REGEN_ENABLED", function()
-- Safe to reinitialize dropdowns out of combat
RGX:SafeUIDropDownMenu_Initialize(myDropdown, myInitFunc)
end, "myAddon_regenDropdown")