570b5ba - Add Mekkatorque mythic auras
[wowui.git] / OmaAB / ActionBars.lua
index facac67..5b2233b 100644 (file)
 -- ActionBars.lua
 local _;
-local min = math.min;
-local CreateFrame, IsResting = CreateFrame, IsResting;
-local UnitXP, UnitXPMax, GetXPExhaustion = UnitXP, UnitXPMax, GetXPExhaustion;
-local CTimerAfter = C_Timer.After;
-local ExhaustionToolTipText = ExhaustionToolTipText;
-local GameTooltip = nil;
-
-local width = 300;
-local running = false;
-
-local ActionBars = CreateFrame("Frame", "OmaActionBars");
-
-local function expBar(parent)
-    local frame = CreateFrame("Frame", "OmaExpBar", parent);
-    frame:SetPoint("BOTTOM");
-    frame:SetWidth(width);
-    frame:SetHeight(10);
-    frame.base = frame:CreateTexture(nil, "BACKGROUND");
-    frame.base:SetAllPoints();
-    frame.base:SetColorTexture(0, 0, 0, 0.5);
-    frame.bar = frame:CreateTexture(nil, "BORDER");
-    frame.bar:SetPoint("TOPLEFT", frame.base, "TOPLEFT");
-    frame.bar:SetPoint("BOTTOMLEFT", frame.base, "BOTTOMLEFT");
-    frame.bar:SetColorTexture(1, 1, 1);
-    frame.rest = frame:CreateTexture(nil, "BORDER");
-    frame.rest:SetPoint("TOPLEFT", frame.bar, "TOPRIGHT");
-    frame.rest:SetPoint("BOTTOMLEFT", frame.bar, "BOTTOMRIGHT");
-    frame.rest:SetColorTexture(0, 0.3, 1, 0.7);
-    frame.text = frame:CreateFontString(nil, "ARTWORK", "GameFontHighlight");
-    frame.text:SetPoint("BOTTOM");
-    frame.text:Hide();
-
-    local function updateXP()
-        local xp, xpmax = UnitXP("player"), UnitXPMax("player");
-        local rested = GetXPExhaustion();
-        local current = xp/xpmax*width;
-        local space = width - current;
-        frame.bar:SetWidth(current);
-        if rested then
-            local restw = rested/xpmax*width;
-            frame.rest:SetWidth(min(space, restw));
-            frame.rest:Show();
-            frame.text:SetFormattedText("%d / %d (+%d)", xp, xpmax, rested/2);
-            frame.bar:SetVertexColor(0, 0.5, 1, 0.9);
+local pairs = pairs;
+local format = string.format;
+local ssub = string.sub;
+local GetActionInfo, GetActionTexture = GetActionInfo, GetActionTexture;
+local GetActionLossOfControlCooldown = GetActionLossOfControlCooldown;
+local GetActionCooldown, GetActionCharges = GetActionCooldown, GetActionCharges;
+local GetActionText, GetBindingKey = GetActionText, GetBindingKey;
+local IsConsumableAction, IsStackableAction = IsConsumableAction, IsStackableAction;
+local IsItemAction, GetActionCount = IsItemAction, GetActionCount;
+local IsSpellOverlayed, GetMacroSpell = IsSpellOverlayed, GetMacroSpell;
+local IsMounted = IsMounted;
+local HasAction, IsUsableAction = HasAction, IsUsableAction;
+local IsCurrentAction, IsAutoRepeatAction = IsCurrentAction, IsAutoRepeatAction;
+local CreateFrame = CreateFrame;
+local RegisterStateDriver = RegisterStateDriver;
+local CooldownFrame_Set, CooldownFrame_Clear = CooldownFrame_Set, CooldownFrame_Clear;
+local GameTooltip = GameTooltip;
+local GameTooltip_SetDefaultAnchor = GameTooltip_SetDefaultAnchor;
+local COOLDOWN_TYPE_LOSS_OF_CONTROL = COOLDOWN_TYPE_LOSS_OF_CONTROL;
+local COOLDOWN_TYPE_NORMAL = COOLDOWN_TYPE_NORMAL;
+local CDTexture = "Interface\\Cooldown\\edge";
+local locCDTexture = "Interface\\Cooldown\\edge-LoC";
+
+local BUTTONLOCK = true; -- change to lock button dragging
+
+local settings = {
+    ["Oma1"] = {
+        bar = 1,
+        start = 1,
+        length = 12,
+        columns = 4,
+        size = 40,
+        x = 580,
+        y = 300,
+        nomouse = true,
+    },
+    ["Oma2"] = {
+        bar = 2,
+        start = 13,
+        length = 12,
+        columns = 4,
+        size = 40,
+        x = 580,
+        y = 180,
+        nomouse = true,
+    },
+    ["Oma3"] = {
+        bar = 3,
+        start = 25,
+        length = 12,
+        columns = 3,
+        x = 1824,
+        y = 128,
+        flyout = "LEFT",
+    },
+    ["Oma4"] = {
+        bar = 4,
+        start = 37,
+        length = 12,
+        columns = 3,
+        x = 1824,
+        y = 256,
+    },
+    ["Oma5"] = {
+        bar = 5,
+        start = 49,
+        length = 12,
+        x = 1000,
+        y = 840,
+    },
+    ["Oma6"] = {
+        bar = 6,
+        start = 61,
+        length = 12,
+        x = 1000,
+        y = 600,
+    },
+    -- used as bonus bars for some classes
+    ["Oma7"] = {
+        bar = 7,
+        start = 73,
+        length = 12,
+        x = 1000,
+        y = 760,
+    },
+    ["Oma8"] = {
+        bar = 8,
+        start = 85,
+        length = 12,
+        x = 1000,
+        y = 720,
+    },
+    ["Oma9"] = {
+        bar = 9,
+        start = 97,
+        length = 12,
+        x = 1000,
+        y = 680,
+    },
+    ["Oma10"] = {
+        bar = 10,
+        start = 109,
+        length = 12,
+        x = 1000,
+        y = 640,
+    },
+};
+
+local usingBonusbars = {
+    --["WARRIOR"] = {[7]=true, [8]=true, [9]=true}, -- not using stance separated actionbars
+    ["DRUID"] = {[7]=true, [8]=true, [9]=true}, -- moonkin form page is usable anyway
+    --["DRUID"] = {[7]=true, [8]=true, [9]=true,[10]=true},
+    ["ROGUE"] = {[7]=true},
+    --["PRIEST"] = {[7]=true}, -- shadowform doesn't change abilities
+};
+
+local chars = {
+    ["Sylvanas"] = {
+        ["Vildana"] = {1, 2, 3, 4,},
+    },
+    ["Stormreaver"] = {
+        ["Vildan"] = {1, 2, 3, 4,},
+        ["Gedren"] = {1, 2, 3, 4,},
+        ["Gazden"] = {1, 2, 3, 4,},
+        ["Gedran"] = {1, 2, 3, 4,},
+        ["Iled"] = {1, 2, 3, 4,},
+        ["Gilden"] = {1, 2, 3, 4,},
+    },
+};
+
+local buttons = {};
+local activeButtons = {};
+
+local ActionBars = CreateFrame("Frame", "OmaActionBars", UIParent);
+local inheritedFrames =
+"SecureActionButtonTemplate,SecureHandlerDragTemplate,SecureHandlerStateTemplate";
+
+local function showTooltip(secure)
+    GameTooltip_SetDefaultAnchor(GameTooltip, secure);
+    GameTooltip:SetAction(secure:GetAttribute("action"));
+end
+
+local function hideTooltip()
+    GameTooltip:Hide();
+end
+
+local numChargeCDs = 0;
+local function createChargeCD(parent)
+    numChargeCDs = numChargeCDs + 1;
+    local frame = CreateFrame("Cooldown", "OmaChargeCD"..numChargeCDs, parent, "CooldownFrameTemplate");
+    frame:SetHideCountdownNumbers(false);
+    frame:SetDrawSwipe(false);
+    frame:SetAllPoints(parent);
+    frame:SetFrameStrata("TOOLTIP");
+    return frame;
+end
+
+local function clearChargeCD(parent)
+    if parent.chargecd then CooldownFrame_Clear(parent.chargecd) end
+end
+
+local function startChargeCD(parent, start, duration, modrate)
+    if start == 0 then
+        return clearChargeCD(parent);
+    end
+    parent.chargecd = parent.chargecd or createChargeCD(parent);
+    CooldownFrame_Set(parent.chargecd, start, duration, true, true, modrate);
+end
+
+local redoCooldown;
+
+local function updateCooldown(button, slot)
+    -- CD update from FrameXML/ActionButton.lua
+    local locstart, locduration = GetActionLossOfControlCooldown(slot);
+    local start, duration, enable, modrate = GetActionCooldown(slot);
+    local charges, maxcharges, chargestart, chargeduration, chargemodrate = GetActionCharges(slot);
+    -- avoid as many updates as possible by checking if there's changes first
+    if button.prev and
+       button.prev[1] == locstart    and button.prev[2] == locduration and
+       button.prev[3] == start       and button.prev[4] == duration and
+       button.prev[5] == enable      and button.prev[6] == modrate and
+       button.prev[7] == charges     and button.prev[8] == maxcharges and
+       button.prev[9] == chargestart and button.prev[10] == chargeduration and
+       button.prev[11] == chargemodrate then
+        return;
+    end
+    button.prev = { locstart, locduration, start, duration, enable, modrate,
+        charges, maxcharges, chargestart, chargeduration, chargemodrate };
+    if (locstart + locduration) > (start + duration) then
+        if button.cd.currentCooldownType ~= COOLDOWN_TYPE_LOSS_OF_CONTROL then
+            button.cd:SetEdgeTexture(locCDTexture);
+            button.cd:SetSwipeColor(0.17, 0, 0);
+            button.cd:SetHideCountdownNumbers(true);
+            button.cd.currentCooldownType = COOLDOWN_TYPE_LOSS_OF_CONTROL;
+        end
+
+        CooldownFrame_Set(button.cd, locstart, locduration, true, true, modrate);
+        clearChargeCD(button);
+    else
+        if button.cd.currentCooldownType ~= COOLDOWN_TYPE_NORMAL then
+            button.cd:SetEdgeTexture(CDTexture);
+            button.cd:SetSwipeColor(0, 0, 0);
+            button.cd:SetHideCountdownNumbers(false);
+            button.cd.currentCooldownType = COOLDOWN_TYPE_NORMAL;
+        end
+
+        if locstart > 0 then
+            button.cd:SetScript("OnCooldownDone", redoCooldown);
+        end
+        if charges and maxcharges and maxcharges > 1 and charges < maxcharges then
+            startChargeCD(button, chargestart, chargeduration, chargemodrate);
+        else
+            clearChargeCD(button);
+        end
+        CooldownFrame_Set(button.cd, start, duration, enable, false, modrate);
+    end
+end
+
+local function redoCooldown(cd)
+    local button = cd:GetParent();
+    cd:SetScript("OnCooldownDone", nil);
+    updateCooldown(button, button.slot);
+end
+
+local function updateCount(button, slot)
+    if IsConsumableAction(slot) or IsStackableAction(slot) or
+            (not IsItemAction(slot) and GetActionCount(slot) > 0) then
+        local count = GetActionCount(slot);
+        if count > 99 then
+            button.count:SetText("*");
+        else
+            button.count:SetText(count);
+        end
+        button.count:Show();
+    else
+        local charges, maxcharges = GetActionCharges(slot);
+        if maxcharges > 1 then
+            button.count:SetText(charges);
+            button.count:Show();
         else
