View Single Post
07-27-17, 04:20 PM   #7
MunkDev
A Scalebane Royal Guard
 
MunkDev's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2015
Posts: 431
I tinkered a bit more with this and you can actually do the same thing with just one button that uses a state driver, a wrapped PreClick and a custom identifier passed as the 'button' argument to the click script.

This solution only uses one secure button, does what you want, and the only drawback is that it won't trigger the first time if you're on a swap page when changing key bindings. This method also works when changing key bindings in combat.

Lua Code:
  1. local header = CreateFrame('Button', 'AllInOneActionButton', nil, 'SecureHandlerBaseTemplate, SecureHandlerStateTemplate, SecureActionButtonTemplate')
  2. header:RegisterForClicks('AnyUp', 'AnyDown') -- process both press and release of an arbitrary key
  3. header:SetAttribute('action', 1) -- this attribute will be used in conjunction with 'actionbar' to swap the bar back
  4.  
  5. do  -- generate a state driver condition with generic values to ensure any bar change pushes an update.
  6.     -- the actual bar ID check is done in the _onstate-actionpage function body instead.
  7.     -- this method seems to work regardless of API changes that cause
  8.     -- some of these macro conditions to shift around on certain specs.
  9.     local conditionBase, driver = '[%s] %d; ', ''
  10.     local conditions = {
  11.         ----------------------------------
  12.         'vehicleui', 'possessbar', 'overridebar', 'shapeshift',
  13.         'bar:2', 'bar:3', 'bar:4', 'bar:5', 'bar:6',
  14.         'bonusbar:1', 'bonusbar:2', 'bonusbar:3', 'bonusbar:4'
  15.         ----------------------------------
  16.     }
  17.     -- concatenate conditions into a long macro condition that can be
  18.     -- evaluated by the state driver and send updates to the header.
  19.     -- e.g. "[vehicleui] 1; [possessbar] 2; [overridebar] 3;" etc.
  20.     for i, macroCondition in ipairs(conditions) do
  21.         driver = driver .. conditionBase:format(macroCondition, i)
  22.     end
  23.     ----------------------------------
  24.     -- append the list for the default bar (1) when none of the conditions apply.
  25.     driver = driver .. (#conditions + 1)
  26.  
  27.     -- register the state driver to handle page updates
  28.     RegisterStateDriver(header, 'actionpage', driver)
  29. end
  30.  
  31. -- set up the _onstate-actionpage function body that sets the new actionpage ID.
  32. -- this is a rewrite of the response in ActionBarController_UpdateAll.
  33. -- state drivers use the _onstate-<name> attribute when looking for the function body to run.
  34. -- 'newstate' is the argument passed to all state drivers expressing the outcome
  35. -- of the registered condition, but in this case it's overwritten by the page calculation.
  36. -- note that all code in a restricted environment is written as strings and compiled in
  37. -- real time using loadstring(), to ensure you don't have access to unrestricted API.
  38. header:SetAttribute('_onstate-actionpage', [[
  39.     -- update actionpage
  40.     if HasVehicleActionBar() then
  41.         newstate = GetVehicleBarIndex()
  42.     elseif HasOverrideActionBar() then
  43.         newstate = GetOverrideBarIndex()
  44.     elseif HasTempShapeshiftActionBar() then
  45.         newstate = GetTempShapeshiftBarIndex()
  46.     elseif GetBonusBarOffset() > 0 then
  47.         newstate = GetBonusBarOffset() + 6
  48.     else
  49.         newstate = GetActionBarPage()
  50.     end
  51.     self:SetAttribute('actionpage', newstate)
  52.  
  53.     -- update bindings
  54.     self:ClearBindings()
  55.     for i=1, 12 do
  56.         local key = GetBindingKey('ACTIONBUTTON' .. i)
  57.         if key then
  58.             self:SetBindingClick(false, key, self, tostring(i))
  59.         end
  60.     end
  61. ]])
  62.  
  63. -- wrap the preclick script on the header
  64. -- use the inherent 'button' argument to tell the header
  65. -- which action button you want to simulate.
  66. -- since PreClick runs before OnClick, you're able to change
  67. -- what the button should do while you're pressing it.
  68. header:WrapScript(header, 'PreClick', [[
  69.     if down then
  70.         -- set up the downpress attributes to perform the action first
  71.         self:SetID(tonumber(button)) -- see ActionButton_CalculateAction
  72.         self:SetAttribute('type', 'action')
  73.     else
  74.         -- set the type to actionbar on release, which swaps the bar back
  75.         self:SetAttribute('type', 'actionbar')
  76.     end
  77.     self:CallMethod('OnButtonPressed', down, button)
  78. ]])
  79.  
  80. -- finally, execute the actionpage response on load,
  81. -- to set the correct action page and your override bindings.
  82. -- 'Execute' is included in the SecureHandlerBaseTemplate,
  83. -- and allows you to execute code in a restricted environment.
  84. header:Execute(header:GetAttribute('_onstate-actionpage'))
  85.  
  86. -- this function is called from the preclick script,
  87. -- which visually indicates a button press on your bar.
  88. -- this part isn't functionally necessary,
  89. -- it just makes it look like you're pressing regular buttons,
  90. -- when you're actually pressing your hidden button.
  91. function header:OnButtonPressed(down, id)
  92.     local button = GetActionButtonForID(id)
  93.     if button then
  94.         if down and button:GetButtonState() == 'NORMAL' then
  95.             button:SetButtonState('PUSHED')
  96.         elseif not down and button:GetButtonState() == 'PUSHED' then
  97.             button:SetButtonState('NORMAL')
  98.         end
  99.     end
  100. end
__________________

Last edited by MunkDev : 07-28-17 at 09:43 PM.
  Reply With Quote