Files
ElvUI_CoA/Modules/ClassResources.lua
T
Narcasung 50824cc0e1 Strip Lock/Unlock Frame entry from class resource frames' right-click menu
Dragging is now permanently disabled on these frames, so the native
Lock/Unlock Frame option is meaningless. Reuses the UIDropDownMenu_AddButton
override pattern already used for LayerPickerFrameDropDown, generalized to
match any of the tracked frames' dropdowns by name convention or parent.
2026-07-16 15:34:06 +02:00

232 lines
6.8 KiB
Lua

local E, L, V, P, G = unpack(ElvUI)
local CoA = E:GetModule("CoA")
local FRAMES = {
{name = "CoAResourceSegmentBar", moverText = "Resource Segment Bar", hideKey = "hideResourceSegmentBar"},
{name = "CoAResourceOrb", moverText = "Resource Orb", hideKey = "hideResourceOrb"},
{name = "CoAResourceBar", moverText = "Resource Bar", hideKey = "hideResourceBar"},
{name = "CoAMultiCastActionBarFrame", moverText = "Multi Cast Action Bar", hideKey = "hideMultiCastActionBar"},
}
local FRAME_NAMES = {}
for _, def in ipairs(FRAMES) do
FRAME_NAMES[def.name] = true
end
-- These frames' native right-click dropdown offers a "Lock/Unlock Frame"
-- entry that's meaningless now that dragging is permanently disabled (see
-- DisableDrag below), so strip it. Matched loosely (case-insensitive,
-- requiring both words) since the exact wording isn't confirmed, and scoped
-- to only our tracked frames' dropdowns so it can't affect unrelated menus.
do
local function IsTrackedDropdown(menu)
if not menu then return false end
local name = menu.GetName and menu:GetName()
local ownerName = name and name:match("^(.-)DropDown$")
if ownerName and FRAME_NAMES[ownerName] then return true end
local parent = menu.GetParent and menu:GetParent()
local parentName = parent and parent.GetName and parent:GetName()
return parentName and FRAME_NAMES[parentName] or false
end
local orig_AddButton = UIDropDownMenu_AddButton
UIDropDownMenu_AddButton = function(info, level)
if info and info.text and IsTrackedDropdown(UIDROPDOWNMENU_INIT_MENU) then
local text = info.text:lower()
if text:find("lock") and text:find("frame") then
return
end
end
return orig_AddButton(info, level)
end
end
-- A single nil-out isn't enough: the frame's own update logic re-attaches
-- OnDragStart/OnDragStop (and re-registers drag buttons) on refresh, and
-- native dragging ends with the engine calling SetPoint directly on the
-- frame, severing the live anchor to our mover holder. So we don't just
-- clear drag once, we permanently intercept any future attempt to turn it
-- back on. The CoAClearing* guards stop our own corrective calls from
-- re-triggering these same hooks.
local function DisableDrag(frame)
if frame.CoADragDisabled then return end
frame.CoADragDisabled = true
frame:SetScript("OnDragStart", nil)
frame:SetScript("OnDragStop", nil)
frame:RegisterForDrag()
hooksecurefunc(frame, "SetScript", function(self, script, handler)
if handler and (script == "OnDragStart" or script == "OnDragStop") and not self.CoAClearingDragScript then
self.CoAClearingDragScript = true
self:SetScript(script, nil)
self.CoAClearingDragScript = false
end
end)
hooksecurefunc(frame, "RegisterForDrag", function(self, ...)
if select("#", ...) > 0 and not self.CoAClearingDrag then
self.CoAClearingDrag = true
self:RegisterForDrag()
self.CoAClearingDrag = false
end
end)
end
-- Anchoring a frame while in combat lockdown can taint it, so anchors
-- queued during combat are batched and applied together on the next
-- PLAYER_REGEN_ENABLED instead of each frame registering its own handler
-- (which would stomp on each other, since AceEvent keeps only the most
-- recently registered callback per event for a given object).
local pendingAnchors = {}
local combatHandlerRegistered = false
local function QueueAnchor(fn)
if not InCombatLockdown() then
fn()
return
end
table.insert(pendingAnchors, fn)
if not combatHandlerRegistered then
combatHandlerRegistered = true
CoA:RegisterEvent("PLAYER_REGEN_ENABLED", function()
for i = 1, #pendingAnchors do
pendingAnchors[i]()
end
wipe(pendingAnchors)
CoA:UnregisterEvent("PLAYER_REGEN_ENABLED")
combatHandlerRegistered = false
end)
end
end
local function AnchorToHolder(frame, holder)
frame:ClearAllPoints()
frame:SetPoint("CENTER", holder, "CENTER")
end
-- Class resource frames re-anchor themselves (e.g. relative to the player/
-- target frame) whenever they refresh, which stomps our mover anchor. Since
-- there's no event that fires only for that self-repositioning, we hook
-- SetPoint itself and snap back to the holder any time something else moves
-- the frame. CoARepositioning guards against the corrective SetPoint call
-- re-triggering this same hook.
local function LockPosition(frame, holder)
if frame.CoAPositionLocked then return end
frame.CoAPositionLocked = true
hooksecurefunc(frame, "SetPoint", function(self)
if self.CoARepositioning then return end
self.CoARepositioning = true
QueueAnchor(function()
AnchorToHolder(self, holder)
self.CoARepositioning = false
end)
end)
end
local function SetupMover(frame, name, moverText)
if frame.CoAMoverCreated then return true end
local width, height = frame:GetSize()
if width == 0 or height == 0 then return false end
local left, bottom = frame:GetLeft(), frame:GetBottom()
if not left or not bottom then return false end
frame.CoAMoverCreated = true
local holder = CreateFrame("Frame", "CoA_"..name.."Holder", E.UIParent)
holder:Size(width, height)
holder:Point("BOTTOMLEFT", E.UIParent, "BOTTOMLEFT", left, bottom)
E:CreateMover(holder, "CoA_"..name.."Mover", moverText, nil, nil, nil, nil, nil, "CoA,skin,classResources")
holder:SetAllPoints(_G["CoA_"..name.."Mover"])
QueueAnchor(function()
AnchorToHolder(frame, holder)
LockPosition(frame, holder)
end)
return true
end
-- CoAForceHidden distinguishes frames we hid ourselves (via the options
-- checkbox) from frames the game itself decided to hide, so unchecking the
-- box only restores frames we were suppressing.
local function ApplyVisibility(frame, hideKey)
if CoA.db.profile[hideKey] then
frame.CoAForceHidden = true
frame:Hide()
elseif frame.CoAForceHidden then
frame.CoAForceHidden = false
frame:Show()
end
end
local function SetupVisibility(frame, hideKey)
if frame.CoAVisibilityHooked then return end
frame.CoAVisibilityHooked = true
frame:HookScript("OnShow", function(self)
ApplyVisibility(self, hideKey)
end)
ApplyVisibility(frame, hideKey)
end
function CoA:UpdateClassResourceVisibility()
for _, def in ipairs(FRAMES) do
local frame = _G[def.name]
if frame then
ApplyVisibility(frame, def.hideKey)
end
end
end
local hooked = {}
local function TryHookAll()
local allHooked = true
for _, def in ipairs(FRAMES) do
if not hooked[def.name] then
local frame = _G[def.name]
if frame then
DisableDrag(frame)
SetupVisibility(frame, def.hideKey)
if SetupMover(frame, def.name, def.moverText) then
hooked[def.name] = true
else
allHooked = false
end
else
allHooked = false
end
end
end
return allHooked
end
function CoA:InitializeClassResources()
if TryHookAll() then return end
self.classResourcesTimer = self:ScheduleRepeatingTimer(function()
if TryHookAll() then
self:CancelTimer(self.classResourcesTimer)
self.classResourcesTimer = nil
end
end, 0.5)
end