-            frame.rest:Hide();
-            frame.text:SetFormattedText("%d / %d", xp, xpmax);
-            frame.bar:SetVertexColor(0.6, 0.2, 1, 0.9);
+            button.count:Hide();
+        end
+    end
+end
+
+local function updateUsable(button, slot)
+    local isUsable, noMana = IsUsableAction(slot);
+    if isUsable then
+        button.icon:SetVertexColor(1, 1, 1);
+    elseif noMana then
+        button.icon:SetVertexColor(0, 0.5, 1);
+    else
+        button.icon:SetVertexColor(0.4, 0.4, 0.4);
+    end
+end
+
+local function updateState(button, slot)
+    button:SetChecked(IsCurrentAction(slot) or IsAutoRepeatAction(slot));
+end
+
+local function updateGlow(button, slot)
+    local stype, id = GetActionInfo(slot);
+    if stype == "spell" and IsSpellOverlayed(id) then
+        button.glow:Show();
+    elseif stype == "macro" then
+        local macroid = GetMacroSpell(id);
+        if macroid and IsSpellOverlayed(macroid) then
+            button.glow:Show();
+        else
+            button.glow:Hide();
+        end
+    else -- TODO FlyoutHasSpell glow
+        button.glow:Hide();
+    end
+end
+
+local function startGlow(button, slot, spell)
+    local stype, id = GetActionInfo(slot);
+    if stype == "spell" and id == spell then
+        button.glow:Show();
+    elseif stype == "macro" then
+        local macroid = GetMacroSpell(id);
+        if macroid and macroid == spell then
+            button.glow:Show();
+        end
+    end
+    -- TODO FlyoutHasSpell glow
+end
+
+local function stopGlow(button, slot, spell)
+    local stype, id = GetActionInfo(slot);
+    if stype == "spell" and id == spell then
+        button.glow:Hide();
+    elseif stype == "macro" then
+        local macroid = GetMacroSpell(id);
+        if macroid and macroid == spell then
+            button.glow:Hide();
+        end
+    end
+    -- TODO FlyoutHasSpell glow
+end
+
+local function updateButton(button, slot)
+    if HasAction(slot) then
+        activeButtons[slot] = button;
+        button.base:Show();
+        button.icon:SetTexture(GetActionTexture(slot));
+        updateCooldown(button, slot);
+        updateUsable(button, slot);
+        updateState(button, slot);
+        updateCount(button, slot);
+        updateGlow(button, slot);
+        if not IsConsumableAction(slot) and not IsStackableAction(slot) then
+            button.text:SetText(ssub(GetActionText(slot) or "", 1, 4));
+            button.text:Show();
         end
