Mercurial > wow > skeletonkey
view SkeletonKey/KeyBinds.lua @ 3:07293831dd7b
- implement unit parameters table inheritance from a stock table
- UnitFrames.unit() and UnitFrames.ui() methods
author | Nenue |
---|---|
date | Tue, 21 Jun 2016 08:14:22 -0400 |
parents | cd7d06bcd98d |
children | 9ac29fe77455 |
line wrap: on
line source
-------------------------------------------- -- KrakTool -- Nick -- @project-revision@ @project-hash@ -- @file-revision@ @file-hash@ -- Created: 6/16/2016 3:47 AM -------------------------------------------- -- kb -- .bind(button, key) bind current keystroke to command -- .assign(button, command, name, icon) set button command -- .release(button) clear button command -- .refresh(button) update button contents -- .ui() invoke interface -- .profile(name) set profile character -- .loadbinds(bindings) walk table with SetBinding() local KT = LibKT.register(KeyBinder) local kb = KeyBinder local db local MIN_BIND_SLOTS = 32 local BINDS_PER_ROW = 8 local KEY_BUTTON_SIZE = 40 local TAB_OFFSET = 12 local TAB_HEIGHT = 40 local TAB_SPACING = 2 local BUTTON_SPACING = 4 local BUTTON_PADDING = 12 local HEADER_OFFSET, FOOTER_OFFSET local SUMMON_RANDOM_FAVORITE_MOUNT_SPELL = 150544; local BINDING_TYPE_SPECIALIZATION = 3 local BINDING_TYPE_CHARACTER = 2 local BINDING_TYPE_GLOBAL = 1 local BINDING_ASSIGNED = '|cFF00FF00%s|r assigned to |cFFFFFF00%s|r (%s).' local BINDING_FAILED_PROTECTED = '|cFF00FF00%s|r used by |cFFFFFF00%s|r!' local BINDING_MODE = { [BINDING_TYPE_SPECIALIZATION] = 'Specialization: %s', [BINDING_TYPE_CHARACTER] = 'Character: %s', [BINDING_TYPE_GLOBAL] = 'Global Binds' } local BINDING_SCHEME_COLOR = { [BINDING_TYPE_SPECIALIZATION] = {0,0,0,0.5}, [BINDING_TYPE_CHARACTER] = {0,0.25,0,0.5}, [BINDING_TYPE_GLOBAL] = {0,.125,.5,.5} } local BINDING_SCHEME_VERTEX = { [BINDING_TYPE_SPECIALIZATION] = {1,1,1,1}, [BINDING_TYPE_CHARACTER] = {0,1,0,1}, [BINDING_TYPE_GLOBAL] = {0,.5,1,1} } local BINDING_SCHEME_TEXT = { [BINDING_TYPE_SPECIALIZATION] = {1, 1, 0}, [BINDING_TYPE_CHARACTER] = {0, 1, 0}, [BINDING_TYPE_GLOBAL] = {0, 1, 1} } local ACTION_SCRIPT = { ['mount'] = "/script C_MountJournal.SummonByID(%d)", ['equipset'] = "/script UseEquipmentSet(%d)", } local COMMAND_SPELL = "^SPELL (%S.+)" local COMMAND_MACRO = "^MACRO (%S.+)" local COMMAND_ITEM = "^ITEM (%S.+)" local COMMAND_MOUNT = "^CLICK KeyBinderMacro:mount(%d+)" local COMMAND_EQUIPSET = "^CLICK KeyBinderMacro:equipset(%d+)" local PICKUP_TYPES = { [COMMAND_SPELL] = PickupSpell, [COMMAND_MACRO] = PickupMacro, [COMMAND_ITEM] = PickupItem, [COMMAND_MOUNT] = C_MountJournal.Pickup, [COMMAND_EQUIPSET] = PickupEquipmentSet } local PICKUP_VALUES = { [COMMAND_SPELL] = function(name) return select(7, GetSpellInfo(name)) end } local CLASS_ICON_TEXTURE = "Interface\\GLUES\\CHARACTERCREATE\\UI-CHARACTERCREATE-CLASSES" local BORDER_UNASSIGNED = {0.2,0.2,0.2,1 } local BORDER_ASSIGNED = {0.5,0.5,0.5,1 } local BORDER_PENDING = {1,0.5,0,1 } local bindMode = 3 local bindHeader = '' local specHeader, specTexture, characterHeader = 'SPEC_NAME', 'Interface\\ICONS\\INV_Misc_QuestionMark', 'PLAYER_NAME' local numButtons = BINDS_PER_ROW * 4 local bindsCommitted = true local profile, character, specialization, global local priority = {} local buttons = {} local reverts = {} local KeyButton = {} -- collection of KeyButton template handlers local Action = {} -- collection of special action buttons for special binds local protected = { ['OPENCHATSLASH'] = true, ['OPENCHAT'] = true, } local saveButton, restoreButton, clearButton --- Returns a value for use with Texture:SetDesaturated() local CommandIsLocked = function(self) print('command check: 1-'..(bindMode-1)) local desaturated, layer = false, 3 for i = 1, bindMode-1 do local tier = priority[i] local existing = tier.commands[self.command] print(' ', i, tier.commands[self.command]) if existing then if self:GetID() ~= existing then -- sanitize bad data tier.commands[self.command] = nil else layer = i desaturated = true break end end end return desaturated, layer end --- Returns a value for use with Texture:SetDesaturated() local BindingIsLocked = function(key) local success = false for i = 1, bindMode-1 do local tier = priority[i] if tier.bindings[key] then success = true break end end return success end --- Translates GetBindingKey() results into a printable string. local BindingString = function(...) local stack = {} for i = 1, select('#', ...) do local key = select(i, ...) stack[i] = key:gsub('SHIFT', 's'):gsub('ALT', 'a'):gsub('CTRL', 'c'):gsub('SPACE', 'Sp') end if #stack >= 1 then return table.concat(stack, ',') else return nil end end --- This keeps our KeyDown handler from getting stuck with game controls KeyButton.OnUpdate = function(self) if not self.command then return end if self:IsMouseOver() then if not self.active then -- only set this handler when the button is activated/mouseOver self.active = true self:SetScript('OnKeyDown', kb.bind) local bindText = self.command if self.bind:GetText() then bindText = bindText .. ': |cFF00FF00' .. self.bind:GetText() end kb.bindlist:SetText(bindText) GameTooltip:SetOwner(self) GameTooltip:SetAnchorType('ANCHOR_BOTTOMRIGHT') GameTooltip:SetText(self.actionName) GameTooltip:Show() end else if self.active then GameTooltip:Hide() self.active = nil self:SetScript('OnKeyDown', nil) end end end --- Cursor pickup handler -- Walks through PICKUP_TYPES and runs the function if match(command, key) turns up a result. -- Passes the result through PICKUP_VALUES[pattern]() if defined. kb.pickup = function(self) for pattern, pickup in pairs(PICKUP_TYPES) do local value = self.command:match(pattern) if value then if PICKUP_VALUES[pattern] then value = PICKUP_VALUES[pattern](value) end pickup(value) kb.release(self) break end end end --- Setup an action button base on template info kb.action = function(type, id) local macroName = type .. id macroName = macroName:gsub(' ', '') local attribute = '*macrotext-'..macroName local value = ACTION_SCRIPT[type]:format(id) local command = 'CLICK KeyBinderMacro:'.. macroName profile.macros[attribute] = {value, command} KeyBinderMacro:SetAttribute(attribute, value) return command end KeyButton.OnDragStart = function(self) if not self.command then return end kb.pickup(self) end KeyButton.OnReceiveDrag = function(self, ...) print(self:GetName(),'|cFF0088FFreceived|r', ...) local type, value, subType, subData = GetCursorInfo() print('GetCursorInfo', type, value, subType, subData) if type then if type == 'spell' then value = subData end local command, name, icon, _ if type == 'spell' then name, _, icon = GetSpellInfo(value) command = 'SPELL ' .. name name = '' elseif type == 'macro' then name, icon = GetMacroInfo(value) command = 'MACRO ' .. name elseif type == 'mount' then if subType == 0 then name, _, icon = GetSpellInfo(SUMMON_RANDOM_FAVORITE_MOUNT_SPELL) value= 0 else name, _, icon = C_MountJournal.GetMountInfoByID(value) end command = kb.action(type, value) elseif type == 'item' then name = GetItemInfo(value) icon = GetItemIcon(value) command = 'ITEM ' .. name end kb.assign(self, command, name, icon) kb.refresh(self) ClearCursor() end end KeyButton.OnMouseDown = function(self, click) print(self:GetName(), 'OnMouseDown', click) if click == 'LeftButton' then KeyButton.OnReceiveDrag(self) elseif click == 'RightButton' then kb.release(self) else kb.bind(self) end end --- Updates the current KeyBinding for the button's command kb.bind = function(self, key) print('|cFFFFFF00bind|cFFFFFF00', self:GetID(), '|cFF00FFFF', key) if not self.command then return end if key:match('[RL]SHIFT') or key:match('[RL]ALT') or key:match('[RL]CTRL') then return end if protected[GetBindingAction(key)] then return kb.bindlist:SetText(BINDING_FAILED_PROTECTED:format(key, GetBindingAction(key))) end if key == 'ESCAPE' then local key1, key2 = GetBindingKey(self.command) if key1 then SetBinding(key1, nil) print('Unbound', key1) end if key2 then SetBinding(key2, nil) print('Unbound', key2) end self.active = false return end local modifier = '' if IsAltKeyDown() then modifier = 'ALT-' end if IsControlKeyDown() then modifier = modifier.. 'CTRL-' end if IsShiftKeyDown() then modifier = modifier..'SHIFT-' end if self.command then self.binding = modifier..key self.pending = true self.border:SetColorTexture(1,.5,0, 1) local old = GetBindingAction(self.binding) local binding1, binding2, new1, new2 if old and old ~= self.command then print('Discarding keybind for', old) local binding1, binding2 = GetBindingKey(old) -- need to preserve argument order end tinsert(reverts, {old, binding1, binding2, new1, new2}) bindsCommitted = false SetBinding(self.binding, self.command) for level, profile in ipairs(priority) do profile.bindings[self.binding] = (level == bindMode) and self.command or nil end print(BINDING_ASSIGNED:format(self.binding, self.command, BINDING_MODE[bindMode]:format(bindHeader))) kb.refresh(self) end end --- Resets button command kb.release = function(self) local index = self:GetID() self.command = nil self.actionName = nil self.macro:SetText(nil) self.profile = nil self.bind:SetText(nil) self.icon:SetTexture(nil) self.border:SetColorTexture(unpack(BORDER_UNASSIGNED)) self:EnableKeyboard(false) self:SetScript('OnKeyDown', nil) if profile.buttons[index] then profile.buttons[index] = nil end end -- Sets button command kb.assign = function(self, command, name, icon) local index = self:GetID() print('|cFF00FFFFassign|cFF0088FF', index, '|cFFFFFF00'.. (command or 'none'), '|cFF00FF00'.. (name or ''), '|cFF00FFFF' .. (icon or '')) if command then if command:match(COMMAND_SPELL) then name = command:match(COMMAND_SPELL) end self:EnableKeyboard(true) print('profile.buttons['..index..'] |cFF00FFFF=|r ', command, name, icon) profile.buttons[index] = {command, name, icon} --- Clean up any residual buttons local previous = profile.commands[command] if previous ~= index and buttons[previous] then kb.release(buttons[previous]) end profile.commands[command] = index end self.profile = bindMode self.actionName = name self.command = command self.icon:SetTexture(icon) self:RegisterForDrag('LeftButton') end --- Retrieves button at index; creates said button and instates any stored parameters kb.keyslot = function(index) if not buttons[index] then local button = CreateFrame('CheckButton', 'KeyBinderSlot'..index, kb, 'KeyButton') button:SetScript('OnMouseDown', KeyButton.OnMouseDown) button:SetScript('OnMouseUp', KeyButton.OnMouseUp) button:SetScript('OnUpdate', KeyButton.OnUpdate) button:SetScript('OnDragStart', KeyButton.OnDragStart) button:SetScript('OnReceiveDrag', KeyButton.OnReceiveDrag) button:SetID(index) if profile.buttons[index] and type(profile.buttons[index] ) == 'table' then kb.assign(button, unpack(profile.buttons[index] )) else kb.release(button) end local x, y = BUTTON_PADDING, - (BUTTON_PADDING + HEADER_OFFSET) if index ~= 1 then local col = mod(index, BINDS_PER_ROW) if col == 0 then col = BINDS_PER_ROW - 1 else col = col - 1 end x = col * (KEY_BUTTON_SIZE + BUTTON_SPACING) + BUTTON_PADDING y = (ceil(index/ BINDS_PER_ROW)-1) * - (KEY_BUTTON_SIZE + BUTTON_SPACING) - BUTTON_PADDING - HEADER_OFFSET end button:SetSize(KEY_BUTTON_SIZE, KEY_BUTTON_SIZE) button:SetPoint('TOPLEFT', kb, 'TOPLEFT', x, y) button:Show() buttons[index] = button end return buttons[index] end --- Updates profile assignment and button contents kb.refresh = function(self) if self.profile ~= bindMode then if profile.buttons[self:GetID()] then kb.assign(self, unpack(profile.buttons[self:GetID()])) else kb.release(self) end end if self.command then if self.pending then self.border:SetColorTexture(unpack(BORDER_PENDING)) else self.border:SetColorTexture(unpack(BORDER_ASSIGNED)) end --self.macro:SetText(self.actionName) self.bind:SetText(BindingString(GetBindingKey(self.command))) local locked, layer = CommandIsLocked(self) self.icon:SetDesaturated(locked) self.icon:SetVertexColor(unpack(BINDING_SCHEME_VERTEX[layer])) else self.border:SetColorTexture(unpack(BORDER_UNASSIGNED)) --self.macro:SetText(nil) self.bind:SetText(nil) end end local SetupUI = function() kb.tabAnchor = {'TOPLEFT', kb, 'TOPRIGHT', 2, -TAB_OFFSET} kb.tabGrowth = {'TOPLEFT', nil,'BOTTOMLEFT', 0, -TAB_SPACING} kb.tabSize = {TAB_HEIGHT, TAB_HEIGHT } kb.UIPanelAnchor = {'TOPLEFT', kb, 'TOPLEFT', BUTTON_PADDING + 12, -BUTTON_PADDING} kb.UIPanelGrowth = {'TOPLEFT', nil, 'TOPRIGHT', 14, 0 } kb.controlsAnchor = {'BOTTOMLEFT', kb, BUTTON_PADDING, BUTTON_PADDING } kb.controlsGrowth = {'BOTTOMLEFT', nil, 'BOTTOMRIGHT', BUTTON_SPACING, 0} --tab() frame, name, tooltip, texture, coords kb:tab('KeyBinderGlobalTab', BINDING_MODE[1], "Interface\\ICONS\\item_azereansphere", {0.15,.85,.15,.85}) kb:tab('KeyBinderCharacterTab', characterHeader, nil) kb:tab('KeyBinderSpecTab', specHeader, specTexture) SetPortraitTexture(KeyBinderCharacterTab.icon, 'player') KeyBinderCharacterTab.icon:SetTexCoord(0.15,.85,.15,.85) saveButton = kb:button('KeyBinderSaveButton', 'Save', 'Commit all changes.', nil, kb.save) restoreButton = kb:button('KeyBinderRestoreButton', 'Discard', 'Revert all changes.', nil, kb.restore) clearButton = kb:button('KeyBinderClearButton', 'Clear Page', 'Release all buttons.', nil, kb.ResetProfile) kb:uibutton( 'KeyBinderSpellBookButton', 'SpellBook', nil, function() ToggleSpellBook(BOOKTYPE_SPELL) end, "Interface\\Spellbook\\Spellbook-Icon") kb:uibutton( 'KeyBinderTalentFrameButton', 'Talents', nil, function() ToggleTalentFrame() end, "Interface\\TargetingFrame\\UI-Classes-Circles", CLASS_ICON_TCOORDS[strupper(select(2,UnitClass("player")))]) kb:uibutton( 'KeyBinderMacroFrameButton', 'Macros', nil, function() if MacroFrame then HideUIPanel(MacroFrame) else ShowMacroFrame() end end, "Interface\\MacroFrame\\MacroFrame-Icon") kb:uibutton( 'KeyBinderInventoryButton', 'Bags', nil, function() OpenAllBags() end, "Interface\\BUTTONS\\Button-Backpack-Up") kb.info:SetPoint('TOPLEFT', kb.UIPanels[1], 'BOTTOMLEFT', 0, -BUTTON_SPACING) HEADER_OFFSET = kb.UIPanels[1]:GetHeight() + BUTTON_PADDING FOOTER_OFFSET = saveButton:GetHeight() + BUTTON_PADDING end --- Invokes the KeyBinder frame (from the /kb function or some other source) kb.ui = function() if not db.showUI then return end if not kb:IsVisible() then kb:Show() db.showUI = true end if not kb.loaded then SetupUI() kb.loaded = true end for i = 1, numButtons do kb.refresh(kb.keyslot(i)) end if bindMode == BINDING_TYPE_SPECIALIZATION then bindHeader = select(2,GetSpecializationInfo(GetSpecialization())) elseif bindMode == BINDING_TYPE_CHARACTER then bindHeader = UnitName('player') else bindHeader = '' end if bindsCommitted then KeyBinderSaveButton:Disable() KeyBinderRestoreButton:Disable() else KeyBinderSaveButton:Enable() KeyBinderRestoreButton:Enable() end --- panel attributes local numRows = numButtons/BINDS_PER_ROW kb:SetHeight( numRows * (KEY_BUTTON_SIZE) + (numRows - 1) * BUTTON_SPACING + HEADER_OFFSET + FOOTER_OFFSET + BUTTON_PADDING * 2) kb:SetWidth((BINDS_PER_ROW - 1) * BUTTON_SPACING + BINDS_PER_ROW * KEY_BUTTON_SIZE + BUTTON_PADDING * 2) kb.bg:SetColorTexture(unpack(BINDING_SCHEME_COLOR[bindMode])) for i, tab in ipairs(kb.tabButtons) do local n = tab:GetNormalTexture() local tabTexture = "Interface\\Buttons\\UI-Quickslot2" local left, top, right, bottom = -12, 12, 13, -13 if i == bindMode then tabTexture = "Interface\\Buttons\\CheckButtonGlow" left, top, right, bottom = -14, 14, 15, -15 end n:SetTexture(tabTexture) n:SetPoint('TOPLEFT', tab, 'TOPLEFT', left, top) n:SetPoint('BOTTOMRIGHT', tab, 'BOTTOMRIGHT', right, bottom) end end kb.loadbinds = function (bindings) for key, command in pairs(bindings) do -- store for reversion local oldAction = GetBindingAction(key) if oldAction ~= command then local bind1, bind2 = GetBindingKey(oldAction) if bind1 and not reverts[bind1] then reverts[bind1] = oldAction end if bind2 and not reverts[bind2] then reverts[bind2] = oldAction end end SetBindings(key, command) end SaveBindings() end local ACTION_BARS = { {'ActionButton', 0}, {'MultiBarLeftButton', 24}, {'MultiBarRightButton', 36}, {'MultiBarBottomRighttButton', 48}, {'MultiBarBottomLeftButton', 60}, } kb.HotKeyText = function (slot) local i, offset = 0, 0 local actionbar -- figure out which bar the slot belongs to for i, bar in ipairs(ACTION_BARS) do actionbar, offset = unpack(ACTION_BARS[i]) if bar[2] > slot then break end end local button = _G[actionbar .. (slot - offset)] if not button then return end local type, id, subType, subID = GetActionInfo(slot) if not type then return end local bind, command if type == 'spell' then local name = GetSpellInfo(id) command = 'SPELL '..name elseif type == 'macro' then command = 'MACRO ' .. id else return end bind = GetBindingKey(command) if bind then button.HotKey:SetText(BindingString(bind)) button.HotKey:Show() end end kb.InitProfile = function(profile) profile.buttons = profile.buttons or {} profile.commands = profile.commands or {} profile.bindings = profile.bindings or {} profile.macros = profile.macros or {} return profile end kb.ResetProfile = function() for i, button in pairs(buttons) do kb.release(button) end profile.commands = {} profile.bindings = {} profile.macros = {} end --- Gives us the profile structure to work with while instating data kb.profile = function(name) global = kb.InitProfile(db) profile = global local subtitle if name then db[name] = db[name] or {} db[name] = kb.InitProfile(db[name]) character = db[name] local spec = GetSpecialization() if spec then db[name][spec] = db[name][spec] or {} profile = kb.InitProfile(db[name][spec]) bindMode = BINDING_TYPE_SPECIALIZATION subtitle = select(2,GetSpecializationInfo(spec)) specialization = db[name][spec] else profile = kb.InitProfile(db[name]) bindMode = BINDING_TYPE_CHARACTER subtitle = name specialization = character end end priority = {global, character, specialization } if not db.bindsPage then db.bindsPage = bindMode end bindMode = db.bindsPage if not BINDING_MODE[bindMode] then bindMode = 3 db.bindsPage = 3 print('overriding', bindMode) end profile = priority[bindMode] local _ _, specHeader, _, specTexture = GetSpecializationInfo(GetSpecialization()) print(GetSpecializationInfo(GetSpecialization())) specHeader = BINDING_MODE[2]:format(specHeader) characterHeader = BINDING_MODE[2]:format(UnitName('player')) print('Using binding profile |cFF00FF88'..BINDING_MODE[bindMode]:format(subtitle)..'|r') end kb.SelectTab = function(self) bindMode = self:GetID() profile = priority[self:GetID()] db.bindsPage = self:GetID() kb.ui() end kb.save = function() SaveBindings(GetCurrentBindingSet()) bindsCommitted = true for i, button in ipairs(buttons) do button.pending = false end kb.ui() print('Bindings saved.') end kb.restore = function() for i, button in pairs(buttons) do button.pending = false end bindsCommitted = true LoadBindings(GetCurrentBindingSet()) print('All changes discarded.') end --- Tells all the hud buttons what to do kb.init = function() KeyBinderMacro:SetAttribute('*type*', 'macro') end --- Get started kb.variables = function() SkeletonKeyDB = SkeletonKeyDB or {} db = SkeletonKeyDB kb.profile(GetUnitName('player', true)) for i = 1, 3 do for attribute, data in pairs(priority[i].macros) do KeyBinderMacro:SetAttribute(attribute, data[1]) end end kb.UPDATE_BINDINGS() kb:RegisterEvent('UPDATE_BINDINGS') kb:RegisterEvent('UPDATE_MACROS') kb:RegisterEvent('PLAYER_SPECIALIZATION_CHANGED') kb:RegisterEvent('PLAYER_EQUIPMENT_CHANGED') kb:RegisterEvent('PLAYER_REGEN_DISABLED') kb:RegisterEvent('PLAYER_REGEN_ENABLED') kb:RegisterEvent('ACTIONBAR_SLOT_CHANGED') end kb.close = function() db.showUI = false kb:Hide() end kb.PLAYER_REGEN_DISABLED = function() if db.showUI then kb:Hide() end end kb.PLAYER_REGEN_ENABLED = function() if db.showUI then kb.ui() end end --- Refresh buttons if macros are updated kb.UPDATE_BINDINGS = function() for i = 1, 120 do kb.HotKeyText(i) end if db.showUI then kb.ui() end end kb.ACTIONBAR_SLOT_CHANGED = function(self, event, slot) kb.HotKeyText(slot) return true end kb.UPDATE_MACROS = kb.UPDATE_BINDINGS SLASH_KB1 = "/kb" SlashCmdList.KB = function(self, input) if db.showUI then db.showUI = false print('|cFFFFFF00KeyBinds|r trace, |cFFFF0000OFF|r.') kb:Hide() else db.showUI = true print('|cFFFFFF00KeyBinds|r trace, |cFF00FF00ON|r.') kb.ui() end end