You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1550 lines
52 KiB

--========================================================--
-- Scorpio Secure Action Button --
-- --
-- Author : kurapica125@outlook.com --
-- Create Date : 2020/11/16 --
--========================================================--
--========================================================--
Scorpio "Scorpio.Secure.SecureActionButton" "1.0.0"
--========================================================--
export { GetProxyUI = UI.GetProxyUI }
_ManagerFrame = SecureFrame("Scorpio_SecureActionButton_Manager", UIParent, "SecureHandlerStateTemplate")
_ManagerFrame:Hide()
_IFActionTypeHandler = {}
_ActionTypeMap = {}
_ActionTargetMap = {}
_ActionTargetDetail = {}
_ReceiveMap = {}
_ActionButtonGroupList = {}
_AutoAttackButtons = {}
_AutoRepeatButtons = {}
_RangeCheckButtons = {}
_Spell4Buttons = {}
local _GridCounter = 0
local _PetGridCounter = 0
local _OnTooltipButton
local _KeyBindingMap = {}
local _Locale = _Locale
local _KeyBindingMode = false
IsSpellOverlayed = _G.IsSpellOverlayed or Toolset.fakefunc
------------------------------------------------------
-- Module Event Handler --
------------------------------------------------------
function OnLoad()
_SVData.Char:SetDefault {
SecureActionButtonNoDragGroup = {},
SecureActionButtonMouseDownGroup= {},
}
_NoDragGroup = _SVData.Char.SecureActionButtonNoDragGroup
_MouseDownGroup = _SVData.Char.SecureActionButtonMouseDownGroup
for group, val in pairs(_NoDragGroup) do
if val then DisableDrag(group) end
end
end
__Service__(true)
function RangeCheckerService()
while true do
for i = 1, 99999 do
local button = _RangeCheckButtons[i]
if not button then
if i == 1 then NextEvent("SCORPIO_SAB_RANGE_CHECK") end
break
end
_IFActionTypeHandler[button.ActionType]:RefreshRange(button)
if i % 20 == 0 then Continue() end
end
Wait(0.2, "PLAYER_TARGET_CHANGED")
end
end
__SystemEvent__()
function ACTIONBAR_SHOWGRID()
_GridCounter = _GridCounter + 1
if _GridCounter == 1 then
for kind, handler in pairs(_IFActionTypeHandler) do
if handler.IsPlayerAction and handler.ReceiveStyle ~= "Block" then
handler:RefershGrid()
end
end
end
end
__SystemEvent__()
function ACTIONBAR_HIDEGRID()
if _GridCounter > 0 then
_GridCounter = _GridCounter - 1
if _GridCounter == 0 then
for kind, handler in pairs(_IFActionTypeHandler) do
if handler.IsPlayerAction and handler.ReceiveStyle ~= "Block" then
handler:RefershGrid()
end
end
end
end
end
__SystemEvent__()
function PET_BAR_SHOWGRID()
_PetGridCounter = _PetGridCounter + 1
if _PetGridCounter == 1 then
for kind, handler in pairs(_IFActionTypeHandler) do
if handler.IsPetAction and handler.ReceiveStyle ~= "Block" then
handler:RefershGrid()
end
end
end
end
__SystemEvent__()
function PET_BAR_HIDEGRID()
if _PetGridCounter > 0 then
_PetGridCounter = _PetGridCounter - 1
if _PetGridCounter == 0 then
for kind, handler in pairs(_IFActionTypeHandler) do
if handler.IsPetAction and handler.ReceiveStyle ~= "Block" then
handler:RefershGrid()
end
end
end
end
end
__SystemEvent__()
function PLAYER_ENTER_COMBAT()
for button in pairs(_AutoAttackButtons) do
button.IsAutoAttacking = true
end
end
__SystemEvent__()
function PLAYER_LEAVE_COMBAT()
for button in pairs(_AutoAttackButtons) do
button.IsAutoAttacking = false
end
end
__SystemEvent__()
function SPELL_ACTIVATION_OVERLAY_GLOW_SHOW(spellId)
local buttons = _Spell4Buttons[spellId]
if not buttons then return end
if getmetatable(buttons) then
buttons.OverlayGlow = true
else
for button in pairs(buttons) do
button.OverlayGlow = true
end
end
end
__SystemEvent__()
function SPELL_ACTIVATION_OVERLAY_GLOW_HIDE(spellId)
local buttons = _Spell4Buttons[spellId]
if not buttons then return end
if getmetatable(buttons) then
buttons.OverlayGlow = false
else
for button in pairs(buttons) do
button.OverlayGlow = false
end
end
end
__SystemEvent__()
function SPELL_UPDATE_CHARGES()
for kind, handler in pairs(_IFActionTypeHandler) do
handler:RefreshCount()
end
end
__SystemEvent__()
function START_AUTOREPEAT_SPELL()
for button in pairs(_AutoRepeatButtons) do
if not _AutoAttackButtons[button] then
button.IsAutoAttacking = true
end
end
end
__SystemEvent__()
function STOP_AUTOREPEAT_SPELL()
for button in pairs(_AutoRepeatButtons) do
if button.IsAutoAttacking and not _AutoAttackButtons[button] then
button.IsAutoAttacking = false
end
end
end
__SystemEvent__ "ARCHAEOLOGY_CLOSED" "TRADE_SKILL_SHOW" "TRADE_SKILL_CLOSE"
function TRADE_SKILL_SHOW()
for kind, handler in pairs(_IFActionTypeHandler) do
handler:RefreshButtonState()
end
end
__SystemEvent__"UNIT_ENTERED_VEHICLE" "UNIT_EXITED_VEHICLE"
function UNIT_ENTERED_VEHICLE(unit)
if unit == "player" then
for kind, handler in pairs(_IFActionTypeHandler) do
handler:RefreshButtonState()
end
end
end
__SystemEvent__"UNIT_INVENTORY_CHANGED" "LEARNED_SPELL_IN_TAB" "ACTIONBAR_UPDATE_COOLDOWN"
function UNIT_INVENTORY_CHANGED(unit)
return (not unit or unit == "player") and _OnTooltipButton and _OnTooltipButton:UpdateTooltip()
end
__SystemEvent__() __Async__()
function PET_BATTLE_OPENING_START()
for i = 1, 6 do
local key = tostring(i)
local button = _KeyBindingMap[key]
if button then ClearOverrideBindings(GetRawUI(button)) end
end
NextEvent("PET_BATTLE_CLOSE") NoCombat()
for i = 1, 6 do
local key = tostring(i)
local button = _KeyBindingMap[key]
if button then SetOverrideBindingClick(GetRawUI(button), false, key, button:GetName(), "LeftButton") end
end
end
------------------------------------------------------
-- Action Type Handler --
------------------------------------------------------
__Sealed__()
enum "ActionTypeHandleStyle" { "Keep", "Clear", "Block" }
-- The handler for action types
__Sealed__() __AnonymousClass__()
interface "ActionTypeHandler" (function(_ENV)
extend "IList"
_RegisterSnippetTemplate = "%s[%q] = %q"
_ActionButtonMap = Toolset.newtable(true, true)
local function refreshButton(self, button)
_AutoAttackButtons[button] = self.IsAttackAction(button) or nil
_AutoRepeatButtons[button] = self.IsAutoRepeatAction(button) or nil
button.HasAction = self.HasAction(button)
button.IsAutoAttack = _AutoAttackButtons[button] or _AutoRepeatButtons[button]
local spell = self.GetSpellId(button)
local ospell = _Spell4Buttons[button]
if ospell ~= spell then
if ospell then
local buttons = _Spell4Buttons[ospell]
if getmetatable(buttons) == nil then
buttons[button] = nil
elseif buttons == button then
_Spell4Buttons[ospell] = nil
end
end
if spell then
local buttons = _Spell4Buttons[spell]
if buttons == nil then
_Spell4Buttons[spell] = button
elseif getmetatable(buttons) == nil then
buttons[button] = true
else
buttons = { [buttons] = true }
buttons[button] = true
_Spell4Buttons[spell] = buttons
end
end
_Spell4Buttons[button] = spell
end
if self.IsRangeSpell(button) then
if not _RangeCheckButtons[button] then
local index = #_RangeCheckButtons + 1
_RangeCheckButtons[button] = true
_RangeCheckButtons[index] = button
if index == 1 then FireSystemEvent("SCORPIO_SAB_RANGE_CHECK") end
end
else
if _RangeCheckButtons[button] then
for i = 1, #_RangeCheckButtons do
if _RangeCheckButtons[i] == button then
tremove(_RangeCheckButtons, i)
break
end
end
_RangeCheckButtons[button] = nil
end
end
if self.ReceiveStyle ~= "Block" then
self:RefershGrid(button)
end
self:RefreshButtonState(button)
self:RefreshUsable(button)
self:RefreshCooldown(button)
self:RefreshFlyout(button)
self:RefreshAutoCastable(button)
self:RefreshAutoCasting(button)
self:RefreshEquipItem(button)
self:RefreshText(button)
self:RefreshIcon(button)
self:RefreshCount(button)
self:RefreshOverlayGlow(button)
self:RefreshShowSearchOverlay(button)
self:RefreshIconLocked(button)
self.Refresh(button)
button:Refresh()
if _OnTooltipButton == button then
return button:UpdateTooltip()
end
end
------------------------------------------------------
-- Event
------------------------------------------------------
-- Fired when the handler is enabled or disabled
event "OnEnableChanged"
------------------------------------------------------
-- Refresh Method
------------------------------------------------------
function RefershGrid(self, button)
local force = (self.IsPlayerAction and _GridCounter or _PetGridCounter) > 0
local HasAction = self.HasAction
if button then
button.GridVisible = force or HasAction(button)
else
for _, button in self:GetIterator() do
button.GridVisible = force or HasAction(button)
end
end
end
function RefreshButtonState(self, button)
local IsActivedAction = self.IsActivedAction
local IsAutoRepeatAction= self.IsAutoRepeatAction
if button then
button:SetChecked(IsActivedAction(button) or IsAutoRepeatAction(button))
else
for _, button in self:GetIterator() do
button:SetChecked(IsActivedAction(button) or IsAutoRepeatAction(button))
end
end
end
function RefreshUsable(self, button)
local IsUsableAction = self.IsUsableAction
if button then
button.IsUsable = IsUsableAction(button)
else
for _, button in self:GetIterator() do
button.IsUsable= IsUsableAction(button)
end
end
end
function RefreshCount(self, button)
local IsConsumableAction= self.IsConsumableAction
local GetActionCount = self.GetActionCount
local GetActionCharges = self.GetActionCharges
if button then
if IsConsumableAction(button) then
button.Count = GetActionCount(button)
else
local cha, max = GetActionCharges(button)
if max and max > 1 then
button.Count= cha
else
button.Count= nil
end
end
else
for _, button in self:GetIterator() do
if IsConsumableAction(button) then
button.Count = GetActionCount(button)
else
local cha, max = GetActionCharges(button)
if max and max > 1 then
button.Count= cha
else
button.Count= nil
end
end
end
end
end
local shareCooldown = { start = 0, duration = 0 }
function RefreshCooldown(self, button)
local GetActionCooldown = self.GetActionCooldown
if button then
local s, d = GetActionCooldown(button)
shareCooldown.start = s or 0
shareCooldown.duration = d or 0
button.Cooldown = shareCooldown
else
for _, button in self:GetIterator() do
local s, d = GetActionCooldown(button)
shareCooldown.start = s or 0
shareCooldown.duration = d or 0
button.Cooldown = shareCooldown
end
end
end
function RefreshFlash(self, button)
local IsAttackAction = self.IsAttackAction
local IsActivedAction = self.IsActivedAction
local IsAutoRepeatAction= self.IsAutoRepeatAction
if button then
button.IsAutoAttacking = (IsAttackAction(button) and IsActivedAction(button)) or IsAutoRepeatAction(button)
else
for _, button in self:GetIterator() do
button.IsAutoAttacking = (IsAttackAction(button) and IsActivedAction(button)) or IsAutoRepeatAction(button)
end
end
end
function RefreshOverlayGlow(self, button)
local GetSpellId = self.GetSpellId
if button then
local spellId = GetSpellId(button)
self.OverlayGlow = spellId and IsSpellOverlayed(spellId)
else
for _, button in self:GetIterator() do
local spellId = GetSpellId(button)
self.OverlayGlow= spellId and IsSpellOverlayed(spellId)
end
end
end
function RefreshRange(self, button)
local IsInRange = self.IsInRange
if button then
button.InRange = IsInRange(button)
else
for _, button in self:GetIterator() do
button.InRange = IsInRange(button)
end
end
end
function RefreshFlyout(self, button)
local IsFlyout = self.IsFlyout
if button then
if button.IsCustomFlyout then return end
button.IsFlyout = IsFlyout(button)
else
for _, button in self:GetIterator() do
if not button.IsCustomFlyout then
button.IsFlyout = IsFlyout(button)
end
end
end
end
function RefreshAutoCastable(self, button)
local IsAutoCastAction = self.IsAutoCastAction
if button then
button.IsAutoCastable= IsAutoCastAction(button)
else
for _, button in self:GetIterator() do
button.IsAutoCastable = IsAutoCastAction(button)
end
end
end
function RefreshAutoCasting(self, button)
local IsAutoCasting = self.IsAutoCasting
if button then
button.IsAutoCasting= IsAutoCasting(button)
else
for _, button in self:GetIterator() do
button.IsAutoCasting = IsAutoCasting(button)
end
end
end
function RefreshIcon(self, button)
local GetActionTexture = self.GetActionTexture
if button then
button.Icon = GetActionTexture(button)
else
for _, button in self:GetIterator() do
button.Icon = GetActionTexture(button)
end
end
end
function RefreshEquipItem(self, button)
local IsEquippedItem = self.IsEquippedItem
if button then
button.IsEquippedItem = IsEquippedItem(button)
else
for _, button in self:GetIterator() do
button.IsEquippedItem = IsEquippedItem(button)
end
end
end
function RefreshText(self, button)
local IsConsumableAction= self.IsConsumableAction
local GetActionText = self.GetActionText
if button then
button.Text = IsConsumableAction(button) and "" or GetActionText(button)
else
for _, button in self:GetIterator() do
button.Text = IsConsumableAction(button) and "" or GetActionText(button)
end
end
end
function RefreshShowSearchOverlay(self, button)
local IsSearchOverlayShow = self.IsSearchOverlayShow
if button then
button.ShowSearchOverlay = IsSearchOverlayShow(button)
else
for _, button in self:GetIterator() do
button.ShowSearchOverlay = IsSearchOverlayShow(button)
end
end
end
function RefreshIconLocked(self, button)
local IsIconLocked = self.IsIconLocked
if button then
button.IconLocked = IsIconLocked(button)
else
for _, button in self:GetIterator() do
button.IconLocked = IsIconLocked(button)
end
end
end
__Delegate__(Continue)
function RefreshActionButtons(self, button)
local refresh = refreshButton
if button then
-- The button may change its action type when waiting
return button.ActionType == self.Type and refresh(self, button)
else
for _, button in self:GetIterator() do
refresh(self, button)
Continue()
end
end
end
------------------------------------------------------
-- Method
------------------------------------------------------
GetIterator = ipairs
function Insert(self, button)
local oldHandler = _ActionButtonMap[button]
if oldHandler then
if oldHandler == self then return end
oldHandler:Remove(button)
end
_ActionButtonMap[button]= self
tinsert(self, button)
self.Enabled = true
end
function Remove(self, button)
if _ActionButtonMap[button] ~= self then return end
_ActionButtonMap[button]= nil
for i, v in ipairs(self) do if v == button then tremove(self, i) break end end
self.Enabled = self[1] and true or false
end
-- Run the snippet in the global environment
__NoCombat__()
function RunSnippet(self, code)
return self.Manager:Execute(code)
end
------------------------------------------------------
-- Overridable Method For Action Buttons
------------------------------------------------------
-- Get the actions's kind, target, detail
function GetActionDetail(self)
local name = self:GetAttribute("actiontype")
return self:GetAttribute(_ActionTargetMap[name]), _ActionTargetDetail[name] and self:GetAttribute(_ActionTargetDetail[name])
end
-- Map the action
function Map(self, ...) return ... end
-- The refresh logic
function Refresh(self) end
-- Custom pick up action
function PickupAction(self, target, detail) end
-- Custom receive action
function ReceiveAction(self, target, detail) end
-- Whether the action button has an action
function HasAction(self) return true end
-- Get the action's text
function GetActionText(self) return "" end
-- Get the action's texture
function GetActionTexture(self) end
-- Get the action's charges
function GetActionCharges(self) end
-- Get the action's count
function GetActionCount(self) return 0 end
-- Get the action's cooldown
function GetActionCooldown(self) return 0, 0, 0 end
-- Whether the action is attackable
function IsAttackAction(self) return false end
-- Whether the action is an item and can be equipped
function IsEquippedItem(self) return false end
-- Whether the action is actived
function IsActivedAction(self) return false end
-- Whether the action is auto-repeat
function IsAutoRepeatAction(self) return false end
-- Whether the action is usable
function IsUsableAction(self) return true end
-- Whether the action is consumable
function IsConsumableAction(self) return false end
-- Whether the action is in range of the target
function IsInRange(self) return end
-- Whether the action is auto-castable
function IsAutoCastAction(self) return false end
-- Whether the action is auto-casting now
function IsAutoCasting(self) return false end
-- Whether need show the search overlay
function IsSearchOverlayShow(self) return false end
-- Whether the icon is locked
function IsIconLocked(self) return false end
-- Show the tooltip for the action
function SetTooltip(self, tip) end
-- Get the spell id of the action
function GetSpellId(self) end
-- Whether the action is a flyout spell
function IsFlyout(self) return false end
-- Whether the action has range spell
function IsRangeSpell(self) return false end
------------------------------------------------------
-- Property
------------------------------------------------------
-- The manager of the action system
property "Manager" { default = _ManagerFrame, set = false }
-- Whether the handler is enabled(has buttons)
property "Enabled" { type = Boolean, event = "OnEnableChanged" }
-- The action's name
property "Name" { type = String }
-- The action type's type
property "Type" { type = String }
-- The target attribute name
property "Target" { type = String }
-- The detail attribute name
property "Detail" { type = String }
-- Whether the action is player action
property "IsPlayerAction" { type = Boolean, default = true }
-- Whether the action is pet action
property "IsPetAction" { type = Boolean, default = false }
-- The drag style of the action type
property "DragStyle" { type = ActionTypeHandleStyle, default = ActionTypeHandleStyle.Clear }
-- The receive style of the action type
property "ReceiveStyle" { type = ActionTypeHandleStyle, default = ActionTypeHandleStyle.Clear }
-- The receive map
property "ReceiveMap" { type = String }
-- The pickup map
property "PickupMap" { type = String }
-- The snippet to setup environment for the action type
property "InitSnippet" { type = String }
-- The snippet used when pick up action
property "PickupSnippet" { type = String }
-- The snippet used to update for new action settings
property "UpdateSnippet" { type = String }
-- The snippet used to receive action
property "ReceiveSnippet" { type = String }
-- The snippet used to clear action
property "ClearSnippet" { type = String }
-- The snippet used for pre click
property "PreClickSnippet" { type = String }
-- The snippet used for post click
property "PostClickSnippet" { type = String }
------------------------------------------------------
-- Initialize
------------------------------------------------------
function __init(self)
-- No repeat definition for action types
if _IFActionTypeHandler[self.Name] then return end
-- Register the action type handler
_IFActionTypeHandler[self.Name] = self
-- Default map
if self.Type == nil then self.Type = self.Name end
if self.Target == nil then self.Target = self.Type end
if self.PickupMap == nil then self.PickupMap = self.Type end
if self.ReceiveMap == nil and self.ReceiveStyle == "Clear" then self.ReceiveMap = self.Type end
-- Register action type map
_ActionTypeMap[self.Name] = self.Type
_ActionTargetMap[self.Name] = self.Target
_ActionTargetDetail[self.Name] = self.Detail
self:RunSnippet( _RegisterSnippetTemplate:format("_ActionTypeMap", self.Name, self.Type) )
self:RunSnippet( _RegisterSnippetTemplate:format("_ActionTargetMap", self.Name, self.Target) )
if self.Detail then self:RunSnippet( _RegisterSnippetTemplate:format("_ActionTargetDetail", self.Name, self.Detail) ) end
-- Init the environment
if self.InitSnippet then self:RunSnippet( self.InitSnippet ) end
-- Register PickupSnippet
if self.PickupSnippet then self:RunSnippet( _RegisterSnippetTemplate:format("_PickupSnippet", self.Name, self.PickupSnippet) ) end
-- Register UpdateSnippet
if self.UpdateSnippet then self:RunSnippet( _RegisterSnippetTemplate:format("_UpdateSnippet", self.Name, self.UpdateSnippet) ) end
-- Register ReceiveSnippet
if self.ReceiveSnippet then self:RunSnippet( _RegisterSnippetTemplate:format("_ReceiveSnippet", self.Name, self.ReceiveSnippet) ) end
-- Register ClearSnippet
if self.ClearSnippet then self:RunSnippet( _RegisterSnippetTemplate:format("_ClearSnippet", self.Name, self.ClearSnippet) ) end
-- Register DragStyle
self:RunSnippet( _RegisterSnippetTemplate:format("_DragStyle", self.Name, self.DragStyle) )
-- Register ReceiveStyle
self:RunSnippet( _RegisterSnippetTemplate:format("_ReceiveStyle", self.Name, self.ReceiveStyle) )
-- Register ReceiveMap
if self.ReceiveMap then
self:RunSnippet( _RegisterSnippetTemplate:format("_ReceiveMap", self.ReceiveMap, self.Name) )
_ReceiveMap[self.ReceiveMap] = self
end
-- Register PickupMap
if self.PickupMap then self:RunSnippet( _RegisterSnippetTemplate:format("_PickupMap", self.Name, self.PickupMap) ) end
-- Register PreClickMap
if self.PreClickSnippet then self:RunSnippet( _RegisterSnippetTemplate:format("_PreClickSnippet", self.Name, self.PreClickSnippet) ) end
-- Register PostClickMap
if self.PostClickSnippet then self:RunSnippet( _RegisterSnippetTemplate:format("_PostClickSnippet", self.Name, self.PostClickSnippet) ) end
-- Clear
self.InitSnippet = nil
self.PickupSnippet = nil
self.UpdateSnippet = nil
self.ReceiveSnippet = nil
self.ClearSnippet = nil
self.PreClickSnippet = nil
self.PostClickSnippet = nil
end
end)
------------------------------------------------------
-- Action Button Manager --
------------------------------------------------------
__SecureMethod__()
function _ManagerFrame:OnPickUp(kind, target, detail)
return not InCombatLockdown() and PickupAny("clear", kind, target, detail)
end
__SecureMethod__()
function _ManagerFrame:OnReceive(kind, target, detail)
return not InCombatLockdown() and _IFActionTypeHandler[kind] and _IFActionTypeHandler[kind]:ReceiveAction(target, detail)
end
__SecureMethod__()
function _ManagerFrame:UpdateActionButton(name)
self = GetProxyUI(_G[name])
local name = self:GetAttribute("actiontype")
local handler = _IFActionTypeHandler[name]
local target, detail = handler.GetActionDetail(self)
if self.__IFActionHandler_Kind ~= name
or self.__IFActionHandler_Target ~= target
or self.__IFActionHandler_Detail ~= detail then
if self.__IFActionHandler_Kind and self.__IFActionHandler_Kind ~= name then
_IFActionTypeHandler[self.__IFActionHandler_Kind]:Remove(self)
end
self.__IFActionHandler_Kind = name
self.__IFActionHandler_Target = target
self.__IFActionHandler_Detail = detail
handler:Insert(self)
return handler:RefreshActionButtons(self)
end
end
------------------------------------------------------
-- Secure Snippet --
------------------------------------------------------
do
-- Init manger frame's enviroment
_ManagerFrame:Execute[[
-- to fix blz error, use Manager not control
Manager = self
_NoDraggable = newtable()
_ActionTypeMap = newtable()
_ActionTargetMap = newtable()
_ActionTargetDetail = newtable()
_ReceiveMap = newtable()
_PickupMap = newtable()
_ClearSnippet = newtable()
_UpdateSnippet = newtable()
_PickupSnippet = newtable()
_ReceiveSnippet = newtable()
_PreClickSnippet = newtable()
_PostClickSnippet = newtable()
_DragStyle = newtable()
_ReceiveStyle = newtable()
UpdateAction = [=[
local name = self:GetAttribute("actiontype")
-- Custom update
if _UpdateSnippet[name] then
Manager:RunFor(
self, _UpdateSnippet[name],
self:GetAttribute(_ActionTargetMap[name]),
_ActionTargetDetail[name] and self:GetAttribute(_ActionTargetDetail[name])
)
end
return Manager:CallMethod("UpdateActionButton", self:GetName())
]=]
ClearAction = [=[
local name = self:GetAttribute("actiontype")
if name and name ~= "empty" then
self:SetAttribute("actiontype", "empty")
self:SetAttribute("type", nil)
self:SetAttribute(_ActionTargetMap[name], nil)
if _ActionTargetDetail[name] then
self:SetAttribute(_ActionTargetDetail[name], nil)
end
-- Custom clear
if _ClearSnippet[name] then
Manager:RunFor(self, _ClearSnippet[name])
end
end
]=]
GetAction = [=[
return self:GetAttribute("actiontype"), self:GetAttribute(_ActionTargetMap[name]), _ActionTargetDetail[name] and self:GetAttribute(_ActionTargetDetail[name])
]=]
SetAction = [=[
local name, target, detail = ...
Manager:RunFor(self, ClearAction)
if name and _ActionTypeMap[name] and target then
self:SetAttribute("actiontype", name)
self:SetAttribute("type", _ActionTypeMap[name])
self:SetAttribute(_ActionTargetMap[name], target)
if detail ~= nil and _ActionTargetDetail[name] then
self:SetAttribute(_ActionTargetDetail[name], detail)
end
end
return Manager:RunFor(self, UpdateAction)
]=]
DragStart = [=[
local name = self:GetAttribute("actiontype")
if _DragStyle[name] == "Block" then return false end
local target = self:GetAttribute(_ActionTargetMap[name])
local detail = _ActionTargetDetail[name] and self:GetAttribute(_ActionTargetDetail[name])
-- Clear and refresh
if _DragStyle[name] == "Clear" then
Manager:RunFor(self, ClearAction)
Manager:RunFor(self, UpdateAction)
end
-- Pickup the target
if _PickupSnippet[name] == "Custom" then
Manager:CallMethod("OnPickUp", name, target, detail)
return false
elseif _PickupSnippet[name] then
return Manager:RunFor(self, _PickupSnippet[name], target, detail)
else
return "clear", _PickupMap[name], target, detail
end
]=]
ReceiveDrag = [=[
local kind, value, extra, extra2 = ...
if not kind or not value then return false end
local oldName = self:GetAttribute("actiontype")
if _ReceiveStyle[oldName] == "Block" then return false end
local oldTarget = oldName and self:GetAttribute(_ActionTargetMap[oldName])
local oldDetail = oldName and _ActionTargetDetail[oldName] and self:GetAttribute(_ActionTargetDetail[oldName])
if _ReceiveStyle[oldName] == "Clear" then
Manager:RunFor(self, ClearAction)
local name, target, detail = _ReceiveMap[kind]
if name then
if _ReceiveSnippet[name] and _ReceiveSnippet[name] ~= "Custom" then
target, detail = Manager:RunFor(self, _ReceiveSnippet[name], value, extra, extra2)
else
target, detail = value, extra
end
if target then
self:SetAttribute("actiontype", name)
self:SetAttribute("type", _ActionTypeMap[name])
self:SetAttribute(_ActionTargetMap[name], target)
if detail ~= nil and _ActionTargetDetail[name] then
self:SetAttribute(_ActionTargetDetail[name], detail)
end
end
end
Manager:RunFor(self, UpdateAction)
end
if _ReceiveStyle[oldName] == "Keep" and _ReceiveSnippet[oldName] == "Custom" then
Manager:CallMethod("OnReceive", oldName, oldTarget, oldDetail)
return Manager:RunFor(self, UpdateAction) or false
end
-- Pickup the target
if _PickupSnippet[oldName] == "Custom" then
Manager:CallMethod("OnPickUp", oldName, oldTarget, oldDetail)
return false
elseif _PickupSnippet[oldName] then
return Manager:RunFor(self, _PickupSnippet[oldName], oldTarget, oldDetail)
else
return "clear", _PickupMap[oldName], oldTarget, oldDetail
end
]=]
]]
_OnDragStartSnippet = [[
if (IsModifierKeyDown() or _NoDraggable[self:GetAttribute("IFActionHandlerGroup")]) and not IsModifiedClick("PICKUPACTION") then return false end
return Manager:RunFor(self, DragStart)
]]
_OnReceiveDragSnippet = [[
return Manager:RunFor(self, ReceiveDrag, kind, value, ...)
]]
_PostReceiveSnippet = [[
return Manager:RunFor(Manager:GetFrameRef("UpdatingButton"), ReceiveDrag, %s, %s, %s, %s)
]]
_SetActionSnippet = [[
return Manager:RunFor(Manager:GetFrameRef("UpdatingButton"), SetAction, %s, %s, %s)
]]
_WrapClickPrev = [[
local name = self:GetAttribute("actiontype")
if _PreClickSnippet[name] then
return Manager:RunFor(self, _PreClickSnippet[name], button, down)
end
]]
_WrapClickPost = [[
local name = self:GetAttribute("actiontype")
if _PostClickSnippet[name] then
return Manager:RunFor(self, _PostClickSnippet[name], message, button, down)
end
]]
_WrapDragPrev = [[ return "message", "update" ]]
_WrapDragPost = [[ Manager:RunFor(self, UpdateAction) ]]
_OnShowSnippet = [[ if self:GetAttribute("autoKeyBinding") and self:GetAttribute("hotKey") then self:SetBindingClick(true, self:GetAttribute("hotKey"), self:GetName(), "LeftButton") end ]]
_OnHideSnippet = [[ if self:GetAttribute("autoKeyBinding") then self:ClearBindings() end ]]
end
------------------------------------------------------
-- Action Script Hanlder --
------------------------------------------------------
do
_GlobalGroup = "GLOBAL"
function GetGroup(group)
group = type(group) == "string" and strtrim(group)
return group and group ~= "" and group:upper() or _GlobalGroup
end
function GetFormatString(param)
return type(param) == "string" and ("%q"):format(param) or tostring(param)
end
function PickupAny(kind, target, detail, ...)
if (kind == "clear") then
ClearCursor()
kind, target, detail= target, detail, ...
end
local handler = _IFActionTypeHandler[kind]
return handler and handler:PickupAction(target, detail)
end
function PreClick(self)
local oldKind = self:GetAttribute("actiontype")
if InCombatLockdown() or (oldKind and _IFActionTypeHandler[oldKind].ReceiveStyle ~= "Clear") then return end
local kind, value = GetCursorInfo()
if not (kind and value) then return end
self.__IFActionHandler_PreType = self:GetAttribute("type")
self.__IFActionHandler_PreMsg = true
-- Make sure no action used
self:SetAttribute("type", nil)
end
function PostClick(self)
_IFActionTypeHandler[self.ActionType]:RefreshButtonState(self)
-- Restore the action
if self.__IFActionHandler_PreMsg then
if not InCombatLockdown() then
if self.__IFActionHandler_PreType then
self:SetAttribute("type", self.__IFActionHandler_PreType)
end
local kind, value, subtype, detail = GetCursorInfo()
if kind and value and _ReceiveMap[kind] then
local oldName = self.__IFActionHandler_Kind
local oldTarget = self.__IFActionHandler_Target
local oldDetail = self.__IFActionHandler_Detail
_ManagerFrame:SetFrameRef("UpdatingButton", self)
_ManagerFrame:Execute(_PostReceiveSnippet:format(GetFormatString(kind), GetFormatString(value), GetFormatString(subtype), GetFormatString(detail)))
PickupAny("clear", oldName, oldTarget, oldDetail)
end
elseif self.__IFActionHandler_PreType then
-- Keep safe
NoCombat(self.SetAttribute, self, "type", self.__IFActionHandler_PreType)
end
self.__IFActionHandler_PreType = false
self.__IFActionHandler_PreMsg = false
end
end
function OnEnter(self)
if _KeyBindingMode then return true end
_OnTooltipButton = self
return self:UpdateTooltip()
end
function OnLeave(self)
_OnTooltipButton = nil
GameTooltip:Hide()
end
function OnShow(self)
_IFActionTypeHandler[self.ActionType]:RefershGrid(self)
end
__NoCombat__()
function DisableDrag(group, value)
group = GetGroup(group)
_NoDragGroup[group] = value or nil
_ManagerFrame:Execute( ("_NoDraggable[%q] = %s"):format(group, tostring(value or nil)) )
end
function IsDragEnabled(group)
return not _NoDragGroup[GetGroup(group)]
end
__NoCombat__()
function EnableButtonDown(group, value)
group = GetGroup(group)
if not _MouseDownGroup[group] then
_MouseDownGroup[group] = value or nil
if _ActionButtonGroupList[group] then
local reg = value and "AnyDown" or "AnyUp"
for btn in pairs(_ActionButtonGroupList[group]) do
btn:RegisterForClicks(reg)
end
end
end
end
function IsButtonDownEnabled(group)
return _MouseDownGroup[GetGroup(group)]
end
function SetActionButtonGroup(self, group, old)
group = GetGroup(group)
old = old and GetGroup(old)
if old and _ActionButtonGroupList[old] then
_ActionButtonGroupList[old][self] = nil
end
_ActionButtonGroupList[group] = _ActionButtonGroupList[group] or {}
_ActionButtonGroupList[group][self] = true
self:SetAttribute("IFActionHandlerGroup", group)
self:RegisterForClicks(_MouseDownGroup[group] and "AnyDown" or "AnyUp")
end
function SetupActionButton(self)
SetActionButtonGroup(self, self.ActionButtonGroup)
self:RegisterForDrag("LeftButton", "RightButton")
_ManagerFrame:WrapScript(self, "OnShow", _OnShowSnippet)
_ManagerFrame:WrapScript(self, "OnHide", _OnHideSnippet)
_ManagerFrame:WrapScript(self, "OnDragStart", _OnDragStartSnippet)
_ManagerFrame:WrapScript(self, "OnReceiveDrag", _OnReceiveDragSnippet)
_ManagerFrame:WrapScript(self, "OnClick", _WrapClickPrev, _WrapClickPost)
_ManagerFrame:WrapScript(self, "OnDragStart", _WrapDragPrev, _WrapDragPost)
_ManagerFrame:WrapScript(self, "OnReceiveDrag", _WrapDragPrev, _WrapDragPost)
-- Register useful attribute snippets to be used in other addons
self:SetFrameRef("_Manager", _ManagerFrame)
self:SetAttribute("SetAction", [[ return self:GetFrameRef("_Manager"):RunFor(self, "Manager:RunFor(self, SetAction, ...)", ...) ]])
self:SetAttribute("ClearAction",[[ return self:GetFrameRef("_Manager"):RunFor(self, "Manager:RunFor(self, ClearAction)") ]])
self:SetAttribute("GetAction", [[ return self:GetFrameRef("_Manager"):RunFor(self, "return Manager:RunFor(self, GetAction)") ]])
if not self:GetAttribute("actiontype") then
self:SetAttribute("actiontype", "empty")
end
self.PreClick = self.PreClick + PreClick
self.PostClick = self.PostClick+ PostClick
self.OnShow = self.OnShow + OnShow
self.OnEnter = self.OnEnter + OnEnter
self.OnLeave = self.OnLeave + OnLeave
end
function SaveAction(self, kind, target, detail)
_ManagerFrame:SetFrameRef("UpdatingButton", self)
_ManagerFrame:Execute(_SetActionSnippet:format(GetFormatString(kind), GetFormatString(target), GetFormatString(detail)))
end
end
class "SecureActionButton" (function(_ENV)
inherit "SecureCheckButton"
import "System.Reactive"
export {
GetRawUI = UI.GetRawUI,
IsObjectType = Class.IsObjectType,
}
local _KeyBindingMask = Mask("Scorpio_SecureActionButton_KeyBindingMask")
_KeyBindingMask:Hide()
_KeyBindingMask.EnableKeyBinding = true
function _KeyBindingMask:OnKeySet(key, old)
local parent = self:GetParent()
if IsObjectType(parent, SecureActionButton) then
parent.HotKey = key
end
end
function _KeyBindingMask:OnKeyClear()
local parent = self:GetParent()
if IsObjectType(parent, SecureActionButton) then
parent.HotKey = nil
end
end
local function handleKeyBinding(self)
if self.HotKey and (not self.AutoKeyBinding or self:IsVisible()) then
SetOverrideBindingClick(GetRawUI(self), self.AutoKeyBinding, self.HotKey, self:GetName(), "LeftButton")
else
ClearOverrideBindings(GetRawUI(self))
end
end
------------------------------------------------------
-- Static Property --
------------------------------------------------------
--- Whether the action button group is draggable
__Static__() __Indexer__(String)
property "Draggable" {
type = Boolean,
get = function(self, group) return IsDragEnabled(group) end,
set = function(self, group, value) DisableDrag(group, not value) end,
}
--- Whether the action button group use mouse down to trigger
__Static__() __Indexer__(String)
property "UseMouseDown" {
type = Boolean,
get = function(self, group) return IsButtonDownEnabled(group) end,
set = function(self, group, value) EnableButtonDown(group, value) end,
}
------------------------------------------------------
-- Property --
------------------------------------------------------
--- The action button group
property "ActionButtonGroup"{ default = _GlobalGroup, handler = SetActionButtonGroup }
--- The action type
property "ActionType" { set = false, field = "__IFActionHandler_Kind", default = "empty" }
--- the action content
property "ActionTarget" { set = false, field = "__IFActionHandler_Target" }
--- The action detail
property "ActionDetail" { set = false, field = "__IFActionHandler_Detail" }
--- The gametool tip anchor
property "GameTooltipAnchor"{ type = AnchorType }
--- Whether use custom flyout logic
property "IsCustomFlyout" { type = Boolean }
------------------------------------------------------
-- Observable Property --
------------------------------------------------------
--- Whether show the button grid
__Observable__()
property "GridVisible" { type = Boolean }
--- Whether always show the button grid
__Observable__()
property "GridAlwaysShow" { type = Boolean }
--- Whether the button is usable
__Observable__()
property "IsUsable" { type = Boolean }
--- Whether the button has action
__Observable__()
property "HasAction" { type = Boolean }
--- The count/charge of the action
__Observable__()
property "Count" { type = Number }
--- The cooldown of the action
__Observable__()
property "Cooldown" { type = CooldownStatus, set = Toolset.fakefunc }
--- Whether the action is auto attack or auto repeat
__Observable__()
property "IsAutoAttack" { type = Boolean }
--- Whether show the IsAutoAttacking
__Observable__()
property "IsAutoAttacking" { type = Boolean }
--- Whether show the overlay glow
__Observable__()
property "OverlayGlow" { type = Boolean }
--- Whether the target is in range
__Observable__()
property "InRange" { type = Any }
--- The action button's flyout direction: UP, DOWN, LEFT, RIGHT
__Observable__()
property "FlyoutDirection" { type = FlyoutDirection, default = "UP", handler = function(self, val) self:SetAttribute("flyoutDirection", val) end }
--- Whether the action is flyout
__Observable__()
property "IsFlyout" { type = Boolean }
--- whether the flyout action bar is shown
__Observable__()
property "Flyouting" { type = Boolean }
--- Whether the action is auto castable
__Observable__()
property "IsAutoCastable" { type = Boolean }
--- Whether the action is auto casting
__Observable__()
property "IsAutoCasting" { type = Boolean }
--- The icon of the action
__Observable__()
property "Icon" { type = Any }
--- Whether the action is an equipped item
__Observable__()
property "IsEquippedItem" { type = Boolean }
--- The action text
__Observable__()
property "Text" { type = String }
--- Whether the icon should be locked
__Observable__()
property "IconLocked" { type = Boolean }
__Observable__()
property "ShowSearchOverlay"{ type = Boolean }
--- The short key of the action
__Observable__()
property "HotKey" { type = String, handler = function(self, key, old)
if old and _KeyBindingMap[old] == self then
_KeyBindingMap[old] = nil
end
if key and key:upper() ~= key then
self.HotKey = key:upper()
return
end
self:SetAttribute("hotKey", key)
if key and not self.AutoKeyBinding and _KeyBindingMap[key] ~= self then
if _KeyBindingMap[key] then _KeyBindingMap[key].HotKey = nil end
_KeyBindingMap[key] = self
end
return handleKeyBinding(self)
end
}
------------------------------------------------------
-- Property --
------------------------------------------------------
--- Whether only bind keys when the button is shown
property "AutoKeyBinding" { type = Boolean, handler = function(self, flag)
self:SetAttribute("autoKeyBinding", flag and true or nil)
if self.HotKey then
if flag then
if _KeyBindingMap[self.HotKey] == self then
_KeyBindingMap[self.HotKey] = nil
end
elseif _KeyBindingMap[self.HotKey] and _KeyBindingMap[self.HotKey] ~= self then
self.HotKey = nil
return
end
end
return handleKeyBinding(self)
end
}
------------------------------------------------------
-- Static Method --
------------------------------------------------------
--- Start the key binding for all secure action buttons
__Static__() __Async__()
function StartKeyBinding()
_KeyBindingMode = true
Next(function()
local current
while _KeyBindingMode do
local button = GetMouseFocus()
if button then
button = GetProxyUI(button)
if IsObjectType(button, SecureActionButton) then
_KeyBindingMask:SetParent(button)
_KeyBindingMask:SetBindingKey(button.HotKey)
_KeyBindingMask:Show()
while button:IsMouseOver() and _KeyBindingMode do
Next()
end
_KeyBindingMask:Hide()
_KeyBindingMask:SetParent(_ManagerFrame)
end
end
Next()
end
end)
Alert(_Locale["Confirm when you finished the key binding"])
_KeyBindingMode = false
_KeyBindingMask:Hide()
_KeyBindingMask:SetParent(_ManagerFrame)
end
------------------------------------------------------
-- Method --
------------------------------------------------------
--- Set action for the actionbutton
function SetAction(self, kind, target, detail)
if kind and not _IFActionTypeHandler[kind] then
error("SecureActionButton:SetAction(kind, target, detail) - no such action kind", 2)
end
if not kind or not target then
kind, target, detail= nil, nil, nil
else
target, detail = _IFActionTypeHandler[kind].Map(self, target, detail)
end
NoCombat(SaveAction, self, kind, target, detail)
end
--- Get action for the actionbutton
function GetAction(self)
return self.ActionType, self.ActionTarget, self.ActionDetail
end
--- Be called when the action content is changed
function Refresh(self)
end
__SecureMethod__()
function UpdateTooltip(self)
if not self.ActionType then return end
local anchor = self.GameTooltipAnchor
if anchor then
GameTooltip:SetOwner(self, anchor)
else
if (GetCVar("UberTooltips") == "1") then
GameTooltip_SetDefaultAnchor(GameTooltip, self)
else
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
end
end
_IFActionTypeHandler[self.ActionType].SetTooltip(self, GameTooltip)
GameTooltip:Show()
end
__Sealed__()
ISecureActionButton = interface { __init = SetupActionButton }
extend(ISecureActionButton)
end)