+        if button.hotkey.shown then button.hotkey:Show() end
+    else
+        activeButtons[slot] = nil;
+        if not button.grid then button.base:Hide() end
+        button.icon:SetTexture(nil);
+        button.cd:Hide();
+        button.count:Hide();
+        button.hotkey:Hide();
+        button.text:Hide();
+        button.glow:Hide();
+        button:SetChecked(false);
     end
-    local function updater()
-        updateXP();
-        if running then CTimerAfter(10, updater) end
+end
+
+local function updateHotkeys(button)
+    local key = GetBindingKey(format("CLICK %s:LeftButton", button:GetName()));
+    if key and key ~= "" then
+        -- from LibKeyBound-1.0
+        key = key:upper();
+        key = key:gsub(" ", "");
+        key = key:gsub("ALT%-", "a");
+        key = key:gsub("CTRL%-", "c");
+        key = key:gsub("SHIFT%-", "s");
+        key = key:gsub("NUMPAD", "n");
+        button.hotkey:SetText(key);
+        button.hotkey.shown = true;
+        button.hotkey:Show();
+    else
+        button.hotkey.shown = nil;
+        button.hotkey:Hide();
     end
-    updateXP();
+end
 
-    frame:SetScript("OnEvent", function(self, event)
-        if event == "PLAYER_XP_UPDATE" or event == "PLAYER_LEVEL_UP" then
-            updateXP();
-        elseif event == "PLAYER_UPDATE_RESTING" then
-            if IsResting() then
-                running = true;
-                CTimerAfter(6, updater);
+local mainbartoggle = "[overridebar][possessbar][shapeshift]possess;";
+mainbartoggle = mainbartoggle.."[bonusbar:1,stealth:1]bonusbar2;"; -- prowl
+mainbartoggle = mainbartoggle.."[bonusbar:1]bonusbar1;[bonusbar:2]bonusbar2;"; -- cat form, unused
+mainbartoggle = mainbartoggle.."[bonusbar:3]bonusbar3;[bonusbar:4]bonusbar4;"; -- bear form, moonkin form
+mainbartoggle = mainbartoggle.."normal";
+local function setupSnippets(secure, slot)
+    -- FrameXML/SecureHandlers.lua has arguments and return value
+    -- args: self, button, kind, value, ... (kind, value, ... from GetCursorInfo())
+    -- returns: kind, target, detail
+    -- or: "clear", kind, target, detail
+    -- used for Pickup* functions
+    -- some of these snippets based on LibActionButton-1.0
+    secure:SetAttribute("_ondragstart", [=[
+        return "action", self:GetAttribute("action");
+    ]=]);
+    secure:SetAttribute("_onreceivedrag", [=[
+        if not kind or not value then return nil end
+        return "action", self:GetAttribute("action");
+    ]=]);
+    -- pre-wrapper can pass a message to post-wrapper
+    secure:WrapScript(secure, "OnDragStart", [=[
+        local kind, value = GetActionInfo(self:GetAttribute("action"));
+        return "message", format("%s|%s", tostring(kind), tostring(value));
+    ]=], [=[
+        local kind, value = GetActionInfo(self:GetAttribute("action"));
+        if message ~= format("%s|%s", tostring(kind), tostring(value)) then
+            self:CallMethod("ActionChanged");
+        end
+    ]=]);
+    secure:WrapScript(secure, "OnReceiveDrag", [=[
+        local kind, value = GetActionInfo(self:GetAttribute("action"));
+        return "message", format("%s|%s", tostring(kind), tostring(value));
+    ]=], [=[
+        local kind, value = GetActionInfo(self:GetAttribute("action"));
+        if message ~= format("%s|%s", tostring(kind), tostring(value)) then
+            self:CallMethod("ActionChanged");
+        end
+    ]=]);
+    function secure:UpdateState()
+        return updateState(self, self.slot);
+    end
+    secure:WrapScript(secure, "OnClick", [=[
+        local kind, value = GetActionInfo(self:GetAttribute("action"));
+        return nil, format("%s|%s", tostring(kind), tostring(value));
+    ]=], [=[
+        local kind, value = GetActionInfo(self:GetAttribute("action"));
+        if message ~= format("%s|%s", tostring(kind), tostring(value)) then
+            self:CallMethod("ActionChanged");
+        else
+            self:CallMethod("UpdateState");
+        end
+    ]=]);
+    if slot < 13 then
+        -- first action bar has possible states based on vehicle/possess etc.
+        secure:SetAttribute("origaction", slot);
+        secure:SetAttribute("_onstate-possess", [=[
+            local oldslot = self:GetAttribute("action");
+            if newstate == "possess" then
+                local slot;
+                if HasVehicleActionBar() then
+                    slot = (GetVehicleBarIndex()-1)*12+self:GetAttribute("origaction");
+                elseif HasOverrideActionBar() then
+                    slot = (GetOverrideBarIndex()-1)*12+self:GetAttribute("origaction");
+                elseif HasTempShapeshiftActionBar() then
+                    slot = (GetTempShapeshiftBarIndex()-1)*12+self:GetAttribute("origaction");
+                else
+                    -- something wrong, just revert to normal
+                    print("Possess bar index not found");
+                    slot = self:GetAttribute("origaction");
+                end
+                self:SetAttribute("action", slot);
+            elseif newstate == "bonusbar1" then
+                self:SetAttribute("action", 72+self:GetAttribute("origaction"));
+            elseif newstate == "bonusbar2" then
+                self:SetAttribute("action", 84+self:GetAttribute("origaction"));
+            elseif newstate == "bonusbar3" then
+                self:SetAttribute("action", 96+self:GetAttribute("origaction"));
+            elseif newstate == "bonusbar4" then
+                --self:SetAttribute("action", 108+self:GetAttribute("origaction"));
+                -- moonkin form, don't change actionbar
+                self:SetAttribute("action", self:GetAttribute("origaction"));
             else
-                running = false;
+                self:SetAttribute("action", self:GetAttribute("origaction"));
             end
+            self:CallMethod("ActionChanged", oldslot);
+        ]=]);
+        RegisterStateDriver(secure, "possess", mainbartoggle);
+    else
+        function secure:ShowButton() if HasAction(slot) then activeButtons[slot] = self end end
+        function secure:HideButton() activeButtons[slot] = nil end
+        -- all other action bar are hidden if with overridebar or vehicleui (not shapeshift, possessbar)
+        -- default Bartender4 options
+        secure:SetAttribute("_onstate-possess", [=[
+            if newstate == "possess" then
+                self:Hide();
+                self:CallMethod("HideButton");
+            else
+                self:Show();
+                self:CallMethod("ShowButton");
+            end
+        ]=]);
+        RegisterStateDriver(secure, "possess", "[overridebar][vehicleui] possess; normal");
+    end
+end
+
+local function createActionBar(parent, config)
+    local prev;
+    local i = 0;
+    local bar = CreateFrame("Frame", "OmaBTBar"..config.bar, parent, "SecureFrameTemplate");
+    bar:SetPoint("TOPLEFT", parent, "BOTTOMLEFT", config.x, config.y);
+    bar:SetWidth(1);
+    bar:SetHeight(1);
+    if config.hidden then
+        bar:Hide();
+    end
+    for slot = config.start, config.start+config.length-1 do
+        local secure = CreateFrame("CheckButton", "OmaBT"..slot, bar, inheritedFrames);
+        secure.slot = slot;
+        if slot == config.start then
+            secure:SetPoint("TOPLEFT");
+        elseif config.columns and i % config.columns == 0 then
+            secure:SetPoint("TOPLEFT", _G["OmaBT"..(slot-config.columns)], "BOTTOMLEFT");
+        else
+            secure:SetPoint("TOPLEFT", prev, "TOPRIGHT");
+        end
+        secure:RegisterForClicks("AnyUp");
+        if not BUTTONLOCK then
+            secure:RegisterForDrag("LeftButton", "RightButton");
+        end
+        if config.nomouse then
+            secure:EnableMouse(false);
+        else
+            -- only show tooltips for bars with mouse interaction
+            secure:SetScript("OnEnter", showTooltip);
+            secure:SetScript("OnLeave", hideTooltip);
+        end
+        secure:SetWidth(config.size or 32);
+        secure:SetHeight(config.size or 32);
+        secure.base = secure:CreateTexture(nil, "BACKGROUND");
+        secure.base:SetAllPoints();
+        secure.base:SetColorTexture(0, 0, 0, 0.5);
+        secure.iconbase = secure:CreateTexture(nil, "BORDER");
+        secure.iconbase:SetPoint("TOPLEFT", secure.base, "TOPLEFT", 1, -1);
+        secure.iconbase:SetPoint("BOTTOMRIGHT", secure.base, "BOTTOMRIGHT", -1, 1);
+        secure.iconbase:SetColorTexture(0, 0, 0, 0.5);
+        secure.iconbase:Hide();
+        secure.icon = secure:CreateTexture(nil, "ARTWORK");
+        secure.icon:SetPoint("TOPLEFT", secure.iconbase, "TOPLEFT");
+        secure.icon:SetPoint("BOTTOMRIGHT", secure.iconbase, "BOTTOMRIGHT");
+        secure.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93);
+        secure:SetCheckedTexture("Interface\\Buttons\\CheckButtonHilight");
+        secure.autocastable = secure:CreateTexture(nil, "OVERLAY");
+        secure.autocastable:SetPoint("CENTER");
+        secure.autocastable:SetWidth(58);
+        secure.autocastable:SetHeight(58);
+        secure.autocastable:SetTexture("Interface\\Buttons\\UI-AutoCastableOverlay");
+        secure.autocastable:Hide();
+        secure.glow = secure:CreateTexture(nil, "OVERLAY", nil, 1);
+        secure.glow:SetPoint("CENTER");
+        secure.glow:SetWidth(config.size and config.size+26 or 53);
+        secure.glow:SetHeight(config.size and config.size+26 or 53);
+        secure.glow:SetTexture("Interface\\SpellActivationOverlay\\IconAlert");
+        secure.glow:SetTexCoord(0.00781250, 0.50781250, 0.27734375, 0.52634375);
+        secure.glow:Hide();
+        secure.hotkey = secure:CreateFontString(nil, "OVERLAY", "NumberFontNormalGray");
+        secure.hotkey:SetPoint("TOPRIGHT", secure, "TOPRIGHT", 2, -1);
+        secure.count = secure:CreateFontString(nil, "OVERLAY", "NumberFontNormal");
+        secure.count:SetPoint("BOTTOMRIGHT", secure, "BOTTOMRIGHT", 2, -1);
+        secure.text = secure:CreateFontString(nil, "OVERLAY", "NumberFontNormal");
+        secure.text:SetPoint("BOTTOMLEFT", secure, "BOTTOMLEFT", 2, -1);
+        secure.text:Hide();
+        secure.cd = CreateFrame("Cooldown", "OmaBTCD"..slot, secure, "CooldownFrameTemplate");
+        secure.cd:SetAllPoints();
+        secure:SetAttribute("type", "action");
+        secure:SetAttribute("action", slot);
+        if config.flyout then
+            secure:SetAttribute("flyoutDirection", config.flyout);
         end
