-------------------------------------------------------------------------------- -- 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