修复拾取界面点击无效问题 修复宠物训练界面不显示训练点问题 新增天赋分享到聊天界面 修复飞行界面无法关闭问题 修复术士宠物的显示问题 为天赋界面添加默认数据库支持 框架现在也可自主选择是否启用 玩家框架和目标框架可取消显示3D头像以及透明度修改 背包和银行也添加透明度自定义支持 优化tooltip性能和背包物品显示方式 修复布局模式在ui缩放后不能正常定位的问题 添加硬核模式危险和死亡的工会通报 添加拾取和已拾取的框体 等等
1894 lines
68 KiB
Lua
1894 lines
68 KiB
Lua
--------------------------------------------------------------------------------
|
||
-- Nanami-UI: ActionBars
|
||
--
|
||
-- DESIGN: Blizzard's ActionButton_GetPagedID() identifies action slots by
|
||
-- checking button:GetParent():GetName(). We MUST NOT reparent multi-bar
|
||
-- buttons. Instead we reposition the original bar FRAMES and style buttons
|
||
-- in-place. Only ActionButton1-12 are safely reparented (page-based calc).
|
||
--
|
||
-- ShapeshiftBarFrame and PetActionBarFrame are children of MainMenuBar, so
|
||
-- they become invisible when we hide MainMenuBar. We reparent them to
|
||
-- UIParent before hiding.
|
||
--------------------------------------------------------------------------------
|
||
|
||
SFrames.ActionBars = {}
|
||
|
||
local AB = SFrames.ActionBars
|
||
|
||
local DEFAULTS = {
|
||
enable = true,
|
||
buttonSize = 36,
|
||
buttonGap = 2,
|
||
smallBarSize = 27,
|
||
scale = 1.0,
|
||
alpha = 1.0,
|
||
barCount = 3,
|
||
showHotkey = true,
|
||
showMacroName = false,
|
||
rangeColoring = true,
|
||
showPetBar = true,
|
||
showStanceBar = true,
|
||
showRightBars = true,
|
||
alwaysShowGrid = false,
|
||
buttonRounded = false,
|
||
buttonInnerShadow = false,
|
||
hideGryphon = true,
|
||
gryphonStyle = "dragonflight",
|
||
gryphonOnTop = false,
|
||
gryphonWidth = 64,
|
||
gryphonHeight = 64,
|
||
gryphonOffsetX = 30,
|
||
gryphonOffsetY = 0,
|
||
bottomOffsetX = 0,
|
||
bottomOffsetY = 2,
|
||
rightOffsetX = -4,
|
||
rightOffsetY = -80,
|
||
}
|
||
|
||
local BUTTONS_PER_ROW = 12
|
||
|
||
-- 狮鹫样式定义:每种样式包含联盟和部落纹理路径
|
||
local GRYPHON_STYLES = {
|
||
{ key = "dragonflight", label = "巨龙时代",
|
||
alliance = "Interface\\AddOns\\Nanami-UI\\img\\df-gryphon",
|
||
horde = "Interface\\AddOns\\Nanami-UI\\img\\df-wyvern" },
|
||
{ key = "dragonflight_beta", label = "巨龙时代 (Beta)",
|
||
alliance = "Interface\\AddOns\\Nanami-UI\\img\\df-gryphon-beta",
|
||
horde = "Interface\\AddOns\\Nanami-UI\\img\\df-gryphon-beta" },
|
||
{ key = "classic", label = "经典",
|
||
alliance = "Interface\\MainMenuBar\\UI-MainMenuBar-EndCap-Human",
|
||
horde = "Interface\\MainMenuBar\\UI-MainMenuBar-EndCap-Human" },
|
||
{ key = "cat", label = "猫",
|
||
alliance = "Interface\\AddOns\\Nanami-UI\\img\\cat",
|
||
horde = "Interface\\AddOns\\Nanami-UI\\img\\cat" },
|
||
}
|
||
|
||
local function GetGryphonTexPath(styleKey)
|
||
local faction = UnitFactionGroup and UnitFactionGroup("player") or "Alliance"
|
||
for _, s in ipairs(GRYPHON_STYLES) do
|
||
if s.key == styleKey then
|
||
return (faction == "Horde") and s.horde or s.alliance
|
||
end
|
||
end
|
||
return GRYPHON_STYLES[1].alliance
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Layout helpers
|
||
--------------------------------------------------------------------------------
|
||
local function LayoutRow(buttons, parent, size, gap)
|
||
for i, b in ipairs(buttons) do
|
||
b:SetWidth(size)
|
||
b:SetHeight(size)
|
||
b:ClearAllPoints()
|
||
if i == 1 then
|
||
b:SetPoint("BOTTOMLEFT", parent, "BOTTOMLEFT", 0, 0)
|
||
else
|
||
b:SetPoint("LEFT", buttons[i - 1], "RIGHT", gap, 0)
|
||
end
|
||
end
|
||
end
|
||
|
||
local function LayoutColumn(buttons, parent, size, gap)
|
||
for i, b in ipairs(buttons) do
|
||
b:SetWidth(size)
|
||
b:SetHeight(size)
|
||
b:ClearAllPoints()
|
||
if i == 1 then
|
||
b:SetPoint("TOPRIGHT", parent, "TOPRIGHT", 0, 0)
|
||
else
|
||
b:SetPoint("TOP", buttons[i - 1], "BOTTOM", 0, -gap)
|
||
end
|
||
end
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- DB
|
||
--------------------------------------------------------------------------------
|
||
function AB:GetDB()
|
||
if not SFramesDB then SFramesDB = {} end
|
||
if type(SFramesDB.ActionBars) ~= "table" then SFramesDB.ActionBars = {} end
|
||
local db = SFramesDB.ActionBars
|
||
for k, v in pairs(DEFAULTS) do
|
||
if db[k] == nil then db[k] = v end
|
||
end
|
||
return db
|
||
end
|
||
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Style helpers
|
||
--------------------------------------------------------------------------------
|
||
local styledButtons = {}
|
||
|
||
local function HideNormalTexture(nt)
|
||
if not nt then return end
|
||
if nt.SetAlpha then nt:SetAlpha(0) end
|
||
if nt.SetWidth then nt:SetWidth(0) end
|
||
if nt.SetHeight then nt:SetHeight(0) end
|
||
end
|
||
|
||
local function StyleButton(b)
|
||
if not b or styledButtons[b] then return end
|
||
styledButtons[b] = true
|
||
|
||
local nt = _G[b:GetName() .. "NormalTexture"]
|
||
HideNormalTexture(nt)
|
||
b.SetNormalTexture = function() end
|
||
|
||
-- pfUI approach: backdrop on a SEPARATE child frame at lower frame level
|
||
-- so it renders behind the button's own textures (Icon etc.)
|
||
if b:GetBackdrop() then b:SetBackdrop(nil) end
|
||
local level = b:GetFrameLevel()
|
||
local bd = CreateFrame("Frame", nil, b)
|
||
bd:SetFrameLevel(level > 0 and (level - 1) or 0)
|
||
bd:SetAllPoints(b)
|
||
SFrames:CreateBackdrop(bd)
|
||
b.sfBackdrop = bd
|
||
|
||
local icon = _G[b:GetName() .. "Icon"]
|
||
if icon then
|
||
icon:ClearAllPoints()
|
||
icon:SetPoint("TOPLEFT", b, "TOPLEFT", 2, -2)
|
||
icon:SetPoint("BOTTOMRIGHT", b, "BOTTOMRIGHT", -2, 2)
|
||
icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
|
||
end
|
||
|
||
local cd = _G[b:GetName() .. "Cooldown"]
|
||
if cd then
|
||
cd:ClearAllPoints()
|
||
cd:SetPoint("TOPLEFT", b, "TOPLEFT", 2, -2)
|
||
cd:SetPoint("BOTTOMRIGHT", b, "BOTTOMRIGHT", -2, 2)
|
||
end
|
||
|
||
local hotkey = _G[b:GetName() .. "HotKey"]
|
||
if hotkey then
|
||
hotkey:SetFont(SFrames:GetFont(), 9, "OUTLINE")
|
||
hotkey:ClearAllPoints()
|
||
hotkey:SetPoint("TOPRIGHT", b, "TOPRIGHT", -2, -2)
|
||
end
|
||
|
||
local count = _G[b:GetName() .. "Count"]
|
||
if count then
|
||
count:SetFont(SFrames:GetFont(), 9, "OUTLINE")
|
||
count:ClearAllPoints()
|
||
count:SetPoint("BOTTOMRIGHT", b, "BOTTOMRIGHT", -2, 2)
|
||
end
|
||
|
||
local macroName = _G[b:GetName() .. "Name"]
|
||
if macroName then
|
||
macroName:SetFont(SFrames:GetFont(), 8, "OUTLINE")
|
||
macroName:ClearAllPoints()
|
||
macroName:SetPoint("BOTTOM", b, "BOTTOM", 0, 2)
|
||
end
|
||
|
||
local floatingBG = _G[b:GetName() .. "FloatingBG"]
|
||
if floatingBG then floatingBG:SetAlpha(0) end
|
||
|
||
local border = _G[b:GetName() .. "Border"]
|
||
if border then border:SetAlpha(0) end
|
||
end
|
||
|
||
local function KillPetNormalTextures(b)
|
||
local name = b:GetName()
|
||
-- Pet buttons use NormalTexture AND NormalTexture2
|
||
for _, suffix in ipairs({"NormalTexture", "NormalTexture2"}) do
|
||
local nt = _G[name .. suffix]
|
||
if nt and nt.SetTexture then nt:SetTexture(nil) end
|
||
if nt and nt.SetAlpha then nt:SetAlpha(0) end
|
||
if nt and nt.Hide then nt:Hide() end
|
||
end
|
||
local gnt = b.GetNormalTexture and b:GetNormalTexture()
|
||
if gnt and gnt.SetTexture then gnt:SetTexture(nil) end
|
||
if gnt and gnt.SetAlpha then gnt:SetAlpha(0) end
|
||
end
|
||
|
||
local function StylePetButton(b)
|
||
if not b or styledButtons[b] then return end
|
||
styledButtons[b] = true
|
||
|
||
KillPetNormalTextures(b)
|
||
b.SetNormalTexture = function() end
|
||
|
||
-- pfUI approach: backdrop on separate child frame at lower frame level
|
||
if b.GetBackdrop and b:GetBackdrop() then b:SetBackdrop(nil) end
|
||
local level = b:GetFrameLevel()
|
||
local bd = CreateFrame("Frame", nil, b)
|
||
bd:SetFrameLevel(level > 0 and (level - 1) or 0)
|
||
bd:SetAllPoints(b)
|
||
SFrames:CreateBackdrop(bd)
|
||
b.sfBackdrop = bd
|
||
|
||
local icon = _G[b:GetName() .. "Icon"]
|
||
if icon then
|
||
icon:ClearAllPoints()
|
||
icon:SetPoint("TOPLEFT", b, "TOPLEFT", 2, -2)
|
||
icon:SetPoint("BOTTOMRIGHT", b, "BOTTOMRIGHT", -2, 2)
|
||
icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
|
||
end
|
||
|
||
local cd = _G[b:GetName() .. "Cooldown"]
|
||
if cd then
|
||
cd:ClearAllPoints()
|
||
cd:SetPoint("TOPLEFT", b, "TOPLEFT", 2, -2)
|
||
cd:SetPoint("BOTTOMRIGHT", b, "BOTTOMRIGHT", -2, 2)
|
||
end
|
||
|
||
local ab = _G[b:GetName() .. "AutoCastable"]
|
||
if ab then
|
||
ab:ClearAllPoints()
|
||
ab:SetPoint("TOPLEFT", b, "TOPLEFT", -4, 4)
|
||
ab:SetPoint("BOTTOMRIGHT", b, "BOTTOMRIGHT", 4, -4)
|
||
end
|
||
|
||
local floatingBG = _G[b:GetName() .. "FloatingBG"]
|
||
if floatingBG then floatingBG:SetAlpha(0) end
|
||
end
|
||
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Button visual effects (rounded corners + inner shadow)
|
||
--------------------------------------------------------------------------------
|
||
local function CreateInnerShadow(btn)
|
||
if btn.sfInnerShadow then return btn.sfInnerShadow end
|
||
local shadow = {}
|
||
local thickness = 4
|
||
|
||
local top = btn:CreateTexture(nil, "OVERLAY")
|
||
top:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||
top:SetHeight(thickness)
|
||
top:SetGradientAlpha("VERTICAL", 0, 0, 0, 0, 0, 0, 0, 0.5)
|
||
shadow.top = top
|
||
|
||
local bot = btn:CreateTexture(nil, "OVERLAY")
|
||
bot:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||
bot:SetHeight(thickness)
|
||
bot:SetGradientAlpha("VERTICAL", 0, 0, 0, 0.5, 0, 0, 0, 0)
|
||
shadow.bottom = bot
|
||
|
||
local left = btn:CreateTexture(nil, "OVERLAY")
|
||
left:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||
left:SetWidth(thickness)
|
||
left:SetGradientAlpha("HORIZONTAL", 0, 0, 0, 0.5, 0, 0, 0, 0)
|
||
shadow.left = left
|
||
|
||
local right = btn:CreateTexture(nil, "OVERLAY")
|
||
right:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||
right:SetWidth(thickness)
|
||
right:SetGradientAlpha("HORIZONTAL", 0, 0, 0, 0, 0, 0, 0, 0.5)
|
||
shadow.right = right
|
||
|
||
btn.sfInnerShadow = shadow
|
||
return shadow
|
||
end
|
||
|
||
local function ApplyButtonVisuals(btn, rounded, shadow)
|
||
local bd = btn.sfBackdrop
|
||
if not bd then return end
|
||
|
||
local inset = rounded and 3 or 2
|
||
btn.sfIconInset = inset
|
||
|
||
if rounded then
|
||
bd:SetBackdrop({
|
||
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||
tile = true, tileSize = 16, edgeSize = 12,
|
||
insets = { left = 3, right = 3, top = 3, bottom = 3 }
|
||
})
|
||
else
|
||
bd:SetBackdrop({
|
||
bgFile = "Interface\\Buttons\\WHITE8X8",
|
||
edgeFile = "Interface\\Buttons\\WHITE8X8",
|
||
tile = false, tileSize = 0, edgeSize = 1,
|
||
insets = { left = 1, right = 1, top = 1, bottom = 1 }
|
||
})
|
||
end
|
||
local A = SFrames.ActiveTheme
|
||
if A and A.panelBg then
|
||
bd:SetBackdropColor(A.panelBg[1], A.panelBg[2], A.panelBg[3], A.panelBg[4] or 0.9)
|
||
bd:SetBackdropBorderColor(A.panelBorder[1], A.panelBorder[2], A.panelBorder[3], A.panelBorder[4] or 1)
|
||
else
|
||
bd:SetBackdropColor(0.1, 0.1, 0.1, 0.9)
|
||
bd:SetBackdropBorderColor(0, 0, 0, 1)
|
||
end
|
||
|
||
local name = btn:GetName()
|
||
if name then
|
||
local icon = _G[name .. "Icon"]
|
||
if icon then
|
||
icon:ClearAllPoints()
|
||
icon:SetPoint("TOPLEFT", btn, "TOPLEFT", inset, -inset)
|
||
icon:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", -inset, inset)
|
||
end
|
||
local cd = _G[name .. "Cooldown"]
|
||
if cd then
|
||
cd:ClearAllPoints()
|
||
cd:SetPoint("TOPLEFT", btn, "TOPLEFT", inset, -inset)
|
||
cd:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", -inset, inset)
|
||
end
|
||
end
|
||
|
||
if btn.sfRangeOverlay then
|
||
btn.sfRangeOverlay:ClearAllPoints()
|
||
btn.sfRangeOverlay:SetPoint("TOPLEFT", btn, "TOPLEFT", inset, -inset)
|
||
btn.sfRangeOverlay:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", -inset, inset)
|
||
end
|
||
|
||
if shadow then
|
||
if not btn.sfInnerShadow then CreateInnerShadow(btn) end
|
||
local s = btn.sfInnerShadow
|
||
s.top:ClearAllPoints()
|
||
s.top:SetPoint("TOPLEFT", btn, "TOPLEFT", inset, -inset)
|
||
s.top:SetPoint("TOPRIGHT", btn, "TOPRIGHT", -inset, -inset)
|
||
s.bottom:ClearAllPoints()
|
||
s.bottom:SetPoint("BOTTOMLEFT", btn, "BOTTOMLEFT", inset, inset)
|
||
s.bottom:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", -inset, inset)
|
||
s.left:ClearAllPoints()
|
||
s.left:SetPoint("TOPLEFT", btn, "TOPLEFT", inset, -inset)
|
||
s.left:SetPoint("BOTTOMLEFT", btn, "BOTTOMLEFT", inset, inset)
|
||
s.right:ClearAllPoints()
|
||
s.right:SetPoint("TOPRIGHT", btn, "TOPRIGHT", -inset, -inset)
|
||
s.right:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", -inset, inset)
|
||
s.top:Show(); s.bottom:Show(); s.left:Show(); s.right:Show()
|
||
else
|
||
if btn.sfInnerShadow then
|
||
local s = btn.sfInnerShadow
|
||
s.top:Hide(); s.bottom:Hide(); s.left:Hide(); s.right:Hide()
|
||
end
|
||
end
|
||
end
|
||
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Hide Blizzard chrome
|
||
--------------------------------------------------------------------------------
|
||
function AB:HideBlizzardBars()
|
||
SHOW_MULTI_ACTIONBAR_1 = 1
|
||
SHOW_MULTI_ACTIONBAR_2 = 1
|
||
SHOW_MULTI_ACTIONBAR_3 = 1
|
||
SHOW_MULTI_ACTIONBAR_4 = 1
|
||
|
||
-- Reparent stance/pet bars BEFORE hiding MainMenuBar (they are children of it)
|
||
if ShapeshiftBarFrame then ShapeshiftBarFrame:SetParent(UIParent) end
|
||
if PetActionBarFrame then PetActionBarFrame:SetParent(UIParent) end
|
||
|
||
if MultiActionBar_Update then
|
||
MultiActionBar_Update()
|
||
end
|
||
|
||
if MainMenuBar then
|
||
MainMenuBar:UnregisterAllEvents()
|
||
MainMenuBar:Hide()
|
||
MainMenuBar.Show = function() end
|
||
end
|
||
|
||
local hideArt = {
|
||
"MainMenuBarArtFrame",
|
||
"MainMenuExpBar",
|
||
"ReputationWatchBar",
|
||
}
|
||
for _, name in ipairs(hideArt) do
|
||
local f = _G[name]
|
||
if f then
|
||
f:Hide()
|
||
if f.SetHeight then f:SetHeight(0) end
|
||
f.Show = function() end
|
||
end
|
||
end
|
||
|
||
-- BonusActionBarFrame: keep functional for druid form switching, just hide art
|
||
if BonusActionBarFrame then
|
||
BonusActionBarFrame:SetParent(UIParent)
|
||
if BonusActionBarFrame.SetBackdrop then
|
||
BonusActionBarFrame:SetBackdrop(nil)
|
||
end
|
||
for i = 0, 4 do
|
||
local tex = _G["BonusActionBarTexture" .. i]
|
||
if tex then tex:SetAlpha(0) end
|
||
end
|
||
end
|
||
|
||
if MultiBarBottomLeftArtFrame then MultiBarBottomLeftArtFrame:Hide() end
|
||
if MultiBarBottomRightArtFrame then MultiBarBottomRightArtFrame:Hide() end
|
||
|
||
-- 隐藏原版狮鹫端帽(Texture 对象,非 Frame)
|
||
local endcaps = { "MainMenuBarLeftEndCap", "MainMenuBarRightEndCap" }
|
||
for _, name in ipairs(endcaps) do
|
||
local f = _G[name]
|
||
if f then
|
||
f:Hide()
|
||
f.Show = function() end
|
||
end
|
||
end
|
||
|
||
if MainMenuBarBackpackButton then MainMenuBarBackpackButton:Hide() end
|
||
for slot = 0, 3 do
|
||
local b = _G["CharacterBag" .. slot .. "Slot"]
|
||
if b then b:Hide() end
|
||
end
|
||
if KeyRingButton then KeyRingButton:Hide() end
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Create bar structure (once)
|
||
--------------------------------------------------------------------------------
|
||
function AB:CreateBars()
|
||
local db = self:GetDB()
|
||
local size = db.buttonSize
|
||
local gap = db.buttonGap
|
||
local rowWidth = (size + gap) * BUTTONS_PER_ROW - gap
|
||
|
||
-- === BOTTOM BARS ===
|
||
local anchor = CreateFrame("Frame", "SFramesActionBarAnchor", UIParent)
|
||
anchor:SetWidth(rowWidth)
|
||
anchor:SetHeight(size * 3 + gap * 2)
|
||
local abPos = SFramesDB and SFramesDB.Positions and SFramesDB.Positions["ActionBarBottom"]
|
||
if abPos and abPos.point and abPos.relativePoint then
|
||
anchor:SetPoint(abPos.point, UIParent, abPos.relativePoint, abPos.xOfs or 0, abPos.yOfs or 0)
|
||
else
|
||
anchor:SetPoint("BOTTOM", UIParent, "BOTTOM", db.bottomOffsetX, db.bottomOffsetY)
|
||
end
|
||
anchor:SetScale(db.scale)
|
||
self.anchor = anchor
|
||
|
||
-- Row 1: ActionButton1-12 (safe to reparent, uses page calc)
|
||
local row1 = CreateFrame("Frame", "SFramesMainBar", anchor)
|
||
row1:SetWidth(rowWidth)
|
||
row1:SetHeight(size)
|
||
row1:SetPoint("BOTTOMLEFT", anchor, "BOTTOMLEFT", 0, 0)
|
||
self.row1 = row1
|
||
|
||
self.mainButtons = {}
|
||
for i = 1, BUTTONS_PER_ROW do
|
||
local b = _G["ActionButton" .. i]
|
||
if b then
|
||
b:SetParent(row1)
|
||
StyleButton(b)
|
||
table.insert(self.mainButtons, b)
|
||
end
|
||
end
|
||
|
||
-- === BONUS ACTION BAR (druid forms, warrior stances, etc.) ===
|
||
self.bonusButtons = {}
|
||
if BonusActionBarFrame then
|
||
BonusActionBarFrame:SetParent(row1)
|
||
BonusActionBarFrame:ClearAllPoints()
|
||
BonusActionBarFrame:SetPoint("BOTTOMLEFT", row1, "BOTTOMLEFT", 0, 0)
|
||
BonusActionBarFrame:SetWidth(rowWidth)
|
||
BonusActionBarFrame:SetHeight(size)
|
||
BonusActionBarFrame:SetFrameLevel(row1:GetFrameLevel() + 5)
|
||
|
||
for i = 1, BUTTONS_PER_ROW do
|
||
local b = _G["BonusActionButton" .. i]
|
||
if b then
|
||
StyleButton(b)
|
||
table.insert(self.bonusButtons, b)
|
||
end
|
||
end
|
||
|
||
BonusActionBarFrame:SetScript("OnShow", function()
|
||
for i = 1, BUTTONS_PER_ROW do
|
||
local b = _G["BonusActionButton" .. i]
|
||
if b and BonusActionButton_Update then
|
||
BonusActionButton_Update(b)
|
||
end
|
||
end
|
||
local db = AB:GetDB()
|
||
local s = db.buttonSize
|
||
local g = db.buttonGap
|
||
LayoutRow(AB.bonusButtons, BonusActionBarFrame, s, g)
|
||
local btnLevel = BonusActionBarFrame:GetFrameLevel() + 1
|
||
for _, btn in ipairs(AB.bonusButtons) do
|
||
btn:EnableMouse(true)
|
||
btn:SetFrameLevel(btnLevel)
|
||
if btn.sfBackdrop then
|
||
btn.sfBackdrop:SetFrameLevel(btnLevel - 1)
|
||
end
|
||
end
|
||
end)
|
||
end
|
||
|
||
-- === PAGE INDICATOR ===
|
||
local pi = row1:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
|
||
pi:SetPoint("RIGHT", row1, "LEFT", -4, 0)
|
||
pi:SetTextColor(0.9, 0.8, 0.2, 0.9)
|
||
self.pageIndicator = pi
|
||
|
||
-- Row 2: reposition the original MultiBarBottomLeft frame
|
||
if MultiBarBottomLeft then
|
||
MultiBarBottomLeft:SetParent(anchor)
|
||
MultiBarBottomLeft:ClearAllPoints()
|
||
MultiBarBottomLeft:SetPoint("BOTTOMLEFT", row1, "TOPLEFT", 0, gap)
|
||
MultiBarBottomLeft:SetWidth(rowWidth)
|
||
MultiBarBottomLeft:SetHeight(size)
|
||
MultiBarBottomLeft:Show()
|
||
end
|
||
self.row2 = MultiBarBottomLeft
|
||
|
||
self.bar2Buttons = {}
|
||
for i = 1, BUTTONS_PER_ROW do
|
||
local b = _G["MultiBarBottomLeftButton" .. i]
|
||
if b then
|
||
b:Show()
|
||
StyleButton(b)
|
||
table.insert(self.bar2Buttons, b)
|
||
end
|
||
end
|
||
|
||
-- Row 3: reposition the original MultiBarBottomRight frame
|
||
if MultiBarBottomRight then
|
||
MultiBarBottomRight:SetParent(anchor)
|
||
MultiBarBottomRight:ClearAllPoints()
|
||
MultiBarBottomRight:SetPoint("BOTTOMLEFT", MultiBarBottomLeft or row1, "TOPLEFT", 0, gap)
|
||
MultiBarBottomRight:SetWidth(rowWidth)
|
||
MultiBarBottomRight:SetHeight(size)
|
||
MultiBarBottomRight:Show()
|
||
end
|
||
self.row3 = MultiBarBottomRight
|
||
|
||
self.bar3Buttons = {}
|
||
for i = 1, BUTTONS_PER_ROW do
|
||
local b = _G["MultiBarBottomRightButton" .. i]
|
||
if b then
|
||
b:Show()
|
||
StyleButton(b)
|
||
table.insert(self.bar3Buttons, b)
|
||
end
|
||
end
|
||
|
||
-- === 狮鹫端帽 ===
|
||
local srcL = MainMenuBarLeftEndCap
|
||
local capW = (srcL and srcL.GetWidth) and srcL:GetWidth() or 128
|
||
local capH = (srcL and srcL.GetHeight) and srcL:GetHeight() or 76
|
||
|
||
local gryphonTex = GetGryphonTexPath(db.gryphonStyle)
|
||
|
||
local leftCap = CreateFrame("Frame", "SFramesGryphonLeft", UIParent)
|
||
leftCap:SetWidth(capW)
|
||
leftCap:SetHeight(capH)
|
||
local leftImg = leftCap:CreateTexture(nil, "ARTWORK")
|
||
leftImg:SetAllPoints()
|
||
leftImg:SetTexture(gryphonTex)
|
||
leftCap._sfTex = leftImg
|
||
self.gryphonLeft = leftCap
|
||
|
||
local rightCap = CreateFrame("Frame", "SFramesGryphonRight", UIParent)
|
||
rightCap:SetWidth(capW)
|
||
rightCap:SetHeight(capH)
|
||
local rightImg = rightCap:CreateTexture(nil, "ARTWORK")
|
||
rightImg:SetAllPoints()
|
||
rightImg:SetTexture(gryphonTex)
|
||
rightImg:SetTexCoord(1, 0, 0, 1)
|
||
rightCap._sfTex = rightImg
|
||
self.gryphonRight = rightCap
|
||
|
||
|
||
-- === RIGHT-SIDE BARS ===
|
||
local rightHolder = CreateFrame("Frame", "SFramesRightBarHolder", UIParent)
|
||
rightHolder:SetWidth(size * 2 + gap)
|
||
rightHolder:SetHeight((size + gap) * BUTTONS_PER_ROW - gap)
|
||
local rbPos = SFramesDB and SFramesDB.Positions and SFramesDB.Positions["ActionBarRight"]
|
||
if rbPos and rbPos.point and rbPos.relativePoint then
|
||
rightHolder:SetPoint(rbPos.point, UIParent, rbPos.relativePoint, rbPos.xOfs or 0, rbPos.yOfs or 0)
|
||
else
|
||
rightHolder:SetPoint("RIGHT", UIParent, "RIGHT", db.rightOffsetX, db.rightOffsetY)
|
||
end
|
||
rightHolder:SetScale(db.scale)
|
||
self.rightHolder = rightHolder
|
||
|
||
if MultiBarRight then
|
||
MultiBarRight:SetParent(rightHolder)
|
||
MultiBarRight:ClearAllPoints()
|
||
MultiBarRight:SetPoint("TOPRIGHT", rightHolder, "TOPRIGHT", 0, 0)
|
||
MultiBarRight:Show()
|
||
end
|
||
|
||
self.rightButtons = {}
|
||
for i = 1, BUTTONS_PER_ROW do
|
||
local b = _G["MultiBarRightButton" .. i]
|
||
if b then
|
||
b:Show()
|
||
StyleButton(b)
|
||
table.insert(self.rightButtons, b)
|
||
end
|
||
end
|
||
|
||
if MultiBarLeft then
|
||
MultiBarLeft:SetParent(rightHolder)
|
||
MultiBarLeft:ClearAllPoints()
|
||
MultiBarLeft:SetPoint("TOPRIGHT", MultiBarRight or rightHolder, "TOPLEFT", -gap, 0)
|
||
MultiBarLeft:Show()
|
||
end
|
||
|
||
self.leftButtons = {}
|
||
for i = 1, BUTTONS_PER_ROW do
|
||
local b = _G["MultiBarLeftButton" .. i]
|
||
if b then
|
||
b:Show()
|
||
StyleButton(b)
|
||
table.insert(self.leftButtons, b)
|
||
end
|
||
end
|
||
|
||
-- === STANCE BAR ===
|
||
local stanceHolder = CreateFrame("Frame", "SFramesStanceHolder", UIParent)
|
||
stanceHolder:SetWidth(rowWidth)
|
||
stanceHolder:SetHeight(size)
|
||
stanceHolder:SetScale(db.scale)
|
||
self.stanceHolder = stanceHolder
|
||
|
||
self.stanceButtons = {}
|
||
for i = 1, 10 do
|
||
local b = _G["ShapeshiftButton" .. i]
|
||
if b then
|
||
b:SetParent(stanceHolder)
|
||
StyleButton(b)
|
||
table.insert(self.stanceButtons, b)
|
||
end
|
||
end
|
||
|
||
-- === PET BAR ===
|
||
local petHolder = CreateFrame("Frame", "SFramesPetHolder", UIParent)
|
||
petHolder:SetWidth(rowWidth)
|
||
petHolder:SetHeight(size)
|
||
petHolder:SetScale(db.scale)
|
||
self.petHolder = petHolder
|
||
|
||
self.petButtons = {}
|
||
for i = 1, 10 do
|
||
local b = _G["PetActionButton" .. i]
|
||
if b then
|
||
b:SetParent(petHolder)
|
||
StylePetButton(b)
|
||
table.insert(self.petButtons, b)
|
||
end
|
||
end
|
||
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Adaptive text sizing: scale hotkey / count / macro-name fonts to button size
|
||
--------------------------------------------------------------------------------
|
||
local function UpdateButtonTexts(buttons, btnSize, showHotkey, showMacroName)
|
||
local fontSize = math.max(6, math.floor(btnSize * 0.25 + 0.5))
|
||
local nameSize = math.max(6, math.floor(btnSize * 0.22 + 0.5))
|
||
local font = SFrames:GetFont()
|
||
for _, b in ipairs(buttons) do
|
||
local bname = b:GetName()
|
||
local hotkey = _G[bname .. "HotKey"]
|
||
if hotkey then
|
||
hotkey:SetFont(font, fontSize, "OUTLINE")
|
||
if showHotkey then hotkey:Show() else hotkey:Hide() end
|
||
end
|
||
local count = _G[bname .. "Count"]
|
||
if count then
|
||
count:SetFont(font, fontSize, "OUTLINE")
|
||
end
|
||
local mn = _G[bname .. "Name"]
|
||
if mn then
|
||
mn:SetFont(font, nameSize, "OUTLINE")
|
||
if showMacroName then mn:Show() else mn:Hide() end
|
||
end
|
||
end
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Apply config
|
||
--------------------------------------------------------------------------------
|
||
function AB:ApplyConfig()
|
||
if not self.anchor then return end
|
||
local db = self:GetDB()
|
||
|
||
local size = db.buttonSize
|
||
local gap = db.buttonGap
|
||
local rowWidth = (size + gap) * BUTTONS_PER_ROW - gap
|
||
local colHeight = (size + gap) * BUTTONS_PER_ROW - gap
|
||
local totalHeight = db.barCount * size + (db.barCount - 1) * gap
|
||
|
||
-- Bottom bars anchor
|
||
self.anchor:SetScale(db.scale)
|
||
self.anchor:SetWidth(rowWidth)
|
||
self.anchor:SetHeight(totalHeight)
|
||
|
||
-- Row 1
|
||
self.row1:SetWidth(rowWidth)
|
||
self.row1:SetHeight(size)
|
||
LayoutRow(self.mainButtons, self.row1, size, gap)
|
||
|
||
-- Bonus bar (druid forms) — same layout as row 1, overlays when active
|
||
if self.bonusButtons and BonusActionBarFrame then
|
||
BonusActionBarFrame:SetWidth(rowWidth)
|
||
BonusActionBarFrame:SetHeight(size)
|
||
LayoutRow(self.bonusButtons, BonusActionBarFrame, size, gap)
|
||
end
|
||
|
||
-- Row 2
|
||
if self.row2 then
|
||
self.row2:SetWidth(rowWidth)
|
||
self.row2:SetHeight(size)
|
||
self.row2:ClearAllPoints()
|
||
self.row2:SetPoint("BOTTOMLEFT", self.row1, "TOPLEFT", 0, gap)
|
||
LayoutRow(self.bar2Buttons, self.row2, size, gap)
|
||
if db.barCount >= 2 then self.row2:Show() else self.row2:Hide() end
|
||
end
|
||
|
||
-- Row 3
|
||
if self.row3 then
|
||
self.row3:SetWidth(rowWidth)
|
||
self.row3:SetHeight(size)
|
||
self.row3:ClearAllPoints()
|
||
self.row3:SetPoint("BOTTOMLEFT", self.row2 or self.row1, "TOPLEFT", 0, gap)
|
||
LayoutRow(self.bar3Buttons, self.row3, size, gap)
|
||
if db.barCount >= 3 then self.row3:Show() else self.row3:Hide() end
|
||
end
|
||
|
||
-- Right-side bars
|
||
if self.rightHolder then
|
||
self.rightHolder:SetScale(db.scale)
|
||
self.rightHolder:SetWidth(size * 2 + gap)
|
||
self.rightHolder:SetHeight(colHeight)
|
||
|
||
if MultiBarRight then
|
||
MultiBarRight:SetWidth(size)
|
||
MultiBarRight:SetHeight(colHeight)
|
||
MultiBarRight:ClearAllPoints()
|
||
MultiBarRight:SetPoint("TOPRIGHT", self.rightHolder, "TOPRIGHT", 0, 0)
|
||
LayoutColumn(self.rightButtons, MultiBarRight, size, gap)
|
||
end
|
||
|
||
if MultiBarLeft then
|
||
MultiBarLeft:SetWidth(size)
|
||
MultiBarLeft:SetHeight(colHeight)
|
||
MultiBarLeft:ClearAllPoints()
|
||
MultiBarLeft:SetPoint("TOPRIGHT", MultiBarRight or self.rightHolder, "TOPLEFT", -gap, 0)
|
||
LayoutColumn(self.leftButtons, MultiBarLeft, size, gap)
|
||
end
|
||
|
||
if db.showRightBars then
|
||
self.rightHolder:Show()
|
||
else
|
||
self.rightHolder:Hide()
|
||
end
|
||
end
|
||
|
||
-- Alpha
|
||
local alpha = db.alpha or 1
|
||
if alpha < 0.1 then alpha = 0.1 end
|
||
if alpha > 1 then alpha = 1 end
|
||
if self.anchor then self.anchor:SetAlpha(alpha) end
|
||
if self.rightHolder then self.rightHolder:SetAlpha(alpha) end
|
||
if self.stanceHolder then self.stanceHolder:SetAlpha(alpha) end
|
||
if self.petHolder then self.petHolder:SetAlpha(alpha) end
|
||
|
||
-- Hotkey / macro name(使用缓存表,避免每次 ApplyConfig 都分配临时表)
|
||
if not self.allButtonsCache then
|
||
self.allButtonsCache = {}
|
||
for _, b in ipairs(self.mainButtons) do table.insert(self.allButtonsCache, b) end
|
||
if self.bonusButtons then
|
||
for _, b in ipairs(self.bonusButtons) do table.insert(self.allButtonsCache, b) end
|
||
end
|
||
for _, b in ipairs(self.bar2Buttons) do table.insert(self.allButtonsCache, b) end
|
||
for _, b in ipairs(self.bar3Buttons) do table.insert(self.allButtonsCache, b) end
|
||
for _, b in ipairs(self.rightButtons) do table.insert(self.allButtonsCache, b) end
|
||
for _, b in ipairs(self.leftButtons) do table.insert(self.allButtonsCache, b) end
|
||
for _, b in ipairs(self.stanceButtons) do table.insert(self.allButtonsCache, b) end
|
||
for _, b in ipairs(self.petButtons) do table.insert(self.allButtonsCache, b) end
|
||
end
|
||
|
||
-- Hotkey / macro name — per-group with adaptive font size
|
||
local showHK = db.showHotkey
|
||
local showMN = db.showMacroName
|
||
local smallSize = db.smallBarSize
|
||
UpdateButtonTexts(self.mainButtons, size, showHK, showMN)
|
||
if self.bonusButtons then
|
||
UpdateButtonTexts(self.bonusButtons, size, showHK, showMN)
|
||
end
|
||
UpdateButtonTexts(self.bar2Buttons, size, showHK, showMN)
|
||
UpdateButtonTexts(self.bar3Buttons, size, showHK, showMN)
|
||
UpdateButtonTexts(self.rightButtons, size, showHK, showMN)
|
||
UpdateButtonTexts(self.leftButtons, size, showHK, showMN)
|
||
UpdateButtonTexts(self.stanceButtons, smallSize, showHK, showMN)
|
||
UpdateButtonTexts(self.petButtons, smallSize, showHK, showMN)
|
||
|
||
-- Button visual style (rounded corners, inner shadow)
|
||
local isRounded = db.buttonRounded
|
||
local isShadow = db.buttonInnerShadow
|
||
for _, b in ipairs(self.allButtonsCache) do
|
||
ApplyButtonVisuals(b, isRounded, isShadow)
|
||
end
|
||
|
||
-- 始终显示动作条:强制 showgrid,让空格子也显示背景框
|
||
self:ApplyAlwaysShowGrid()
|
||
|
||
self:ApplyPosition()
|
||
self:ApplyStanceBar()
|
||
self:ApplyPetBar()
|
||
self:ApplyGryphon()
|
||
self:UpdateBonusBar()
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Always-show-grid: 对当前所有已显示行中的空格子,强制显示背景框和格子材质
|
||
-- 不控制哪些行显示/隐藏,只控制空格子是否绘制背景
|
||
--------------------------------------------------------------------------------
|
||
function AB:ApplyAlwaysShowGrid()
|
||
local db = self:GetDB()
|
||
|
||
-- 收集所有当前参与布局的按钮(已样式化的按钮)
|
||
local allBars = {
|
||
self.mainButtons,
|
||
self.bar2Buttons,
|
||
self.bar3Buttons,
|
||
self.rightButtons,
|
||
self.leftButtons,
|
||
}
|
||
|
||
if db.alwaysShowGrid then
|
||
for _, bar in ipairs(allBars) do
|
||
if bar then
|
||
for _, b in ipairs(bar) do
|
||
if styledButtons[b] then
|
||
-- showgrid > 0 时 Blizzard 不会隐藏空格按钮
|
||
b.showgrid = 1
|
||
b:Show()
|
||
-- 背景子帧始终随按钮显示
|
||
if b.sfBackdrop then b.sfBackdrop:Show() end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
else
|
||
for _, bar in ipairs(allBars) do
|
||
if bar then
|
||
for _, b in ipairs(bar) do
|
||
if styledButtons[b] then
|
||
b.showgrid = 0
|
||
-- 恢复 Blizzard 默认:无技能的格子隐藏
|
||
local ok, action = pcall(ActionButton_GetPagedID, b)
|
||
if ok and action and not HasAction(action) then
|
||
b:Hide()
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
if BonusActionBarFrame and BonusActionBarFrame:IsShown() then
|
||
for _, b in ipairs(self.mainButtons) do
|
||
b:Hide()
|
||
end
|
||
end
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Stance bar
|
||
--------------------------------------------------------------------------------
|
||
function AB:ApplyStanceBar()
|
||
local db = self:GetDB()
|
||
local size = db.smallBarSize
|
||
local gap = db.buttonGap
|
||
|
||
local numForms = GetNumShapeshiftForms and GetNumShapeshiftForms() or 0
|
||
|
||
if not db.showStanceBar or numForms == 0 then
|
||
if self.stanceHolder then self.stanceHolder:Hide() end
|
||
return
|
||
end
|
||
|
||
self.stanceHolder:SetScale(db.scale)
|
||
local topRow = self.row1
|
||
if db.barCount >= 3 and self.row3 then topRow = self.row3
|
||
elseif db.barCount >= 2 and self.row2 then topRow = self.row2 end
|
||
|
||
self.stanceHolder:ClearAllPoints()
|
||
self.stanceHolder:SetPoint("BOTTOMLEFT", topRow, "TOPLEFT", 0, gap)
|
||
|
||
local totalW = numForms * size + (numForms - 1) * gap
|
||
self.stanceHolder:SetWidth(totalW)
|
||
self.stanceHolder:SetHeight(size)
|
||
self.stanceHolder:Show()
|
||
|
||
for i, b in ipairs(self.stanceButtons) do
|
||
if i <= numForms then
|
||
b:SetWidth(size)
|
||
b:SetHeight(size)
|
||
b:ClearAllPoints()
|
||
if i == 1 then
|
||
b:SetPoint("BOTTOMLEFT", self.stanceHolder, "BOTTOMLEFT", 0, 0)
|
||
else
|
||
b:SetPoint("LEFT", self.stanceButtons[i - 1], "RIGHT", gap, 0)
|
||
end
|
||
b:Show()
|
||
else
|
||
b:Hide()
|
||
end
|
||
end
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Pet bar
|
||
--------------------------------------------------------------------------------
|
||
function AB:ApplyPetBar()
|
||
local db = self:GetDB()
|
||
local size = db.smallBarSize
|
||
local gap = db.buttonGap
|
||
|
||
local hasPet = HasPetUI and HasPetUI()
|
||
if not db.showPetBar or not hasPet then
|
||
if self.petHolder then self.petHolder:Hide() end
|
||
return
|
||
end
|
||
|
||
self.petHolder:SetScale(db.scale)
|
||
|
||
self.petHolder:ClearAllPoints()
|
||
local numForms = GetNumShapeshiftForms and GetNumShapeshiftForms() or 0
|
||
if db.showStanceBar and numForms > 0 and self.stanceHolder:IsShown() then
|
||
self.petHolder:SetPoint("BOTTOMLEFT", self.stanceHolder, "TOPLEFT", 0, gap)
|
||
else
|
||
local topRow = self.row1
|
||
if db.barCount >= 3 and self.row3 then topRow = self.row3
|
||
elseif db.barCount >= 2 and self.row2 then topRow = self.row2 end
|
||
self.petHolder:SetPoint("BOTTOMLEFT", topRow, "TOPLEFT", 0, gap)
|
||
end
|
||
|
||
local numPet = 10
|
||
local totalW = numPet * size + (numPet - 1) * gap
|
||
self.petHolder:SetWidth(totalW)
|
||
self.petHolder:SetHeight(size)
|
||
self.petHolder:Show()
|
||
|
||
for i, b in ipairs(self.petButtons) do
|
||
b:SetWidth(size)
|
||
b:SetHeight(size)
|
||
b:ClearAllPoints()
|
||
if i == 1 then
|
||
b:SetPoint("BOTTOMLEFT", self.petHolder, "BOTTOMLEFT", 0, 0)
|
||
else
|
||
b:SetPoint("LEFT", self.petButtons[i - 1], "RIGHT", gap, 0)
|
||
end
|
||
b:Show()
|
||
end
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Gryphon end-caps
|
||
--------------------------------------------------------------------------------
|
||
function AB:ApplyGryphon()
|
||
if not self.gryphonLeft or not self.gryphonRight then return end
|
||
if not self.anchor then return end
|
||
|
||
local db = self:GetDB()
|
||
|
||
if db.hideGryphon then
|
||
self.gryphonLeft:Hide()
|
||
self.gryphonRight:Hide()
|
||
return
|
||
end
|
||
|
||
-- 切换纹理样式
|
||
local texPath = GetGryphonTexPath(db.gryphonStyle)
|
||
if self.gryphonLeft._sfTex then
|
||
self.gryphonLeft._sfTex:SetTexture(texPath)
|
||
end
|
||
if self.gryphonRight._sfTex then
|
||
self.gryphonRight._sfTex:SetTexture(texPath)
|
||
self.gryphonRight._sfTex:SetTexCoord(1, 0, 0, 1)
|
||
end
|
||
|
||
local scale = db.scale or 1.0
|
||
local gw = db.gryphonWidth or 64
|
||
local gh = db.gryphonHeight or 64
|
||
|
||
self.gryphonLeft:SetScale(scale)
|
||
self.gryphonRight:SetScale(scale)
|
||
self.gryphonLeft:SetWidth(gw)
|
||
self.gryphonLeft:SetHeight(gh)
|
||
self.gryphonRight:SetWidth(gw)
|
||
self.gryphonRight:SetHeight(gh)
|
||
|
||
if db.gryphonOnTop then
|
||
self.gryphonLeft:SetFrameLevel(self.anchor:GetFrameLevel() + 10)
|
||
self.gryphonRight:SetFrameLevel(self.anchor:GetFrameLevel() + 10)
|
||
else
|
||
self.gryphonLeft:SetFrameLevel(0)
|
||
self.gryphonRight:SetFrameLevel(0)
|
||
end
|
||
|
||
local ox = db.gryphonOffsetX or 30
|
||
local oy = db.gryphonOffsetY or 0
|
||
|
||
local positions = SFramesDB and SFramesDB.Positions
|
||
|
||
self.gryphonLeft:ClearAllPoints()
|
||
local posL = positions and positions["GryphonLeft"]
|
||
if posL and posL.point and posL.relativePoint then
|
||
self.gryphonLeft:SetPoint(posL.point, UIParent, posL.relativePoint, posL.xOfs or 0, posL.yOfs or 0)
|
||
else
|
||
self.gryphonLeft:SetPoint("BOTTOMRIGHT", self.anchor, "BOTTOMLEFT", ox, oy)
|
||
end
|
||
|
||
self.gryphonRight:ClearAllPoints()
|
||
local posR = positions and positions["GryphonRight"]
|
||
if posR and posR.point and posR.relativePoint then
|
||
self.gryphonRight:SetPoint(posR.point, UIParent, posR.relativePoint, posR.xOfs or 0, posR.yOfs or 0)
|
||
else
|
||
self.gryphonRight:SetPoint("BOTTOMLEFT", self.anchor, "BOTTOMRIGHT", -ox, oy)
|
||
end
|
||
|
||
self.gryphonLeft:Show()
|
||
self.gryphonRight:Show()
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Slider-based position update
|
||
--------------------------------------------------------------------------------
|
||
function AB:ApplyPosition()
|
||
local db = self:GetDB()
|
||
local positions = SFramesDB and SFramesDB.Positions
|
||
|
||
if self.anchor then
|
||
self.anchor:ClearAllPoints()
|
||
local pos = positions and positions["ActionBarBottom"]
|
||
if pos and pos.point and pos.relativePoint then
|
||
self.anchor:SetPoint(pos.point, UIParent, pos.relativePoint, pos.xOfs or 0, pos.yOfs or 0)
|
||
else
|
||
self.anchor:SetPoint("BOTTOM", UIParent, "BOTTOM", db.bottomOffsetX, db.bottomOffsetY)
|
||
end
|
||
end
|
||
if self.rightHolder then
|
||
self.rightHolder:ClearAllPoints()
|
||
local pos = positions and positions["ActionBarRight"]
|
||
if pos and pos.point and pos.relativePoint then
|
||
self.rightHolder:SetPoint(pos.point, UIParent, pos.relativePoint, pos.xOfs or 0, pos.yOfs or 0)
|
||
else
|
||
self.rightHolder:SetPoint("RIGHT", UIParent, "RIGHT", db.rightOffsetX, db.rightOffsetY)
|
||
end
|
||
end
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Page indicator: shows which page / bonus offset the main bar is displaying
|
||
--------------------------------------------------------------------------------
|
||
function AB:UpdatePageIndicator()
|
||
if not self.pageIndicator then return end
|
||
local page = CURRENT_ACTIONBAR_PAGE or 1
|
||
local offset = GetBonusBarOffset and GetBonusBarOffset() or 0
|
||
if offset > 0 and page == 1 then
|
||
self.pageIndicator:SetTextColor(1.0, 0.5, 0.0)
|
||
self.pageIndicator:SetText("B" .. offset)
|
||
else
|
||
self.pageIndicator:SetTextColor(0.9, 0.8, 0.2)
|
||
self.pageIndicator:SetText(tostring(page))
|
||
end
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Bonus bar show/hide (warriors, druids, rogues — any class with stance/form)
|
||
-- Blizzard's ShowBonusActionBar() is triggered in MainMenuBar's OnEvent, but we
|
||
-- unregistered MainMenuBar events, so we must manage this ourselves.
|
||
--------------------------------------------------------------------------------
|
||
function AB:UpdateBonusBar()
|
||
if not BonusActionBarFrame then return end
|
||
local offset = GetBonusBarOffset and GetBonusBarOffset() or 0
|
||
local page = CURRENT_ACTIONBAR_PAGE or 1
|
||
if page == 1 and offset > 0 then
|
||
for _, b in ipairs(self.mainButtons) do b:Hide() end
|
||
BonusActionBarFrame:Show()
|
||
for i = 0, 4 do
|
||
local tex = _G["BonusActionBarTexture" .. i]
|
||
if tex then tex:SetAlpha(0) end
|
||
end
|
||
local db = self:GetDB()
|
||
local size = db.buttonSize
|
||
local gap = db.buttonGap
|
||
local rowWidth = (size + gap) * BUTTONS_PER_ROW - gap
|
||
BonusActionBarFrame:ClearAllPoints()
|
||
BonusActionBarFrame:SetPoint("BOTTOMLEFT", self.row1, "BOTTOMLEFT", 0, 0)
|
||
BonusActionBarFrame:SetWidth(rowWidth)
|
||
BonusActionBarFrame:SetHeight(size)
|
||
BonusActionBarFrame:SetFrameLevel(self.row1:GetFrameLevel() + 5)
|
||
LayoutRow(self.bonusButtons, BonusActionBarFrame, size, gap)
|
||
local btnLevel = BonusActionBarFrame:GetFrameLevel() + 1
|
||
for _, b in ipairs(self.bonusButtons) do
|
||
b:EnableMouse(true)
|
||
b:SetFrameLevel(btnLevel)
|
||
if b.sfBackdrop then
|
||
b.sfBackdrop:SetFrameLevel(btnLevel - 1)
|
||
end
|
||
end
|
||
else
|
||
BonusActionBarFrame:Hide()
|
||
for _, b in ipairs(self.mainButtons) do b:Show() end
|
||
end
|
||
self:UpdatePageIndicator()
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Range coloring - uses a separate red overlay; NEVER touches icon VertexColor
|
||
--------------------------------------------------------------------------------
|
||
local function GetOrCreateRangeOverlay(b)
|
||
if b.sfRangeOverlay then return b.sfRangeOverlay end
|
||
local inset = b.sfIconInset or 2
|
||
local ov = b:CreateTexture(nil, "OVERLAY")
|
||
ov:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||
ov:SetPoint("TOPLEFT", b, "TOPLEFT", inset, -inset)
|
||
ov:SetPoint("BOTTOMRIGHT", b, "BOTTOMRIGHT", -inset, inset)
|
||
ov:SetVertexColor(1.0, 0.1, 0.1, 0.35)
|
||
ov:Hide()
|
||
b.sfRangeOverlay = ov
|
||
return ov
|
||
end
|
||
|
||
function AB:SetupRangeCheck()
|
||
local rangeFrame = CreateFrame("Frame", "SFramesActionBarRangeCheck", UIParent)
|
||
rangeFrame.timer = 0
|
||
self.rangeFrame = rangeFrame
|
||
|
||
rangeFrame:SetScript("OnUpdate", function()
|
||
this.timer = this.timer + arg1
|
||
if this.timer < 0.2 then return end
|
||
this.timer = 0
|
||
|
||
local db = AB:GetDB()
|
||
if not db.rangeColoring then return end
|
||
|
||
local function CheckRange(buttons, idFunc)
|
||
local getID = idFunc or ActionButton_GetPagedID
|
||
if not getID then return end
|
||
for _, b in ipairs(buttons) do
|
||
local ok, action = pcall(getID, b)
|
||
if ok and action and HasAction(action) then
|
||
local inRange = IsActionInRange(action)
|
||
local ov = GetOrCreateRangeOverlay(b)
|
||
if inRange == 0 then
|
||
ov:Show()
|
||
else
|
||
ov:Hide()
|
||
end
|
||
else
|
||
if b.sfRangeOverlay then b.sfRangeOverlay:Hide() end
|
||
end
|
||
end
|
||
end
|
||
|
||
CheckRange(AB.mainButtons)
|
||
if AB.bonusButtons and BonusActionBarFrame and BonusActionBarFrame:IsShown() then
|
||
CheckRange(AB.bonusButtons, BonusActionButton_GetPagedID)
|
||
end
|
||
CheckRange(AB.bar2Buttons)
|
||
CheckRange(AB.bar3Buttons)
|
||
end)
|
||
end
|
||
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Initialize
|
||
--------------------------------------------------------------------------------
|
||
function AB:Initialize()
|
||
local db = self:GetDB()
|
||
if not db.enable then return end
|
||
|
||
self:HideBlizzardBars()
|
||
self:CreateBars()
|
||
self:ApplyConfig()
|
||
self:SetupRangeCheck()
|
||
|
||
-- Hook Blizzard ActionButton functions that reset NormalTexture.
|
||
-- These use 'this' (no explicit args in vanilla), so parameterless hook is safe.
|
||
local function SuppressNormalTexture()
|
||
if this and styledButtons[this] then
|
||
local nt = _G[this:GetName() .. "NormalTexture"]
|
||
HideNormalTexture(nt)
|
||
end
|
||
end
|
||
-- alwaysShowGrid 模式下,阻止空格子被隐藏
|
||
local function ForceShowIfGrid()
|
||
if this and styledButtons[this] then
|
||
local adb = AB:GetDB()
|
||
if adb.alwaysShowGrid then
|
||
this.showgrid = 1
|
||
this:Show()
|
||
if this.sfBackdrop then this.sfBackdrop:Show() end
|
||
end
|
||
end
|
||
end
|
||
local hookTargets = {
|
||
"ActionButton_Update",
|
||
"ActionButton_UpdateUsable",
|
||
"ActionButton_UpdateState",
|
||
}
|
||
for _, fn in ipairs(hookTargets) do
|
||
local orig = _G[fn]
|
||
if orig then
|
||
_G[fn] = function()
|
||
pcall(orig)
|
||
SuppressNormalTexture()
|
||
ForceShowIfGrid()
|
||
end
|
||
end
|
||
end
|
||
-- Hook ActionButton_ShowGrid / ActionButton_HideGrid
|
||
local origShowGrid = _G["ActionButton_ShowGrid"]
|
||
local origHideGrid = _G["ActionButton_HideGrid"]
|
||
|
||
-- 判断某个按钮是否属于主动作条(ActionButton1-12)
|
||
local function IsMainButton(b)
|
||
if not b or not AB.mainButtons then return false end
|
||
for _, mb in ipairs(AB.mainButtons) do
|
||
if mb == b then return true end
|
||
end
|
||
return false
|
||
end
|
||
|
||
-- 当前是否处于 BonusBar 激活状态(潜行、变身等)
|
||
local function IsBonusBarActive()
|
||
local offset = GetBonusBarOffset and GetBonusBarOffset() or 0
|
||
local page = CURRENT_ACTIONBAR_PAGE or 1
|
||
return (page == 1 and offset > 0)
|
||
end
|
||
|
||
if origShowGrid then
|
||
_G["ActionButton_ShowGrid"] = function()
|
||
if this and this.showgrid == nil then this.showgrid = 0 end
|
||
if this and styledButtons[this] and IsMainButton(this) and IsBonusBarActive() then
|
||
SuppressNormalTexture()
|
||
return
|
||
end
|
||
pcall(origShowGrid)
|
||
SuppressNormalTexture()
|
||
end
|
||
end
|
||
if origHideGrid then
|
||
_G["ActionButton_HideGrid"] = function()
|
||
if this and this.showgrid == nil then this.showgrid = 0 end
|
||
if this and styledButtons[this] then
|
||
local adb = AB:GetDB()
|
||
if IsMainButton(this) and IsBonusBarActive() then
|
||
this.showgrid = 0
|
||
this:Hide()
|
||
SuppressNormalTexture()
|
||
return
|
||
end
|
||
if adb.alwaysShowGrid then
|
||
this.showgrid = 1
|
||
this:Show()
|
||
SuppressNormalTexture()
|
||
return
|
||
end
|
||
end
|
||
pcall(origHideGrid)
|
||
SuppressNormalTexture()
|
||
end
|
||
end
|
||
|
||
-- BonusActionButton functions take an explicit button arg — must pass it through
|
||
local function SuppressNT(b)
|
||
if b and styledButtons[b] then
|
||
local nt = _G[b:GetName() .. "NormalTexture"]
|
||
HideNormalTexture(nt)
|
||
end
|
||
end
|
||
local origBonusUpdate = _G["BonusActionButton_Update"]
|
||
if origBonusUpdate then
|
||
_G["BonusActionButton_Update"] = function(button)
|
||
pcall(origBonusUpdate, button)
|
||
local b = button or this
|
||
SuppressNT(b)
|
||
if b and styledButtons[b] and AB.bonusButtons then
|
||
local db = AB:GetDB()
|
||
local size = db.buttonSize
|
||
local gap = db.buttonGap
|
||
for idx, btn in ipairs(AB.bonusButtons) do
|
||
if btn == b then
|
||
btn:SetWidth(size)
|
||
btn:SetHeight(size)
|
||
btn:ClearAllPoints()
|
||
if idx == 1 then
|
||
btn:SetPoint("BOTTOMLEFT", BonusActionBarFrame, "BOTTOMLEFT", 0, 0)
|
||
else
|
||
btn:SetPoint("LEFT", AB.bonusButtons[idx - 1], "RIGHT", gap, 0)
|
||
end
|
||
break
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
local origBonusUsable = _G["BonusActionButton_UpdateUsable"]
|
||
if origBonusUsable then
|
||
_G["BonusActionButton_UpdateUsable"] = function(button)
|
||
pcall(origBonusUsable, button)
|
||
SuppressNT(button or this)
|
||
end
|
||
end
|
||
|
||
-- Hook PetActionBar_Update: Blizzard resets NormalTexture2 vertex color/alpha
|
||
local origPetBarUpdate = PetActionBar_Update
|
||
if origPetBarUpdate then
|
||
PetActionBar_Update = function()
|
||
pcall(origPetBarUpdate)
|
||
for _, b in ipairs(AB.petButtons or {}) do
|
||
KillPetNormalTextures(b)
|
||
end
|
||
end
|
||
end
|
||
|
||
-- Hook MultiActionBar_Update: Blizzard 在 PLAYER_ENTERING_WORLD 等事件中调用此函数,
|
||
-- 它会根据经验条/声望条的高度重设 MultiBarBottomLeft/Right 的位置,覆盖我们的布局。
|
||
-- 仅修正 row2/row3 的锚点,不调用 ApplyConfig 避免触发 MultiBarBottomLeft:Show 再次递归。
|
||
local origMultiActionBarUpdate = MultiActionBar_Update
|
||
if origMultiActionBarUpdate then
|
||
local inMultiBarHook = false
|
||
MultiActionBar_Update = function()
|
||
pcall(origMultiActionBarUpdate)
|
||
if AB.anchor and not inMultiBarHook then
|
||
inMultiBarHook = true
|
||
local g = AB:GetDB().buttonGap
|
||
if AB.row2 then
|
||
AB.row2:ClearAllPoints()
|
||
AB.row2:SetPoint("BOTTOMLEFT", AB.row1, "TOPLEFT", 0, g)
|
||
end
|
||
if AB.row3 then
|
||
AB.row3:ClearAllPoints()
|
||
AB.row3:SetPoint("BOTTOMLEFT", AB.row2 or AB.row1, "TOPLEFT", 0, g)
|
||
end
|
||
inMultiBarHook = false
|
||
end
|
||
end
|
||
end
|
||
|
||
-- Hook UIParent_ManageFramePositions: Blizzard 在进出战斗、切换地图等场景调用此
|
||
-- 函数重排 MultiBar 位置(会考虑经验条/声望条高度),覆盖我们的布局。
|
||
-- 在原函数执行完毕后立即修正 row2/row3 锚点。
|
||
local origManageFramePositions = UIParent_ManageFramePositions
|
||
if origManageFramePositions then
|
||
UIParent_ManageFramePositions = function(a1, a2, a3)
|
||
origManageFramePositions(a1, a2, a3)
|
||
if AB.anchor and AB.row1 then
|
||
local g = AB:GetDB().buttonGap
|
||
if AB.row2 then
|
||
AB.row2:ClearAllPoints()
|
||
AB.row2:SetPoint("BOTTOMLEFT", AB.row1, "TOPLEFT", 0, g)
|
||
end
|
||
if AB.row3 then
|
||
AB.row3:ClearAllPoints()
|
||
AB.row3:SetPoint("BOTTOMLEFT", AB.row2 or AB.row1, "TOPLEFT", 0, g)
|
||
end
|
||
end
|
||
if SFrames and SFrames.Chat then
|
||
SFrames.Chat:ReanchorChatFrames()
|
||
end
|
||
end
|
||
end
|
||
|
||
-- Direct event-driven bonus bar update + safety poller.
|
||
-- Events trigger immediate update; poller catches any missed state every 0.2s.
|
||
SFrames:RegisterEvent("PLAYER_ENTERING_WORLD", function()
|
||
AB:UpdateBonusBar()
|
||
AB:ApplyConfig()
|
||
AB:RefreshAllHotkeys()
|
||
end)
|
||
|
||
SFrames:RegisterEvent("UPDATE_BINDINGS", function()
|
||
AB:RefreshAllHotkeys()
|
||
end)
|
||
|
||
SFrames:RegisterEvent("UPDATE_BONUS_ACTIONBAR", function()
|
||
AB:UpdateBonusBar()
|
||
end)
|
||
|
||
SFrames:RegisterEvent("ACTIONBAR_PAGE_CHANGED", function()
|
||
AB:UpdateBonusBar()
|
||
end)
|
||
|
||
SFrames:RegisterEvent("UPDATE_SHAPESHIFT_FORMS", function()
|
||
AB:UpdateBonusBar()
|
||
AB:ApplyStanceBar()
|
||
AB:ApplyPetBar()
|
||
end)
|
||
|
||
-- Safety poller: if Blizzard code or timing issues cause a mismatch,
|
||
-- correct it within 0.2 seconds.
|
||
local bonusPoller = CreateFrame("Frame")
|
||
bonusPoller.timer = 0
|
||
bonusPoller:SetScript("OnUpdate", function()
|
||
this.timer = this.timer + arg1
|
||
if this.timer < 0.2 then return end
|
||
this.timer = 0
|
||
local offset = GetBonusBarOffset and GetBonusBarOffset() or 0
|
||
local page = CURRENT_ACTIONBAR_PAGE or 1
|
||
local want = (page == 1 and offset > 0)
|
||
local have = BonusActionBarFrame and BonusActionBarFrame:IsShown()
|
||
if (want and not have) or (not want and have) then
|
||
AB:UpdateBonusBar()
|
||
elseif have then
|
||
local db = AB:GetDB()
|
||
local size = db.buttonSize
|
||
local gap = db.buttonGap
|
||
local rowWidth = (size + gap) * BUTTONS_PER_ROW - gap
|
||
BonusActionBarFrame:ClearAllPoints()
|
||
BonusActionBarFrame:SetPoint("BOTTOMLEFT", AB.row1, "BOTTOMLEFT", 0, 0)
|
||
BonusActionBarFrame:SetWidth(rowWidth)
|
||
BonusActionBarFrame:SetHeight(size)
|
||
BonusActionBarFrame:SetFrameLevel(AB.row1:GetFrameLevel() + 5)
|
||
LayoutRow(AB.bonusButtons, BonusActionBarFrame, size, gap)
|
||
local btnLevel = BonusActionBarFrame:GetFrameLevel() + 1
|
||
for _, b in ipairs(AB.bonusButtons) do
|
||
b:EnableMouse(true)
|
||
b:SetFrameLevel(btnLevel)
|
||
if b.sfBackdrop then
|
||
b.sfBackdrop:SetFrameLevel(btnLevel - 1)
|
||
end
|
||
end
|
||
for _, b in ipairs(AB.mainButtons) do
|
||
if b:IsShown() then b:Hide() end
|
||
end
|
||
end
|
||
AB:UpdatePageIndicator()
|
||
end)
|
||
|
||
SFrames:RegisterEvent("PET_BAR_UPDATE", function()
|
||
AB:ApplyPetBar()
|
||
end)
|
||
|
||
SFrames:RegisterEvent("UNIT_PET", function()
|
||
if arg1 == "player" then
|
||
AB:ApplyPetBar()
|
||
end
|
||
end)
|
||
|
||
-- 进出战斗和切换区域时立即修正行间距,防止 Blizzard 布局覆盖
|
||
local function FixRowAnchors()
|
||
if not AB.anchor or not AB.row1 then return end
|
||
local g = AB:GetDB().buttonGap
|
||
if AB.row2 then
|
||
AB.row2:ClearAllPoints()
|
||
AB.row2:SetPoint("BOTTOMLEFT", AB.row1, "TOPLEFT", 0, g)
|
||
end
|
||
if AB.row3 then
|
||
AB.row3:ClearAllPoints()
|
||
AB.row3:SetPoint("BOTTOMLEFT", AB.row2 or AB.row1, "TOPLEFT", 0, g)
|
||
end
|
||
end
|
||
|
||
SFrames:RegisterEvent("PLAYER_REGEN_DISABLED", FixRowAnchors)
|
||
SFrames:RegisterEvent("PLAYER_REGEN_ENABLED", FixRowAnchors)
|
||
SFrames:RegisterEvent("ZONE_CHANGED", FixRowAnchors)
|
||
SFrames:RegisterEvent("ZONE_CHANGED_NEW_AREA", FixRowAnchors)
|
||
SFrames:RegisterEvent("ZONE_CHANGED_INDOORS", FixRowAnchors)
|
||
|
||
-- Register movers
|
||
if SFrames.Movers and SFrames.Movers.RegisterMover then
|
||
if self.anchor then
|
||
SFrames.Movers:RegisterMover("ActionBarBottom", self.anchor, "底部动作条",
|
||
"BOTTOM", "UIParent", "BOTTOM", db.bottomOffsetX, db.bottomOffsetY,
|
||
function() AB:ApplyStanceBar(); AB:ApplyPetBar(); AB:ApplyGryphon() end)
|
||
end
|
||
if self.rightHolder then
|
||
SFrames.Movers:RegisterMover("ActionBarRight", self.rightHolder, "右侧动作条",
|
||
"RIGHT", "UIParent", "RIGHT", db.rightOffsetX, db.rightOffsetY)
|
||
end
|
||
if self.stanceHolder then
|
||
SFrames.Movers:RegisterMover("StanceBar", self.stanceHolder, "姿态条",
|
||
"BOTTOMLEFT", "SFramesActionBarAnchor", "TOPLEFT", 0, db.buttonGap)
|
||
end
|
||
if self.petHolder then
|
||
SFrames.Movers:RegisterMover("PetBar", self.petHolder, "宠物条",
|
||
"BOTTOMLEFT", "SFramesActionBarAnchor", "TOPLEFT", 0, db.buttonGap)
|
||
end
|
||
if self.gryphonLeft then
|
||
SFrames.Movers:RegisterMover("GryphonLeft", self.gryphonLeft, "狮鹫(左)",
|
||
"BOTTOMRIGHT", "SFramesActionBarAnchor", "BOTTOMLEFT", db.gryphonOffsetX or 30, db.gryphonOffsetY or 0)
|
||
end
|
||
if self.gryphonRight then
|
||
SFrames.Movers:RegisterMover("GryphonRight", self.gryphonRight, "狮鹫(右)",
|
||
"BOTTOMLEFT", "SFramesActionBarAnchor", "BOTTOMRIGHT", -(db.gryphonOffsetX or 30), db.gryphonOffsetY or 0)
|
||
end
|
||
end
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Key Binding Mode
|
||
-- Hover any action/stance/pet button and press a key to bind it.
|
||
-- ESC exits. Right-click clears. Mouse wheel supported.
|
||
--------------------------------------------------------------------------------
|
||
local BUTTON_BINDING_MAP = {}
|
||
|
||
do
|
||
for i = 1, 12 do
|
||
BUTTON_BINDING_MAP["ActionButton" .. i] = "ACTIONBUTTON" .. i
|
||
BUTTON_BINDING_MAP["MultiBarBottomLeftButton" .. i] = "MULTIACTIONBAR1BUTTON" .. i
|
||
BUTTON_BINDING_MAP["MultiBarBottomRightButton" .. i] = "MULTIACTIONBAR2BUTTON" .. i
|
||
BUTTON_BINDING_MAP["MultiBarRightButton" .. i] = "MULTIACTIONBAR3BUTTON" .. i
|
||
BUTTON_BINDING_MAP["MultiBarLeftButton" .. i] = "MULTIACTIONBAR4BUTTON" .. i
|
||
end
|
||
for i = 1, 10 do
|
||
BUTTON_BINDING_MAP["ShapeshiftButton" .. i] = "SHAPESHIFTBUTTON" .. i
|
||
BUTTON_BINDING_MAP["PetActionButton" .. i] = "BONUSACTIONBUTTON" .. i
|
||
end
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Hotkey text refresh: update the HotKey FontString on buttons to reflect
|
||
-- current keybindings (works for all bars including stance and pet).
|
||
--------------------------------------------------------------------------------
|
||
local function RefreshButtonHotkey(button)
|
||
if not button then return end
|
||
local name = button:GetName()
|
||
if not name then return end
|
||
local cmd = BUTTON_BINDING_MAP[name]
|
||
if not cmd then return end
|
||
local hotkey = _G[name .. "HotKey"]
|
||
if not hotkey then return end
|
||
local key1 = GetBindingKey(cmd)
|
||
if key1 then
|
||
local text = key1
|
||
if GetBindingText then
|
||
text = GetBindingText(key1, "KEY_", 1) or key1
|
||
end
|
||
hotkey:SetText(text)
|
||
else
|
||
hotkey:SetText("")
|
||
end
|
||
end
|
||
|
||
function AB:RefreshAllHotkeys()
|
||
local function Refresh(list)
|
||
if not list then return end
|
||
for _, b in ipairs(list) do RefreshButtonHotkey(b) end
|
||
end
|
||
Refresh(self.mainButtons)
|
||
Refresh(self.bonusButtons)
|
||
Refresh(self.bar2Buttons)
|
||
Refresh(self.bar3Buttons)
|
||
Refresh(self.rightButtons)
|
||
Refresh(self.leftButtons)
|
||
Refresh(self.stanceButtons)
|
||
Refresh(self.petButtons)
|
||
end
|
||
|
||
local IGNORE_KEYS = {
|
||
LSHIFT = true, RSHIFT = true,
|
||
LCTRL = true, RCTRL = true,
|
||
LALT = true, RALT = true,
|
||
UNKNOWN = true,
|
||
}
|
||
|
||
local function BuildKeyString(key)
|
||
if IGNORE_KEYS[key] then return nil end
|
||
local mods = ""
|
||
if IsAltKeyDown() then mods = mods .. "ALT-" end
|
||
if IsControlKeyDown() then mods = mods .. "CTRL-" end
|
||
if IsShiftKeyDown() then mods = mods .. "SHIFT-" end
|
||
return mods .. key
|
||
end
|
||
|
||
local function GetButtonBindingCmd(button)
|
||
if not button then return nil end
|
||
local name = button:GetName()
|
||
return name and BUTTON_BINDING_MAP[name]
|
||
end
|
||
|
||
local function ShortKeyText(key)
|
||
if not key then return "" end
|
||
key = string.gsub(key, "SHIFT%-", "S-")
|
||
key = string.gsub(key, "CTRL%-", "C-")
|
||
key = string.gsub(key, "ALT%-", "A-")
|
||
key = string.gsub(key, "MOUSEWHEELUP", "WhlUp")
|
||
key = string.gsub(key, "MOUSEWHEELDOWN", "WhlDn")
|
||
key = string.gsub(key, "BUTTON3", "M3")
|
||
key = string.gsub(key, "BUTTON4", "M4")
|
||
key = string.gsub(key, "BUTTON5", "M5")
|
||
return key
|
||
end
|
||
|
||
local keyBindActive = false
|
||
local hoveredBindButton = nil
|
||
local keyBindOverlays = {}
|
||
local captureFrame = nil
|
||
local statusFrame = nil
|
||
|
||
local function UpdateOverlayText(button)
|
||
local ov = keyBindOverlays[button]
|
||
if not ov then return end
|
||
local cmd = GetButtonBindingCmd(button)
|
||
if not cmd then ov.label:SetText(""); return end
|
||
local key1, key2 = GetBindingKey(cmd)
|
||
local t = ""
|
||
if key1 then t = ShortKeyText(key1) end
|
||
if key2 then t = t .. "\n" .. ShortKeyText(key2) end
|
||
ov.label:SetText(t)
|
||
end
|
||
|
||
local CLICK_TO_KEY = {
|
||
MiddleButton = "BUTTON3",
|
||
Button4 = "BUTTON4",
|
||
Button5 = "BUTTON5",
|
||
}
|
||
|
||
local function CreateBindOverlay(button)
|
||
if keyBindOverlays[button] then return keyBindOverlays[button] end
|
||
|
||
local ov = CreateFrame("Button", nil, button)
|
||
ov:SetAllPoints(button)
|
||
ov:SetFrameLevel(button:GetFrameLevel() + 10)
|
||
ov:RegisterForClicks("RightButtonUp", "MiddleButtonUp", "Button4Up", "Button5Up")
|
||
|
||
local bg = ov:CreateTexture(nil, "BACKGROUND")
|
||
bg:SetAllPoints()
|
||
bg:SetTexture(0, 0, 0, 0.55)
|
||
|
||
local label = ov:CreateFontString(nil, "OVERLAY")
|
||
label:SetFont(SFrames:GetFont(), 7, "OUTLINE")
|
||
label:SetPoint("CENTER", ov, "CENTER", 0, 0)
|
||
label:SetWidth(button:GetWidth() - 2)
|
||
label:SetJustifyH("CENTER")
|
||
label:SetTextColor(1, 0.82, 0)
|
||
ov.label = label
|
||
|
||
local highlight = ov:CreateTexture(nil, "HIGHLIGHT")
|
||
highlight:SetAllPoints()
|
||
highlight:SetTexture(1, 1, 1, 0.18)
|
||
|
||
ov:SetScript("OnEnter", function()
|
||
hoveredBindButton = button
|
||
GameTooltip:SetOwner(this, "ANCHOR_TOP")
|
||
local cmd = GetButtonBindingCmd(button)
|
||
if cmd then
|
||
GameTooltip:AddLine(cmd, 1, 0.82, 0)
|
||
local k1, k2 = GetBindingKey(cmd)
|
||
if k1 then GameTooltip:AddLine("按键 1: " .. k1, 1, 1, 1) end
|
||
if k2 then GameTooltip:AddLine("按键 2: " .. k2, 0.7, 0.7, 0.7) end
|
||
end
|
||
GameTooltip:AddLine("按下按键/鼠标键/滚轮绑定", 0.5, 0.8, 0.5)
|
||
GameTooltip:AddLine("右键点击清除绑定", 0.8, 0.5, 0.5)
|
||
GameTooltip:Show()
|
||
end)
|
||
ov:SetScript("OnLeave", function()
|
||
hoveredBindButton = nil
|
||
GameTooltip:Hide()
|
||
end)
|
||
ov:SetScript("OnClick", function()
|
||
if arg1 == "RightButton" then
|
||
local cmd = GetButtonBindingCmd(button)
|
||
if cmd then
|
||
local k1, k2 = GetBindingKey(cmd)
|
||
if k2 then SetBinding(k2, nil) end
|
||
if k1 then SetBinding(k1, nil) end
|
||
SaveBindings(2)
|
||
UpdateOverlayText(button)
|
||
RefreshButtonHotkey(button)
|
||
DEFAULT_CHAT_FRAME:AddMessage("|cff88ccff[Nanami-UI]|r 已清除 " .. cmd .. " 的绑定")
|
||
end
|
||
else
|
||
local mouseKey = CLICK_TO_KEY[arg1]
|
||
if mouseKey then
|
||
local cmd = GetButtonBindingCmd(button)
|
||
if not cmd then return end
|
||
local keyStr = BuildKeyString(mouseKey)
|
||
if not keyStr then return end
|
||
local old = GetBindingAction(keyStr)
|
||
if old and old ~= "" and old ~= cmd then SetBinding(keyStr, nil) end
|
||
SetBinding(keyStr, cmd)
|
||
SaveBindings(2)
|
||
UpdateOverlayText(button)
|
||
RefreshButtonHotkey(button)
|
||
DEFAULT_CHAT_FRAME:AddMessage("|cff88ccff[Nanami-UI]|r " .. keyStr .. " -> " .. cmd)
|
||
end
|
||
end
|
||
end)
|
||
|
||
ov:EnableMouseWheel(true)
|
||
ov:SetScript("OnMouseWheel", function()
|
||
local cmd = GetButtonBindingCmd(button)
|
||
if not cmd then return end
|
||
local wheel = (arg1 > 0) and "MOUSEWHEELUP" or "MOUSEWHEELDOWN"
|
||
local mods = ""
|
||
if IsAltKeyDown() then mods = mods .. "ALT-" end
|
||
if IsControlKeyDown() then mods = mods .. "CTRL-" end
|
||
if IsShiftKeyDown() then mods = mods .. "SHIFT-" end
|
||
local keyStr = mods .. wheel
|
||
local old = GetBindingAction(keyStr)
|
||
if old and old ~= "" and old ~= cmd then SetBinding(keyStr, nil) end
|
||
SetBinding(keyStr, cmd)
|
||
SaveBindings(2)
|
||
UpdateOverlayText(button)
|
||
RefreshButtonHotkey(button)
|
||
DEFAULT_CHAT_FRAME:AddMessage("|cff88ccff[Nanami-UI]|r " .. keyStr .. " -> " .. cmd)
|
||
end)
|
||
|
||
ov:Hide()
|
||
keyBindOverlays[button] = ov
|
||
return ov
|
||
end
|
||
|
||
function AB:EnterKeyBindMode()
|
||
if keyBindActive then return end
|
||
keyBindActive = true
|
||
|
||
local allButtons = {}
|
||
local function Collect(list)
|
||
if not list then return end
|
||
for _, b in ipairs(list) do table.insert(allButtons, b) end
|
||
end
|
||
Collect(self.mainButtons)
|
||
Collect(self.bar2Buttons)
|
||
Collect(self.bar3Buttons)
|
||
Collect(self.rightButtons)
|
||
Collect(self.leftButtons)
|
||
Collect(self.stanceButtons)
|
||
Collect(self.petButtons)
|
||
|
||
for _, b in ipairs(allButtons) do
|
||
local ov = CreateBindOverlay(b)
|
||
ov:Show()
|
||
UpdateOverlayText(b)
|
||
end
|
||
|
||
if not captureFrame then
|
||
captureFrame = CreateFrame("Frame", "SFramesKeyBindCapture", UIParent)
|
||
captureFrame:SetFrameStrata("TOOLTIP")
|
||
captureFrame:SetWidth(1)
|
||
captureFrame:SetHeight(1)
|
||
captureFrame:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 0, 0)
|
||
captureFrame:EnableKeyboard(true)
|
||
captureFrame:EnableMouse(false)
|
||
captureFrame:SetScript("OnKeyDown", function()
|
||
local key = arg1
|
||
if key == "ESCAPE" then
|
||
AB:ExitKeyBindMode()
|
||
return
|
||
end
|
||
if not hoveredBindButton then return end
|
||
local keyStr = BuildKeyString(key)
|
||
if not keyStr then return end
|
||
local cmd = GetButtonBindingCmd(hoveredBindButton)
|
||
if not cmd then return end
|
||
local old = GetBindingAction(keyStr)
|
||
if old and old ~= "" and old ~= cmd then SetBinding(keyStr, nil) end
|
||
SetBinding(keyStr, cmd)
|
||
SaveBindings(2)
|
||
UpdateOverlayText(hoveredBindButton)
|
||
RefreshButtonHotkey(hoveredBindButton)
|
||
DEFAULT_CHAT_FRAME:AddMessage("|cff88ccff[Nanami-UI]|r " .. keyStr .. " -> " .. cmd)
|
||
end)
|
||
end
|
||
captureFrame:Show()
|
||
|
||
if not statusFrame then
|
||
statusFrame = CreateFrame("Frame", "SFramesKeyBindStatus", UIParent)
|
||
statusFrame:SetFrameStrata("TOOLTIP")
|
||
statusFrame:SetWidth(340)
|
||
statusFrame:SetHeight(100)
|
||
statusFrame:SetPoint("TOP", UIParent, "TOP", 0, -40)
|
||
statusFrame:SetMovable(true)
|
||
statusFrame:EnableMouse(true)
|
||
statusFrame:RegisterForDrag("LeftButton")
|
||
statusFrame:SetScript("OnDragStart", function() this:StartMoving() end)
|
||
statusFrame:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
|
||
SFrames:CreateBackdrop(statusFrame)
|
||
|
||
local title = statusFrame:CreateFontString(nil, "OVERLAY")
|
||
title:SetFont(SFrames:GetFont(), 13, "OUTLINE")
|
||
title:SetPoint("TOP", statusFrame, "TOP", 0, -12)
|
||
title:SetText("|cffffcc00按键绑定模式|r")
|
||
|
||
local desc = statusFrame:CreateFontString(nil, "OVERLAY")
|
||
desc:SetFont(SFrames:GetFont(), 10, "OUTLINE")
|
||
desc:SetPoint("TOP", title, "BOTTOM", 0, -6)
|
||
desc:SetWidth(300)
|
||
desc:SetJustifyH("CENTER")
|
||
desc:SetText("悬停按钮 + 按键/鼠标键/滚轮绑定 | 右键清除")
|
||
desc:SetTextColor(0.82, 0.82, 0.82)
|
||
|
||
local saveBtn = CreateFrame("Button", "SFramesKeyBindSave", statusFrame, "UIPanelButtonTemplate")
|
||
saveBtn:SetWidth(120)
|
||
saveBtn:SetHeight(26)
|
||
saveBtn:SetPoint("BOTTOM", statusFrame, "BOTTOM", 0, 10)
|
||
saveBtn:SetText("保存并退出")
|
||
saveBtn:SetScript("OnClick", function()
|
||
AB:ExitKeyBindMode()
|
||
end)
|
||
|
||
local _T = SFrames.ActiveTheme
|
||
local function HideBindBtnTex(tex)
|
||
if not tex then return end
|
||
if tex.SetTexture then tex:SetTexture(nil) end
|
||
if tex.SetAlpha then tex:SetAlpha(0) end
|
||
if tex.Hide then tex:Hide() end
|
||
end
|
||
HideBindBtnTex(saveBtn:GetNormalTexture())
|
||
HideBindBtnTex(saveBtn:GetPushedTexture())
|
||
HideBindBtnTex(saveBtn:GetHighlightTexture())
|
||
HideBindBtnTex(saveBtn:GetDisabledTexture())
|
||
for _, sfx in ipairs({"Left","Right","Middle"}) do
|
||
local t = _G["SFramesKeyBindSave"..sfx]
|
||
if t then t:SetAlpha(0); t:Hide() end
|
||
end
|
||
saveBtn:SetBackdrop({
|
||
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||
tile = true, tileSize = 16, edgeSize = 14,
|
||
insets = { left = 3, right = 3, top = 3, bottom = 3 },
|
||
})
|
||
saveBtn:SetBackdropColor(_T.btnBg[1], _T.btnBg[2], _T.btnBg[3], _T.btnBg[4])
|
||
saveBtn:SetBackdropBorderColor(_T.btnBorder[1], _T.btnBorder[2], _T.btnBorder[3], _T.btnBorder[4])
|
||
local fs = saveBtn:GetFontString()
|
||
if fs then
|
||
fs:SetFont(SFrames:GetFont(), 11, "OUTLINE")
|
||
fs:SetTextColor(_T.btnText[1], _T.btnText[2], _T.btnText[3])
|
||
end
|
||
saveBtn:SetScript("OnEnter", function()
|
||
this:SetBackdropColor(_T.btnHoverBg[1], _T.btnHoverBg[2], _T.btnHoverBg[3], _T.btnHoverBg[4])
|
||
this:SetBackdropBorderColor(_T.btnHoverBorder[1], _T.btnHoverBorder[2], _T.btnHoverBorder[3], _T.btnHoverBorder[4])
|
||
local t = this:GetFontString()
|
||
if t then t:SetTextColor(_T.btnActiveText[1], _T.btnActiveText[2], _T.btnActiveText[3]) end
|
||
end)
|
||
saveBtn:SetScript("OnLeave", function()
|
||
this:SetBackdropColor(_T.btnBg[1], _T.btnBg[2], _T.btnBg[3], _T.btnBg[4])
|
||
this:SetBackdropBorderColor(_T.btnBorder[1], _T.btnBorder[2], _T.btnBorder[3], _T.btnBorder[4])
|
||
local t = this:GetFontString()
|
||
if t then t:SetTextColor(_T.btnText[1], _T.btnText[2], _T.btnText[3]) end
|
||
end)
|
||
saveBtn:SetScript("OnMouseDown", function()
|
||
this:SetBackdropColor(_T.btnDownBg[1], _T.btnDownBg[2], _T.btnDownBg[3], _T.btnDownBg[4])
|
||
end)
|
||
saveBtn:SetScript("OnMouseUp", function()
|
||
this:SetBackdropColor(_T.btnHoverBg[1], _T.btnHoverBg[2], _T.btnHoverBg[3], _T.btnHoverBg[4])
|
||
end)
|
||
end
|
||
statusFrame:Show()
|
||
|
||
DEFAULT_CHAT_FRAME:AddMessage("|cff88ccff[Nanami-UI]|r 按键绑定模式已开启。悬停按钮后按键/鼠标键/滚轮绑定,右键清除,ESC或点击保存退出。")
|
||
end
|
||
|
||
function AB:ExitKeyBindMode()
|
||
if not keyBindActive then return end
|
||
keyBindActive = false
|
||
hoveredBindButton = nil
|
||
|
||
for _, ov in pairs(keyBindOverlays) do
|
||
ov:Hide()
|
||
end
|
||
if captureFrame then captureFrame:Hide() end
|
||
if statusFrame then statusFrame:Hide() end
|
||
|
||
self:RefreshAllHotkeys()
|
||
DEFAULT_CHAT_FRAME:AddMessage("|cff88ccff[Nanami-UI]|r 按键绑定已保存。")
|
||
end
|
||
|
||
function AB:ToggleKeyBindMode()
|
||
if keyBindActive then
|
||
self:ExitKeyBindMode()
|
||
else
|
||
self:EnterKeyBindMode()
|
||
end
|
||
end
|
||
|
||
function AB:IsKeyBindMode()
|
||
return keyBindActive
|
||
end
|