-- TotemBar - bind.lua -- Key-binding labels (for the Esc > Key Bindings menu) + the cast entry -- points the Bindings.xml bodies call. Hover-bind mode is added below in a -- later step. Loaded AFTER Bindings.xml (see the .toc). TotemBar = TotemBar or {} local ChatOut = DEFAULT_CHAT_FRAME or ChatFrame1 -- Section + fixed-action labels. BINDING_HEADER_TOTEMBAR = "TotemBar" BINDING_NAME_TOTEMBAR_DROPSET = "Drop all totems" BINDING_NAME_TOTEMBAR_RECALL = "Totemic Recall" BINDING_NAME_TOTEMBAR_TOGGLEBAR = "Toggle bar" BINDING_NAME_TOTEMBAR_TOGGLEOPTIONS = "Toggle options" BINDING_NAME_TOTEMBAR_TOGGLEBIND = "Toggle key-bind mode" -- Per-element "cast the chosen totem" labels + per-element sub-headers. do local elements = TotemBar.TOTEM_ELEMENTS for i = 1, table.getn(elements) do local e = elements[i] setglobal("BINDING_NAME_TOTEMBAR_CAST_" .. string.upper(e), "Cast " .. e .. " totem (chosen)") setglobal("BINDING_HEADER_TOTEMBAR_" .. string.upper(e), "TotemBar: " .. e .. " Totems") end -- Per-totem labels (must match Bindings.xml names via bindingSuffix). for i = 1, table.getn(elements) do local e = elements[i] local list = TotemBar.TOTEMS_BY_ELEMENT[e] for j = 1, table.getn(list) do local totem = list[j] setglobal("BINDING_NAME_TOTEMBAR_TOTEM_" .. TotemBar.bindingSuffix(totem), "Cast " .. totem) end end end -- Cast a specific totem by name (no-op in-game if not known). function TotemBar.CastTotem(name) if name then CastSpellByName(name) -- keybind casts must feed the own-tracking timers exactly like the -- flyout click handler does (ui.lua) - without this, vanilla-only -- setups get no duration ring/pulse for keybind-cast totems. local element = TotemBar.elementOf(name) if element then TotemBar.recordCast(element, name) end end end -- Cast the currently-chosen totem for an element. function TotemBar.CastElement(element) local n = TotemBarDB and TotemBarDB.chosen and TotemBarDB.chosen[element] if n then CastSpellByName(n) -- keybind casts must feed the own-tracking timers exactly like the -- flyout click handler does (ui.lua) - without this, vanilla-only -- setups get no duration ring/pulse for keybind-cast totems. TotemBar.recordCast(element, n) else ChatOut:AddMessage("TotemBar: no totem chosen for " .. tostring(element) .. ".") end end -- Cast Totemic Recall and clear own-tracking. Runs the exact same -- TotemBar.manualRecall (core/cast.lua) as the Recall button's left-click, so -- the keybind and the button can never diverge: it owns the "nothing is out" -- gate, the "is Recall even castable right now" check (a press that cannot go -- out must NOT wipe the timers of totems still standing), the queue-safe cast -- that keeps nampower from firing a late Recall into a fresh set, the refund -- snapshot and the own-tracking wipe. Only the chat feedback lives here. function TotemBar.CastRecall() if not TotemBar.manualRecall then return end local action = TotemBar.manualRecall() if action == "none-out" then ChatOut:AddMessage("TotemBar: no totems out - not recalling (saves the 6s cooldown). Press again to recall anyway.") elseif action == "cooldown" then ChatOut:AddMessage("TotemBar: Totemic Recall isn't ready yet - totem timers kept.") end end -- Stub so the TOTEMBAR_TOGGLEBIND binding never calls nil before Task 4 -- fills it in. Replaced (same name) by the real implementation below. if not TotemBar.ToggleBindMode then function TotemBar.ToggleBindMode() ChatOut:AddMessage("TotemBar: key-bind mode not available.") end end -- ===== Hover-bind mode ===== -- Toggle a mode where hovering a bar button / flyout totem and pressing a -- key binds that key to the thing's action (buttons -> CLICK binding, flyout -- totems -> the named per-totem binding). ESC clears the hovered thing's -- binding. Bindings persist via the client (SaveBindings), no addon SV. local bindMode = false local captureFrame = nil local infoBox = nil -- Base keys that are pure modifiers - never bind these alone. local BARE_MODIFIERS = { LSHIFT = true, RSHIFT = true, LCTRL = true, RCTRL = true, LALT = true, RALT = true, UNKNOWN = true, } function TotemBar.isBindMode() return bindMode end -- Resolves the mouse-focused frame to a binding action. Uses the pure -- TotemBar.actionForButton with the frame's global name and, for a flyout -- icon, its current .totemName. local function actionForFocus(focus) if not focus then return nil end local totemName = focus.totemName -- set on flyout icon buttons when shown local fname = focus.GetName and focus:GetName() or nil return TotemBar.actionForButton(fname, totemName) end local function ensureCaptureFrame() if captureFrame then return captureFrame end local f = CreateFrame("Frame", "TotemBarBindCapture", UIParent) f:SetAllPoints(UIParent) f:SetFrameStrata("FULLSCREEN_DIALOG") f:EnableKeyboard(false) f:Hide() f:SetScript("OnKeyUp", function() local key = arg1 -- NOTE: OnKeyUp arg1 = key name string (matches pfUI's hoverbind capture) if not key or BARE_MODIFIERS[key] then return end local focus = GetMouseFocus() local action = actionForFocus(focus) if key == "ESCAPE" and not action then -- Keyboard capture swallows all keys while bind mode is on, so -- ESC over nothing is the keyboard exit (the options button also -- toggles it off). ESC over a button clears that button below. TotemBar.ToggleBindMode() return end if not action then return end if key == "ESCAPE" then local k1, k2 = GetBindingKey(action) if k1 then SetBinding(k1) end if k2 then SetBinding(k2) end else local full = TotemBar.modifierPrefix(IsAltKeyDown(), IsControlKeyDown(), IsShiftKeyDown()) .. key SetBinding(full) -- clear whatever `full` was bound to SetBinding(full, action) -- bind it to this action end SaveBindings(GetCurrentBindingSet()) if TotemBar.refreshBindOverlays then TotemBar.refreshBindOverlays() end end) captureFrame = f return f end -- Lazily builds a visible on-screen info box shown while bind mode is -- active, so the mode (which otherwise only shows via a chat message and -- stays on until ESC) is hard to miss. Rev 4 UI chrome: shared bespoke -- panel skin (ui.lua's PANEL_BACKDROP), matching the rest of the addon's -- chrome, regardless of pfUI presence. local function ensureBindInfoBox() if infoBox then return infoBox end local f = CreateFrame("Frame", "TotemBarBindInfo", UIParent) f:SetWidth(380) f:SetHeight(80) f:SetPoint("TOP", UIParent, "TOP", 0, -160) f:SetFrameStrata("FULLSCREEN_DIALOG") -- Cheap guard: bind.lua loads LAST per the .toc (after ui.lua), so -- TotemBar.PANEL_BACKDROP is always set by the time this runs - the -- fallback only protects against a future load-order change. local backdrop = TotemBar.PANEL_BACKDROP or { bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background", edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border", tile = true, tileSize = 32, edgeSize = 32, insets = { left = 11, right = 12, top = 12, bottom = 11 }, } f:SetBackdrop(backdrop) f:SetBackdropColor(1, 1, 1, 0.97) local t = f:CreateFontString("TotemBarBindInfoText", "OVERLAY", "GameFontNormal") t:SetPoint("CENTER", f, "CENTER", 0, 0) t:SetJustifyH("CENTER") t:SetText("|cff33ffccKey-Bind Mode ACTIVE|r\nHover a bar button or flyout totem and press a key to bind it.\nESC over a button clears its key. ESC over empty space (or the options button) exits.") if pfUI and pfUI.font_default then t:SetFont(pfUI.font_default, 12) end f:Hide() infoBox = f return f end function TotemBar.ToggleBindMode() bindMode = not bindMode local f = ensureCaptureFrame() if bindMode then f:EnableKeyboard(true) f:Show() ensureBindInfoBox():Show() ChatOut:AddMessage("TotemBar: key-bind mode ON - hover a button or flyout totem and press a key to bind. ESC over a button clears its key; ESC over nothing (or the options button) exits.") else f:EnableKeyboard(false) f:Hide() if infoBox then infoBox:Hide() end ChatOut:AddMessage("TotemBar: key-bind mode OFF.") end if TotemBar.refreshBindOverlays then TotemBar.refreshBindOverlays() end end -- Refresh overlays whenever the client's bindings change. local bindEvents = CreateFrame("Frame", "TotemBarBindEventFrame", UIParent) bindEvents:RegisterEvent("UPDATE_BINDINGS") bindEvents:SetScript("OnEvent", function() if TotemBar.refreshBindOverlays then TotemBar.refreshBindOverlays() end end)