-------------------------------------------------------------------------------- -- Nanami-UI: ExtraBar -- -- A fully configurable extra action bar using action slots from page 7+ -- (slots 73-120). Buttons are created from scratch since no spare Blizzard -- ActionButton frames exist. -- -- Supports: grid layout, per-row count, alignment, drag-and-drop, cooldown, -- range coloring, tooltip, and Mover integration for position saving. -------------------------------------------------------------------------------- SFrames.ExtraBar = {} local EB = SFrames.ExtraBar local DEFAULTS = { enable = false, buttonCount = 12, perRow = 12, buttonSize = 36, buttonGap = 2, align = "center", startSlot = 73, alpha = 1.0, showHotkey = true, showCount = true, buttonRounded = false, buttonInnerShadow = false, } local MAX_BUTTONS = 48 function EB:GetDB() if not SFramesDB then SFramesDB = {} end if type(SFramesDB.ExtraBar) ~= "table" then SFramesDB.ExtraBar = {} end local db = SFramesDB.ExtraBar for k, v in pairs(DEFAULTS) do if db[k] == nil then db[k] = v end end return db end -------------------------------------------------------------------------------- -- Layout helper (local LayoutGrid equivalent) -------------------------------------------------------------------------------- local function LayoutGrid(buttons, parent, size, gap, perRow, count) if count == 0 then return end local numCols = math.min(perRow, count) local numRows = math.ceil(count / perRow) parent:SetWidth(numCols * size + math.max(numCols - 1, 0) * gap) parent:SetHeight(numRows * size + math.max(numRows - 1, 0) * gap) for i = 1, count do local b = buttons[i] if b then b:SetWidth(size) b:SetHeight(size) b:ClearAllPoints() local col = math.fmod(i - 1, perRow) local row = math.floor((i - 1) / perRow) b:SetPoint("TOPLEFT", parent, "TOPLEFT", col * (size + gap), -row * (size + gap)) b:Show() end end for i = count + 1, MAX_BUTTONS do if buttons[i] then buttons[i]:Hide() end end end -------------------------------------------------------------------------------- -- Button visual helpers (mirrors ActionBars.lua approach) -------------------------------------------------------------------------------- local function CreateBackdropFor(btn) if btn.sfBackdrop then return end local level = btn:GetFrameLevel() local bd = CreateFrame("Frame", nil, btn) bd:SetFrameLevel(level > 0 and (level - 1) or 0) bd:SetAllPoints(btn) SFrames:CreateBackdrop(bd) btn.sfBackdrop = bd end 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, innerShadow) 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 icon = btn.sfIcon if icon then icon:ClearAllPoints() icon:SetPoint("TOPLEFT", btn, "TOPLEFT", inset, -inset) icon:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", -inset, inset) end if btn.sfCdOverlay then btn.sfCdOverlay:ClearAllPoints() btn.sfCdOverlay:SetPoint("TOPLEFT", btn, "TOPLEFT", inset, -inset) btn.sfCdOverlay:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", -inset, inset) 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 innerShadow 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 -------------------------------------------------------------------------------- -- Single-button refresh helpers -------------------------------------------------------------------------------- local function RefreshButtonIcon(btn) local slot = btn.sfActionSlot if not slot then return end if HasAction(slot) then local tex = GetActionTexture(slot) btn.sfIcon:SetTexture(tex) btn.sfIcon:Show() btn.sfIcon:SetVertexColor(1, 1, 1, 1) btn:SetAlpha(1) else btn.sfIcon:SetTexture(nil) btn.sfIcon:Hide() end end local function RefreshButtonCooldown(btn) local slot = btn.sfActionSlot if not slot or not HasAction(slot) then if btn.sfCdOverlay then btn.sfCdOverlay:Hide() end if btn.sfCdText then btn.sfCdText:SetText("") end btn.sfCdStart = 0 btn.sfCdDuration = 0 return end local start, duration, enable = GetActionCooldown(slot) if not start or not duration or duration == 0 or enable == 0 then if btn.sfCdOverlay then btn.sfCdOverlay:Hide() end if btn.sfCdText then btn.sfCdText:SetText("") end btn.sfCdStart = 0 btn.sfCdDuration = 0 return end btn.sfCdStart = start btn.sfCdDuration = duration local now = GetTime() local remaining = (start + duration) - now if remaining > 0 then if btn.sfCdOverlay then btn.sfCdOverlay:Show() end if btn.sfCdText then if remaining >= 60 then btn.sfCdText:SetText(math.floor(remaining / 60) .. "m") else btn.sfCdText:SetText(math.floor(remaining + 0.5) .. "") end end else if btn.sfCdOverlay then btn.sfCdOverlay:Hide() end if btn.sfCdText then btn.sfCdText:SetText("") end btn.sfCdStart = 0 btn.sfCdDuration = 0 end end local function RefreshButtonUsable(btn) local slot = btn.sfActionSlot if not slot or not HasAction(slot) then return end local isUsable, notEnoughMana = IsUsableAction(slot) if isUsable then btn.sfIcon:SetVertexColor(1, 1, 1, 1) elseif notEnoughMana then btn.sfIcon:SetVertexColor(0.2, 0.2, 0.8, 1) else btn.sfIcon:SetVertexColor(0.4, 0.4, 0.4, 1) end end local function RefreshButtonCount(btn) local slot = btn.sfActionSlot if not slot or not HasAction(slot) then btn.sfCount:SetText("") return end local count = GetActionCount(slot) if count and count > 0 then btn.sfCount:SetText(tostring(count)) else btn.sfCount:SetText("") end end local function RefreshButtonRange(btn) local slot = btn.sfActionSlot if not slot or not HasAction(slot) then if btn.sfRangeOverlay then btn.sfRangeOverlay:Hide() end return end local inRange = IsActionInRange(slot) if not btn.sfRangeOverlay then local inset = btn.sfIconInset or 2 local ov = btn:CreateTexture(nil, "OVERLAY") ov:SetTexture("Interface\\Buttons\\WHITE8X8") ov:SetPoint("TOPLEFT", btn, "TOPLEFT", inset, -inset) ov:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", -inset, inset) ov:SetVertexColor(1.0, 0.1, 0.1, 0.35) ov:Hide() btn.sfRangeOverlay = ov end if inRange == 0 then btn.sfRangeOverlay:Show() else btn.sfRangeOverlay:Hide() end end local function RefreshButtonState(btn) local slot = btn.sfActionSlot if not slot or not HasAction(slot) then return end if IsCurrentAction(slot) then if btn.sfBackdrop then btn.sfBackdrop:SetBackdropBorderColor(1, 1, 1, 0.8) end else local A = SFrames.ActiveTheme if btn.sfBackdrop then if A and A.panelBorder then btn.sfBackdrop:SetBackdropBorderColor(A.panelBorder[1], A.panelBorder[2], A.panelBorder[3], A.panelBorder[4] or 1) else btn.sfBackdrop:SetBackdropBorderColor(0, 0, 0, 1) end end end end local function RefreshButtonAll(btn) RefreshButtonIcon(btn) RefreshButtonCooldown(btn) RefreshButtonUsable(btn) RefreshButtonCount(btn) RefreshButtonState(btn) end -------------------------------------------------------------------------------- -- Custom action button factory -------------------------------------------------------------------------------- local btnIndex = 0 local function CreateExtraButton(parent) btnIndex = btnIndex + 1 local name = "SFramesExtraBarButton" .. btnIndex local btn = CreateFrame("Button", name, parent) btn:SetWidth(36) btn:SetHeight(36) btn:RegisterForClicks("LeftButtonUp", "RightButtonUp") btn:RegisterForDrag("LeftButton") CreateBackdropFor(btn) local icon = btn:CreateTexture(name .. "Icon", "ARTWORK") icon:SetPoint("TOPLEFT", btn, "TOPLEFT", 2, -2) icon:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", -2, 2) icon:SetTexCoord(0.07, 0.93, 0.07, 0.93) btn.sfIcon = icon local cdOverlay = btn:CreateTexture(nil, "OVERLAY") cdOverlay:SetTexture("Interface\\Buttons\\WHITE8X8") cdOverlay:SetAllPoints(icon) cdOverlay:SetVertexColor(0, 0, 0, 0.6) cdOverlay:Hide() btn.sfCdOverlay = cdOverlay local cdText = btn:CreateFontString(nil, "OVERLAY") cdText:SetFont(SFrames:GetFont(), 12, "OUTLINE") cdText:SetPoint("CENTER", btn, "CENTER", 0, 0) cdText:SetTextColor(1, 1, 0.2) cdText:SetText("") btn.sfCdText = cdText btn.sfCdStart = 0 btn.sfCdDuration = 0 local font = SFrames:GetFont() local hotkey = btn:CreateFontString(name .. "HotKey", "OVERLAY") hotkey:SetFont(font, 9, "OUTLINE") hotkey:SetPoint("TOPRIGHT", btn, "TOPRIGHT", -2, -2) hotkey:SetJustifyH("RIGHT") btn.sfHotKey = hotkey local count = btn:CreateFontString(name .. "Count", "OVERLAY") count:SetFont(font, 9, "OUTLINE") count:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", -2, 2) count:SetJustifyH("RIGHT") btn.sfCount = count -- Highlight texture local hl = btn:CreateTexture(nil, "HIGHLIGHT") hl:SetTexture("Interface\\Buttons\\ButtonHilight-Square") hl:SetBlendMode("ADD") hl:SetPoint("TOPLEFT", btn, "TOPLEFT", 2, -2) hl:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", -2, 2) hl:SetAlpha(0.3) -- Pushed texture overlay local pushed = btn:CreateTexture(nil, "OVERLAY") pushed:SetTexture("Interface\\Buttons\\WHITE8X8") pushed:SetPoint("TOPLEFT", btn, "TOPLEFT", 2, -2) pushed:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", -2, 2) pushed:SetVertexColor(1, 1, 1, 0.15) pushed:Hide() btn.sfPushed = pushed btn.sfActionSlot = nil btn:SetScript("OnClick", function() local slot = this.sfActionSlot if not slot then return end if arg1 == "LeftButton" then if IsShiftKeyDown() and ChatFrameEditBox and ChatFrameEditBox:IsVisible() then -- Shift-click: link to chat (fallback: pickup) pcall(PickupAction, slot) else UseAction(slot, 0, 1) end elseif arg1 == "RightButton" then UseAction(slot, 0, 1) end RefreshButtonAll(this) end) btn:SetScript("OnDragStart", function() local slot = this.sfActionSlot if slot then pcall(PickupAction, slot) RefreshButtonAll(this) end end) btn:SetScript("OnReceiveDrag", function() local slot = this.sfActionSlot if slot then pcall(PlaceAction, slot) RefreshButtonAll(this) end end) btn:SetScript("OnEnter", function() local slot = this.sfActionSlot if slot and HasAction(slot) then GameTooltip:SetOwner(this, "ANCHOR_RIGHT") GameTooltip:SetAction(slot) GameTooltip:Show() end end) btn:SetScript("OnLeave", function() GameTooltip:Hide() end) btn:SetScript("OnMouseDown", function() if this.sfPushed then this.sfPushed:Show() end end) btn:SetScript("OnMouseUp", function() if this.sfPushed then this.sfPushed:Hide() end end) btn:Hide() return btn end -------------------------------------------------------------------------------- -- Create all buttons once -------------------------------------------------------------------------------- function EB:CreateButtons() local db = self:GetDB() self.holder = CreateFrame("Frame", "SFramesExtraBarHolder", UIParent) self.holder:SetWidth(200) self.holder:SetHeight(40) local pos = SFramesDB and SFramesDB.Positions and SFramesDB.Positions["ExtraBar"] if pos and pos.point and pos.relativePoint then self.holder:SetPoint(pos.point, UIParent, pos.relativePoint, pos.xOfs or 0, pos.yOfs or 0) else self.holder:SetPoint("CENTER", UIParent, "CENTER", 0, -200) end self.buttons = {} for i = 1, MAX_BUTTONS do local btn = CreateExtraButton(self.holder) table.insert(self.buttons, btn) end if SFrames.ActionBars and SFrames.ActionBars.RegisterBindButton then for i = 1, MAX_BUTTONS do local bname = self.buttons[i]:GetName() SFrames.ActionBars:RegisterBindButton(bname, "NANAMI_EXTRABAR" .. i) end end end -------------------------------------------------------------------------------- -- Apply configuration -------------------------------------------------------------------------------- function EB:ApplyConfig() if not self.holder then return end local db = self:GetDB() if not db.enable then self.holder:Hide() return end local count = math.min(db.buttonCount or 12, MAX_BUTTONS) local perRow = db.perRow or 12 local size = db.buttonSize or 36 local gap = db.buttonGap or 2 local startSlot = db.startSlot or 73 -- Assign action slots for i = 1, MAX_BUTTONS do local btn = self.buttons[i] btn.sfActionSlot = startSlot + i - 1 end -- Layout LayoutGrid(self.buttons, self.holder, size, gap, perRow, count) -- Alignment via anchor local positions = SFramesDB and SFramesDB.Positions local pos = positions and positions["ExtraBar"] if not (pos and pos.point and pos.relativePoint) then self.holder:ClearAllPoints() local align = db.align or "center" if align == "left" then self.holder:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", 20, 200) elseif align == "right" then self.holder:SetPoint("BOTTOMRIGHT", UIParent, "BOTTOMRIGHT", -20, 200) else self.holder:SetPoint("CENTER", UIParent, "CENTER", 0, -200) 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 self.holder:SetAlpha(alpha) -- Scale (inherit from main action bars if available) local abDb = SFrames.ActionBars and SFrames.ActionBars.GetDB and SFrames.ActionBars:GetDB() local scale = abDb and abDb.scale or 1.0 self.holder:SetScale(scale) -- Button visuals local isRounded = db.buttonRounded local isShadow = db.buttonInnerShadow local showHK = db.showHotkey local showCt = db.showCount local font = SFrames:GetFont() local fontSize = math.max(6, math.floor(size * 0.25 + 0.5)) for i = 1, count do local btn = self.buttons[i] ApplyButtonVisuals(btn, isRounded, isShadow) if btn.sfHotKey then btn.sfHotKey:SetFont(font, fontSize, "OUTLINE") if showHK then btn.sfHotKey:Show() else btn.sfHotKey:Hide() end end if btn.sfCount then btn.sfCount:SetFont(font, fontSize, "OUTLINE") if showCt then btn.sfCount:Show() else btn.sfCount:Hide() end end if btn.sfCdText then local cdFontSize = math.max(8, math.floor(size * 0.35 + 0.5)) btn.sfCdText:SetFont(font, cdFontSize, "OUTLINE") end RefreshButtonAll(btn) end self.holder:Show() -- Update mover if in layout mode if SFrames.Movers and SFrames.Movers:IsLayoutMode() then SFrames.Movers:SyncMoverToFrame("ExtraBar") end end -------------------------------------------------------------------------------- -- Refresh all visible buttons -------------------------------------------------------------------------------- function EB:RefreshAll() if not self.buttons then return end local db = self:GetDB() if not db.enable then return end local count = math.min(db.buttonCount or 12, MAX_BUTTONS) for i = 1, count do RefreshButtonAll(self.buttons[i]) end end function EB:RefreshCooldowns() if not self.buttons then return end local db = self:GetDB() if not db.enable then return end local count = math.min(db.buttonCount or 12, MAX_BUTTONS) for i = 1, count do RefreshButtonCooldown(self.buttons[i]) end end function EB:RefreshUsable() if not self.buttons then return end local db = self:GetDB() if not db.enable then return end local count = math.min(db.buttonCount or 12, MAX_BUTTONS) for i = 1, count do RefreshButtonUsable(self.buttons[i]) end end function EB:RefreshStates() if not self.buttons then return end local db = self:GetDB() if not db.enable then return end local count = math.min(db.buttonCount or 12, MAX_BUTTONS) for i = 1, count do RefreshButtonState(self.buttons[i]) end end function EB:RefreshHotkeys() if not self.buttons then return end local db = self:GetDB() if not db.enable then return end local count = math.min(db.buttonCount or 12, MAX_BUTTONS) for i = 1, count do local btn = self.buttons[i] local cmd = "NANAMI_EXTRABAR" .. i local hotkey = btn.sfHotKey if hotkey then 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 end end -------------------------------------------------------------------------------- -- OnUpdate poller for cooldown / range / usability -------------------------------------------------------------------------------- function EB:SetupPoller() local poller = CreateFrame("Frame", "SFramesExtraBarPoller", UIParent) poller.timer = 0 self.poller = poller poller:SetScript("OnUpdate", function() this.timer = this.timer + arg1 if this.timer < 0.2 then return end this.timer = 0 local db = EB:GetDB() if not db.enable then return end local count = math.min(db.buttonCount or 12, MAX_BUTTONS) for i = 1, count do local btn = EB.buttons[i] if btn and btn:IsShown() then RefreshButtonCooldown(btn) RefreshButtonUsable(btn) RefreshButtonRange(btn) RefreshButtonState(btn) end end end) end -------------------------------------------------------------------------------- -- Initialize -------------------------------------------------------------------------------- function EB:Initialize() local db = self:GetDB() if not db.enable then return end self:CreateButtons() self:ApplyConfig() self:SetupPoller() -- Events SFrames:RegisterEvent("ACTIONBAR_SLOT_CHANGED", function() if not EB.buttons then return end local db = EB:GetDB() if not db.enable then return end local slot = arg1 local startSlot = db.startSlot or 73 local count = math.min(db.buttonCount or 12, MAX_BUTTONS) if slot then local idx = slot - startSlot + 1 if idx >= 1 and idx <= count then RefreshButtonAll(EB.buttons[idx]) end else EB:RefreshAll() end end) SFrames:RegisterEvent("ACTIONBAR_UPDATE_COOLDOWN", function() EB:RefreshCooldowns() end) SFrames:RegisterEvent("ACTIONBAR_UPDATE_USABLE", function() EB:RefreshUsable() end) SFrames:RegisterEvent("ACTIONBAR_UPDATE_STATE", function() EB:RefreshStates() end) SFrames:RegisterEvent("PLAYER_ENTERING_WORLD", function() EB:RefreshAll() EB:RefreshHotkeys() end) SFrames:RegisterEvent("UPDATE_BINDINGS", function() EB:RefreshHotkeys() end) -- Register mover if SFrames.Movers and SFrames.Movers.RegisterMover then SFrames.Movers:RegisterMover("ExtraBar", self.holder, "额外动作条", "CENTER", "UIParent", "CENTER", 0, -200) end end -------------------------------------------------------------------------------- -- Late enable: called from ConfigUI when user toggles enable on -------------------------------------------------------------------------------- function EB:Enable() local db = self:GetDB() db.enable = true if not self.holder then self:CreateButtons() self:SetupPoller() SFrames:RegisterEvent("ACTIONBAR_SLOT_CHANGED", function() if not EB.buttons then return end local db2 = EB:GetDB() if not db2.enable then return end local slot = arg1 local startSlot = db2.startSlot or 73 local count = math.min(db2.buttonCount or 12, MAX_BUTTONS) if slot then local idx = slot - startSlot + 1 if idx >= 1 and idx <= count then RefreshButtonAll(EB.buttons[idx]) end else EB:RefreshAll() end end) SFrames:RegisterEvent("ACTIONBAR_UPDATE_COOLDOWN", function() EB:RefreshCooldowns() end) SFrames:RegisterEvent("ACTIONBAR_UPDATE_USABLE", function() EB:RefreshUsable() end) SFrames:RegisterEvent("ACTIONBAR_UPDATE_STATE", function() EB:RefreshStates() end) SFrames:RegisterEvent("UPDATE_BINDINGS", function() EB:RefreshHotkeys() end) if SFrames.Movers and SFrames.Movers.RegisterMover then SFrames.Movers:RegisterMover("ExtraBar", self.holder, "额外动作条", "CENTER", "UIParent", "CENTER", 0, -200) end end self:ApplyConfig() self:RefreshHotkeys() end function EB:Disable() local db = self:GetDB() db.enable = false if self.holder then self.holder:Hide() end if self.holder and SFrames.Movers and SFrames.Movers.IsLayoutMode and SFrames.Movers:IsLayoutMode() then pcall(SFrames.Movers.SyncMoverToFrame, SFrames.Movers, "ExtraBar") end end function EB:RunButton(index) if not self.buttons then return end local db = self:GetDB() if not db.enable then return end if index < 1 or index > math.min(db.buttonCount or 12, MAX_BUTTONS) then return end local btn = self.buttons[index] if not btn or not btn:IsVisible() then return end local slot = btn.sfActionSlot if slot and HasAction(slot) then UseAction(slot) RefreshButtonAll(btn) end end