TradeSkillMaster/Core/UI/Elements/ActionButton.lua

437 lines
14 KiB
Lua
Raw Normal View History

2020-11-13 14:13:12 -05:00
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
--- ActionButton UI Element Class.
-- An action button is a button which uses specific background textures and has a pressed state. It is a subclass of the
-- @{Text} class.
-- @classmod ActionButton
local _, TSM = ...
local ActionButton = TSM.Include("LibTSMClass").DefineClass("ActionButton", TSM.UI.Text)
local NineSlice = TSM.Include("Util.NineSlice")
local Vararg = TSM.Include("Util.Vararg")
local Event = TSM.Include("Util.Event")
local Theme = TSM.Include("Util.Theme")
local Color = TSM.Include("Util.Color")
local ScriptWrapper = TSM.Include("Util.ScriptWrapper")
local UIElements = TSM.Include("UI.UIElements")
UIElements.Register(ActionButton)
TSM.UI.ActionButton = ActionButton
local private = {}
local ICON_PADDING = 2
local CLICK_COOLDOWN = 0.2
-- ============================================================================
-- Public Class Methods
-- ============================================================================
function ActionButton.__init(self, name, isSecure)
local frame = UIElements.CreateFrame(self, "Button", name, nil, isSecure and "SecureActionButtonTemplate" or nil)
ScriptWrapper.Set(frame, isSecure and "PostClick" or "OnClick", private.OnClick, self)
ScriptWrapper.Set(frame, "OnMouseDown", private.OnMouseDown, self)
ScriptWrapper.Set(frame, "OnEnter", private.OnEnter, self)
ScriptWrapper.Set(frame, "OnLeave", private.OnLeave, self)
self.__super:__init(frame)
self._nineSlice = NineSlice.New(frame)
-- create the icon
frame.icon = frame:CreateTexture(nil, "ARTWORK")
frame.icon:SetPoint("RIGHT", frame.text, "LEFT", -ICON_PADDING, 0)
Event.Register("MODIFIER_STATE_CHANGED", function()
if self:IsVisible() and next(self._modifierText) then
self:Draw()
if GameTooltip:IsOwned(self:_GetBaseFrame()) then
self:ShowTooltip(self._tooltip)
end
end
end)
self._iconTexturePack = nil
self._pressed = nil
self._disabled = false
self._locked = false
self._lockedColor = nil
self._justifyH = "CENTER"
self._font = "BODY_BODY2_MEDIUM"
self._defaultText = ""
self._modifierText = {}
self._onClickHandler = nil
self._onEnterHandler = nil
self._onLeaveHandler = nil
self._clickCooldown = nil
self._clickCooldownDisabled = false
self._defaultNoBackground = false
self._isMouseDown = false
self._manualRequired = false
end
function ActionButton.Release(self)
self._iconTexturePack = nil
self._pressed = nil
self._disabled = false
self._locked = false
self._lockedColor = nil
self._defaultText = ""
wipe(self._modifierText)
self._onClickHandler = nil
self._onEnterHandler = nil
self._onLeaveHandler = nil
self._clickCooldown = nil
self._clickCooldownDisabled = false
self._defaultNoBackground = false
self._manualRequired = false
self._isMouseDown = false
local frame = self:_GetBaseFrame()
ScriptWrapper.Clear(frame, "OnUpdate")
frame:Enable()
frame:RegisterForClicks("LeftButtonUp")
frame:UnlockHighlight()
self.__super:Release()
self._justifyH = "CENTER"
self._font = "BODY_BODY2_MEDIUM"
end
--- Sets the icon that shows within the button.
-- @tparam ActionButton self The action button object
-- @tparam[opt=nil] string texturePack A texture pack string to set the icon and its size to
-- @treturn ActionButton The action button object
function ActionButton.SetIcon(self, texturePack)
if texturePack then
assert(TSM.UI.TexturePacks.IsValid(texturePack))
self._iconTexturePack = texturePack
else
self._iconTexturePack = nil
end
return self
end
--- Set the text.
-- @tparam ActionButton self The action button object
-- @tparam ?string|number text The text
-- @treturn ActionButton The action button object
function ActionButton.SetText(self, text)
self.__super:SetText(text)
self._defaultText = self:GetText()
return self
end
--- Sets a script handler.
-- @see Element.SetScript
-- @tparam ActionButton self The action button object
-- @tparam string script The script to register for (currently only supports `OnClick`)
-- @tparam function handler The script handler which will be called with the action button object followed by any
-- arguments to the script
-- @treturn ActionButton The action button object
function ActionButton.SetScript(self, script, handler)
if script == "OnClick" then
self._onClickHandler = handler
elseif script == "OnEnter" then
self._onEnterHandler = handler
elseif script == "OnLeave" then
self._onLeaveHandler = handler
elseif script == "OnMouseDown" or script == "OnMouseUp" then
self.__super:SetScript(script, handler)
else
error("Unknown ActionButton script: "..tostring(script))
end
return self
end
--- Sets a script to propagate to the parent element.
-- @tparam ActionButton self The action button object
-- @tparam string script The script to propagate
-- @treturn ActionButton The action button object
function ActionButton.PropagateScript(self, script)
if script == "OnMouseDown" or script == "OnMouseUp" then
self.__super:PropagateScript(script)
else
error("Cannot propagate ActionButton script: "..tostring(script))
end
return self
end
--- Set whether or not the action button is disabled.
-- @tparam ActionButton self The action button object
-- @tparam boolean disabled Whether or not the action button should be disabled
-- @treturn ActionButton The action button object
function ActionButton.SetDisabled(self, disabled)
self._disabled = disabled
self:_UpdateDisabled()
return self
end
--- Set whether or not the action button is pressed.
-- @tparam ActionButton self The action button object
-- @tparam boolean locked Whether or not to lock the action button's highlight
-- @tparam[opt=nil] string color The locked highlight color as a theme color key
-- @treturn ActionButton The action button object
function ActionButton.SetHighlightLocked(self, locked, color)
self._locked = locked
self._lockedColor = color
if locked then
self:_GetBaseFrame():LockHighlight()
else
self:_GetBaseFrame():UnlockHighlight()
end
return self
end
--- Set whether or not the action button is pressed.
-- @tparam ActionButton self The action button object
-- @tparam boolean pressed Whether or not the action button should be pressed
-- @treturn ActionButton The action button object
function ActionButton.SetPressed(self, pressed)
self._pressed = pressed and private.GetModifierKey(IsShiftKeyDown(), IsControlKeyDown(), IsAltKeyDown()) or nil
self:_UpdateDisabled()
return self
end
--- Disables the default click cooldown to allow the button to be spammed (i.e. for macro-able buttons).
-- @tparam ActionButton self The action button object
-- @treturn ActionButton The action button object
function ActionButton.DisableClickCooldown(self)
self._clickCooldownDisabled = true
return self
end
function ActionButton.SetDefaultNoBackground(self)
self._defaultNoBackground = true
return self
end
function ActionButton.SetModifierText(self, text, ...)
local key = private.GetModifierKey(private.ParseModifiers(...))
assert(key and key ~= "NONE")
self._modifierText[key] = text
return self
end
--- Set whether a manual click (vs. a macro) is required.
-- @tparam ActionButton self The action button object
-- @tparam boolean required Whether or not a manual click is required
-- @treturn ActionButton The action button object
function ActionButton.SetRequireManualClick(self, required)
self._manualRequired = required
return self
end
--- Click on the action button.
-- @tparam ActionButton self The action button object
function ActionButton.Click(self)
local frame = self:_GetBaseFrame()
if frame:IsEnabled() and frame:IsVisible() then
private.OnClick(self)
end
end
function ActionButton.Draw(self)
local maxRank, maxRankKey, numMaxRank = nil, nil, nil
local currentModifier = self._pressed or private.GetModifierKey(IsShiftKeyDown(), IsControlKeyDown(), IsAltKeyDown())
local currentShift, currentControl, currentAlt = private.ParseModifiers(strsplit("-", currentModifier))
for key in pairs(self._modifierText) do
local hasShift, hasControl, hasAlt = private.ParseModifiers(strsplit("-", key))
if (not hasShift or currentShift) and (not hasControl or currentControl) and (not hasAlt or currentAlt) then
-- this key matches the current state
local rank = select("#", strsplit("-", key))
if not maxRank or rank > maxRank then
maxRank = rank
numMaxRank = 1
maxRankKey = key
elseif rank == maxRank then
numMaxRank = numMaxRank + 1
end
end
end
if maxRank then
assert(numMaxRank == 1)
self.__super:SetText(self._modifierText[maxRankKey])
else
self.__super:SetText(self._defaultText)
end
self.__super:Draw()
local frame = self:_GetBaseFrame()
-- set nine-slice and text color depending on the state
local textColor, nineSliceTheme, nineSliceColor = nil, nil, nil
if self._pressed or self._clickCooldown then
textColor = Color.GetFullBlack()
nineSliceTheme = "rounded"
nineSliceColor = Theme.GetColor("INDICATOR")
elseif self._disabled then
textColor = Theme.GetColor("ACTIVE_BG_ALT")
nineSliceTheme = "global"
nineSliceColor = Theme.GetColor("ACTIVE_BG")
elseif self._locked then
textColor = Color.GetFullBlack()
nineSliceTheme = "rounded"
nineSliceColor = Theme.GetColor(self._lockedColor or "ACTIVE_BG+HOVER")
elseif frame:IsMouseOver() then
textColor = Theme.GetColor("TEXT")
nineSliceTheme = "rounded"
nineSliceColor = Theme.GetColor("ACTIVE_BG+HOVER")
else
textColor = self:_GetTextColor()
if not self._defaultNoBackground then
nineSliceTheme = "rounded"
nineSliceColor = Theme.GetColor("ACTIVE_BG")
end
end
frame.text:SetTextColor(textColor:GetFractionalRGBA())
if nineSliceTheme then
self._nineSlice:SetStyle(nineSliceTheme)
self._nineSlice:SetVertexColor(nineSliceColor:GetFractionalRGBA())
else
self._nineSlice:Hide()
end
if self._iconTexturePack then
TSM.UI.TexturePacks.SetTextureAndSize(frame.icon, self._iconTexturePack)
frame.icon:Show()
frame.icon:SetVertexColor(textColor:GetFractionalRGBA())
local xOffset = self:GetText() ~= "" and ((TSM.UI.TexturePacks.GetWidth(self._iconTexturePack) + ICON_PADDING) / 2) or (self:_GetDimension("WIDTH") / 2)
frame.text:ClearAllPoints()
frame.text:SetPoint("TOP", xOffset, 0)
frame.text:SetPoint("BOTTOM", xOffset, 0)
frame.text:SetWidth(frame.text:GetStringWidth())
else
frame.icon:Hide()
frame.text:ClearAllPoints()
frame.text:SetPoint("TOPLEFT")
frame.text:SetPoint("BOTTOMRIGHT")
end
end
-- ============================================================================
-- Private Class Methods
-- ============================================================================
function ActionButton._UpdateDisabled(self)
local frame = self:_GetBaseFrame()
if self._disabled or self._pressed or self._clickCooldown then
frame:Disable()
else
frame:Enable()
end
end
-- ============================================================================
-- Local Script Handlers
-- ============================================================================
function private.OnClick(self)
if not self._acquired then
return
end
if self._manualRequired and not self._isMouseDown then
return
end
self._isMouseDown = false
if not self._clickCooldownDisabled then
self._clickCooldown = CLICK_COOLDOWN
self:_UpdateDisabled()
ScriptWrapper.Set(self:_GetBaseFrame(), "OnUpdate", private.OnUpdate, self)
end
self:Draw()
if self._onClickHandler then
self:_onClickHandler()
end
end
function private.OnMouseDown(self)
self._isMouseDown = true
end
function private.OnEnter(self)
if self._onEnterHandler then
self:_onEnterHandler()
end
if self._disabled or self._pressed or self._clickCooldown then
return
end
self:Draw()
end
function private.OnLeave(self)
if not self:IsVisible() then
return
end
if self._onLeaveHandler then
self:_onLeaveHandler()
end
if self._disabled or self._pressed or self._clickCooldown then
return
end
self:Draw()
end
function private.OnUpdate(self, elapsed)
self._clickCooldown = self._clickCooldown - elapsed
if self._clickCooldown <= 0 then
ScriptWrapper.Clear(self:_GetBaseFrame(), "OnUpdate")
self._clickCooldown = nil
self:_UpdateDisabled()
self:Draw()
end
end
-- ============================================================================
-- Private Helper Functions
-- ============================================================================
function private.GetModifierKey(hasShift, hasControl, hasAlt)
if hasShift and hasControl and hasAlt then
return "SHIFT-CTRL-ALT"
elseif hasShift and hasControl then
return "SHIFT-CTRL"
elseif hasShift and hasAlt then
return "SHIFT-ALT"
elseif hasShift then
return "SHIFT"
elseif hasControl and hasAlt then
return "CTRL-ALT"
elseif hasControl then
return "CTRL"
elseif hasAlt then
return "ALT"
else
return "NONE"
end
end
function private.ParseModifiers(...)
local hasShift, hasControl, hasAlt, hasNone = false, false, false, false
for _, modifier in Vararg.Iterator(...) do
if modifier == "SHIFT" then
assert(not hasShift and not hasNone)
hasShift = true
elseif modifier == "CTRL" then
assert(not hasControl and not hasNone)
hasControl = true
elseif modifier == "ALT" then
assert(not hasAlt and not hasNone)
hasAlt = true
elseif modifier == "NONE" then
assert(not hasShift and not hasControl and not hasAlt and not hasNone)
hasNone = true
else
error("Invalid modifier: "..tostring(modifier))
end
end
return hasShift, hasControl, hasAlt
end