-    end);
-    frame:RegisterEvent("PLAYER_XP_UPDATE");
-    frame:RegisterEvent("PLAYER_LEVEL_UP");
-    frame:RegisterEvent("PLAYER_UPDATE_RESTING");
-    -- from FrameXML/MainMenuBar.lua
-    frame:SetScript("OnEnter", function(frame) frame.text:Show(); ExhaustionToolTipText(); end);
-    frame:SetScript("OnLeave", function(frame) frame.text:Hide(); GameTooltip:Hide(); end);
+        function secure:ActionChanged(oldslot)
+            if oldslot then
+                activeButtons[oldslot] = nil;
+                self.prev = nil; -- invalidate previous CD when slot changes
+            end
+            self.slot = self:GetAttribute("action");
+            return updateButton(self, self.slot);
+        end
+        secure:ActionChanged(); -- initial update
+        setupSnippets(secure, slot);
+        updateHotkeys(secure);
+        buttons[slot] = secure;
+        prev = secure;
+        i = i + 1;
+    end
 end
 
 local function initialize()
-    -- let other addons hook this to anchor tooltip elsewhere
-    GameTooltip = _G["GameTooltip"];
-    if UnitLevel("player") < 110 and not IsXPUserDisabled() then
-        expBar(UIParent);
+    local _, class = UnitClass("player");
+    local name, realm = UnitFullName("player");
+    ActionBars:SetFrameStrata("LOW");
+    ActionBars:SetPoint("BOTTOMLEFT");
+    ActionBars:SetWidth(1);
+    ActionBars:SetHeight(1);
+    for _, config in pairs(settings) do
+        if (not usingBonusbars[class] or not usingBonusbars[class][config.bar]) and
+           (not chars[realm] or not chars[realm][name] or chars[realm][name][config.bar]) then
+            createActionBar(ActionBars, config);
+        end
     end
 end
 
