Files
TotemBar/bind.lua
T
2026-08-17 15:19:36 +02:00

234 lines
9.0 KiB
Lua

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