- dialogs carry the reference's toplevel bit, so the stack-split window opens above Bagnon instead of behind it - the failure line fills its blank — "Requires Blacksmith Hammer", not "Requires" — and Opening casts stop printing a label the spell itself marks "no text" - the pet bar's Attack/Follow/Stay tooltips name their keybinding, and typed text reaches the focused frame through the client's own delivery walk - AtlasLoot parses again: our Lua keeps 5.0's compatibility semicolon Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1144 lines
59 KiB
XML
1144 lines
59 KiB
XML
<!--
|
|
The dropdown kit — our own implementation of the 1.12 dropdown widget set. The reference
|
|
(FrameXML UIDropDownMenuTemplates.xml + UIDropDownMenu.lua) is the SPECIFICATION it answers to,
|
|
not the source of this text: the global names, the `info` table's field names, every geometry
|
|
constant and every observable behaviour below are the reference's and are load-bearing (addons
|
|
call, replace and read them by name); the code that expresses them is ours, and is written the
|
|
way we would write it — shared helpers, field lists and lookup tables where the reference
|
|
repeats itself. Decision 0203 phase 2's closing sub-slice, widened for decision 0434 phase 5:
|
|
the unit popups are the first MENU-mode + submenu customer; the world map's continent/zone
|
|
pickers and the trainer/tradeskill filters stay on the level-1 capsule path.
|
|
|
|
IN: UIDropDownMenuTemplate (the capsule: LabelFrame art trio, $parentText, the scroll-down
|
|
toggle button), UIDropDownMenuButtonTemplate (row: highlight, check, right-side icon, expand
|
|
arrow, the disabled-row InvisibleButton), UIDropDownListTemplate (Backdrop + MenuBackdrop
|
|
swap, 32 rows) instanced as DropDownList1/2, and the reference API: Initialize (displayMode +
|
|
level), AddButton(info, level) with isTitle/disabled/notClickable/notCheckable/hasArrow/
|
|
icon+texcoords/textR-G-B/justifyH/keepShownOnClick, Refresh, SetWidth/SetText/GetText,
|
|
SetSelectedID/Name/Value + getters, ClearAll, ToggleDropDownMenu(level, value, frame,
|
|
anchorName ["cursor" included], x, y) with the off-screen flip, HideDropDownMenu,
|
|
CloseDropDownMenus(level), Disable/EnableButton, SetButtonText, SetAnchor, JustifyText,
|
|
GetCurrentDropDown, the 2-second hover-away auto-close counting through the level chain.
|
|
|
|
THE COLOR SWATCH ROW came in with the colour picker (ColorPickerFrame.xml): the full
|
|
`hasColorSwatch` / `r` / `g` / `b` / `swatchFunc` / `hasOpacity` / `opacity` / `opacityFunc` /
|
|
`cancelFunc` field set, the `$parentColorSwatch` button on the row template, and
|
|
`UIDropDownMenuButton_OpenColorPicker` (spec: UIDropDownMenu.lua l.801-815). It is the second
|
|
half of one feature, not a separate one: `hasColorSwatch` draws the square, clicking the square
|
|
is the ONLY way the reference's colour picker ever opens, and `hasColorSwatch` is **145 files /
|
|
82 corpus addons**.
|
|
|
|
OUT (each waits for its first customer): menu level 3 (UIDROPDOWNMENU_MAXLEVELS is 2 — the
|
|
unit popups' deepest law; the XML gains DropDownList3 the day a customer needs it),
|
|
info.textHeight's per-row SetFont, the newbie
|
|
tooltips on rows (the ref gates them behind a default-off cvar), message() on the row/level
|
|
overflow guards (no dev-error dialog surface; the guard returns silently). The uiScale
|
|
correction came IN with its first scaled customer (0992, the options window's dropdown
|
|
rows): Toggle wears the owner's effective scale on the list.
|
|
|
|
Deviations, deliberate:
|
|
- THE WIDTH SETTLE. The ref sizes each row at AddButton time from normalText:GetWidth() and
|
|
applies the list max in OnShow — but our text measures land at extract, AFTER the script
|
|
tick that set the text (ui_script/extract's measure round-trip), so an AddButton-time
|
|
read is stale. The ref's width FORMULA (text + 60, arrow +10, icon +10, notCheckable -30,
|
|
list = max + 15) runs in the list's OnUpdate instead and applies on the first update
|
|
whose measures are real — one frame after open, imperceptible. A capsule that declared a
|
|
width (UIDropDownMenu_SetWidth → dropWidth/noResize: the world map pickers) keeps the
|
|
declared width applied synchronously at Toggle, the proven pre-0434 path (rows dropWidth,
|
|
list dropWidth + 32).
|
|
- THE FLIP SETTLES TOO: the off-screen check needs the list's resolved rect, which also
|
|
lands a frame late — it runs from the same OnUpdate chain, after the width applies.
|
|
- The default (no anchorName) seat anchors the list to the dropdown FRAME at (8, 7) —
|
|
pixel-identical to the reference's "$parentLeft BOTTOMLEFT (8, 22)" through the Left
|
|
texture's own (0, 17) seat.
|
|
- MENU displayMode hides the capsule art trio when present but skips the ref's toggle-button
|
|
texture blanking + re-anchor (every MENU customer here is a born-hidden 10x10 frame whose
|
|
capsule never draws).
|
|
- (Was: "the disabled-row InvisibleButton spans to the row's RIGHT — the ref stops it at the
|
|
color swatch, which is OUT". The swatch is IN now, so the ref's own anchor is restored:
|
|
`RIGHT relativeTo="$parentColorSwatch" relativePoint="LEFT"`.)
|
|
- The list OnShow resets showTimer AND isCounting (the ref writes `showTime`, a field
|
|
nothing reads — we implement the intent, not the typo).
|
|
-->
|
|
<Ui xmlns="http://www.blizzard.com/wow/ui/">
|
|
<Script><![CDATA[
|
|
UIDROPDOWNMENU_MAXBUTTONS = 32;
|
|
UIDROPDOWNMENU_MAXLEVELS = 2;
|
|
UIDROPDOWNMENU_BUTTON_HEIGHT = 16;
|
|
UIDROPDOWNMENU_BORDER_HEIGHT = 15;
|
|
UIDROPDOWNMENU_OPEN_MENU = nil;
|
|
UIDROPDOWNMENU_INIT_MENU = nil;
|
|
UIDROPDOWNMENU_MENU_LEVEL = 1;
|
|
UIDROPDOWNMENU_MENU_VALUE = nil;
|
|
UIDROPDOWNMENU_SHOW_TIME = 2;
|
|
-- Which menus are open, keyed by level. UnitPopup writes it and its OnUpdate enable/disable
|
|
-- pass walks it; each list's own OnHide clears its entry.
|
|
OPEN_DROPDOWNMENUS = {};
|
|
|
|
-- ── The kit's vocabulary ──────────────────────────────────────────────────────────────
|
|
-- Four things this file says over and over, said once here.
|
|
|
|
-- Every named child in the kit is `<owner's name><suffix>`: a row's Check/NormalText/Icon/
|
|
-- ExpandArrow/ColorSwatch/InvisibleButton, a capsule's Left/Middle/Right/Text/Button, the
|
|
-- swatch's NormalTexture. (The reference spells this two different ways for the same
|
|
-- object — off the list name plus the row index, and off the row itself.)
|
|
local function menuChild(frame, suffix)
|
|
return getglobal(frame:GetName()..suffix);
|
|
end
|
|
|
|
-- A row addressed the way the by-address API addresses it: level + index.
|
|
local function listRow(level, index)
|
|
return getglobal("DropDownList"..level.."Button"..index);
|
|
end
|
|
|
|
-- The `info` fields a row simply carries. AddButton copies them onto the row so the row's
|
|
-- own handlers — its click, the width settle, the colour square — can read them back later.
|
|
-- These names are the published API: the list is the documentation.
|
|
local ROW_FIELDS = {
|
|
"func", "owner", "arg1", "arg2", "keepShownOnClick", "tooltipTitle", "tooltipText",
|
|
"hasArrow", "notCheckable", "icon",
|
|
"hasColorSwatch", "hasOpacity", "opacity", "opacityFunc", "swatchFunc", "cancelFunc",
|
|
};
|
|
|
|
-- A capsule remembers its selection under exactly one of these; adopting one drops the
|
|
-- other two, which is why they can never disagree.
|
|
local SELECTION_KEYS = { "selectedName", "selectedID", "selectedValue" };
|
|
|
|
-- The capsule's three-piece LabelFrame art.
|
|
local CAPSULE_ART = { "Left", "Middle", "Right" };
|
|
|
|
-- Reset the levels this dropdown is about to rebuild, remember + run its init function,
|
|
-- then apply displayMode. A DIFFERENT dropdown taking the menu over resets the whole stack
|
|
-- from level 1; the same one keeps whatever sits above the level it is rebuilding.
|
|
function UIDropDownMenu_Initialize(frame, initFunction, displayMode, level)
|
|
frame = frame or this;
|
|
local name = frame:GetName();
|
|
local takeover = (name ~= UIDROPDOWNMENU_OPEN_MENU);
|
|
if ( takeover ) then
|
|
UIDROPDOWNMENU_MENU_LEVEL = 1;
|
|
end
|
|
UIDROPDOWNMENU_INIT_MENU = name;
|
|
for i = 1, UIDROPDOWNMENU_MAXLEVELS, 1 do
|
|
if ( takeover or i >= UIDROPDOWNMENU_MENU_LEVEL ) then
|
|
local list = getglobal("DropDownList"..i);
|
|
list.numButtons = 0;
|
|
for j = 1, UIDROPDOWNMENU_MAXBUTTONS, 1 do
|
|
listRow(i, j):Hide();
|
|
end
|
|
end
|
|
end
|
|
frame:SetHeight(UIDROPDOWNMENU_BUTTON_HEIGHT * 2);
|
|
if ( initFunction ) then
|
|
frame.initialize = initFunction;
|
|
initFunction(level);
|
|
end
|
|
if ( displayMode == "MENU" ) then
|
|
-- Drop the capsule art, when the frame has any (see the header deviation).
|
|
if ( getglobal(name.."Left") ) then
|
|
for _, part in ipairs(CAPSULE_ART) do
|
|
getglobal(name..part):Hide();
|
|
end
|
|
end
|
|
frame.displayMode = "MENU";
|
|
end
|
|
end
|
|
|
|
-- The reference's width law, run from the list's OnUpdate (the header's WIDTH SETTLE): rows
|
|
-- measure text + 60, +10 for an arrow OR a colour swatch, +10 for an icon, -30 when
|
|
-- notCheckable; the list takes max + 15. Waits until every text row has a real (nonzero)
|
|
-- measure. (The ref's `if ( info.hasArrow or info.hasColorSwatch )` is ONE +10, not two —
|
|
-- the two ride the same right-hand slot, and a row never has both.)
|
|
function UIDropDownMenu_SettleWidth(listFrame)
|
|
local listFrameName = listFrame:GetName();
|
|
if ( listFrame.fixedWidth ) then
|
|
listFrame.pendingWidth = nil;
|
|
return;
|
|
end
|
|
local maxWidth = 0;
|
|
local want, seen = 0, 0;
|
|
for i=1, listFrame.numButtons, 1 do
|
|
local button = getglobal(listFrameName.."Button"..i);
|
|
local text = button:GetText();
|
|
if ( text and text ~= "" ) then
|
|
want = want + 1;
|
|
local width = menuChild(button, "NormalText"):GetWidth();
|
|
if ( width > 0 ) then
|
|
seen = seen + 1;
|
|
width = width + 60;
|
|
if ( button.hasArrow or button.hasColorSwatch ) then
|
|
width = width + 10;
|
|
end
|
|
if ( button.notCheckable ) then
|
|
width = width - 30;
|
|
end
|
|
if ( button.icon ) then
|
|
width = width + 10;
|
|
end
|
|
if ( width > maxWidth ) then
|
|
maxWidth = width;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if ( want > 0 and seen < want ) then
|
|
return; -- measures not landed yet; next update
|
|
end
|
|
if ( maxWidth == 0 ) then
|
|
maxWidth = 130;
|
|
end
|
|
for i=1, UIDROPDOWNMENU_MAXBUTTONS, 1 do
|
|
getglobal(listFrameName.."Button"..i):SetWidth(maxWidth);
|
|
end
|
|
listFrame:SetWidth(maxWidth + 15);
|
|
listFrame.pendingWidth = nil;
|
|
end
|
|
|
|
-- Which of the list's corners meets which corner of its seat once the list is known to hang
|
|
-- off the bottom and/or the right of the screen. Level 1 re-seats against the anchor Toggle
|
|
-- stored and needs no nudge; a submenu flips around the row that opened it, and the nudge
|
|
-- is what keeps the flipped list glued to that row.
|
|
local FLIP = {
|
|
bottomRight = { seat = { "BOTTOMRIGHT", "BOTTOMLEFT" },
|
|
row = { "BOTTOMRIGHT", "BOTTOMLEFT", -11, -14 } },
|
|
bottom = { seat = { "BOTTOMLEFT", "TOPLEFT" },
|
|
row = { "BOTTOMLEFT", "BOTTOMRIGHT", 0, -14 } },
|
|
right = { seat = { "TOPRIGHT", "TOPLEFT" },
|
|
row = { "TOPRIGHT", "TOPLEFT", -11, 14 } },
|
|
};
|
|
|
|
-- The off-screen flip, from the same OnUpdate chain as the width (it needs the resolved
|
|
-- rect — the header's second settle).
|
|
function UIDropDownMenu_SettleFlip(listFrame)
|
|
local x, y = listFrame:GetCenter();
|
|
if ( not x or not y ) then
|
|
return; -- not resolved yet; next update
|
|
end
|
|
listFrame.pendingFlip = nil;
|
|
local rightEdge = listFrame:GetRight();
|
|
local offBottom = (y - listFrame:GetHeight()/2) < 0;
|
|
local offRight = (rightEdge ~= nil) and (rightEdge > GetScreenWidth());
|
|
local flip;
|
|
if ( offBottom and offRight ) then
|
|
flip = FLIP.bottomRight;
|
|
elseif ( offBottom ) then
|
|
flip = FLIP.bottom;
|
|
elseif ( offRight ) then
|
|
flip = FLIP.right;
|
|
else
|
|
return; -- it fits; the seat Toggle gave it stands
|
|
end
|
|
listFrame:ClearAllPoints();
|
|
if ( listFrame:GetID() == 1 ) then
|
|
local corner = flip.seat;
|
|
if ( listFrame.cursorMode ) then
|
|
-- Cursor menus keep their screen-space seat; only the corner changes.
|
|
listFrame:SetPoint(corner[1], "UIParent", "BOTTOMLEFT", listFrame.seatX, listFrame.seatY);
|
|
else
|
|
listFrame:SetPoint(corner[1], listFrame.seatTo, corner[2], listFrame.seatX, listFrame.seatY);
|
|
end
|
|
else
|
|
local corner = flip.row;
|
|
listFrame:SetPoint(corner[1], listFrame.anchorFrame, corner[2], corner[3], corner[4]);
|
|
end
|
|
end
|
|
|
|
-- The list's per-frame work: settle the width, then the flip (both wait on measures that
|
|
-- land a frame after the open — the header), then run the hover-away countdown.
|
|
function UIDropDownMenu_OnUpdate(elapsed)
|
|
if ( this.pendingWidth ) then
|
|
UIDropDownMenu_SettleWidth(this);
|
|
elseif ( this.pendingFlip ) then
|
|
UIDropDownMenu_SettleFlip(this);
|
|
end
|
|
-- The countdown runs only on a visible list that a StartCounting has armed (never on
|
|
-- open); reaching zero is what closes a menu the mouse walked away from.
|
|
if ( not this:IsVisible() or not this.isCounting or not this.showTimer ) then
|
|
return;
|
|
end
|
|
if ( this.showTimer < 0 ) then
|
|
this.showTimer = nil;
|
|
this.isCounting = nil;
|
|
this:Hide();
|
|
else
|
|
this.showTimer = this.showTimer - elapsed;
|
|
end
|
|
end
|
|
|
|
-- A submenu's hover state drives its ROOT list's timer, so the whole open stack lives or
|
|
-- dies together: both ends of the countdown climb `parent` to the list that owns the stack.
|
|
local function timerRoot(frame)
|
|
while ( frame.parent ) do
|
|
frame = frame.parent;
|
|
end
|
|
return frame;
|
|
end
|
|
|
|
function UIDropDownMenu_StartCounting(frame)
|
|
local root = timerRoot(frame);
|
|
root.showTimer = UIDROPDOWNMENU_SHOW_TIME;
|
|
root.isCounting = 1;
|
|
end
|
|
|
|
function UIDropDownMenu_StopCounting(frame)
|
|
local root = timerRoot(frame);
|
|
root.isCounting = nil;
|
|
end
|
|
|
|
-- The kit's one selection test, answering in the reference's own `[nil, 1]` currency. Name
|
|
-- beats id beats value, and only the first key the capsule actually carries is consulted —
|
|
-- note that GetSelectedID answers for a value-selected capsule too (it scans the open level
|
|
-- for the row), so the value arm below is reached only when that scan found nothing.
|
|
local function rowChecked(frame, button)
|
|
local name = UIDropDownMenu_GetSelectedName(frame);
|
|
if ( name ) then
|
|
return button:GetText() == name and 1 or nil;
|
|
end
|
|
local id = UIDropDownMenu_GetSelectedID(frame);
|
|
if ( id ) then
|
|
return button:GetID() == id and 1 or nil;
|
|
end
|
|
local value = UIDropDownMenu_GetSelectedValue(frame);
|
|
if ( value ) then
|
|
return button.value == value and 1 or nil;
|
|
end
|
|
end
|
|
|
|
-- Paint one row's checked state. The tick and the locked highlight always move together,
|
|
-- and the row remembers what it was told (its click handler reads it back).
|
|
local function paintChecked(button, checked)
|
|
if ( checked ) then
|
|
button:LockHighlight();
|
|
menuChild(button, "Check"):Show();
|
|
else
|
|
button:UnlockHighlight();
|
|
menuChild(button, "Check"):Hide();
|
|
end
|
|
button.checked = checked;
|
|
end
|
|
|
|
-- Build one menu row from an `info` table — `.text`/`.value`/`.func`/`.checked`/`.isTitle`/
|
|
-- `.disabled`/`.notClickable`/`.notCheckable`/`.hasArrow`/`.icon` + `.tCoord*`/`.textR-G-B`/
|
|
-- `.justifyH`/`.keepShownOnClick`/`.owner`/`.arg1`/`.arg2`/the tooltip pair/the colour-swatch
|
|
-- set. That table is the published API and its field names are the contract. Width rides the
|
|
-- settle (header), not an AddButton-time measure.
|
|
function UIDropDownMenu_AddButton(info, level)
|
|
level = level or 1;
|
|
local listFrame = getglobal("DropDownList"..level);
|
|
local listFrameName = listFrame:GetName();
|
|
local index = listFrame.numButtons + 1;
|
|
if ( index > UIDROPDOWNMENU_MAXBUTTONS or level > UIDROPDOWNMENU_MAXLEVELS ) then
|
|
return; -- the ref raises a dev-error dialog here (OUT, header)
|
|
end
|
|
listFrame.numButtons = index;
|
|
|
|
local button = listRow(level, index);
|
|
local normalText = menuChild(button, "NormalText");
|
|
local icon = menuChild(button, "Icon");
|
|
-- Captures OnEnter/OnLeave over a disabled row so the menu doesn't time out.
|
|
local invisibleButton = menuChild(button, "InvisibleButton");
|
|
invisibleButton:Hide();
|
|
button:Enable();
|
|
|
|
-- The label, and its colour: a row carrying its own textR/G/B wears it in both the
|
|
-- normal and the highlight state, so the label doesn't change colour under the mouse.
|
|
if ( info.text ) then
|
|
button:SetText(info.text);
|
|
local r, g, b = info.textR, info.textG, info.textB;
|
|
if ( not r ) then
|
|
r, g, b = HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b;
|
|
end
|
|
button:SetTextColor(r, g, b);
|
|
button:SetHighlightTextColor(r, g, b);
|
|
else
|
|
button:SetText("");
|
|
end
|
|
|
|
-- The right-hand icon rides on the label: a row with no text has no icon either.
|
|
if ( info.text and info.icon ) then
|
|
icon:SetTexture(info.icon);
|
|
if ( info.tCoordLeft ) then
|
|
icon:SetTexCoord(info.tCoordLeft, info.tCoordRight, info.tCoordTop, info.tCoordBottom);
|
|
end
|
|
icon:Show();
|
|
else
|
|
icon:Hide();
|
|
end
|
|
|
|
-- Everything the row simply carries over, then the value, which falls back to the label
|
|
-- so a menu built from plain text rows still has values to dispatch on.
|
|
for _, field in ipairs(ROW_FIELDS) do
|
|
button[field] = info[field];
|
|
end
|
|
button.value = info.value or info.text;
|
|
|
|
local expandArrow = menuChild(button, "ExpandArrow");
|
|
if ( info.hasArrow ) then
|
|
expandArrow:Show();
|
|
else
|
|
expandArrow:Hide();
|
|
end
|
|
|
|
-- The row's left inset, and its label's seat inside it. A notCheckable row (titles,
|
|
-- plain actions) pulls the label to the row's left edge and sits 10px in; a checkable
|
|
-- row leaves the 27px check column and sits 12px in — 6px tighter under MENU mode,
|
|
-- whose frames have no capsule for the rows to line up beneath.
|
|
local xPos = 5;
|
|
local yPos = -((button:GetID() - 1) * UIDROPDOWNMENU_BUTTON_HEIGHT) - UIDROPDOWNMENU_BORDER_HEIGHT;
|
|
normalText:ClearAllPoints();
|
|
if ( not info.notCheckable ) then
|
|
xPos = xPos + 12;
|
|
normalText:SetPoint("LEFT", button:GetName(), "LEFT", 27, 0);
|
|
elseif ( info.justifyH == "CENTER" ) then
|
|
xPos = xPos + 10;
|
|
normalText:SetPoint("CENTER", button:GetName(), "CENTER", -7, 0);
|
|
else
|
|
xPos = xPos + 10;
|
|
normalText:SetPoint("LEFT", button:GetName(), "LEFT", 0, 0);
|
|
end
|
|
|
|
-- Whose menu this is: the open capsule, or — nothing open yet — the one initializing.
|
|
local frame = UIDROPDOWNMENU_OPEN_MENU and getglobal(UIDROPDOWNMENU_OPEN_MENU);
|
|
if ( frame and frame.displayMode == "MENU" and not info.notCheckable ) then
|
|
xPos = xPos - 6;
|
|
end
|
|
frame = frame or getglobal(UIDROPDOWNMENU_INIT_MENU);
|
|
button:SetPoint("TOPLEFT", listFrameName, "TOPLEFT", xPos, yPos);
|
|
|
|
-- A row the owning capsule points at comes up checked. `info.checked` is only ever set
|
|
-- here, never cleared: a caller that declared the row checked keeps it.
|
|
if ( frame and rowChecked(frame, button) ) then
|
|
info.checked = 1;
|
|
end
|
|
paintChecked(button, info.checked);
|
|
|
|
-- The colour square: its NormalTexture IS the swatch art, tinted to the row's colour,
|
|
-- and the row keeps r/g/b so the square's own OnClick can seed the picker from them.
|
|
local colorSwatch = menuChild(button, "ColorSwatch");
|
|
if ( info.hasColorSwatch ) then
|
|
button.r, button.g, button.b = info.r, info.g, info.b;
|
|
menuChild(colorSwatch, "NormalTexture"):SetVertexColor(button.r, button.g, button.b);
|
|
colorSwatch:Show();
|
|
else
|
|
colorSwatch:Hide();
|
|
end
|
|
|
|
-- Three ways a row lands disabled, each choosing the colour its label wears while it is:
|
|
-- a title is gold, a notClickable row is white, a plainly disabled one keeps the gray
|
|
-- default. A title wins, which is the reference's own order (it applies isTitle second).
|
|
local disabledColor = GRAY_FONT_COLOR;
|
|
if ( info.notClickable ) then
|
|
info.disabled = 1;
|
|
disabledColor = HIGHLIGHT_FONT_COLOR;
|
|
end
|
|
if ( info.isTitle ) then
|
|
info.disabled = 1;
|
|
disabledColor = NORMAL_FONT_COLOR;
|
|
end
|
|
button:SetDisabledTextColor(disabledColor.r, disabledColor.g, disabledColor.b);
|
|
if ( info.disabled ) then
|
|
button:Disable();
|
|
invisibleButton:Show();
|
|
end
|
|
|
|
listFrame:SetHeight((index * UIDROPDOWNMENU_BUTTON_HEIGHT) + (UIDROPDOWNMENU_BORDER_HEIGHT * 2));
|
|
button:Show();
|
|
end
|
|
|
|
-- Re-derive every row's check + lock state on one level, and push the selected row's label
|
|
-- into the capsule's own text. Width stays the settle's job (header).
|
|
function UIDropDownMenu_Refresh(frame, useValue, dropdownLevel)
|
|
frame = frame or this;
|
|
dropdownLevel = dropdownLevel or UIDROPDOWNMENU_MENU_LEVEL;
|
|
for i = 1, UIDROPDOWNMENU_MAXBUTTONS, 1 do
|
|
local button = listRow(dropdownLevel, i);
|
|
local checked = rowChecked(frame, button);
|
|
if ( checked ) then
|
|
if ( useValue ) then
|
|
UIDropDownMenu_SetText(button.value, frame);
|
|
else
|
|
UIDropDownMenu_SetText(button:GetText(), frame);
|
|
end
|
|
end
|
|
paintChecked(button, checked);
|
|
end
|
|
end
|
|
|
|
-- The three setters are one operation: adopt one selection key, drop the other two, repaint.
|
|
local function selectBy(frame, key, selection, useValue)
|
|
for _, field in ipairs(SELECTION_KEYS) do
|
|
frame[field] = nil;
|
|
end
|
|
frame[key] = selection;
|
|
UIDropDownMenu_Refresh(frame, useValue);
|
|
end
|
|
|
|
function UIDropDownMenu_SetSelectedName(frame, name, useValue)
|
|
selectBy(frame, "selectedName", name, useValue);
|
|
end
|
|
|
|
function UIDropDownMenu_SetSelectedValue(frame, value, useValue)
|
|
selectBy(frame, "selectedValue", value, useValue);
|
|
end
|
|
|
|
function UIDropDownMenu_SetSelectedID(frame, id, useValue)
|
|
selectBy(frame, "selectedID", id, useValue);
|
|
end
|
|
|
|
function UIDropDownMenu_GetSelectedName(frame)
|
|
return frame.selectedName;
|
|
end
|
|
|
|
function UIDropDownMenu_GetSelectedValue(frame)
|
|
return frame.selectedValue;
|
|
end
|
|
|
|
-- With no explicit id, answer with the index of the row the selected name — failing that,
|
|
-- the selected value — points at on the open level.
|
|
function UIDropDownMenu_GetSelectedID(frame)
|
|
if ( frame.selectedID ) then
|
|
return frame.selectedID;
|
|
end
|
|
local name = UIDropDownMenu_GetSelectedName(frame);
|
|
local value = UIDropDownMenu_GetSelectedValue(frame);
|
|
if ( not name and not value ) then
|
|
return nil;
|
|
end
|
|
for i = 1, UIDROPDOWNMENU_MAXBUTTONS, 1 do
|
|
local button = listRow(UIDROPDOWNMENU_MENU_LEVEL, i);
|
|
if ( name ) then
|
|
if ( button:GetText() == name ) then
|
|
return i;
|
|
end
|
|
elseif ( button.value == value ) then
|
|
return i;
|
|
end
|
|
end
|
|
end
|
|
|
|
-- A row's click: run its func with `this` still on the row, then either flip the check in
|
|
-- place (keepShownOnClick) or shut the list the row belongs to. A row with no func does
|
|
-- nothing at all — not even the sound.
|
|
function UIDropDownMenuButton_OnClick()
|
|
if ( not this.func ) then
|
|
return;
|
|
end
|
|
this.func(this.arg1, this.arg2);
|
|
if ( not this.keepShownOnClick ) then
|
|
this:GetParent():Hide();
|
|
elseif ( this.checked ) then
|
|
this.checked = nil;
|
|
menuChild(this, "Check"):Hide();
|
|
else
|
|
this.checked = 1;
|
|
menuChild(this, "Check"):Show();
|
|
end
|
|
PlaySound("UChatScrollButton");
|
|
end
|
|
|
|
function HideDropDownMenu(level)
|
|
getglobal("DropDownList"..(level or 1)):Hide();
|
|
end
|
|
|
|
-- Open (or re-open) one level of the shared menu. The capsule that is already showing this
|
|
-- level closes it; anything else rebuilds through the stored initialize, seats the list (the
|
|
-- capsule seat, a named anchor, or the cursor), dresses it for its display mode and shows
|
|
-- it. Width and the off-screen flip settle from OnUpdate (header).
|
|
function ToggleDropDownMenu(level, value, dropDownFrame, anchorName, xOffset, yOffset)
|
|
level = level or 1;
|
|
UIDROPDOWNMENU_MENU_LEVEL = level;
|
|
UIDROPDOWNMENU_MENU_VALUE = value;
|
|
local listFrameName = "DropDownList"..level;
|
|
local listFrame = getglobal(listFrameName);
|
|
-- Called straight off a capsule's scroll button, `this` is that button and the capsule
|
|
-- it serves is its parent.
|
|
local owner = dropDownFrame or this:GetParent();
|
|
if ( listFrame:IsVisible() and UIDROPDOWNMENU_OPEN_MENU == owner:GetName() ) then
|
|
listFrame:Hide();
|
|
return;
|
|
end
|
|
listFrame:Hide();
|
|
|
|
-- The uiScale correction, arriving with its first scaled customer (0992: the options
|
|
-- window rides SetScale, and a list at scale 1 would draw its rows ~28% larger than the
|
|
-- capsule they serve): the shared list wears the OWNER's effective scale for this open.
|
|
-- Every pre-0992 customer sits at effective scale 1 — no-op.
|
|
local listScale = owner:GetEffectiveScale();
|
|
listFrame:SetScale(listScale);
|
|
|
|
if ( level == 1 ) then
|
|
dropDownFrame = owner;
|
|
UIDROPDOWNMENU_OPEN_MENU = dropDownFrame:GetName();
|
|
listFrame:ClearAllPoints();
|
|
listFrame.cursorMode = nil;
|
|
local point, relativeTo, relativePoint;
|
|
if ( not anchorName ) then
|
|
-- The capsule seat (header deviation), overridable via SetAnchor.
|
|
point = dropDownFrame.point or "TOPLEFT";
|
|
relativeTo = dropDownFrame.relativeTo or dropDownFrame:GetName();
|
|
relativePoint = dropDownFrame.relativePoint or "BOTTOMLEFT";
|
|
xOffset = dropDownFrame.xOffset or 8;
|
|
yOffset = dropDownFrame.yOffset or 7;
|
|
elseif ( anchorName == "cursor" ) then
|
|
-- Cursor coords are scale-1 units, and a scaled list's anchor offsets are in
|
|
-- ITS space, so divide (the reference's own cursor/uiScale dance).
|
|
local cursorX, cursorY = GetCursorPosition();
|
|
point = "TOPLEFT";
|
|
relativeTo = "UIParent";
|
|
relativePoint = "BOTTOMLEFT";
|
|
xOffset = cursorX / listScale + (xOffset or 0);
|
|
yOffset = cursorY / listScale + (yOffset or 0);
|
|
listFrame.cursorMode = 1;
|
|
else
|
|
point = "TOPLEFT";
|
|
relativeTo = anchorName;
|
|
relativePoint = "BOTTOMLEFT";
|
|
if ( not xOffset or not yOffset ) then
|
|
xOffset = 8;
|
|
yOffset = 22;
|
|
end
|
|
end
|
|
listFrame:SetPoint(point, relativeTo, relativePoint, xOffset, yOffset);
|
|
-- What the flip re-seats against (SettleFlip).
|
|
listFrame.seatTo = relativeTo;
|
|
listFrame.seatX = xOffset;
|
|
listFrame.seatY = yOffset;
|
|
else
|
|
dropDownFrame = dropDownFrame or getglobal(UIDROPDOWNMENU_OPEN_MENU);
|
|
listFrame:ClearAllPoints();
|
|
-- Anchor to the row itself, whether `this` is the row (whose parent is the bare
|
|
-- list — name length 13) or the row's ExpandArrow child.
|
|
local parentName = this:GetParent():GetName();
|
|
local anchorFrame = parentName;
|
|
if ( string.sub(parentName, 1, 12) == "DropDownList" and string.len(parentName) == 13 ) then
|
|
anchorFrame = this:GetName();
|
|
end
|
|
listFrame:SetPoint("TOPLEFT", anchorFrame, "TOPRIGHT", 0, 0);
|
|
listFrame.anchorFrame = anchorFrame;
|
|
end
|
|
|
|
-- MENU mode swaps the dialog box for the tooltip-style backdrop; exactly one is up.
|
|
local wanted, unwanted = "Backdrop", "MenuBackdrop";
|
|
if ( dropDownFrame and dropDownFrame.displayMode == "MENU" ) then
|
|
wanted, unwanted = "MenuBackdrop", "Backdrop";
|
|
end
|
|
getglobal(listFrameName..wanted):Show();
|
|
getglobal(listFrameName..unwanted):Hide();
|
|
|
|
UIDropDownMenu_Initialize(dropDownFrame, dropDownFrame.initialize, nil, level);
|
|
if ( listFrame.numButtons == 0 ) then
|
|
return;
|
|
end
|
|
|
|
-- A capsule that declared a width applies it synchronously (the proven pre-0434 path);
|
|
-- auto-sized menus ride the settle.
|
|
listFrame.fixedWidth = nil;
|
|
if ( dropDownFrame.noResize and dropDownFrame.dropWidth ) then
|
|
listFrame.fixedWidth = dropDownFrame.dropWidth;
|
|
for i = 1, UIDROPDOWNMENU_MAXBUTTONS, 1 do
|
|
listRow(level, i):SetWidth(listFrame.fixedWidth);
|
|
end
|
|
listFrame:SetWidth(listFrame.fixedWidth + 32);
|
|
end
|
|
listFrame:Show();
|
|
end
|
|
|
|
function CloseDropDownMenus(level)
|
|
for i = (level or 1), UIDROPDOWNMENU_MAXLEVELS, 1 do
|
|
getglobal("DropDownList"..i):Hide();
|
|
end
|
|
end
|
|
|
|
-- CloseMenus() — in the ref a C function (the ESC ladder's rung between the game-menu
|
|
-- checks and SpellStopCasting, UIParent.lua l.1488): close any open menu, truthy iff one
|
|
-- was open so the ladder eats the press. Every menu we have is a DropDownList, so the
|
|
-- Lua equivalent lives here with them (decision 0454).
|
|
function CloseMenus()
|
|
local closed
|
|
for i=1, UIDROPDOWNMENU_MAXLEVELS, 1 do
|
|
local list = getglobal("DropDownList"..i)
|
|
if list and list:IsVisible() then
|
|
closed = 1
|
|
end
|
|
end
|
|
if closed then
|
|
CloseDropDownMenus()
|
|
end
|
|
return closed
|
|
end
|
|
|
|
-- The capsule's declared width: the Middle art stretches to it, the frame itself carries the
|
|
-- two 25px end caps, and the label gets 25px less than the art. `dropWidth` is ours — the
|
|
-- fixed-width path in Toggle reuses the number (see the header).
|
|
function UIDropDownMenu_SetWidth(width, frame)
|
|
frame = frame or this;
|
|
menuChild(frame, "Middle"):SetWidth(width);
|
|
frame:SetWidth(width + 25 + 25);
|
|
menuChild(frame, "Text"):SetWidth(width - 25);
|
|
frame.dropWidth = width;
|
|
frame.noResize = 1;
|
|
end
|
|
|
|
function UIDropDownMenu_SetButtonWidth(width, frame)
|
|
frame = frame or this;
|
|
if ( width == "TEXT" ) then
|
|
width = menuChild(frame, "Text"):GetWidth();
|
|
end
|
|
menuChild(frame, "Button"):SetWidth(width);
|
|
frame.noResize = 1;
|
|
end
|
|
|
|
function UIDropDownMenu_SetText(text, frame)
|
|
frame = frame or this;
|
|
menuChild(frame, "Text"):SetText(text);
|
|
end
|
|
|
|
function UIDropDownMenu_GetText(frame)
|
|
frame = frame or this;
|
|
return menuChild(frame, "Text"):GetText();
|
|
end
|
|
|
|
-- Forget the selection and strip the marks off the open level's rows. Deliberately NOT a
|
|
-- Refresh: the reference found refreshing from here a bottleneck, and the rows are about to
|
|
-- be rebuilt anyway.
|
|
function UIDropDownMenu_ClearAll(frame)
|
|
for _, field in ipairs(SELECTION_KEYS) do
|
|
frame[field] = nil;
|
|
end
|
|
UIDropDownMenu_SetText("", frame);
|
|
for i = 1, UIDROPDOWNMENU_MAXBUTTONS, 1 do
|
|
local button = listRow(UIDROPDOWNMENU_MENU_LEVEL, i);
|
|
button:UnlockHighlight();
|
|
menuChild(button, "Check"):Hide();
|
|
end
|
|
end
|
|
|
|
-- Where the capsule's own label sits: against the left cap, against the right one (clear of
|
|
-- the scroll button), or centred on the Middle art. {point, art piece, its point, x, y}.
|
|
local JUSTIFY = {
|
|
LEFT = { "LEFT", "Left", "LEFT", 27, 2 },
|
|
RIGHT = { "RIGHT", "Right", "RIGHT", -43, 2 },
|
|
CENTER = { "CENTER", "Middle", "CENTER", -5, 2 },
|
|
};
|
|
|
|
function UIDropDownMenu_JustifyText(justification, frame)
|
|
frame = frame or this;
|
|
local text = menuChild(frame, "Text");
|
|
text:ClearAllPoints();
|
|
local seat = JUSTIFY[justification];
|
|
if ( seat ) then
|
|
text:SetPoint(seat[1], frame:GetName()..seat[2], seat[3], seat[4], seat[5]);
|
|
end
|
|
end
|
|
|
|
function UIDropDownMenu_SetAnchor(xOffset, yOffset, dropdown, point, relativeTo, relativePoint)
|
|
dropdown = dropdown or this;
|
|
dropdown.point, dropdown.relativeTo, dropdown.relativePoint = point, relativeTo, relativePoint;
|
|
dropdown.xOffset, dropdown.yOffset = xOffset, yOffset;
|
|
end
|
|
|
|
-- The dropdown a menu handler is acting for: the open capsule, or `this` when nothing is
|
|
-- open (a row's own func asking who it belongs to).
|
|
function UIDropDownMenu_GetCurrentDropDown()
|
|
if ( not UIDROPDOWNMENU_OPEN_MENU ) then
|
|
return this;
|
|
end
|
|
return getglobal(UIDROPDOWNMENU_OPEN_MENU);
|
|
end
|
|
|
|
function UIDropDownMenuButton_GetChecked()
|
|
return menuChild(this, "Check"):IsVisible();
|
|
end
|
|
|
|
function UIDropDownMenuButton_GetName()
|
|
return menuChild(this, "NormalText"):GetText();
|
|
end
|
|
|
|
-- The picker fields a swatch row seeds under the same name on both sides. `swatchFunc` is
|
|
-- the exception, set beside them: it lands on the picker as `func`, which is why it is the
|
|
-- LIVE-preview callback and not an on-accept one.
|
|
local PICKER_FIELDS = { "hasOpacity", "opacity", "opacityFunc", "cancelFunc" };
|
|
|
|
-- THE entry point to the colour picker (spec: UIDropDownMenu.lua l.801-815): every colour
|
|
-- option in every FrameXML dropdown and every Ace2/Dewdrop menu row lands here. Note what it
|
|
-- does that the picker itself does not — `previousValues` is captured HERE, and it is the
|
|
-- table `cancelFunc` is handed, so Cancel restoring the old colour is this function's doing.
|
|
function UIDropDownMenuButton_OpenColorPicker(button)
|
|
CloseMenus();
|
|
button = button or this;
|
|
UIDROPDOWNMENU_MENU_VALUE = button.value;
|
|
ColorPickerFrame.func = button.swatchFunc;
|
|
for _, field in ipairs(PICKER_FIELDS) do
|
|
ColorPickerFrame[field] = button[field];
|
|
end
|
|
ColorPickerFrame:SetColorRGB(button.r, button.g, button.b);
|
|
ColorPickerFrame.previousValues = {r = button.r, g = button.g, b = button.b, opacity = button.opacity};
|
|
ShowUIPanel(ColorPickerFrame);
|
|
end
|
|
|
|
-- The by-address trio: an addon that knows a row's level and index, rather than holding the
|
|
-- row itself, reaches it through these.
|
|
function UIDropDownMenu_DisableButton(level, id)
|
|
listRow(level, id):Disable();
|
|
end
|
|
|
|
function UIDropDownMenu_EnableButton(level, id)
|
|
listRow(level, id):Enable();
|
|
end
|
|
|
|
function UIDropDownMenu_SetButtonText(level, id, text, r, g, b)
|
|
local button = listRow(level, id);
|
|
button:SetText(text);
|
|
if ( r ) then
|
|
button:SetTextColor(r, g, b);
|
|
button:SetHighlightTextColor(r, g, b);
|
|
end
|
|
end
|
|
|
|
-- ── What the row template's handlers do ───────────────────────────────────────────────
|
|
-- Everything that sits on a menu row — the row itself, its colour square, its expand arrow,
|
|
-- the invisible cover over a disabled row — means the same two things to the menu when the
|
|
-- mouse arrives and when it leaves, and both are about the row's LIST rather than the row:
|
|
-- the timeout that closes a menu the mouse walked away from lives on the list, and so does
|
|
-- the level number that decides which lists count as deeper than this one. Five handlers
|
|
-- want exactly that, so it is written here once instead of five times in markup.
|
|
|
|
function UIDropDownMenuRow_HoverIn(list)
|
|
UIDropDownMenu_StopCounting(list);
|
|
CloseDropDownMenus(list:GetID() + 1);
|
|
end
|
|
|
|
function UIDropDownMenuRow_HoverOut(list)
|
|
UIDropDownMenu_StartCounting(list);
|
|
end
|
|
|
|
-- A `hasArrow` row opens its submenu one level below the list it sits in, carrying the row's
|
|
-- value as that submenu's context. Hovering the row, hovering the arrow and clicking the
|
|
-- arrow are three ways to ask for the same thing.
|
|
function UIDropDownMenuRow_OpenSubmenu(row)
|
|
ToggleDropDownMenu(row:GetParent():GetID() + 1, row.value);
|
|
end
|
|
|
|
-- The colour square's hover feedback. The square itself IS the row's colour, so the only
|
|
-- thing free to react is the 14x14 backing behind it: gold under the mouse, white off it.
|
|
function UIDropDownMenuSwatch_Highlight(swatch, color)
|
|
getglobal(swatch:GetName().."SwatchBg"):SetVertexColor(color.r, color.g, color.b);
|
|
end
|
|
]]></Script>
|
|
|
|
<!-- One menu row (ref UIDropDownMenuButtonTemplate: highlight, check, right-side icon, the
|
|
expand arrow, the disabled-row InvisibleButton; text re-anchored per AddButton). -->
|
|
<Button name="UIDropDownMenuButtonTemplate" virtual="true" hidden="true">
|
|
<Size><AbsDimension x="128" y="16"/></Size>
|
|
<Layers>
|
|
<Layer level="BACKGROUND">
|
|
<Texture name="$parentHighlight" file="Interface\QuestFrame\UI-QuestTitleHighlight" alphaMode="ADD" setAllPoints="true" hidden="true"/>
|
|
</Layer>
|
|
<Layer level="ARTWORK">
|
|
<Texture name="$parentCheck" file="Interface\Buttons\UI-CheckBox-Check">
|
|
<Size><AbsDimension x="24" y="24"/></Size>
|
|
<Anchors><Anchor point="LEFT"/></Anchors>
|
|
</Texture>
|
|
<Texture name="$parentIcon" hidden="true">
|
|
<Size><AbsDimension x="15" y="15"/></Size>
|
|
<Anchors><Anchor point="RIGHT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
|
|
</Texture>
|
|
</Layer>
|
|
</Layers>
|
|
<Frames>
|
|
<!-- ref UIDropDownMenuTemplates.xml l.38-83. The colour square: a 16x16 button whose
|
|
NormalTexture is the ChatFrameColorSwatch art, tinted per row, over a 14x14
|
|
`$parentSwatchBg` that the hover recolours (gold in, white out) — that background
|
|
is the row's only hover feedback, since the square itself is the row's colour.
|
|
Clicking it is the ONLY thing in the whole reference UI that opens the colour
|
|
picker. -->
|
|
<Button name="$parentColorSwatch" hidden="true">
|
|
<Size><AbsDimension x="16" y="16"/></Size>
|
|
<Anchors><Anchor point="RIGHT"><Offset><AbsDimension x="-6" y="0"/></Offset></Anchor></Anchors>
|
|
<Layers>
|
|
<Layer level="BACKGROUND">
|
|
<Texture name="$parentSwatchBg">
|
|
<Size><AbsDimension x="14" y="14"/></Size>
|
|
<Anchors><Anchor point="CENTER"/></Anchors>
|
|
<Color r="1.0" g="1.0" b="1.0"/>
|
|
</Texture>
|
|
</Layer>
|
|
</Layers>
|
|
<Scripts>
|
|
<!-- The open closes the menu first — that is `OpenColorPicker`'s own first
|
|
line, so it is not repeated here. -->
|
|
<OnClick>
|
|
UIDropDownMenuButton_OpenColorPicker(this:GetParent());
|
|
</OnClick>
|
|
<OnEnter>
|
|
UIDropDownMenuRow_HoverIn(this:GetParent():GetParent());
|
|
UIDropDownMenuSwatch_Highlight(this, NORMAL_FONT_COLOR);
|
|
</OnEnter>
|
|
<OnLeave>
|
|
UIDropDownMenuRow_HoverOut(this:GetParent():GetParent());
|
|
UIDropDownMenuSwatch_Highlight(this, HIGHLIGHT_FONT_COLOR);
|
|
</OnLeave>
|
|
</Scripts>
|
|
<NormalTexture name="$parentNormalTexture" file="Interface\ChatFrame\ChatFrameColorSwatch"/>
|
|
</Button>
|
|
<Button name="$parentExpandArrow" hidden="true">
|
|
<Size><AbsDimension x="16" y="16"/></Size>
|
|
<Anchors><Anchor point="RIGHT"><Offset><AbsDimension x="-6" y="0"/></Offset></Anchor></Anchors>
|
|
<Scripts>
|
|
<OnClick>
|
|
UIDropDownMenuRow_OpenSubmenu(this:GetParent());
|
|
</OnClick>
|
|
<!-- Hovering the arrow opens the submenu, so this is the one hover on the row
|
|
that must NOT close the deeper levels — only the timeout stops. -->
|
|
<OnEnter>
|
|
UIDropDownMenuRow_OpenSubmenu(this:GetParent());
|
|
UIDropDownMenu_StopCounting(this:GetParent():GetParent());
|
|
</OnEnter>
|
|
<OnLeave>
|
|
UIDropDownMenuRow_HoverOut(this:GetParent():GetParent());
|
|
</OnLeave>
|
|
</Scripts>
|
|
<NormalTexture file="Interface\ChatFrame\ChatFrameExpandArrow"/>
|
|
</Button>
|
|
<!-- Disabled rows don't get mouse events; this transparent cover keeps the hover
|
|
alive (timeout + deeper-level close). It stops at the colour swatch's LEFT edge
|
|
(ref l.111-113) so a disabled row's swatch is still clickable. -->
|
|
<Button name="$parentInvisibleButton" hidden="true">
|
|
<Anchors>
|
|
<Anchor point="TOPLEFT"/>
|
|
<Anchor point="BOTTOMLEFT"/>
|
|
<Anchor point="RIGHT" relativeTo="$parentColorSwatch" relativePoint="LEFT"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
|
|
</Anchors>
|
|
<Scripts>
|
|
<OnEnter>
|
|
UIDropDownMenuRow_HoverIn(this:GetParent():GetParent());
|
|
</OnEnter>
|
|
<OnLeave>
|
|
UIDropDownMenuRow_HoverOut(this:GetParent():GetParent());
|
|
</OnLeave>
|
|
</Scripts>
|
|
</Button>
|
|
</Frames>
|
|
<Scripts>
|
|
<OnClick>
|
|
UIDropDownMenuButton_OnClick();
|
|
</OnClick>
|
|
<!-- A row that has a submenu opens it on hover; a row that has not closes whatever is
|
|
deeper. Either way the list's timeout stops and the row lights up. -->
|
|
<OnEnter>
|
|
if ( this.hasArrow ) then
|
|
UIDropDownMenuRow_OpenSubmenu(this);
|
|
UIDropDownMenu_StopCounting(this:GetParent());
|
|
else
|
|
UIDropDownMenuRow_HoverIn(this:GetParent());
|
|
end
|
|
getglobal(this:GetName().."Highlight"):Show();
|
|
</OnEnter>
|
|
<OnLeave>
|
|
getglobal(this:GetName().."Highlight"):Hide();
|
|
UIDropDownMenuRow_HoverOut(this:GetParent());
|
|
</OnLeave>
|
|
</Scripts>
|
|
<ButtonText name="$parentNormalText">
|
|
<Anchors><Anchor point="LEFT"><Offset><AbsDimension x="27" y="0"/></Offset></Anchor></Anchors>
|
|
</ButtonText>
|
|
<NormalFont inherits="GameFontHighlightSmall" justifyH="LEFT"/>
|
|
<HighlightFont inherits="GameFontHighlightSmall" justifyH="LEFT"/>
|
|
<DisabledFont inherits="GameFontDisableSmall" justifyH="LEFT"/>
|
|
</Button>
|
|
|
|
<!-- The list (ref UIDropDownListTemplate): the two swappable backdrops + 32 rows.
|
|
FULLSCREEN_DIALOG, not the ref's DIALOG: the list must sort above ANY opener, and the
|
|
world map is FULLSCREEN strata — DIALOG would render the list underneath it (the strata
|
|
escalation later reference versions added to ToggleDropDownMenu, applied statically
|
|
here since our customer set is known). -->
|
|
<Button name="UIDropDownListTemplate" frameStrata="FULLSCREEN_DIALOG" virtual="true" hidden="true">
|
|
<Size><AbsDimension x="180" y="32"/></Size>
|
|
<Frames>
|
|
<Frame name="$parentBackdrop" setAllPoints="true">
|
|
<Backdrop bgFile="Interface\DialogFrame\UI-DialogBox-Background" edgeFile="Interface\DialogFrame\UI-DialogBox-Border" tile="true">
|
|
<BackgroundInsets><AbsInset left="11" right="12" top="12" bottom="11"/></BackgroundInsets>
|
|
<TileSize><AbsValue val="32"/></TileSize>
|
|
<EdgeSize><AbsValue val="32"/></EdgeSize>
|
|
</Backdrop>
|
|
</Frame>
|
|
<Frame name="$parentMenuBackdrop" setAllPoints="true" hidden="true">
|
|
<Backdrop bgFile="Interface\Tooltips\UI-Tooltip-Background" edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true">
|
|
<BackgroundInsets><AbsInset left="5" right="5" top="5" bottom="5"/></BackgroundInsets>
|
|
<TileSize><AbsValue val="16"/></TileSize>
|
|
<EdgeSize><AbsValue val="16"/></EdgeSize>
|
|
</Backdrop>
|
|
<Scripts>
|
|
<OnLoad>
|
|
this:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g, TOOLTIP_DEFAULT_COLOR.b);
|
|
this:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b);
|
|
</OnLoad>
|
|
</Scripts>
|
|
</Frame>
|
|
<Button name="$parentButton1" inherits="UIDropDownMenuButtonTemplate" id="1"/>
|
|
<Button name="$parentButton2" inherits="UIDropDownMenuButtonTemplate" id="2"/>
|
|
<Button name="$parentButton3" inherits="UIDropDownMenuButtonTemplate" id="3"/>
|
|
<Button name="$parentButton4" inherits="UIDropDownMenuButtonTemplate" id="4"/>
|
|
<Button name="$parentButton5" inherits="UIDropDownMenuButtonTemplate" id="5"/>
|
|
<Button name="$parentButton6" inherits="UIDropDownMenuButtonTemplate" id="6"/>
|
|
<Button name="$parentButton7" inherits="UIDropDownMenuButtonTemplate" id="7"/>
|
|
<Button name="$parentButton8" inherits="UIDropDownMenuButtonTemplate" id="8"/>
|
|
<Button name="$parentButton9" inherits="UIDropDownMenuButtonTemplate" id="9"/>
|
|
<Button name="$parentButton10" inherits="UIDropDownMenuButtonTemplate" id="10"/>
|
|
<Button name="$parentButton11" inherits="UIDropDownMenuButtonTemplate" id="11"/>
|
|
<Button name="$parentButton12" inherits="UIDropDownMenuButtonTemplate" id="12"/>
|
|
<Button name="$parentButton13" inherits="UIDropDownMenuButtonTemplate" id="13"/>
|
|
<Button name="$parentButton14" inherits="UIDropDownMenuButtonTemplate" id="14"/>
|
|
<Button name="$parentButton15" inherits="UIDropDownMenuButtonTemplate" id="15"/>
|
|
<Button name="$parentButton16" inherits="UIDropDownMenuButtonTemplate" id="16"/>
|
|
<Button name="$parentButton17" inherits="UIDropDownMenuButtonTemplate" id="17"/>
|
|
<Button name="$parentButton18" inherits="UIDropDownMenuButtonTemplate" id="18"/>
|
|
<Button name="$parentButton19" inherits="UIDropDownMenuButtonTemplate" id="19"/>
|
|
<Button name="$parentButton20" inherits="UIDropDownMenuButtonTemplate" id="20"/>
|
|
<Button name="$parentButton21" inherits="UIDropDownMenuButtonTemplate" id="21"/>
|
|
<Button name="$parentButton22" inherits="UIDropDownMenuButtonTemplate" id="22"/>
|
|
<Button name="$parentButton23" inherits="UIDropDownMenuButtonTemplate" id="23"/>
|
|
<Button name="$parentButton24" inherits="UIDropDownMenuButtonTemplate" id="24"/>
|
|
<Button name="$parentButton25" inherits="UIDropDownMenuButtonTemplate" id="25"/>
|
|
<Button name="$parentButton26" inherits="UIDropDownMenuButtonTemplate" id="26"/>
|
|
<Button name="$parentButton27" inherits="UIDropDownMenuButtonTemplate" id="27"/>
|
|
<Button name="$parentButton28" inherits="UIDropDownMenuButtonTemplate" id="28"/>
|
|
<Button name="$parentButton29" inherits="UIDropDownMenuButtonTemplate" id="29"/>
|
|
<Button name="$parentButton30" inherits="UIDropDownMenuButtonTemplate" id="30"/>
|
|
<Button name="$parentButton31" inherits="UIDropDownMenuButtonTemplate" id="31"/>
|
|
<Button name="$parentButton32" inherits="UIDropDownMenuButtonTemplate" id="32"/>
|
|
</Frames>
|
|
<Scripts>
|
|
<OnClick>
|
|
this:Hide();
|
|
</OnClick>
|
|
<OnEnter>
|
|
UIDropDownMenu_StopCounting(this);
|
|
</OnEnter>
|
|
<OnLeave>
|
|
UIDropDownMenu_StartCounting(this);
|
|
</OnLeave>
|
|
<OnUpdate>
|
|
UIDropDownMenu_OnUpdate(arg1);
|
|
</OnUpdate>
|
|
<OnShow>
|
|
if ( this:GetID() > 1 ) then
|
|
this.parent = getglobal("DropDownList"..(this:GetID() - 1));
|
|
end
|
|
this.showTimer = nil;
|
|
this.isCounting = nil;
|
|
if ( not this.fixedWidth ) then
|
|
this.pendingWidth = 1;
|
|
end
|
|
this.pendingFlip = 1;
|
|
</OnShow>
|
|
</Scripts>
|
|
</Button>
|
|
|
|
<!-- The capsule (spec: ref UIDropDownMenuTemplate). Its art, geometry and texcoords are the
|
|
reference's own numbers — that is what makes it the same widget rather than a lookalike. -->
|
|
<Frame name="UIDropDownMenuTemplate" virtual="true">
|
|
<Size><AbsDimension x="40" y="32"/></Size>
|
|
<Layers>
|
|
<Layer level="ARTWORK">
|
|
<Texture name="$parentLeft" file="Interface\Glues\CharacterCreate\CharacterCreate-LabelFrame">
|
|
<Size><AbsDimension x="25" y="64"/></Size>
|
|
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="0" y="17"/></Offset></Anchor></Anchors>
|
|
<TexCoords left="0" right="0.1953125" top="0" bottom="1"/>
|
|
</Texture>
|
|
<Texture name="$parentMiddle" file="Interface\Glues\CharacterCreate\CharacterCreate-LabelFrame">
|
|
<Size><AbsDimension x="115" y="64"/></Size>
|
|
<Anchors><Anchor point="LEFT" relativeTo="$parentLeft" relativePoint="RIGHT"/></Anchors>
|
|
<TexCoords left="0.1953125" right="0.8046875" top="0" bottom="1"/>
|
|
</Texture>
|
|
<Texture name="$parentRight" file="Interface\Glues\CharacterCreate\CharacterCreate-LabelFrame">
|
|
<Size><AbsDimension x="25" y="64"/></Size>
|
|
<Anchors><Anchor point="LEFT" relativeTo="$parentMiddle" relativePoint="RIGHT"/></Anchors>
|
|
<TexCoords left="0.8046875" right="1" top="0" bottom="1"/>
|
|
</Texture>
|
|
<FontString name="$parentText" inherits="GameFontHighlightSmall" justifyH="RIGHT">
|
|
<Size><AbsDimension x="0" y="10"/></Size>
|
|
<Anchors><Anchor point="RIGHT" relativeTo="$parentRight"><Offset><AbsDimension x="-43" y="2"/></Offset></Anchor></Anchors>
|
|
</FontString>
|
|
</Layer>
|
|
</Layers>
|
|
<Frames>
|
|
<Button name="$parentButton">
|
|
<Size><AbsDimension x="24" y="24"/></Size>
|
|
<Anchors>
|
|
<Anchor point="TOPRIGHT" relativeTo="$parentRight">
|
|
<Offset><AbsDimension x="-16" y="-18"/></Offset>
|
|
</Anchor>
|
|
</Anchors>
|
|
<Scripts>
|
|
<OnClick>
|
|
ToggleDropDownMenu();
|
|
PlaySound("igMainMenuOptionCheckBoxOn");
|
|
</OnClick>
|
|
</Scripts>
|
|
<NormalTexture file="Interface\ChatFrame\UI-ChatIcon-ScrollDown-Up"/>
|
|
<PushedTexture file="Interface\ChatFrame\UI-ChatIcon-ScrollDown-Down"/>
|
|
<DisabledTexture file="Interface\ChatFrame\UI-ChatIcon-ScrollDown-Disabled"/>
|
|
<HighlightTexture file="Interface\Buttons\UI-Common-MouseHilight" alphaMode="ADD"/>
|
|
</Button>
|
|
</Frames>
|
|
<Scripts>
|
|
<OnHide>
|
|
CloseDropDownMenus();
|
|
</OnHide>
|
|
</Scripts>
|
|
</Frame>
|
|
|
|
<!-- The two shared lists (the ref authors DropDownList1..3; level 3 waits for a customer).
|
|
|
|
The per-instance <Scripts> answer the three jobs the reference hangs off these two elements
|
|
(spec: ref UIDropDownMenu.xml l.9-18, l.25-28). They were missing here entirely until they
|
|
were measured; each earns its place separately:
|
|
|
|
· `CloseDropDownMenus(level+1)` is the SUBMENU CATCH-ALL, and it is why the ref hangs it
|
|
off OnHide rather than off the toggle. `ToggleDropDownMenu`'s close arm is a bare
|
|
`listFrame:Hide()` (l.15-19 of the function) — so with no OnHide, opening a menu with a
|
|
`hasArrow` row, hovering the submenu open and then toggling level 1 shut LEFT
|
|
DropDownList2 on screen with its parent gone. OnHide catches that path and every other
|
|
one that hides level 1 (a parent Hide, the ESC ladder, an addon calling
|
|
`DropDownList1:Hide()` itself) — which is the point of putting it on the event.
|
|
|
|
· `OPEN_DROPDOWNMENUS[level] = nil` closes a real leak: UnitPopup.xml WRITES the level
|
|
(l.191, l.239) and its every-frame driver READS it (l.481), and nothing here ever
|
|
cleared it, so an entry survived its menu forever. No player-visible symptom is
|
|
reachable today and that is stated rather than implied — `UnitPopup_OnUpdate` returns
|
|
early unless DropDownList1 is visible AND a UnitPopupFrame owns the open menu, and
|
|
`UIDropDownMenu_AddButton` calls `button:Enable()` unconditionally, so a stale level's
|
|
Disable is undone the next time that list is built. Both of those are guards belonging
|
|
to other code; the divergence from the ref is here, so it is fixed here.
|
|
|
|
· UIDROPDOWNMENU_DEFAULT_TEXT_HEIGHT is DERIVED, never a literal. The ref forward-declares
|
|
it nil (UIDropDownMenu.lua l.16) and this OnLoad is the only thing that ever assigns it,
|
|
reading the height off Button1's own NormalText. `UIDropDownMenu_AddButton` then passes
|
|
it straight into `SetFont` (l.187) for every row without an explicit `info.textHeight`.
|
|
Dewdrop-2.0 does the same at its own l.489 — 64 corpus addon folders embed a copy, but
|
|
it is ONE library file replicated, and only that one of its two sites is unguarded (the
|
|
other is `info.textHeight or UIDROPDOWNMENU_DEFAULT_TEXT_HEIGHT or 10`). Writing a
|
|
literal here would have been a silent fidelity divergence, not a fix. -->
|
|
<!-- `toplevel` is authored on the ref's INSTANCES, not its template (decision 1318). -->
|
|
<Button name="DropDownList1" inherits="UIDropDownListTemplate" id="1" toplevel="true" parent="UIParent">
|
|
<Scripts>
|
|
<OnLoad>
|
|
local fontName, fontHeight, fontFlags = getglobal("DropDownList1Button1NormalText"):GetFont();
|
|
UIDROPDOWNMENU_DEFAULT_TEXT_HEIGHT = fontHeight;
|
|
</OnLoad>
|
|
<OnHide>
|
|
CloseDropDownMenus(2);
|
|
OPEN_DROPDOWNMENUS[1] = nil;
|
|
</OnHide>
|
|
</Scripts>
|
|
</Button>
|
|
<Button name="DropDownList2" inherits="UIDropDownListTemplate" id="2" toplevel="true" parent="UIParent">
|
|
<Scripts>
|
|
<OnHide>
|
|
CloseDropDownMenus(3);
|
|
OPEN_DROPDOWNMENUS[2] = nil;
|
|
</OnHide>
|
|
</Scripts>
|
|
</Button>
|
|
</Ui>
|