-ActionBars:RegisterEvent("PLAYER_LOGIN");
-ActionBars:SetScript("OnEvent", function(self, event)
-    if event == "PLAYER_LOGIN" then
-        initialize();
+local function setupBindings()
+    _G["BINDING_HEADER_OmaAB"] = "Oma Action Bar";
+    for i = 1,10 do
+        _G["BINDING_HEADER_OMAABBLANK"..i] = "Bar "..i;
+        for j = 1,12 do
+            _G[format("BINDING_NAME_CLICK OmaBT%d:LeftButton", (i-1)*12+j)] = format("Bar %d Button %d", i, j);
+        end
     end
+end
+
+local mounted = false;
+
+local events = {
+    ["ACTIONBAR_UPDATE_COOLDOWN"] = function()
+        for _, button in pairs(activeButtons) do
+            updateCooldown(button, button.slot);
+        end
+    end,
+    ["SPELL_UPDATE_CHARGES"] = function()
+        for _, button in pairs(activeButtons) do
+            updateCount(button, button.slot);
+        end
+    end,
+    ["ACTIONBAR_SLOT_CHANGED"] = function(slot)
+        if buttons[slot] then buttons[slot]:ActionChanged() end
+    end,
+    ["ACTIONBAR_SHOWGRID"] = function()
+        for _, button in pairs(buttons) do
+            button.grid = true;
+            button.iconbase:Show();
+            if not activeButtons[button.slot] then button.base:Show() end
+        end
+    end,
+    ["ACTIONBAR_HIDEGRID"] = function()
+        for _, button in pairs(buttons) do
+            button.grid = nil;
+            button.iconbase:Hide();
+            if not activeButtons[button.slot] then button.base:Hide() end
+        end
+    end,
+    ["ACTIONBAR_UPDATE_STATE"] = function()
+        for _, button in pairs(activeButtons) do
+            updateState(button, button.slot);
+        end
+    end,
+    ["ACTIONBAR_UPDATE_USABLE"] = function()
+        for _, button in pairs(activeButtons) do
+            updateUsable(button, button.slot);
+        end
+    end,
+    ["UPDATE_OVERRIDE_ACTIONBAR"] = function()
+        if buttons[1] then -- called before PLAYER_LOGIN
+            for _, button in pairs(buttons) do
+                updateButton(button, button.slot);
+            end
+        end
+    end,
+    ["START_AUTOREPEAT_SPELL"] = function()
+        for _, button in pairs(activeButtons) do
+            if IsAutoRepeatAction(button.slot) then
+                button.autorepeating = true;
+                button.icon:SetVertexColor(0, 1, 0.5);
+            end
+        end
+    end,
+    ["STOP_AUTOREPEAT_SPELL"] = function()
+        for _, button in pairs(activeButtons) do
+            if button.autorepeating then
+                button.autorepeating = nil;
+                updateUsable(button, button.slot);
+            end
+        end
+    end,
+    ["SPELL_ACTIVATION_OVERLAY_GLOW_SHOW"] = function(spell)
+        -- TODO create mapping from spellIDs to buttons
+        for _, button in pairs(activeButtons) do
+            startGlow(button, button.slot, spell);
+        end
+    end,
+    ["SPELL_ACTIVATION_OVERLAY_GLOW_HIDE"] = function(spell)
+        -- TODO create mapping from spellIDs to buttons
+        for _, button in pairs(activeButtons) do
+            stopGlow(button, button.slot, spell);
+        end
+    end,
+    ["UPDATE_BINDINGS"] = function()
+        for _, button in pairs(buttons) do
+            updateHotkeys(button);
+        end
+    end,
+    ["UNIT_AURA"] = function(unit)
+        -- using UNIT_AURA instead of COMPANION_UPDATE to not update every time
+        -- someone mounts, tracking player mount status with COMPANION_UPDATE is
+        -- inconsistent
+        if (not mounted and IsMounted()) or (mounted and not IsMounted()) then
+            mounted = not mounted;
+            for _, button in pairs(activeButtons) do
+                updateState(button, button.slot);
+            end
+        end
+    end,
+    ["UPDATE_ALL_BUTTONS"] = function()
+        for _, button in pairs(buttons) do
+            updateButton(button, button.slot);
+        end
+    end,
+    ["PLAYER_LOGIN"] = function()
+        initialize();
+    end,
+    ["ADDON_LOADED"] = function(addon)
+        if addon == "OmaAB" then
+            setupBindings();
+            ActionBars:UnregisterEvent("ADDON_LOADED");
+        end
+    end,
+};
+events["LOSS_OF_CONTROL_ADDED"] = events["ACTIONBAR_UPDATE_COOLDOWN"];
+events["LOSS_OF_CONTROL_UPDATE"] = events["ACTIONBAR_UPDATE_COOLDOWN"];
+events["PLAYER_MOUNT_DISPLAY_CHANGED"] = events["ACTIONBAR_UPDATE_USABLE"];
+events["TRADE_SKILL_SHOW"] = events["ACTIONBAR_UPDATE_STATE"];
+events["TRADE_SKILL_CLOSE"] = events["ACTIONBAR_UPDATE_STATE"];
+events["ARCHAEOLOGY_CLOSED"] = events["ACTIONBAR_UPDATE_STATE"];
+events["PLAYER_ENTERING_WORLD"] = events["UPDATE_ALL_BUTTONS"];
+events["UPDATE_VEHICLE_ACTIONBAR"] = events["UPDATE_ALL_BUTTONS"];
+events["UPDATE_SHAPESHIFT_FORM"] = events["UPDATE_ALL_BUTTONS"];
+events["SPELL_UPDATE_ICON"] = events["UPDATE_ALL_BUTTONS"];
+events["PET_STABLE_UPDATE"] = events["UPDATE_ALL_BUTTONS"];
+events["PET_STABLE_SHOW"] = events["UPDATE_ALL_BUTTONS"];
+events["PLAYER_SPECIALIZATION_CHANGED"] = events["UPDATE_ALL_BUTTONS"];
+events["UNIT_ENTERED_VEHICLE"] = function(unit)
+    if unit == "player" then events["ACTIONBAR_UPDATE_STATE"]() end
+end
+events["UNIT_EXITED_VEHICLE"] = events["UNIT_ENTERED_VEHICLE"];
+
+ActionBars:RegisterEvent("ADDON_LOADED");
+ActionBars:RegisterEvent("PLAYER_LOGIN");
+ActionBars:RegisterEvent("ACTIONBAR_UPDATE_COOLDOWN");
+ActionBars:RegisterEvent("ACTIONBAR_UPDATE_USABLE");
+ActionBars:RegisterEvent("ACTIONBAR_UPDATE_STATE");
+ActionBars:RegisterEvent("ACTIONBAR_SLOT_CHANGED");
+ActionBars:RegisterEvent("ACTIONBAR_SHOWGRID");
+ActionBars:RegisterEvent("ACTIONBAR_HIDEGRID");
+ActionBars:RegisterEvent("SPELL_UPDATE_ICON");
+ActionBars:RegisterEvent("SPELL_UPDATE_CHARGES");
+ActionBars:RegisterEvent("SPELL_ACTIVATION_OVERLAY_GLOW_SHOW");
+ActionBars:RegisterEvent("SPELL_ACTIVATION_OVERLAY_GLOW_HIDE");
+ActionBars:RegisterEvent("UPDATE_VEHICLE_ACTIONBAR");
+ActionBars:RegisterEvent("UPDATE_OVERRIDE_ACTIONBAR");
+ActionBars:RegisterEvent("PLAYER_MOUNT_DISPLAY_CHANGED");
+ActionBars:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED");
+ActionBars:RegisterEvent("UNIT_ENTERED_VEHICLE");
+ActionBars:RegisterEvent("UNIT_EXITED_VEHICLE");
+ActionBars:RegisterEvent("PET_STABLE_UPDATE");
+ActionBars:RegisterEvent("PET_STABLE_SHOW");
+ActionBars:RegisterEvent("UPDATE_BINDINGS");
+ActionBars:RegisterUnitEvent("UNIT_AURA", "player");
+ActionBars:SetScript("OnEvent", function(self, event, arg1)
+    events[event](arg1);
 end);