5842eb7a2b
Traduction - Identifiants en anglais dans tout le module : addon, ecrans d'accueil, outils Python, banc d'essai et patcheur C#. - Fichiers au nom francais renommes (Gabarits.lua -> Templates.lua, Textes_*.lua -> Texts_*.lua, deployer.py -> deploy.py, tools/patcheur -> tools/patcher, etc.). - Commandes /fui et options de deploy.py en anglais (--check, --server, --remove-dbc). - Cles des variables sauvegardees et de data/dbc/statistics.json traduites, sans reprise des anciennes cles. - API de la micro-barre : ForeverUI.AddMicroButton(def) avec les champs name, atlasSet, after, tooltip, onClick, ready, et ForeverUI.UpdateMicro(name, isOpen). - Commentaires reecrits en anglais, courts, sans dates ni historique ; les fonctions au nom peu parlant et leurs parametres sont expliques. Code mort - Exports, fonctions et variables jamais lus retires. - Retires a la demande des modules : ForeverUI.PvPJauge, les exports FondEntete / TuilerEntete / Polices de TradeSkill, l'art legacy-up-c60, honorfill(half)blue et inv_misc_book_09, leurs entrees d'atlas et les parties d'outils correspondantes. Verifications : liaisons des noms comparees avant et apres, banc vert, chemins de textures inchanges, generateurs reproduisant les fichiers, patcheur identique octet pour octet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
598 lines
20 KiB
Lua
598 lines
20 KiB
Lua
-- Dropdown lists with camelot's art. 3.3.5 opens every menu (dropdowns and right-click menus)
|
|
-- in the global DropDownList1 (DropDownList2 for a submenu), so skinning it skins them all.
|
|
-- Style 1 follows camelot's MenuStyle1Mixin (Blizzard_Menu, mainline family, c60 art):
|
|
-- common-dropdown-bg background, 20-high rows, GameFontHighlight, and the
|
|
-- common-dropdown-ticksquare box with the yellow checkmark over it. 3.3.5 has no radio
|
|
-- buttons (only info.checked), so the box and checkmark serve everywhere.
|
|
-- UIDROPDOWNMENU_BORDER_HEIGHT stays 15 for both top and bottom (camelot uses 8 and 15):
|
|
-- changing it would move every row of every menu.
|
|
|
|
ForeverUI = ForeverUI or {}
|
|
|
|
-- Background in nine slices. Camelot stretches one texture, but common-dropdown-bg-c60
|
|
-- (68 x 68) has a shadow around the panel (9 left and right, 6 top, 12 bottom) and 6 px cut
|
|
-- corners: stretched, the shadow grows and the gold edge moves inward. Corners keep their
|
|
-- size (18 = thickest shadow 12 + cut 6). The margins are the image's shadow, so the gold
|
|
-- edge falls exactly on the frame edge.
|
|
local BACKGROUND_ATLAS = "common-dropdown-bg-c60"
|
|
local BACKGROUND_CORNER = 18
|
|
local BACKGROUND_MARGINS = { 9, 6, 9, 12 } -- left, top, right, bottom
|
|
local BACKGROUND_ALPHA = 0.925
|
|
local ROW_HEIGHT = 20
|
|
local CHECKBOX_ATLAS = "common-dropdown-ticksquare"
|
|
local CHECKBOX = 12
|
|
local CHECKMARK_ATLAS = "common-dropdown-icon-checkmark-yellow"
|
|
local CHECKMARK_W, CHECKMARK_H = 15, 14
|
|
local CHECKMARK_X, CHECKMARK_Y = 2, 1
|
|
local BACKDROPS = { "Backdrop", "MenuBackdrop" }
|
|
|
|
-- Style 2, for settings: a dropdown of the options windows has foreverStyle = 2
|
|
-- (Settings.lua), and its list matches the login screen options (ForeverUIGlueDropDown.lua).
|
|
-- Camelot's MenuStyle2Mixin (mainline/menutemplates.lua): nine-slice common-dropdown-c-bg
|
|
-- from (-17, 12) to (17, -22), margins 3 / 6 / 3 / 7, 20-high rows, radial tick for a
|
|
-- single choice, width = content but at least the button, list below the button. Rows use
|
|
-- size 10 (GameFont*SmallLeft) instead of 12. Other menus keep style 1.
|
|
local STYLE2 = {
|
|
background = "common-dropdown-c-bg",
|
|
backgroundA = { -17, 12 },
|
|
backgroundB = { 17, -22 },
|
|
margins = { 3, 6, 3, 7 }, -- left, top, right, bottom
|
|
circle = "common-dropdown-tickradial",
|
|
point = "common-dropdown-icon-radialtick-yellow",
|
|
span = 16 + 20, -- circle (-3 .. 15) + 1, plus the padding
|
|
noRadio = 20,
|
|
}
|
|
|
|
local function style2(frame)
|
|
return frame ~= nil and type(frame) == "table" and frame.foreverStyle == 2
|
|
end
|
|
|
|
-- The menu the list serves now: the one being filled, else the open one
|
|
local function currentMenu()
|
|
return UIDROPDOWNMENU_INIT_MENU or UIDROPDOWNMENU_OPEN_MENU
|
|
end
|
|
|
|
-- Gap the client keeps between the list and its rows: list at maxWidth + 25, rows at
|
|
-- maxWidth. Kept so the right margin does not move when the list widens.
|
|
local LIST_MARGIN = 25
|
|
|
|
-- Width. The client sizes the list on its longest text (maxWidth + 25); camelot uses the
|
|
-- opening button as a floor (DropdownButtonMixin:RegisterMenu, SetMinimumWidth). Here the
|
|
-- list gets exactly the button width, even when an entry is longer (its text is squeezed).
|
|
-- It is set on show: ToggleDropDownMenu sets UIDROPDOWNMENU_OPEN_MENU before showing the
|
|
-- list and clamps it to the screen after, so the width must be final by then.
|
|
-- Only the first level of a dropdown does this. Context menus (displayMode "MENU", whose
|
|
-- opener is an invisible 40-wide frame) and submenus take the width of their content,
|
|
-- measured again per row (measureButton) in the displayed font: the client measures in
|
|
-- the smaller GameFontHighlightSmallLeft.
|
|
local function applyWidth(list, wantedValue)
|
|
if math.abs(list:GetWidth() - wantedValue) < 0.5 then
|
|
return
|
|
end
|
|
list:SetWidth(wantedValue)
|
|
for index = 1, (list.numButtons or 0) do
|
|
local button = _G[list:GetName() .. "Button" .. index]
|
|
if button then
|
|
button:SetWidth(wantedValue - LIST_MARGIN)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Style 2: content inside its margins, at least the button on the first level
|
|
local function style2Width(list, opener, level)
|
|
local m = STYLE2.margins
|
|
local wantedValue = (list.foreverContent2 or 0) + m[1] + m[3]
|
|
if level == 1 and opener.foreverButton and opener.displayMode ~= "MENU" then
|
|
wantedValue = math.max(wantedValue, opener.foreverButton:GetWidth() or 0)
|
|
end
|
|
if not wantedValue or wantedValue <= 0 then
|
|
return
|
|
end
|
|
list:SetWidth(wantedValue)
|
|
for index = 1, (list.numButtons or 0) do
|
|
local button = _G[list:GetName() .. "Button" .. index]
|
|
if button then
|
|
button:SetWidth(wantedValue - m[1] - m[3])
|
|
end
|
|
end
|
|
end
|
|
|
|
local function fitWidth(list)
|
|
local opener = UIDROPDOWNMENU_OPEN_MENU
|
|
local level = list.foreverLevel or list:GetID()
|
|
if list.foreverStyle2 and style2(opener) then
|
|
style2Width(list, opener, level)
|
|
return
|
|
end
|
|
if level ~= 1 or not opener or not opener.GetWidth or opener.displayMode == "MENU" then
|
|
local content = list.foreverContent
|
|
if content and content > 0 then
|
|
-- A ForeverUI menu can ask for a floor: the width of the button that opens it, as
|
|
-- camelot's SetMinimumWidth
|
|
local minimum = level == 1 and opener and opener.foreverMinimum or 0
|
|
applyWidth(list, math.max(content + LIST_MARGIN, minimum))
|
|
end
|
|
return
|
|
end
|
|
|
|
local wantedValue = opener:GetWidth()
|
|
if not wantedValue or wantedValue <= 0 then
|
|
return
|
|
end
|
|
-- A ForeverUI menu can ask for camelot's rule as is: the button as a floor, the list
|
|
-- growing with its content (SetMinimumWidth)
|
|
if opener.foreverFloor and list.foreverContent then
|
|
wantedValue = math.max(wantedValue, list.foreverContent + LIST_MARGIN)
|
|
end
|
|
applyWidth(list, wantedValue)
|
|
end
|
|
|
|
-- Row width by the UIDropDownMenu_AddButton formula: text + 40, + 10 for an arrow or a
|
|
-- color swatch, - 30 without a checkbox, + 10 for an icon, + the requested padding.
|
|
local function measureButton(list, button, info)
|
|
if list.numButtons == 1 then
|
|
list.foreverContent = 0
|
|
end
|
|
local text = _G[button:GetName() .. "NormalText"]
|
|
if not (text and info and info.text) then
|
|
return
|
|
end
|
|
local width = text:GetStringWidth() + 40
|
|
if info.hasArrow or info.hasColorSwatch then width = width + 10 end
|
|
if info.notCheckable then width = width - 30 end
|
|
if info.icon then width = width + 10 end
|
|
if info.padding then width = width + info.padding end
|
|
if width > (list.foreverContent or 0) then
|
|
list.foreverContent = width
|
|
end
|
|
end
|
|
|
|
-- The list drops its two old backdrops for camelot's background. The backdrop is removed
|
|
-- instead of hiding the frame: ToggleDropDownMenu shows one frame or the other on each
|
|
-- opening, by displayMode. A frame without backdrop draws nothing.
|
|
local function skinList(list)
|
|
if list.foreverBackground then
|
|
return
|
|
end
|
|
|
|
local name = list:GetName()
|
|
for _, suffix in ipairs(BACKDROPS) do
|
|
local frame = name and _G[name .. suffix]
|
|
if frame and frame.SetBackdrop then
|
|
frame:SetBackdrop(nil)
|
|
end
|
|
end
|
|
|
|
local slices = ForeverUI.CreateNineSlice(list, BACKGROUND_ATLAS, BACKGROUND_CORNER,
|
|
BACKGROUND_MARGINS, "BACKGROUND")
|
|
if not slices then
|
|
return
|
|
end
|
|
for _, slice in ipairs(slices) do
|
|
slice:SetAlpha(BACKGROUND_ALPHA)
|
|
end
|
|
list.foreverBackground = slices[1]
|
|
list.foreverSlices = slices
|
|
|
|
list:HookScript("OnShow", fitWidth)
|
|
end
|
|
|
|
-- Checkbox in BORDER, checkmark in ARTWORK: 3.3.5 has no draw sublevels, so only the
|
|
-- layer puts the checkmark over its box.
|
|
local function skinButton(button)
|
|
if button.foreverCell then
|
|
return
|
|
end
|
|
|
|
local checkbox = button:CreateTexture(nil, "BORDER")
|
|
if not ForeverUI.SetAtlas(checkbox, CHECKBOX_ATLAS, true) then
|
|
checkbox:Hide()
|
|
end
|
|
checkbox:SetWidth(CHECKBOX)
|
|
checkbox:SetHeight(CHECKBOX)
|
|
checkbox:SetPoint("LEFT", button, "LEFT", 0, 0)
|
|
checkbox:Hide()
|
|
button.foreverCell = checkbox
|
|
|
|
local checkMark = _G[button:GetName() .. "Check"]
|
|
if checkMark then
|
|
ForeverUI.SetAtlas(checkMark, CHECKMARK_ATLAS, true)
|
|
checkMark:SetWidth(CHECKMARK_W)
|
|
checkMark:SetHeight(CHECKMARK_H)
|
|
checkMark:ClearAllPoints()
|
|
checkMark:SetPoint("CENTER", checkbox, "CENTER", CHECKMARK_X, CHECKMARK_Y)
|
|
end
|
|
end
|
|
|
|
-- Restores the style 1 checkmark after a style 2 list changed it
|
|
local function checkMarkStyle1(button)
|
|
if not button.foreverStyle2 then
|
|
return
|
|
end
|
|
button.foreverStyle2 = nil
|
|
if button.foreverCircle then
|
|
button.foreverCircle:Hide()
|
|
end
|
|
local checkMark = _G[button:GetName() .. "Check"]
|
|
if checkMark and button.foreverCell then
|
|
ForeverUI.SetAtlas(checkMark, CHECKMARK_ATLAS, true)
|
|
checkMark:SetWidth(CHECKMARK_W)
|
|
checkMark:SetHeight(CHECKMARK_H)
|
|
checkMark:ClearAllPoints()
|
|
checkMark:SetPoint("CENTER", button.foreverCell, "CENTER", CHECKMARK_X, CHECKMARK_Y)
|
|
end
|
|
end
|
|
|
|
-- A style 2 row: the circle under the client's checkmark (which becomes the yellow dot),
|
|
-- text on its right, the style 1 box hidden
|
|
local function style2Row(list, button, info)
|
|
local Tpl = ForeverUI.Templates
|
|
local m = STYLE2.margins
|
|
local i = button:GetID()
|
|
if not button.foreverCircle then
|
|
button.foreverCircle = button:CreateTexture(nil, "BORDER")
|
|
Tpl.Place(button.foreverCircle, STYLE2.circle, true)
|
|
button.foreverCircle:SetPoint("LEFT", button, "LEFT", -3, 0)
|
|
end
|
|
button.foreverStyle2 = true
|
|
if button.foreverCell then
|
|
button.foreverCell:Hide()
|
|
end
|
|
local checkMark = _G[button:GetName() .. "Check"]
|
|
if checkMark then
|
|
Tpl.Place(checkMark, STYLE2.point, true)
|
|
checkMark:ClearAllPoints()
|
|
checkMark:SetPoint("TOPLEFT", button.foreverCircle, "TOPLEFT")
|
|
end
|
|
button:SetHeight(ROW_HEIGHT)
|
|
button:ClearAllPoints()
|
|
button:SetPoint("TOPLEFT", list, "TOPLEFT", m[1], -(m[2] + (i - 1) * ROW_HEIGHT))
|
|
button:SetNormalFontObject(GameFontHighlightSmallLeft)
|
|
button:SetHighlightFontObject(GameFontHighlightSmallLeft)
|
|
if button.SetDisabledFontObject then
|
|
if info and info.isTitle then
|
|
button:SetDisabledFontObject(GameFontNormalSmallLeft)
|
|
elseif info and info.notClickable then
|
|
button:SetDisabledFontObject(GameFontHighlightSmallLeft)
|
|
else
|
|
button:SetDisabledFontObject(GameFontDisableSmallLeft)
|
|
end
|
|
end
|
|
local raw = button:GetText()
|
|
local clean = Tpl.Utf8Text(raw)
|
|
if clean ~= raw then
|
|
button:SetText(clean)
|
|
end
|
|
local text = _G[button:GetName() .. "NormalText"]
|
|
local checkable = not (info and info.notCheckable)
|
|
if checkable then
|
|
button.foreverCircle:Show()
|
|
else
|
|
button.foreverCircle:Hide()
|
|
end
|
|
if text then
|
|
text:ClearAllPoints()
|
|
if checkable then
|
|
text:SetPoint("LEFT", button.foreverCircle, "RIGHT", 1, 0)
|
|
elseif info and info.justifyH == "CENTER" then
|
|
text:SetPoint("CENTER", button, "CENTER", 0, 0)
|
|
else
|
|
text:SetPoint("LEFT", button, "LEFT", 0, 0)
|
|
end
|
|
-- Row extent (MeasureFrameExtents + padding of 20)
|
|
local e = (checkable and STYLE2.span or STYLE2.noRadio) + (text:GetStringWidth() or 0)
|
|
if i == 1 or e > (list.foreverContent2 or 0) then
|
|
list.foreverContent2 = e
|
|
end
|
|
end
|
|
list:SetHeight(m[2] + (list.numButtons or i) * ROW_HEIGHT + m[4])
|
|
end
|
|
|
|
-- The two backgrounds of a list: style 1 (slices) or style 2, by the menu it serves
|
|
local function applyListBackgrounds(list, isStyle2)
|
|
list.foreverStyle2 = isStyle2 and true or nil
|
|
if isStyle2 and not list.foreverBackground2 then
|
|
local Tpl = ForeverUI.Templates
|
|
local f = Tpl.StretchedAtlas(list, STYLE2.background, "BACKGROUND")
|
|
f.rect:SetPoint("TOPLEFT", list, "TOPLEFT", STYLE2.backgroundA[1], STYLE2.backgroundA[2])
|
|
f.rect:SetPoint("BOTTOMRIGHT", list, "BOTTOMRIGHT", STYLE2.backgroundB[1], STYLE2.backgroundB[2])
|
|
list.foreverBackground2 = f
|
|
end
|
|
if list.foreverBackground2 then
|
|
list.foreverBackground2:SetShown(isStyle2)
|
|
end
|
|
for _, slice in ipairs(list.foreverSlices or {}) do
|
|
if isStyle2 then slice:Hide() else slice:Show() end
|
|
end
|
|
end
|
|
|
|
-- Redone for every row added: UIDropDownMenu_AddButton resets the font to
|
|
-- GameFontHighlightSmallLeft each time, and sets whether the row has a checkbox
|
|
-- (info.notCheckable).
|
|
local function adjustButton(button)
|
|
checkMarkStyle1(button)
|
|
button:SetHeight(ROW_HEIGHT)
|
|
if GameFontHighlightLeft then
|
|
button:SetNormalFontObject(GameFontHighlightLeft)
|
|
button:SetHighlightFontObject(GameFontHighlightLeft)
|
|
end
|
|
|
|
if button.foreverCell then
|
|
if button.notCheckable then
|
|
button.foreverCell:Hide()
|
|
else
|
|
button.foreverCell:Show()
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Row spacing follows row height. The client reads UIDROPDOWNMENU_BUTTON_HEIGHT for each
|
|
-- row, the list height and the dropdown height (UIDropDownMenu_InitializeHelper), but
|
|
-- writing that global from the addon would taint every client menu, including the
|
|
-- protected actions of a player's right-click menu. So the client lays rows at 16 and
|
|
-- we move them to 20 right after it.
|
|
local BORDER = UIDROPDOWNMENU_BORDER_HEIGHT or 15
|
|
|
|
local function relayoutRow(list, button)
|
|
local point, relativeTo, relativePoint, x = button:GetPoint(1)
|
|
if point then
|
|
button:ClearAllPoints()
|
|
button:SetPoint(point, relativeTo, relativePoint, x,
|
|
-((button:GetID() - 1) * ROW_HEIGHT) - BORDER)
|
|
end
|
|
list:SetHeight(((list.numButtons or 1) * ROW_HEIGHT) + (BORDER * 2))
|
|
end
|
|
|
|
if hooksecurefunc and type(UIDropDownMenu_InitializeHelper) == "function" then
|
|
hooksecurefunc("UIDropDownMenu_InitializeHelper", function(frame)
|
|
if frame and frame.SetHeight then
|
|
frame:SetHeight(ROW_HEIGHT * 2)
|
|
end
|
|
end)
|
|
end
|
|
|
|
if hooksecurefunc and type(UIDropDownMenu_AddButton) == "function" then
|
|
hooksecurefunc("UIDropDownMenu_AddButton", function(info, level)
|
|
level = level or 1
|
|
local list = _G["DropDownList" .. level]
|
|
if not list then
|
|
return
|
|
end
|
|
|
|
list.foreverLevel = level
|
|
skinList(list)
|
|
|
|
local isStyle2 = style2(currentMenu())
|
|
applyListBackgrounds(list, isStyle2)
|
|
local button = _G[list:GetName() .. "Button" .. (list.numButtons or 1)]
|
|
if button then
|
|
skinButton(button)
|
|
if isStyle2 then
|
|
style2Row(list, button, info)
|
|
else
|
|
relayoutRow(list, button)
|
|
adjustButton(button)
|
|
measureButton(list, button, info)
|
|
end
|
|
end
|
|
end)
|
|
end
|
|
|
|
-- Style 2: after ToggleDropDownMenu, put the list under camelot's button (even when the
|
|
-- client anchors it elsewhere) at its width, and repaint the button (arrow, open state)
|
|
-- with foreverPaint, set by Settings.lua
|
|
if hooksecurefunc and type(ToggleDropDownMenu) == "function" then
|
|
hooksecurefunc("ToggleDropDownMenu", function(level)
|
|
level = level or 1
|
|
local list = _G["DropDownList" .. level]
|
|
local opener = UIDROPDOWNMENU_OPEN_MENU
|
|
if not (list and style2(opener)) then
|
|
return
|
|
end
|
|
if list:IsShown() then
|
|
fitWidth(list)
|
|
if level == 1 and opener.foreverButton and opener.displayMode ~= "MENU" then
|
|
list:ClearAllPoints()
|
|
list:SetPoint("TOPLEFT", opener.foreverButton, "BOTTOMLEFT", 0, 0)
|
|
end
|
|
end
|
|
if opener.foreverPaint then
|
|
opener.foreverPaint()
|
|
end
|
|
end)
|
|
end
|
|
|
|
-- The button arrow goes back to rest when the list closes
|
|
for level = 1, (UIDROPDOWNMENU_MAXLEVELS or 2) do
|
|
local list = _G["DropDownList" .. level]
|
|
if list and list.HookScript then
|
|
list:HookScript("OnHide", function()
|
|
local opener = UIDROPDOWNMENU_OPEN_MENU
|
|
if style2(opener) and opener.foreverPaint then
|
|
opener.foreverPaint()
|
|
end
|
|
end)
|
|
end
|
|
end
|
|
|
|
-- UIDropDownMenu_Refresh resizes the list on its text (maxWidth + 25), erasing the wanted
|
|
-- width: set it again after it.
|
|
if hooksecurefunc and type(UIDropDownMenu_Refresh) == "function" then
|
|
hooksecurefunc("UIDropDownMenu_Refresh", function(frame, value, level)
|
|
local list = _G["DropDownList" .. (level or UIDROPDOWNMENU_MENU_LEVEL or 1)]
|
|
if list and list:IsShown() then
|
|
fitWidth(list)
|
|
end
|
|
end)
|
|
end
|
|
|
|
ForeverUI.DropDown = {
|
|
Skin = skinList,
|
|
SkinButton = skinButton,
|
|
Refresh = adjustButton,
|
|
Fit = fitWidth,
|
|
}
|
|
|
|
-- ------------------------------------------------------------ Unit menus
|
|
--
|
|
-- Our unit frames open the right-click menu through our own function (the "menu" of
|
|
-- their secure action), so the client builds the whole menu as addon code and blocks the
|
|
-- rows that call a protected function: SET_FOCUS, CLEAR_FOCUS, TARGET and PET_DISMISS
|
|
-- (UnitPopup.lua:1201-1384). Out of combat, a secure ForeverUI button covers each of these
|
|
-- rows and runs the action as a macro on the player's click. In combat no addon may place
|
|
-- or show a secure button, so these rows are grayed (UnitPopup_OnUpdate enables them
|
|
-- every frame, so they are disabled again). Only menus opened by our frames (M.open) are
|
|
-- touched: the client's own menus already work.
|
|
|
|
local M = {}
|
|
ForeverUI.UnitMenu = M
|
|
M.overlays = {}
|
|
M.grayedRows = {}
|
|
|
|
local HIGHLIGHT = "Interface" .. string.char(92) .. "QuestFrame" .. string.char(92) .. "UI-QuestTitleHighlight"
|
|
|
|
-- Full name, as UnitPopup_OnClick builds it (UnitPopup.lua:1169-1174)
|
|
local function fullName(menu)
|
|
local name, server = menu.name, menu.server
|
|
if name and server and (not menu.unit or not UnitIsSameServer("player", menu.unit)) then
|
|
return name .. "-" .. server
|
|
end
|
|
return name
|
|
end
|
|
|
|
-- Secure action of each protected row
|
|
local PROTECTED = {
|
|
SET_FOCUS = function(o, menu)
|
|
if not menu.unit then return false end
|
|
o:SetAttribute("type", "focus")
|
|
o:SetAttribute("unit", menu.unit)
|
|
end,
|
|
CLEAR_FOCUS = function(o)
|
|
o:SetAttribute("type", "macro")
|
|
o:SetAttribute("macrotext", "/clearfocus")
|
|
end,
|
|
TARGET = function(o, menu)
|
|
local name = fullName(menu)
|
|
if not name then return false end
|
|
o:SetAttribute("type", "macro")
|
|
o:SetAttribute("macrotext", "/targetexact " .. name)
|
|
end,
|
|
PET_DISMISS = function(o)
|
|
o:SetAttribute("type", "macro")
|
|
o:SetAttribute("macrotext", "/script PetDismiss()")
|
|
end,
|
|
}
|
|
M.PROTECTED = PROTECTED
|
|
|
|
-- Secure overlay button k, created on first use
|
|
local function overlay(k)
|
|
local o = M.overlays[k]
|
|
if o then return o end
|
|
o = CreateFrame("Button", "ForeverUIUnitMenuSecure" .. k, UIParent, "SecureActionButtonTemplate")
|
|
o:RegisterForClicks("LeftButtonUp")
|
|
o:SetHighlightTexture(HIGHLIGHT)
|
|
local h = o:GetHighlightTexture()
|
|
if h then h:SetBlendMode("ADD") end
|
|
-- The list closes by itself when the mouse leaves its rows: keep it open over the
|
|
-- overlay as over a row
|
|
o:SetScript("OnEnter", function() UIDropDownMenu_StopCounting(DropDownList1) end)
|
|
o:SetScript("OnLeave", function() UIDropDownMenu_StartCounting(DropDownList1) end)
|
|
o:SetScript("PostClick", function() CloseDropDownMenus() end)
|
|
o:Hide()
|
|
M.overlays[k] = o
|
|
return o
|
|
end
|
|
|
|
-- Out of combat only: a secure button cannot be touched under lockdown
|
|
function M.hide()
|
|
if InCombatLockdown() then return end
|
|
for _, o in ipairs(M.overlays) do
|
|
o:Hide()
|
|
o:ClearAllPoints()
|
|
end
|
|
end
|
|
|
|
local function grayOut(row)
|
|
row:Disable()
|
|
table.insert(M.grayedRows, row)
|
|
end
|
|
|
|
-- After UnitPopup_ShowMenu, on the first list: cover or gray the protected rows.
|
|
-- menu: the unit menu frame given to UnitPopup_ShowMenu
|
|
function M.place(menu)
|
|
M.hide()
|
|
table.wipe(M.grayedRows)
|
|
local list = DropDownList1
|
|
local combat = InCombatLockdown()
|
|
local k = 0
|
|
for i = 1, (list.numButtons or 0) do
|
|
local row = _G["DropDownList1Button" .. i]
|
|
local configure = row and PROTECTED[row.value]
|
|
if configure then
|
|
if combat then
|
|
grayOut(row)
|
|
else
|
|
local o = overlay(k + 1)
|
|
if configure(o, menu) ~= false then
|
|
k = k + 1
|
|
o:ClearAllPoints()
|
|
o:SetAllPoints(row)
|
|
-- DropDownList1 is toplevel and rises to the top of its strata when shown:
|
|
-- the TOOLTIP strata stays above it (as in SocialRaid.lua)
|
|
o:SetFrameStrata("TOOLTIP")
|
|
o:Show()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Opens a menu from one of our frames. fn: the client function that opens it;
|
|
-- ...: its arguments
|
|
function M.open(fn, ...)
|
|
M.ownCall = true
|
|
local ok, err = pcall(fn, ...)
|
|
M.ownCall = false
|
|
if not ok then error(err, 0) end
|
|
end
|
|
|
|
if hooksecurefunc and type(UnitPopup_ShowMenu) == "function" then
|
|
hooksecurefunc("UnitPopup_ShowMenu", function(menu)
|
|
if not M.ownCall or not menu or (UIDROPDOWNMENU_MENU_LEVEL or 1) ~= 1 then return end
|
|
M.place(menu)
|
|
end)
|
|
end
|
|
if hooksecurefunc and type(UnitPopup_OnUpdate) == "function" then
|
|
hooksecurefunc("UnitPopup_OnUpdate", function()
|
|
if #M.grayedRows == 0 or not DropDownList1:IsShown() then return end
|
|
for _, row in ipairs(M.grayedRows) do
|
|
if row:IsEnabled() == 1 then row:Disable() end
|
|
end
|
|
end)
|
|
end
|
|
if DropDownList1 then
|
|
DropDownList1:HookScript("OnHide", function()
|
|
M.hide()
|
|
table.wipe(M.grayedRows)
|
|
end)
|
|
end
|
|
|
|
-- On entering combat, before the lockdown: overlays go away and their rows are grayed
|
|
local watcher = CreateFrame("Frame")
|
|
watcher:RegisterEvent("PLAYER_REGEN_DISABLED")
|
|
watcher:SetScript("OnEvent", function()
|
|
local placedCount = false
|
|
for _, o in ipairs(M.overlays) do
|
|
if o:IsShown() then placedCount = true end
|
|
o:Hide()
|
|
o:ClearAllPoints()
|
|
end
|
|
if placedCount and DropDownList1:IsShown() then
|
|
for i = 1, (DropDownList1.numButtons or 0) do
|
|
local row = _G["DropDownList1Button" .. i]
|
|
if row and PROTECTED[row.value] then grayOut(row) end
|
|
end
|
|
end
|
|
end)
|
|
M.watcher = watcher
|