initial commit
This commit is contained in:
436
Core/UI/Elements/ActionButton.lua
Normal file
436
Core/UI/Elements/ActionButton.lua
Normal file
@@ -0,0 +1,436 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- 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
|
||||
82
Core/UI/Elements/AlphaAnimatedFrame.lua
Normal file
82
Core/UI/Elements/AlphaAnimatedFrame.lua
Normal file
@@ -0,0 +1,82 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- AlphaAnimatedFrame UI Element Class.
|
||||
-- An alpha animated frame is a frame which allows for animating its alpha. It is a subclass of the @{Frame} class.
|
||||
-- @classmod AlphaAnimatedFrame
|
||||
|
||||
local _, TSM = ...
|
||||
local AlphaAnimatedFrame = TSM.Include("LibTSMClass").DefineClass("AlphaAnimatedFrame", TSM.UI.Frame)
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(AlphaAnimatedFrame)
|
||||
TSM.UI.AlphaAnimatedFrame = AlphaAnimatedFrame
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function AlphaAnimatedFrame.__init(self)
|
||||
self.__super:__init()
|
||||
|
||||
local frame = self:_GetBaseFrame()
|
||||
self._ag = frame:CreateAnimationGroup()
|
||||
self._ag:SetLooping("BOUNCE")
|
||||
self._alpha = self._ag:CreateAnimation("Alpha")
|
||||
end
|
||||
|
||||
function AlphaAnimatedFrame.Acquire(self)
|
||||
self._alpha:SetFromAlpha(1)
|
||||
self._alpha:SetToAlpha(1)
|
||||
self._alpha:SetDuration(1)
|
||||
self.__super:Acquire()
|
||||
end
|
||||
|
||||
function AlphaAnimatedFrame.Release(self)
|
||||
self._ag:Stop()
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
--- Sets the range of the alpha animation.
|
||||
-- @tparam AlphaAnimatedFrame self The alpha animated frame object
|
||||
-- @tparam number fromAlpha The initial alpha value (usually 1)
|
||||
-- @tparam number toAlpha The end alpha value (between 0 and 1 inclusive)
|
||||
-- @treturn AlphaAnimatedFrame The alpha animated frame object
|
||||
function AlphaAnimatedFrame.SetRange(self, fromAlpha, toAlpha)
|
||||
self._alpha:SetFromAlpha(fromAlpha)
|
||||
self._alpha:SetToAlpha(toAlpha)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the duration of the animation.
|
||||
-- @tparam AlphaAnimatedFrame self The alpha animated frame object
|
||||
-- @tparam number duration The duration in seconds
|
||||
-- @treturn AlphaAnimatedFrame The alpha animated frame object
|
||||
function AlphaAnimatedFrame.SetDuration(self, duration)
|
||||
self._alpha:SetDuration(duration)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets whether or not the animation is playing.
|
||||
-- @tparam AlphaAnimatedFrame self The alpha animated frame object
|
||||
-- @tparam boolean play Whether the animation should be playing or not
|
||||
-- @treturn AlphaAnimatedFrame The alpha animated frame object
|
||||
function AlphaAnimatedFrame.SetPlaying(self, play)
|
||||
if play then
|
||||
self._ag:Play()
|
||||
else
|
||||
self._ag:Stop()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Gets whether or not the animation is playing.
|
||||
-- @tparam AlphaAnimatedFrame self The alpha animated frame object
|
||||
-- @treturn boolean Whether the animation is playing
|
||||
function AlphaAnimatedFrame.IsPlaying(self)
|
||||
return self._ag:IsPlaying()
|
||||
end
|
||||
682
Core/UI/Elements/ApplicationFrame.lua
Normal file
682
Core/UI/Elements/ApplicationFrame.lua
Normal file
@@ -0,0 +1,682 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- ApplicationFrame UI Element Class.
|
||||
-- An application frame is the base frame of all of the TSM UIs. It is a subclass of the @{Frame} class.
|
||||
-- @classmod ApplicationFrame
|
||||
|
||||
local _, TSM = ...
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local Math = TSM.Include("Util.Math")
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local Color = TSM.Include("Util.Color")
|
||||
local NineSlice = TSM.Include("Util.NineSlice")
|
||||
local Table = TSM.Include("Util.Table")
|
||||
local ScriptWrapper = TSM.Include("Util.ScriptWrapper")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local Tooltip = TSM.Include("UI.Tooltip")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local ApplicationFrame = TSM.Include("LibTSMClass").DefineClass("ApplicationFrame", TSM.UI.Frame)
|
||||
UIElements.Register(ApplicationFrame)
|
||||
TSM.UI.ApplicationFrame = ApplicationFrame
|
||||
local private = {
|
||||
menuDialogContext = {},
|
||||
}
|
||||
local SECONDS_PER_HOUR = 60 * 60
|
||||
local SECONDS_PER_DAY = 24 * SECONDS_PER_HOUR
|
||||
local CONTENT_FRAME_OFFSET = 8
|
||||
local DIALOG_RELATIVE_LEVEL = 18
|
||||
local HEADER_HEIGHT = 40
|
||||
local MIN_SCALE = 0.3
|
||||
local DIALOG_OPACITY_PCT = 65
|
||||
local MIN_ON_SCREEN_PX = 50
|
||||
local function NoOp() end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function ApplicationFrame.__init(self)
|
||||
self.__super:__init()
|
||||
self._contentFrame = nil
|
||||
self._contextTable = nil
|
||||
self._defaultContextTable = nil
|
||||
self._isScaling = nil
|
||||
self._protected = nil
|
||||
self._minWidth = 0
|
||||
self._minHeight = 0
|
||||
self._dialogStack = {}
|
||||
|
||||
local frame = self:_GetBaseFrame()
|
||||
local globalFrameName = tostring(frame)
|
||||
_G[globalFrameName] = frame
|
||||
-- insert our frames before other addons (i.e. Skillet) to avoid conflicts
|
||||
tinsert(UISpecialFrames, 1, globalFrameName)
|
||||
|
||||
self._nineSlice = NineSlice.New(frame)
|
||||
self._nineSlice:SetStyle("outerFrame")
|
||||
|
||||
frame.resizeIcon = frame:CreateTexture(nil, "ARTWORK")
|
||||
frame.resizeIcon:SetPoint("BOTTOMRIGHT")
|
||||
TSM.UI.TexturePacks.SetTextureAndSize(frame.resizeIcon, "iconPack.14x14/Resize")
|
||||
|
||||
frame.resizeBtn = CreateFrame("Button", nil, frame)
|
||||
frame.resizeBtn:SetAllPoints(frame.resizeIcon)
|
||||
frame.resizeBtn:RegisterForClicks("LeftButtonUp", "RightButtonUp")
|
||||
ScriptWrapper.Set(frame.resizeBtn, "OnEnter", private.ResizeButtonOnEnter, self)
|
||||
ScriptWrapper.Set(frame.resizeBtn, "OnLeave", private.ResizeButtonOnLeave, self)
|
||||
ScriptWrapper.Set(frame.resizeBtn, "OnMouseDown", private.ResizeButtonOnMouseDown, self)
|
||||
ScriptWrapper.Set(frame.resizeBtn, "OnMouseUp", private.ResizeButtonOnMouseUp, self)
|
||||
ScriptWrapper.Set(frame.resizeBtn, "OnClick", private.ResizeButtonOnClick, self)
|
||||
Theme.RegisterChangeCallback(function()
|
||||
if self:IsVisible() then
|
||||
self:Draw()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function ApplicationFrame.Acquire(self)
|
||||
self:AddChildNoLayout(UIElements.New("Frame", "titleFrame")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:AddAnchor("TOPLEFT", 8, -8)
|
||||
:AddAnchor("TOPRIGHT", -8, -8)
|
||||
:SetBackgroundColor("FRAME_BG")
|
||||
:AddChild(UIElements.New("Texture", "icon")
|
||||
:SetMargin(0, 16, 0, 0)
|
||||
:SetTextureAndSize("uiFrames.SmallLogo")
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "title")
|
||||
:AddAnchor("CENTER")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY2_BOLD")
|
||||
:SetTextColor("TEXT_ALT")
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
:AddChild(UIElements.New("Button", "closeBtn")
|
||||
:SetBackgroundAndSize("iconPack.24x24/Close/Default")
|
||||
:SetScript("OnClick", private.CloseButtonOnClick)
|
||||
)
|
||||
)
|
||||
self.__super:Acquire()
|
||||
local frame = self:_GetBaseFrame()
|
||||
frame:EnableMouse(true)
|
||||
frame:SetMovable(true)
|
||||
frame:SetResizable(true)
|
||||
frame:RegisterForDrag("LeftButton")
|
||||
self:SetScript("OnDragStart", private.FrameOnDragStart)
|
||||
self:SetScript("OnDragStop", private.FrameOnDragStop)
|
||||
end
|
||||
|
||||
function ApplicationFrame.Release(self)
|
||||
if self._protected then
|
||||
tinsert(UISpecialFrames, 1, tostring(self:_GetBaseFrame()))
|
||||
end
|
||||
self._contentFrame = nil
|
||||
self._contextTable = nil
|
||||
self._defaultContextTable = nil
|
||||
self:_GetBaseFrame():SetMinResize(0, 0)
|
||||
self:_GetBaseFrame():SetMaxResize(0, 0)
|
||||
self._isScaling = nil
|
||||
self._protected = nil
|
||||
self._minWidth = 0
|
||||
self._minHeight = 0
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
--- Adds player gold text to the title frame.
|
||||
-- @tparam ApplicationFrame self The application frame object
|
||||
-- @treturn ApplicationFrame The application frame object
|
||||
function ApplicationFrame.AddPlayerGold(self)
|
||||
local titleFrame = self:GetElement("titleFrame")
|
||||
titleFrame:AddChildBeforeById(titleFrame:HasChildById("switchBtn") and "switchBtn" or "closeBtn", UIElements.New("PlayerGoldText", "playerGold")
|
||||
:SetWidth("AUTO")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Adds the app status icon to the title frame.
|
||||
-- @tparam ApplicationFrame self The application frame object
|
||||
-- @treturn ApplicationFrame The application frame object
|
||||
function ApplicationFrame.AddAppStatusIcon(self)
|
||||
local color, texture = nil, nil
|
||||
local appUpdateAge = time() - TSM.GetAppUpdateTime()
|
||||
local auctionDBRealmTime, auctionDBRegionTime = TSM.AuctionDB.GetAppDataUpdateTimes()
|
||||
local auctionDBRealmAge = time() - auctionDBRealmTime
|
||||
local auctionDBRegionAge = time() - auctionDBRegionTime
|
||||
if appUpdateAge >= 2 * SECONDS_PER_DAY or auctionDBRealmAge > 2 * SECONDS_PER_DAY or auctionDBRegionAge > 2 * SECONDS_PER_DAY then
|
||||
color = "RED"
|
||||
texture = "iconPack.14x14/Attention"
|
||||
elseif appUpdateAge >= 2 * SECONDS_PER_HOUR or auctionDBRealmAge >= 4 * SECONDS_PER_HOUR then
|
||||
color = "YELLOW"
|
||||
texture = "iconPack.14x14/Attention"
|
||||
else
|
||||
color = "GREEN"
|
||||
texture = "iconPack.14x14/Checkmark/Circle"
|
||||
end
|
||||
local titleFrame = self:GetElement("titleFrame")
|
||||
titleFrame:AddChildBeforeById("playerGold", UIElements.New("Button", "playerGold")
|
||||
:SetBackgroundAndSize(TSM.UI.TexturePacks.GetColoredKey(texture, Theme.GetFeedbackColor(color)))
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetTooltip(private.GetAppStatusTooltip)
|
||||
)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Adds a switch button to the title frame.
|
||||
-- @tparam ApplicationFrame self The application frame object
|
||||
-- @tparam function onClickHandler The handler for the OnClick script for the button
|
||||
-- @treturn ApplicationFrame The application frame object
|
||||
function ApplicationFrame.AddSwitchButton(self, onClickHandler)
|
||||
local titleFrame = self:GetElement("titleFrame")
|
||||
titleFrame:AddChildBeforeById("closeBtn", UIElements.New("ActionButton", "switchBtn")
|
||||
:SetSize(95, 20)
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetText(L["WOW UI"])
|
||||
:SetScript("OnClick", onClickHandler)
|
||||
)
|
||||
return self
|
||||
end
|
||||
|
||||
function ApplicationFrame.SetProtected(self, protected)
|
||||
self._protected = protected
|
||||
local globalFrameName = tostring(self:_GetBaseFrame())
|
||||
if protected then
|
||||
Table.RemoveByValue(UISpecialFrames, globalFrameName)
|
||||
else
|
||||
if not Table.KeyByValue(UISpecialFrames, globalFrameName) then
|
||||
-- insert our frames before other addons (i.e. Skillet) to avoid conflicts
|
||||
tinsert(UISpecialFrames, 1, globalFrameName)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the title text.
|
||||
-- @tparam ApplicationFrame self The application frame object
|
||||
-- @tparam string title The title text
|
||||
-- @treturn ApplicationFrame The application frame object
|
||||
function ApplicationFrame.SetTitle(self, title)
|
||||
local titleFrame = self:GetElement("titleFrame")
|
||||
titleFrame:GetElement("title"):SetText(title)
|
||||
titleFrame:Draw()
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the content frame.
|
||||
-- @tparam ApplicationFrame self The application frame object
|
||||
-- @tparam Frame frame The frame's content frame
|
||||
-- @treturn ApplicationFrame The application frame object
|
||||
function ApplicationFrame.SetContentFrame(self, frame)
|
||||
assert(frame:__isa(TSM.UI.Frame))
|
||||
frame:WipeAnchors()
|
||||
frame:AddAnchor("TOPLEFT", CONTENT_FRAME_OFFSET, -HEADER_HEIGHT)
|
||||
frame:AddAnchor("BOTTOMRIGHT", -CONTENT_FRAME_OFFSET, CONTENT_FRAME_OFFSET)
|
||||
frame:SetPadding(2)
|
||||
frame:SetBorderColor("ACTIVE_BG", 2)
|
||||
self._contentFrame = frame
|
||||
self:AddChildNoLayout(frame)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the context table.
|
||||
-- This table can be used to preserve position and size information across lifecycles of the application frame and even
|
||||
-- WoW sessions if it's within the settings DB.
|
||||
-- @tparam ApplicationFrame self The application frame object
|
||||
-- @tparam table tbl The context table
|
||||
-- @tparam table defaultTbl Default values (required attributes: `width`, `height`, `centerX`, `centerY`)
|
||||
-- @treturn ApplicationFrame The application frame object
|
||||
function ApplicationFrame.SetContextTable(self, tbl, defaultTbl)
|
||||
assert(defaultTbl.width > 0 and defaultTbl.height > 0)
|
||||
assert(defaultTbl.centerX and defaultTbl.centerY)
|
||||
tbl.width = tbl.width or defaultTbl.width
|
||||
tbl.height = tbl.height or defaultTbl.height
|
||||
tbl.centerX = tbl.centerX or defaultTbl.centerX
|
||||
tbl.centerY = tbl.centerY or defaultTbl.centerY
|
||||
tbl.scale = tbl.scale or defaultTbl.scale
|
||||
self._contextTable = tbl
|
||||
self._defaultContextTable = defaultTbl
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the context table from a settings object.
|
||||
-- @tparam ApplicationFrame self The application frame object
|
||||
-- @tparam Settings settings The settings object
|
||||
-- @tparam string key The setting key
|
||||
-- @treturn ApplicationFrame The application frame object
|
||||
function ApplicationFrame.SetSettingsContext(self, settings, key)
|
||||
return self:SetContextTable(settings[key], settings:GetDefaultReadOnly(key))
|
||||
end
|
||||
|
||||
--- Sets the minimum size the application frame can be resized to.
|
||||
-- @tparam ApplicationFrame self The application frame object
|
||||
-- @tparam number minWidth The minimum width
|
||||
-- @tparam number minHeight The minimum height
|
||||
-- @treturn ApplicationFrame The application frame object
|
||||
function ApplicationFrame.SetMinResize(self, minWidth, minHeight)
|
||||
self._minWidth = minWidth
|
||||
self._minHeight = minHeight
|
||||
return self
|
||||
end
|
||||
|
||||
--- Shows a dialog frame.
|
||||
-- @tparam ApplicationFrame self The application frame object
|
||||
-- @tparam Element frame The element to show in a dialog
|
||||
-- @param context The context to set on the dialog frame
|
||||
function ApplicationFrame.ShowDialogFrame(self, frame, context)
|
||||
local dialogFrame = UIElements.New("Frame", "_dialog_"..random())
|
||||
:SetRelativeLevel(DIALOG_RELATIVE_LEVEL * (#self._dialogStack + 1))
|
||||
:SetBackgroundColor(Color.GetFullBlack():GetOpacity(DIALOG_OPACITY_PCT))
|
||||
:AddAnchor("TOPLEFT")
|
||||
:AddAnchor("BOTTOMRIGHT")
|
||||
:SetMouseEnabled(true)
|
||||
:SetMouseWheelEnabled(true)
|
||||
:SetContext(context)
|
||||
:SetScript("OnMouseWheel", NoOp)
|
||||
:SetScript("OnMouseUp", private.DialogOnMouseUp)
|
||||
:SetScript("OnHide", private.DialogOnHide)
|
||||
:AddChildNoLayout(frame)
|
||||
tinsert(self._dialogStack, dialogFrame)
|
||||
self._contentFrame:AddChildNoLayout(dialogFrame)
|
||||
dialogFrame:Show()
|
||||
dialogFrame:Draw()
|
||||
end
|
||||
|
||||
--- Show a confirmation dialog.
|
||||
-- @tparam ApplicationFrame self The application frame object
|
||||
-- @tparam string title The title of the dialog
|
||||
-- @tparam string subTitle The sub-title of the dialog
|
||||
-- @tparam function callback The callback for when the dialog is closed
|
||||
-- @tparam[opt] varag ... Arguments to pass to the callback
|
||||
function ApplicationFrame.ShowConfirmationDialog(self, title, subTitle, callback, ...)
|
||||
local context = TempTable.Acquire(...)
|
||||
context.callback = callback
|
||||
local frame = UIElements.New("Frame", "frame")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetSize(328, 158)
|
||||
:SetPadding(12, 12, 8, 12)
|
||||
:AddAnchor("CENTER")
|
||||
:SetBackgroundColor("FRAME_BG", true)
|
||||
:SetMouseEnabled(true)
|
||||
:AddChild(UIElements.New("Frame", "header")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:AddChild(UIElements.New("Text", "title")
|
||||
:SetHeight(20)
|
||||
:SetMargin(32, 8, 0, 0)
|
||||
:SetFont("BODY_BODY2_BOLD")
|
||||
:SetJustifyH("CENTER")
|
||||
:SetText(title)
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "closeBtn")
|
||||
:SetBackgroundAndSize("iconPack.24x24/Close/Default")
|
||||
:SetScript("OnClick", private.DialogCancelBtnOnClick)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "desc")
|
||||
:SetMargin(0, 0, 16, 16)
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetJustifyV("TOP")
|
||||
:SetText(subTitle)
|
||||
)
|
||||
:AddChild(UIElements.New("ActionButton", "confirmBtn")
|
||||
:SetHeight(24)
|
||||
:SetText(L["Confirm"])
|
||||
:SetScript("OnClick", private.DialogConfirmBtnOnClick)
|
||||
)
|
||||
self:ShowDialogFrame(frame, context)
|
||||
end
|
||||
|
||||
--- Show a dialog triggered by a "more" button.
|
||||
-- @tparam ApplicationFrame self The application frame object
|
||||
-- @tparam Button moreBtn The "more" button
|
||||
-- @tparam function iter A dialog menu row iterator with the following fields: `index, text, callback`
|
||||
function ApplicationFrame.ShowMoreButtonDialog(self, moreBtn, iter)
|
||||
local frame = UIElements.New("PopupFrame", "moreDialog")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetWidth(200)
|
||||
:SetPadding(0, 0, 8, 4)
|
||||
:AddAnchor("TOPRIGHT", moreBtn:_GetBaseFrame(), "BOTTOM", 22, -16)
|
||||
local numRows = 0
|
||||
for i, text, callback in iter do
|
||||
frame:AddChild(UIElements.New("Button", "row"..i)
|
||||
:SetHeight(20)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(text)
|
||||
:SetScript("OnClick", callback)
|
||||
)
|
||||
numRows = numRows + 1
|
||||
end
|
||||
frame:SetHeight(12 + numRows * 20)
|
||||
self:ShowDialogFrame(frame)
|
||||
end
|
||||
|
||||
--- Show a menu dialog.
|
||||
-- @tparam ApplicationFrame self The application frame object
|
||||
-- @tparam string frame The frame to anchor the dialog to
|
||||
-- @tparam function iter A menu row iterator with the following fields: `index, text, subIter`
|
||||
-- @param context Context to pass to the iter / subIter
|
||||
-- @tparam function clickCallback The function to be called when a menu row is clicked
|
||||
-- @tparam boolean flip Flip the anchor to the other side
|
||||
function ApplicationFrame.ShowMenuDialog(self, frame, iter, context, clickCallback, flip)
|
||||
wipe(private.menuDialogContext)
|
||||
private.menuDialogContext.context = context
|
||||
private.menuDialogContext.clickCallback = clickCallback
|
||||
self:ShowDialogFrame(private.CreateMenuDialogFrame("_menuDialog", iter)
|
||||
:AddAnchor(flip and "TOPRIGHT" or "TOPLEFT", frame, flip and "BOTTOMRIGHT" or "BOTTOMLEFT", 2, -4)
|
||||
)
|
||||
end
|
||||
|
||||
--- Hides the current dialog.
|
||||
-- @tparam ApplicationFrame self The application frame object
|
||||
function ApplicationFrame.HideDialog(self)
|
||||
local dialogFrame = tremove(self._dialogStack)
|
||||
if not dialogFrame then
|
||||
return
|
||||
end
|
||||
dialogFrame:GetParentElement():RemoveChild(dialogFrame)
|
||||
dialogFrame:Hide()
|
||||
dialogFrame:Release()
|
||||
end
|
||||
|
||||
function ApplicationFrame.Draw(self)
|
||||
local frame = self:_GetBaseFrame()
|
||||
frame:SetToplevel(true)
|
||||
frame:Raise()
|
||||
self._nineSlice:SetVertexColor(Theme.GetColor("FRAME_BG"):GetFractionalRGBA())
|
||||
|
||||
-- update the size if it's less than the set min size
|
||||
assert(self._minWidth > 0 and self._minHeight > 0)
|
||||
self._contextTable.width = max(self._contextTable.width, self._minWidth)
|
||||
self._contextTable.height = max(self._contextTable.height, self._minHeight)
|
||||
self._contextTable.scale = max(self._contextTable.scale, MIN_SCALE)
|
||||
|
||||
-- set the frame size from the contextTable
|
||||
self:SetScale(self._contextTable.scale)
|
||||
self:SetSize(self._contextTable.width, self._contextTable.height)
|
||||
|
||||
-- make sure at least 50px of the frame is on the screen and offset by at least 1 scaled pixel to fix some rendering issues
|
||||
local maxAbsCenterX = (UIParent:GetWidth() / self._contextTable.scale + self._contextTable.width) / 2 - MIN_ON_SCREEN_PX
|
||||
local maxAbsCenterY = (UIParent:GetHeight() / self._contextTable.scale + self._contextTable.height) / 2 - MIN_ON_SCREEN_PX
|
||||
local effectiveScale = UIParent:GetEffectiveScale()
|
||||
if self._contextTable.centerX < 0 then
|
||||
self._contextTable.centerX = min(max(self._contextTable.centerX, -maxAbsCenterX), -effectiveScale)
|
||||
else
|
||||
self._contextTable.centerX = max(min(self._contextTable.centerX, maxAbsCenterX), effectiveScale)
|
||||
end
|
||||
if self._contextTable.centerY < 0 then
|
||||
self._contextTable.centerY = min(max(self._contextTable.centerY, -maxAbsCenterY), -effectiveScale)
|
||||
else
|
||||
self._contextTable.centerY = max(min(self._contextTable.centerY, maxAbsCenterY), effectiveScale)
|
||||
end
|
||||
|
||||
-- adjust the position of the frame based on the UI scale to make rendering more consistent
|
||||
self._contextTable.centerX = Math.Round(self._contextTable.centerX, effectiveScale)
|
||||
self._contextTable.centerY = Math.Round(self._contextTable.centerY, effectiveScale)
|
||||
|
||||
-- set the frame position from the contextTable
|
||||
self:WipeAnchors()
|
||||
self:AddAnchor("CENTER", self._contextTable.centerX, self._contextTable.centerY)
|
||||
|
||||
self.__super:Draw()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function ApplicationFrame._SavePositionAndSize(self, wasScaling)
|
||||
local frame = self:_GetBaseFrame()
|
||||
local parentFrame = frame:GetParent()
|
||||
local width = frame:GetWidth()
|
||||
local height = frame:GetHeight()
|
||||
if wasScaling then
|
||||
-- the anchor is in our old frame's scale, so convert the parent measurements to our old scale and then the resuslt to our new scale
|
||||
local scaleAdjustment = width / self._contextTable.width
|
||||
local frameLeftOffset = frame:GetLeft() - parentFrame:GetLeft() / self._contextTable.scale
|
||||
self._contextTable.centerX = (frameLeftOffset - (parentFrame:GetWidth() / self._contextTable.scale - width) / 2) / scaleAdjustment
|
||||
local frameBottomOffset = frame:GetBottom() - parentFrame:GetBottom() / self._contextTable.scale
|
||||
self._contextTable.centerY = (frameBottomOffset - (parentFrame:GetHeight() / self._contextTable.scale - height) / 2) / scaleAdjustment
|
||||
self._contextTable.scale = self._contextTable.scale * scaleAdjustment
|
||||
else
|
||||
self._contextTable.width = width
|
||||
self._contextTable.height = height
|
||||
-- the anchor is in our frame's scale, so convert the parent measurements to our scale
|
||||
local frameLeftOffset = frame:GetLeft() - parentFrame:GetLeft() / self._contextTable.scale
|
||||
self._contextTable.centerX = (frameLeftOffset - (parentFrame:GetWidth() / self._contextTable.scale - width) / 2)
|
||||
local frameBottomOffset = frame:GetBottom() - parentFrame:GetBottom() / self._contextTable.scale
|
||||
self._contextTable.centerY = (frameBottomOffset - (parentFrame:GetHeight() / self._contextTable.scale - height) / 2)
|
||||
end
|
||||
end
|
||||
|
||||
function ApplicationFrame._SetResizing(self, resizing)
|
||||
if resizing then
|
||||
self:GetElement("titleFrame"):Hide()
|
||||
self._contentFrame:_GetBaseFrame():SetAlpha(0)
|
||||
self._contentFrame:_GetBaseFrame():SetFrameStrata("LOW")
|
||||
self._contentFrame:Draw()
|
||||
else
|
||||
self:GetElement("titleFrame"):Show()
|
||||
self._contentFrame:_GetBaseFrame():SetAlpha(1)
|
||||
self._contentFrame:_GetBaseFrame():SetFrameStrata(self._strata)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.CloseButtonOnClick(button)
|
||||
button:GetElement("__parent.__parent"):Hide()
|
||||
end
|
||||
|
||||
function private.ResizeButtonOnEnter(self)
|
||||
local tooltip = L["Click and drag to resize this window."].."\n"..
|
||||
L["Hold SHIFT while dragging to scale the window instead."].."\n"..
|
||||
L["Right-Click to reset the window size, scale, and position to their defaults."]
|
||||
Tooltip.Show(self:_GetBaseFrame().resizeBtn, tooltip, true)
|
||||
end
|
||||
|
||||
function private.ResizeButtonOnLeave(self)
|
||||
Tooltip.Hide()
|
||||
end
|
||||
|
||||
function private.ResizeButtonOnMouseDown(self, mouseButton)
|
||||
if mouseButton ~= "LeftButton" then
|
||||
return
|
||||
end
|
||||
self._isScaling = IsShiftKeyDown()
|
||||
local frame = self:_GetBaseFrame()
|
||||
local width = frame:GetWidth()
|
||||
local height = frame:GetHeight()
|
||||
if self._isScaling then
|
||||
local minWidth = width * MIN_SCALE / self._contextTable.scale
|
||||
local minHeight = height * MIN_SCALE / self._contextTable.scale
|
||||
frame:SetMinResize(minWidth, minHeight)
|
||||
frame:SetMaxResize(width * 10, height * 10)
|
||||
else
|
||||
frame:SetMinResize(self._minWidth, self._minHeight)
|
||||
frame:SetMaxResize(width * 10, height * 10)
|
||||
end
|
||||
self:_SetResizing(true)
|
||||
frame:StartSizing("BOTTOMRIGHT")
|
||||
-- force updating the size here, to prevent using cached values from previously opened application frames
|
||||
frame:SetWidth(width)
|
||||
frame:SetHeight(height)
|
||||
end
|
||||
|
||||
function private.ResizeButtonOnMouseUp(self, mouseButton)
|
||||
if mouseButton ~= "LeftButton" then
|
||||
return
|
||||
end
|
||||
self:_GetBaseFrame():StopMovingOrSizing()
|
||||
self:_SetResizing(false)
|
||||
self:_SavePositionAndSize(self._isScaling)
|
||||
self._isScaling = nil
|
||||
self:Draw()
|
||||
end
|
||||
|
||||
function private.ResizeButtonOnClick(self, mouseButton)
|
||||
if mouseButton ~= "RightButton" then
|
||||
return
|
||||
end
|
||||
self._contextTable.scale = self._defaultContextTable.scale
|
||||
self._contextTable.width = self._defaultContextTable.width
|
||||
self._contextTable.height = self._defaultContextTable.height
|
||||
self._contextTable.centerX = self._defaultContextTable.centerX
|
||||
self._contextTable.centerY = self._defaultContextTable.centerY
|
||||
self:Draw()
|
||||
end
|
||||
|
||||
function private.FrameOnDragStart(self)
|
||||
self:_GetBaseFrame():StartMoving()
|
||||
end
|
||||
|
||||
function private.FrameOnDragStop(self)
|
||||
self:_GetBaseFrame():StopMovingOrSizing()
|
||||
self:_SavePositionAndSize()
|
||||
self:Draw()
|
||||
end
|
||||
|
||||
function private.DialogOnMouseUp(dialog)
|
||||
local self = dialog:GetParentElement():GetParentElement()
|
||||
self:HideDialog()
|
||||
end
|
||||
|
||||
function private.DialogOnHide(dialog)
|
||||
local context = dialog:GetContext()
|
||||
if context then
|
||||
TempTable.Release(context)
|
||||
end
|
||||
end
|
||||
|
||||
function private.DialogCancelBtnOnClick(button)
|
||||
local self = button:GetBaseElement()
|
||||
self:HideDialog()
|
||||
end
|
||||
|
||||
function private.DialogConfirmBtnOnClick(button)
|
||||
local self = button:GetBaseElement()
|
||||
local dialogFrame = button:GetParentElement():GetParentElement()
|
||||
local context = dialogFrame:GetContext()
|
||||
dialogFrame:SetContext(nil)
|
||||
self:HideDialog()
|
||||
context.callback(TempTable.UnpackAndRelease(context))
|
||||
end
|
||||
|
||||
function private.CreateMenuDialogFrame(id, iter)
|
||||
local frame = UIElements.New("Frame", id)
|
||||
:SetLayout("VERTICAL")
|
||||
:SetWidth(180)
|
||||
:SetPadding(2)
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT")
|
||||
:SetBorderColor("ACTIVE_BG_ALT")
|
||||
local numRows = 0
|
||||
for i, text, subIter in iter, private.menuDialogContext.context do
|
||||
frame:AddChild(UIElements.New("Frame", "row"..i)
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(21)
|
||||
:SetContext(subIter)
|
||||
:AddChild(UIElements.New("Button", "btn")
|
||||
:SetHeight(21)
|
||||
:SetPadding(8, 0, 0, 0)
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetBackground("PRIMARY_BG_ALT")
|
||||
:SetHighlightEnabled(true)
|
||||
:SetJustifyH("LEFT")
|
||||
:SetIcon(subIter and "iconPack.12x12/Chevron/Right" or nil, subIter and "RIGHT" or nil)
|
||||
:SetText(text)
|
||||
:SetContext(i)
|
||||
:SetScript("OnEnter", subIter and private.MenuDialogRowSubIterOnEnter or private.MenuDialogRowDefaultOnEnter)
|
||||
:SetScript("OnClick", not subIter and private.MenuDialogRowOnClick or nil)
|
||||
)
|
||||
)
|
||||
numRows = numRows + 1
|
||||
end
|
||||
frame:SetHeight(4 + numRows * 21)
|
||||
return frame
|
||||
end
|
||||
|
||||
function private.MenuDialogRowOnClick(button)
|
||||
local path = TempTable.Acquire()
|
||||
tinsert(path, button:GetContext())
|
||||
local parentFrame = button:GetParentElement():GetParentElement():GetParentElement()
|
||||
local self = parentFrame:GetBaseElement()
|
||||
while parentFrame:GetParentElement() ~= self._contentFrame do
|
||||
local selectedButton = parentFrame:GetContext():GetElement("btn")
|
||||
tinsert(path, 1, selectedButton:GetContext())
|
||||
parentFrame = parentFrame:GetParentElement()
|
||||
end
|
||||
private.menuDialogContext.clickCallback(button, private.menuDialogContext.context, TempTable.UnpackAndRelease(path))
|
||||
end
|
||||
|
||||
function private.MenuDialogRowDefaultOnEnter(button)
|
||||
local frame = button:GetParentElement():GetParentElement()
|
||||
if frame:HasChildById("subFrame") then
|
||||
local subFrame = frame:GetElement("subFrame")
|
||||
local prevRow = frame:GetContext()
|
||||
frame:SetContext(nil)
|
||||
if prevRow then
|
||||
prevRow:GetElement("btn"):SetHighlightLocked(false)
|
||||
end
|
||||
frame:RemoveChild(subFrame)
|
||||
subFrame:Release()
|
||||
frame:Draw()
|
||||
end
|
||||
end
|
||||
|
||||
function private.MenuDialogRowSubIterOnEnter(button)
|
||||
private.MenuDialogRowDefaultOnEnter(button)
|
||||
button:SetHighlightLocked(true)
|
||||
local row = button:GetParentElement()
|
||||
local frame = row:GetParentElement()
|
||||
frame:SetContext(row)
|
||||
local subFrame = private.CreateMenuDialogFrame("subFrame", row:GetContext())
|
||||
:AddAnchor("TOPLEFT", button:_GetBaseFrame(), "TOPRIGHT", 4, 2)
|
||||
frame:AddChildNoLayout(subFrame)
|
||||
subFrame:Draw()
|
||||
end
|
||||
|
||||
function private.GetAppStatusTooltip()
|
||||
local tooltipLines = TempTable.Acquire()
|
||||
tinsert(tooltipLines, format(L["TSM Desktop App Status (%s)"], TSM.GetRegion().."-"..GetRealmName()))
|
||||
|
||||
local appUpdateAge = time() - TSM.GetAppUpdateTime()
|
||||
if appUpdateAge < 2 * SECONDS_PER_HOUR then
|
||||
tinsert(tooltipLines, Theme.GetFeedbackColor("GREEN"):ColorText(format(L["App Synced %s Ago"], SecondsToTime(appUpdateAge))))
|
||||
elseif appUpdateAge < 2 * SECONDS_PER_DAY then
|
||||
tinsert(tooltipLines, Theme.GetFeedbackColor("YELLOW"):ColorText(format(L["App Synced %s Ago"], SecondsToTime(appUpdateAge))))
|
||||
else
|
||||
tinsert(tooltipLines, Theme.GetFeedbackColor("RED"):ColorText(L["App Not Synced"]))
|
||||
end
|
||||
|
||||
local auctionDBRealmTime, auctionDBRegionTime = TSM.AuctionDB.GetAppDataUpdateTimes()
|
||||
local auctionDBRealmAge = time() - auctionDBRealmTime
|
||||
local auctionDBRegionAge = time() - auctionDBRegionTime
|
||||
if auctionDBRealmAge < 4 * SECONDS_PER_HOUR then
|
||||
tinsert(tooltipLines, Theme.GetFeedbackColor("GREEN"):ColorText(format(L["AuctionDB Realm Data is %s Old"], SecondsToTime(auctionDBRealmAge))))
|
||||
elseif auctionDBRealmAge < 2 * SECONDS_PER_DAY then
|
||||
tinsert(tooltipLines, Theme.GetFeedbackColor("YELLOW"):ColorText(format(L["AuctionDB Realm Data is %s Old"], SecondsToTime(auctionDBRealmAge))))
|
||||
else
|
||||
tinsert(tooltipLines, Theme.GetFeedbackColor("RED"):ColorText(L["No AuctionDB Realm Data"]))
|
||||
end
|
||||
if auctionDBRegionAge < 2 * SECONDS_PER_DAY then
|
||||
tinsert(tooltipLines, Theme.GetFeedbackColor("GREEN"):ColorText(format(L["AuctionDB Region Data is %s Old"], SecondsToTime(auctionDBRegionAge))))
|
||||
else
|
||||
tinsert(tooltipLines, Theme.GetFeedbackColor("RED"):ColorText(L["No AuctionDB Region Data"]))
|
||||
end
|
||||
|
||||
return strjoin("\n", TempTable.UnpackAndRelease(tooltipLines)), true, 16
|
||||
end
|
||||
189
Core/UI/Elements/ApplicationGroupTree.lua
Normal file
189
Core/UI/Elements/ApplicationGroupTree.lua
Normal file
@@ -0,0 +1,189 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- ApplicationGroupTree UI Element Class.
|
||||
-- An application group tree displays the group tree in a way which allows the user to select any number of them. This
|
||||
-- element is used wherever the user needs to select groups to perform some action on. It is a subclass of the
|
||||
-- @{GroupTree} class.
|
||||
-- @classmod ApplicationGroupTree
|
||||
|
||||
local _, TSM = ...
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local ApplicationGroupTree = TSM.Include("LibTSMClass").DefineClass("ApplicationGroupTree", TSM.UI.GroupTree)
|
||||
UIElements.Register(ApplicationGroupTree)
|
||||
TSM.UI.ApplicationGroupTree = ApplicationGroupTree
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function ApplicationGroupTree.__init(self)
|
||||
self.__super:__init()
|
||||
self._selectedGroupsChangedHandler = nil
|
||||
end
|
||||
|
||||
function ApplicationGroupTree.Release(self)
|
||||
self._selectedGroupsChangedHandler = nil
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
--- Registers a script handler.
|
||||
-- @tparam ApplicationGroupTree self The application group tree object
|
||||
-- @tparam string script The script to register for (supported scripts: `OnGroupSelectionChanged`)
|
||||
-- @tparam function handler The script handler which will be called with the application group tree object followed by
|
||||
-- any arguments to the script
|
||||
-- @treturn ApplicationGroupTree The application group tree object
|
||||
function ApplicationGroupTree.SetScript(self, script, handler)
|
||||
if script == "OnGroupSelectionChanged" then
|
||||
self._selectedGroupsChangedHandler = handler
|
||||
else
|
||||
error("Unknown ApplicationGroupTree script: "..tostring(script))
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Iterates through the selected groups.
|
||||
-- @tparam ApplicationGroupTree self The application group tree object
|
||||
-- @return Iterator with the following fields: `index, groupPath`
|
||||
function ApplicationGroupTree.SelectedGroupsIterator(self)
|
||||
local groups = TempTable.Acquire()
|
||||
for _, groupPath in ipairs(self._allData) do
|
||||
if self:_IsSelected(groupPath) then
|
||||
tinsert(groups, groupPath)
|
||||
end
|
||||
end
|
||||
return TempTable.Iterator(groups)
|
||||
end
|
||||
|
||||
--- Sets the context table.
|
||||
-- This table can be used to preserve selection state across lifecycles of the application group tree and even WoW
|
||||
-- sessions if it's within the settings DB.
|
||||
-- @see GroupTree.SetContextTable
|
||||
-- @tparam ApplicationGroupTree self The application group tree object
|
||||
-- @tparam table tbl The context table
|
||||
-- @tparam table defaultTbl The default table (required fields: `unselected` OR `selected`, `collapsed`)
|
||||
-- @treturn ApplicationGroupTree The application group tree object
|
||||
function ApplicationGroupTree.SetContextTable(self, tbl, defaultTbl)
|
||||
if defaultTbl.unselected then
|
||||
assert(type(defaultTbl.unselected) == "table" and not defaultTbl.selected)
|
||||
tbl.unselected = tbl.unselected or CopyTable(defaultTbl.unselected)
|
||||
tbl.selected = nil
|
||||
else
|
||||
assert(type(defaultTbl.selected) == "table" and not defaultTbl.unselected)
|
||||
tbl.selected = tbl.selected or CopyTable(defaultTbl.selected)
|
||||
tbl.unselected = nil
|
||||
end
|
||||
self.__super:SetContextTable(tbl, defaultTbl)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Gets whether or not a group is currently selected.
|
||||
-- @tparam ApplicationGroupTree self The application group tree object
|
||||
-- @tparam string groupPath The group to check
|
||||
-- @treturn boolean Whether or not the group is selected
|
||||
function ApplicationGroupTree.IsGroupSelected(self, groupPath)
|
||||
return self:_IsSelected(groupPath)
|
||||
end
|
||||
|
||||
--- Gets whether or not a group is currently selected.
|
||||
-- @tparam ApplicationGroupTree self The application group tree object
|
||||
-- @tparam string groupPath The group to set the selected state of
|
||||
-- @tparam boolean selected Whether or not the group should be selected
|
||||
-- @treturn ApplicationGroupTree The application group tree object
|
||||
function ApplicationGroupTree.SetGroupSelected(self, groupPath, selected)
|
||||
self:_SetSelected(groupPath, selected)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Gets whether or not the selection is cleared.
|
||||
-- @tparam ApplicationGroupTree self The application group tree object
|
||||
-- @tparam[opt=false] boolean updateData Whether or not to update the data first
|
||||
-- @treturn boolean Whether or not the selection is cleared
|
||||
function ApplicationGroupTree.IsSelectionCleared(self, updateData)
|
||||
if updateData then
|
||||
self:_UpdateData()
|
||||
end
|
||||
for _, groupPath in ipairs(self._searchStr == "" and self._allData or self._data) do
|
||||
if self:_IsSelected(groupPath) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--- Toggle the selection state of the application group tree.
|
||||
-- @tparam ApplicationGroupTree self The application group tree object
|
||||
-- @treturn ApplicationGroupTree The application group tree object
|
||||
function ApplicationGroupTree.ToggleSelectAll(self)
|
||||
local isCleared = self:IsSelectionCleared()
|
||||
for _, groupPath in ipairs(self._searchStr == "" and self._allData or self._data) do
|
||||
self:_SetSelected(groupPath, isCleared)
|
||||
end
|
||||
self:Draw()
|
||||
if self._selectedGroupsChangedHandler then
|
||||
self:_selectedGroupsChangedHandler()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function ApplicationGroupTree._UpdateData(self)
|
||||
self.__super:_UpdateData()
|
||||
-- remove data which is no longer present from _contextTable
|
||||
local selectedGroups = TempTable.Acquire()
|
||||
for _, groupPath in ipairs(self._allData) do
|
||||
if self:_IsSelected(groupPath) then
|
||||
selectedGroups[groupPath] = true
|
||||
end
|
||||
end
|
||||
wipe(self._contextTable.selected or self._contextTable.unselected)
|
||||
for _, groupPath in ipairs(self._allData) do
|
||||
self:_SetSelected(groupPath, selectedGroups[groupPath])
|
||||
end
|
||||
TempTable.Release(selectedGroups)
|
||||
end
|
||||
|
||||
function ApplicationGroupTree._IsSelected(self, data)
|
||||
if self._contextTable.unselected then
|
||||
return not self._contextTable.unselected[data]
|
||||
else
|
||||
return self._contextTable.selected[data]
|
||||
end
|
||||
end
|
||||
|
||||
function ApplicationGroupTree._SetSelected(self, data, selected)
|
||||
if self._contextTable.unselected then
|
||||
self._contextTable.unselected[data] = not selected or nil
|
||||
else
|
||||
self._contextTable.selected[data] = selected or nil
|
||||
end
|
||||
end
|
||||
|
||||
function ApplicationGroupTree._HandleRowClick(self, data, mouseButton)
|
||||
if mouseButton == "RightButton" then
|
||||
self.__super:_HandleRowClick(data, mouseButton)
|
||||
return
|
||||
end
|
||||
self:_SetSelected(data, not self:_IsSelected(data))
|
||||
-- also set the selection for all child groups to the same as this group
|
||||
for _, groupPath in ipairs(self._allData) do
|
||||
if TSM.Groups.Path.IsChild(groupPath, data) and data ~= TSM.CONST.ROOT_GROUP_PATH then
|
||||
self:_SetSelected(groupPath, self:_IsSelected(data))
|
||||
end
|
||||
end
|
||||
self:Draw()
|
||||
if self._selectedGroupsChangedHandler then
|
||||
self:_selectedGroupsChangedHandler()
|
||||
end
|
||||
end
|
||||
1005
Core/UI/Elements/AuctionScrollingTable.lua
Normal file
1005
Core/UI/Elements/AuctionScrollingTable.lua
Normal file
File diff suppressed because it is too large
Load Diff
245
Core/UI/Elements/BaseDropdown.lua
Normal file
245
Core/UI/Elements/BaseDropdown.lua
Normal file
@@ -0,0 +1,245 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- Base Dropdown UI Element Class.
|
||||
-- The base dropdown class is an abstract class which provides shared functionality between the @{SelectionDropdown} and
|
||||
-- @{MultiselectionDropdown} classes. It is a subclass of the @{Text} class.
|
||||
-- @classmod BaseDropdown
|
||||
|
||||
local _, TSM = ...
|
||||
local NineSlice = TSM.Include("Util.NineSlice")
|
||||
local Color = TSM.Include("Util.Color")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local ScriptWrapper = TSM.Include("Util.ScriptWrapper")
|
||||
local BaseDropdown = TSM.Include("LibTSMClass").DefineClass("BaseDropdown", TSM.UI.Text, "ABSTRACT")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(BaseDropdown)
|
||||
TSM.UI.BaseDropdown = BaseDropdown
|
||||
local private = {}
|
||||
local EXPANDER_SIZE = 18
|
||||
local TEXT_PADDING = 8
|
||||
local EXPANDER_PADDING = 8
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Meta Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function BaseDropdown.__init(self)
|
||||
local frame = UIElements.CreateFrame(self, "Button", nil, nil, nil)
|
||||
|
||||
self.__super:__init(frame)
|
||||
|
||||
self._nineSlice = NineSlice.New(frame)
|
||||
|
||||
ScriptWrapper.Set(frame, "OnClick", private.FrameOnClick, self)
|
||||
frame.arrow = frame:CreateTexture(nil, "ARTWORK")
|
||||
|
||||
self._widthText = UIElements.CreateFontString(self, frame)
|
||||
self._widthText:Hide()
|
||||
|
||||
self._font = "BODY_BODY2"
|
||||
self._hintText = ""
|
||||
self._items = {}
|
||||
self._itemKeyLookup = {}
|
||||
self._disabled = false
|
||||
self._isOpen = false
|
||||
self._onSelectionChangedHandler = nil
|
||||
end
|
||||
|
||||
function BaseDropdown.Release(self)
|
||||
self._hintText = ""
|
||||
wipe(self._items)
|
||||
wipe(self._itemKeyLookup)
|
||||
self._disabled = false
|
||||
self._isOpen = false
|
||||
self._onSelectionChangedHandler = nil
|
||||
self:_GetBaseFrame():Enable()
|
||||
self.__super:Release()
|
||||
self._font = "BODY_BODY2"
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
--- Sets the hint text which is shown when there's no selection.
|
||||
-- @tparam BaseDropdown self The dropdown object
|
||||
-- @tparam string text The hint text string
|
||||
-- @treturn BaseDropdown The dropdown object
|
||||
function BaseDropdown.SetHintText(self, text)
|
||||
self._hintText = text
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add an item to be shown in the dropdown dialog list.
|
||||
-- @tparam BaseDropdown self The dropdown object
|
||||
-- @tparam string item The item to add to the list (localized string)
|
||||
-- @tparam[opt] string|number itemKey The internal representation of the item (if not specified will be the index)
|
||||
-- @treturn BaseDropdown The dropdown object
|
||||
function BaseDropdown.AddItem(self, item, itemKey)
|
||||
tinsert(self._items, item)
|
||||
self._itemKeyLookup[item] = itemKey or #self._items
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the items to show in the dropdown dialog list.
|
||||
-- @tparam BaseDropdown self The dropdown object
|
||||
-- @tparam table items A list of items to be shown in the dropdown list
|
||||
-- @tparam[opt] table itemKeys A list of keys which go with the item at the corresponding index in the items list
|
||||
-- @treturn BaseDropdown The dropdown object
|
||||
function BaseDropdown.SetItems(self, items, itemKeys)
|
||||
wipe(self._items)
|
||||
wipe(self._itemKeyLookup)
|
||||
assert(not itemKeys or #itemKeys == #items)
|
||||
for i, item in ipairs(items) do
|
||||
self:AddItem(item, itemKeys and itemKeys[i])
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether or not the dropdown is disabled.
|
||||
-- @tparam BaseDropdown self The dropdown object
|
||||
-- @tparam boolean disabled Whether or not to disable the dropdown
|
||||
-- @treturn BaseDropdown The dropdown object
|
||||
function BaseDropdown.SetDisabled(self, disabled)
|
||||
self._disabled = disabled
|
||||
if disabled then
|
||||
self:_GetBaseFrame():Disable()
|
||||
else
|
||||
self:_GetBaseFrame():Enable()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Registers a script handler.
|
||||
-- @tparam BaseDropdown self The dropdown object
|
||||
-- @tparam string script The script to register for (supported scripts: `OnSelectionChanged`)
|
||||
-- @tparam function handler The script handler which will be called with the dropdown object followed by any arguments
|
||||
-- to the script
|
||||
-- @treturn BaseDropdown The dropdown object
|
||||
function BaseDropdown.SetScript(self, script, handler)
|
||||
if script == "OnSelectionChanged" then
|
||||
self._onSelectionChangedHandler = handler
|
||||
else
|
||||
error("Invalid BaseDropdown script: "..tostring(script))
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets whether or not the dropdown is open.
|
||||
-- @tparam BaseDropdown self The dropdown object
|
||||
-- @tparam boolean open Whether or not the dropdown is open
|
||||
-- @treturn BaseDropdown The dropdown object
|
||||
function BaseDropdown.SetOpen(self, open)
|
||||
assert(type(open) == "boolean")
|
||||
if open == self._isOpen then
|
||||
return self
|
||||
end
|
||||
self._isOpen = open
|
||||
if open then
|
||||
local width, height = self:_GetDialogSize()
|
||||
local dialogFrame = UIElements.New("Frame", "dropdown")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetContext(self)
|
||||
:AddAnchor("TOPLEFT", self:_GetBaseFrame(), "BOTTOMLEFT", 0, -4)
|
||||
:SetPadding(0, 0, 4, 4)
|
||||
:SetBackgroundColor("ACTIVE_BG", true)
|
||||
:SetSize(max(width, self:_GetDimension("WIDTH")), height)
|
||||
:SetScript("OnHide", private.DialogOnHide)
|
||||
self:_AddDialogChildren(dialogFrame)
|
||||
dialogFrame:GetElement("list"):SetScript("OnSelectionChanged", private.ListOnSelectionChanged)
|
||||
self:GetBaseElement():ShowDialogFrame(dialogFrame)
|
||||
else
|
||||
self:GetBaseElement():HideDialog()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function BaseDropdown.SetText(self)
|
||||
error("BaseDropdown does not support this method")
|
||||
end
|
||||
|
||||
function BaseDropdown.SetTextColor(self, color)
|
||||
error("BaseDropdown does not support this method")
|
||||
end
|
||||
|
||||
function BaseDropdown.Draw(self)
|
||||
self.__super:SetText(self:_GetCurrentSelectionString())
|
||||
self.__super:Draw()
|
||||
local frame = self:_GetBaseFrame()
|
||||
TSM.UI.TexturePacks.SetTexture(frame.arrow, "iconPack.18x18/Chevron/Down")
|
||||
local frameHeight = frame:GetHeight()
|
||||
local paddingX = EXPANDER_PADDING
|
||||
local paddingY = (frameHeight - EXPANDER_SIZE) / 2
|
||||
frame.text:ClearAllPoints()
|
||||
frame.text:SetPoint("TOPLEFT", TEXT_PADDING, 0)
|
||||
frame.text:SetPoint("BOTTOMRIGHT", -EXPANDER_SIZE, 0)
|
||||
frame.arrow:ClearAllPoints()
|
||||
frame.arrow:SetPoint("BOTTOMLEFT", frame.text, "BOTTOMRIGHT", -paddingX, paddingY)
|
||||
frame.arrow:SetPoint("TOPRIGHT", -paddingX, -paddingY)
|
||||
|
||||
-- set textures and text color depending on the state
|
||||
self._nineSlice:SetStyle("rounded")
|
||||
local textColor = self:_GetTextColor()
|
||||
frame.text:SetTextColor(textColor:GetFractionalRGBA())
|
||||
self._nineSlice:SetVertexColor(Theme.GetColor(self._disabled and "PRIMARY_BG_ALT" or "ACTIVE_BG"):GetFractionalRGBA())
|
||||
frame.arrow:SetVertexColor(textColor:GetFractionalRGBA())
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function BaseDropdown._GetTextColor(self)
|
||||
local color = Theme.GetColor(self._disabled and "PRIMARY_BG_ALT" or "ACTIVE_BG")
|
||||
-- the text color should have maximum contrast with the dropdown color, so set it to white/black based on the dropdown color
|
||||
if color:IsLight() then
|
||||
-- the dropdown is light, so set the text to black
|
||||
return Color.GetFullBlack():GetTint(self._disabled and "-DISABLED" or 0)
|
||||
else
|
||||
-- the dropdown is dark, so set the text to white
|
||||
return Color.GetFullWhite():GetTint(self._disabled and "+DISABLED" or 0)
|
||||
end
|
||||
end
|
||||
|
||||
function BaseDropdown._GetDialogSize(self)
|
||||
local maxStringWidth = 100 -- no smaller than 100
|
||||
self._widthText:Show()
|
||||
self._widthText:SetFont(Theme.GetFont(self._font):GetWowFont())
|
||||
for _, item in ipairs(self._items) do
|
||||
self._widthText:SetText(item)
|
||||
maxStringWidth = max(maxStringWidth, self._widthText:GetUnboundedStringWidth())
|
||||
end
|
||||
self._widthText:Hide()
|
||||
return maxStringWidth + Theme.GetColSpacing() * 2, 8 + max(16, min(8, #self._items) * 20)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.FrameOnClick(self)
|
||||
self:SetOpen(true)
|
||||
end
|
||||
|
||||
function private.ListOnSelectionChanged(dropdownList, selection)
|
||||
local self = dropdownList:GetParentElement():GetContext()
|
||||
self:_OnListSelectionChanged(dropdownList, selection)
|
||||
self:Draw()
|
||||
end
|
||||
|
||||
function private.DialogOnHide(frame)
|
||||
local self = frame:GetContext()
|
||||
self._isOpen = false
|
||||
end
|
||||
584
Core/UI/Elements/BaseInput.lua
Normal file
584
Core/UI/Elements/BaseInput.lua
Normal file
@@ -0,0 +1,584 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- Base Input UI Element Class.
|
||||
-- The base input class is an abstract class which provides shared functionality between the @{Input} and
|
||||
-- @{MultiLineInput} classes. It is a subclass of the @{Element} class.
|
||||
-- @classmod BaseInput
|
||||
|
||||
local _, TSM = ...
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local NineSlice = TSM.Include("Util.NineSlice")
|
||||
local Log = TSM.Include("Util.Log")
|
||||
local Color = TSM.Include("Util.Color")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local Delay = TSM.Include("Util.Delay")
|
||||
local ScriptWrapper = TSM.Include("Util.ScriptWrapper")
|
||||
local CustomPrice = TSM.Include("Service.CustomPrice")
|
||||
local BaseInput = TSM.Include("LibTSMClass").DefineClass("BaseInput", TSM.UI.Element, "ABSTRACT")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(BaseInput)
|
||||
TSM.UI.BaseInput = BaseInput
|
||||
local private = {}
|
||||
local BORDER_THICKNESS = 1
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function BaseInput.__init(self, frame)
|
||||
self.__super:__init(frame)
|
||||
|
||||
self._borderNineSlice = NineSlice.New(frame)
|
||||
self._borderNineSlice:Hide()
|
||||
|
||||
self._backgroundNineSlice = NineSlice.New(frame, 1)
|
||||
self._backgroundNineSlice:Hide()
|
||||
|
||||
self._editBox:SetShadowColor(0, 0, 0, 0)
|
||||
self._editBox:SetAutoFocus(false)
|
||||
ScriptWrapper.Set(self._editBox, "OnEscapePressed", private.OnEscapePressed, self)
|
||||
ScriptWrapper.Set(self._editBox, "OnTabPressed", private.OnTabPressed, self)
|
||||
ScriptWrapper.Set(self._editBox, "OnEditFocusGained", private.OnEditFocusGained, self)
|
||||
ScriptWrapper.Set(self._editBox, "OnEditFocusLost", private.OnEditFocusLost, self)
|
||||
ScriptWrapper.Set(self._editBox, "OnChar", self._OnChar, self)
|
||||
|
||||
self._lostFocusDelayName = "INPUT_LOST_FOCUS_"..tostring(frame)
|
||||
self._backgroundColor = "ACTIVE_BG_ALT"
|
||||
self._borderColor = nil
|
||||
self._value = ""
|
||||
self._escValue = nil
|
||||
self._justifyH = "LEFT"
|
||||
self._justifyV = "MIDDLE"
|
||||
self._font = "BODY_BODY2"
|
||||
self._pasteMode = nil
|
||||
self._validateFunc = nil
|
||||
self._validateContext = nil
|
||||
self._settingTable = nil
|
||||
self._settingKey = nil
|
||||
self._disabled = false
|
||||
self._isValid = true
|
||||
self._tabPrevPath = nil
|
||||
self._tabNextPath = nil
|
||||
self._onValueChangedHandler = nil
|
||||
self._onEnterPressedHandler = nil
|
||||
self._onValidationChangedHandler = nil
|
||||
self._onFocusLostHandler = nil
|
||||
self._pasteChars = {}
|
||||
end
|
||||
|
||||
function BaseInput.Acquire(self)
|
||||
self.__super:Acquire()
|
||||
ScriptWrapper.Set(self._editBox, "OnEnterPressed", private.OnEnterPressed, self)
|
||||
ScriptWrapper.Set(self._editBox, "OnTextChanged", private.OnTextChanged, self)
|
||||
end
|
||||
|
||||
function BaseInput.Release(self)
|
||||
Delay.Cancel(self._lostFocusDelayName)
|
||||
ScriptWrapper.Clear(self._editBox, "OnEnterPressed")
|
||||
ScriptWrapper.Clear(self._editBox, "OnTextChanged")
|
||||
self._editBox:SetText("")
|
||||
self._editBox:ClearFocus()
|
||||
self._editBox:Enable()
|
||||
self._editBox:EnableMouse(true)
|
||||
self._editBox:EnableKeyboard(true)
|
||||
self._editBox:HighlightText(0, 0)
|
||||
self._editBox:SetHitRectInsets(0, 0, 0, 0)
|
||||
self._editBox:SetMaxLetters(2147483647)
|
||||
self._editBox:SetMaxBytes(2147483647)
|
||||
self._backgroundColor = "ACTIVE_BG_ALT"
|
||||
self._borderColor = nil
|
||||
self._value = ""
|
||||
self._escValue = nil
|
||||
self._justifyH = "LEFT"
|
||||
self._justifyV = "MIDDLE"
|
||||
self._font = "BODY_BODY2"
|
||||
self._pasteMode = nil
|
||||
self._validateFunc = nil
|
||||
self._validateContext = nil
|
||||
self._settingTable = nil
|
||||
self._settingKey = nil
|
||||
self._disabled = false
|
||||
self._isValid = true
|
||||
self._tabPrevPath = nil
|
||||
self._tabNextPath = nil
|
||||
self._onValueChangedHandler = nil
|
||||
self._onEnterPressedHandler = nil
|
||||
self._onValidationChangedHandler = nil
|
||||
self._onFocusLostHandler = nil
|
||||
wipe(self._pasteChars)
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
--- Sets the background of the input.
|
||||
-- @tparam BaseInput self The input object
|
||||
-- @tparam ?string|nil color The background color as a theme color key or nil
|
||||
-- @treturn BaseInput The input object
|
||||
function BaseInput.SetBackgroundColor(self, color)
|
||||
assert(color == nil or Theme.GetColor(color))
|
||||
self._backgroundColor = color
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the border of the input.
|
||||
-- @tparam BaseInput self The input object
|
||||
-- @tparam ?string|nil color The border color as a theme color key or nil
|
||||
-- @treturn BaseInput The input object
|
||||
function BaseInput.SetBorderColor(self, color)
|
||||
assert(color == nil or Theme.GetColor(color))
|
||||
self._borderColor = color
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the horizontal justification of the input.
|
||||
-- @tparam BaseInput self The input object
|
||||
-- @tparam string justifyH The horizontal justification (either "LEFT", "CENTER" or "RIGHT")
|
||||
-- @treturn BaseInput The input object
|
||||
function BaseInput.SetJustifyH(self, justifyH)
|
||||
assert(justifyH == "LEFT" or justifyH == "CENTER" or justifyH == "RIGHT")
|
||||
self._justifyH = justifyH
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the vertical justification of the input.
|
||||
-- @tparam BaseInput self The input object
|
||||
-- @tparam string justifyV The vertical justification (either "TOP", "MIDDLE" or "BOTTOM")
|
||||
-- @treturn BaseInput The input object
|
||||
function BaseInput.SetJustifyV(self, justifyV)
|
||||
assert(justifyV == "TOP" or justifyV == "MIDDLE" or justifyV == "BOTTOM")
|
||||
self._justifyV = justifyV
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the font.
|
||||
-- @tparam BaseInput self The input object
|
||||
-- @tparam string font The font key
|
||||
-- @treturn BaseInput The input object
|
||||
function BaseInput.SetFont(self, font)
|
||||
assert(Theme.GetFont(font))
|
||||
self._font = font
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the path of the inputs to jump to when tab (or shift-tab to go backwards) is pressed.
|
||||
-- @tparam BaseInput self The input object
|
||||
-- @tparam string prevPath The path to the previous input (for shift-tab)
|
||||
-- @tparam string nextPath The path to the next input (for tab)
|
||||
-- @treturn BaseInput The input object
|
||||
function BaseInput.SetTabPaths(self, prevPath, nextPath)
|
||||
self._tabPrevPath = prevPath
|
||||
self._tabNextPath = nextPath
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the highlight to all or some of the input's text.
|
||||
-- @tparam BaseInput self The input object
|
||||
-- @tparam number starting The position at which to start the highlight
|
||||
-- @tparam number ending The position at which to stop the highlight
|
||||
-- @treturn BaseInput The input object
|
||||
function BaseInput.HighlightText(self, starting, ending)
|
||||
if starting and ending then
|
||||
self._editBox:HighlightText(starting, ending)
|
||||
else
|
||||
self._editBox:HighlightText()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the current value.
|
||||
-- @tparam BaseInput self The input object
|
||||
-- @tparam string value The value
|
||||
-- @treturn BaseInput The input object
|
||||
function BaseInput.SetValue(self, value)
|
||||
if type(value) == "number" then
|
||||
value = tostring(value)
|
||||
end
|
||||
assert(type(value) == "string")
|
||||
if self:_SetValueHelper(value, true) then
|
||||
self._escValue = self._value
|
||||
else
|
||||
self._escValue = nil
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets whether or not the input is disabled.
|
||||
-- @tparam BaseInput self The input object
|
||||
-- @tparam boolean disabled Whether or not the input is disabled
|
||||
-- @treturn BaseInput The input object
|
||||
function BaseInput.SetDisabled(self, disabled)
|
||||
self._disabled = disabled
|
||||
if disabled then
|
||||
self._editBox:Disable()
|
||||
else
|
||||
self._editBox:Enable()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the function to use to validate the input text.
|
||||
-- @tparam BaseInput self The input object
|
||||
-- @tparam ?string|function validateFunc A function which returns true if the passed text is valid
|
||||
-- or false and an error message if not, or one of the following strings for built in validate
|
||||
-- functions: "CUSTOM_PRICE"
|
||||
-- @param[opt=nil] context Extra context to pass to the validate function. For the built-in
|
||||
-- "CUSTOM_PRICE" function, this is optionally a list of bad sources. For the built-in "NUMBER"
|
||||
-- function, this must be a string such as "0:1000" to specify the min and max values.
|
||||
-- @treturn BaseInput The input object
|
||||
function BaseInput.SetValidateFunc(self, validateFunc, context)
|
||||
if type(validateFunc) == "function" then
|
||||
self._validateFunc = validateFunc
|
||||
self._validateContext = context
|
||||
elseif validateFunc == "CUSTOM_PRICE" then
|
||||
assert(context == nil or type(context) == "table")
|
||||
self._validateFunc = private.CustomPriceValidateFunc
|
||||
self._validateContext = context
|
||||
elseif validateFunc == "NUMBER" then
|
||||
local minVal, maxVal, extra = strsplit(":", context)
|
||||
assert(tonumber(minVal) <= tonumber(maxVal) and not extra)
|
||||
self._validateFunc = private.NumberValidateFunc
|
||||
self._validateContext = context
|
||||
else
|
||||
error("Invalid validateFunc: "..tostring(validateFunc))
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Returns the input's focus state.
|
||||
-- @tparam BaseInput self The input object
|
||||
function BaseInput.HasFocus(self)
|
||||
return self._editBox:HasFocus()
|
||||
end
|
||||
|
||||
--- Sets whether or not this input is focused.
|
||||
-- @tparam BaseInput self The input object
|
||||
-- @tparam boolean focused Whether or not this input is focused
|
||||
-- @treturn BaseInput The input object
|
||||
function BaseInput.SetFocused(self, focused)
|
||||
if focused then
|
||||
self._editBox:SetFocus()
|
||||
else
|
||||
self._editBox:ClearFocus()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Clears the highlight.
|
||||
-- @tparam BaseInput self The input object
|
||||
-- @treturn BaseInput The input object
|
||||
function BaseInput.ClearHighlight(self)
|
||||
self._editBox:HighlightText(0, 0)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the maximum number of letters for the input's entered text.
|
||||
-- @tparam BaseInput self The input object
|
||||
-- @tparam number number The number of letters for entered text
|
||||
-- @treturn BaseInput The input object
|
||||
function BaseInput.SetMaxLetters(self, number)
|
||||
self._editBox:SetMaxLetters(number)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Gets the input value.
|
||||
-- @tparam BaseInput self The input object
|
||||
-- @treturn string The input value
|
||||
function BaseInput.GetValue(self)
|
||||
return self._ignoreEnter and self._value or strtrim(self._value)
|
||||
end
|
||||
|
||||
--- Registers a script handler.
|
||||
-- @tparam BaseInput self The input object
|
||||
-- @tparam string script The script to register for
|
||||
-- @tparam[opt=nil] function handler The script handler which should be called
|
||||
-- @treturn BaseInput The element object
|
||||
function BaseInput.SetScript(self, script, handler)
|
||||
if script == "OnValueChanged" then
|
||||
self._onValueChangedHandler = handler
|
||||
elseif script == "OnEnterPressed" then
|
||||
self._onEnterPressedHandler = handler
|
||||
elseif script == "OnValidationChanged" then
|
||||
self._onValidationChangedHandler = handler
|
||||
elseif script == "OnFocusLost" then
|
||||
self._onFocusLostHandler = handler
|
||||
else
|
||||
error("Invalid base input script: "..tostring(script))
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the setting info.
|
||||
-- This method is used to have the value of the input automatically correspond with the value of a field in a table.
|
||||
-- This is useful for inputs which are tied directly to settings.
|
||||
-- @tparam BaseInput self The input object
|
||||
-- @tparam table tbl The table which the field to set belongs to
|
||||
-- @tparam string key The key into the table to be set based on the input state
|
||||
-- @treturn BaseInput The input object
|
||||
function BaseInput.SetSettingInfo(self, tbl, key)
|
||||
assert(self._value == "")
|
||||
self._settingTable = tbl
|
||||
self._settingKey = key
|
||||
self:SetValue(tbl[key])
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the current validation state.
|
||||
-- @tparam BaseInput self The input object
|
||||
-- @treturn boolean The current valiation state
|
||||
function BaseInput.IsValid(self)
|
||||
return self._isValid
|
||||
end
|
||||
|
||||
--- Sets the input into paste mode for supporting the pasting of large strings.
|
||||
-- @tparam BaseInput self The input object
|
||||
-- @treturn BaseInput The input object
|
||||
function BaseInput.SetPasteMode(self)
|
||||
self._pasteMode = true
|
||||
ScriptWrapper.Clear(self._editBox, "OnTextChanged")
|
||||
self._editBox:SetMaxBytes(1)
|
||||
return self
|
||||
end
|
||||
|
||||
function BaseInput.Draw(self)
|
||||
self.__super:Draw()
|
||||
|
||||
self:_DrawBackgroundAndBorder()
|
||||
|
||||
-- set the font
|
||||
self._editBox:SetFont(Theme.GetFont(self._font):GetWowFont())
|
||||
|
||||
-- set the justification
|
||||
self._editBox:SetJustifyH(self._justifyH)
|
||||
self._editBox:SetJustifyV(self._justifyV)
|
||||
|
||||
-- set the text color
|
||||
self._editBox:SetTextColor(self:_GetTextColor():GetFractionalRGBA())
|
||||
|
||||
-- set the highlight color
|
||||
self._editBox:SetHighlightColor(Theme.GetColor("TEXT%HIGHLIGHT"):GetFractionalRGBA())
|
||||
|
||||
if not self._editBox:HasFocus() then
|
||||
-- set the text
|
||||
self._editBox:SetText(self._value)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function BaseInput._GetTextColor(self, tint)
|
||||
local color = Theme.GetColor(self._disabled and "PRIMARY_BG_ALT" or self._backgroundColor)
|
||||
-- the text color should have maximum contrast with the input color, so set it to white/black based on the input color
|
||||
if color:IsLight() then
|
||||
-- the input is light, so set the text to black
|
||||
return Color.GetFullBlack():GetTint(self._disabled and "-DISABLED" or tint or 0)
|
||||
else
|
||||
-- the input is dark, so set the text to white
|
||||
return Color.GetFullWhite():GetTint(self._disabled and "+DISABLED" or tint or 0)
|
||||
end
|
||||
end
|
||||
|
||||
function BaseInput._SetValueHelper(self, value, noCallback)
|
||||
if not self._validateFunc or self:_validateFunc(strtrim(value), self._validateContext) then
|
||||
self._value = value
|
||||
if self._settingTable then
|
||||
if type(self._settingTable[self._settingKey]) == "number" then
|
||||
value = tonumber(value)
|
||||
assert(value)
|
||||
end
|
||||
self._settingTable[self._settingKey] = value
|
||||
end
|
||||
if not noCallback and self._onValueChangedHandler then
|
||||
self:_onValueChangedHandler()
|
||||
end
|
||||
if not self._isValid then
|
||||
self._isValid = true
|
||||
self:_DrawBackgroundAndBorder()
|
||||
if self._onValidationChangedHandler then
|
||||
self:_onValidationChangedHandler()
|
||||
end
|
||||
end
|
||||
return true
|
||||
else
|
||||
if self._isValid then
|
||||
self._isValid = false
|
||||
self:_DrawBackgroundAndBorder()
|
||||
if self._onValidationChangedHandler then
|
||||
self:_onValidationChangedHandler()
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function BaseInput._DrawBackgroundAndBorder(self)
|
||||
assert(self._backgroundColor)
|
||||
self._backgroundNineSlice:SetStyle("rounded", (self._borderColor or not self._isValid) and BORDER_THICKNESS or nil)
|
||||
self._backgroundNineSlice:SetVertexColor(Theme.GetColor(self._disabled and "PRIMARY_BG_ALT" or self._backgroundColor):GetFractionalRGBA())
|
||||
if self._borderColor or not self._isValid then
|
||||
self._borderNineSlice:SetStyle("rounded")
|
||||
self._borderNineSlice:SetVertexColor((not self._isValid and Theme.GetFeedbackColor("RED") or Theme.GetColor(self._borderColor)):GetFractionalRGBA())
|
||||
else
|
||||
self._borderNineSlice:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
function BaseInput._OnChar(self, c)
|
||||
-- can be overridden
|
||||
if not self._pasteMode then
|
||||
return
|
||||
end
|
||||
tinsert(self._pasteChars, c)
|
||||
ScriptWrapper.Set(self._editBox, "OnUpdate", private.OnUpdate, self)
|
||||
end
|
||||
|
||||
function BaseInput._OnTextChanged(self, value)
|
||||
-- can be overridden
|
||||
end
|
||||
|
||||
function BaseInput._ShouldKeepFocus(self)
|
||||
-- can be overridden
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.OnEscapePressed(self)
|
||||
if self._escValue then
|
||||
self._value = self._escValue
|
||||
assert(self:_SetValueHelper(self._escValue))
|
||||
end
|
||||
self:SetFocused(false)
|
||||
self:HighlightText(0, 0)
|
||||
self:Draw()
|
||||
end
|
||||
|
||||
function private.OnTabPressed(self)
|
||||
local isValid, err = true, nil
|
||||
if self._validateFunc then
|
||||
local value = strtrim(self._editBox:GetText())
|
||||
isValid, err = self:_validateFunc(value, self._validateContext)
|
||||
end
|
||||
if not isValid and err then
|
||||
-- TODO: better way to show the error message?
|
||||
Log.PrintUser(err)
|
||||
end
|
||||
self:SetFocused(false)
|
||||
self:HighlightText(0, 0)
|
||||
if self._tabPrevPath and IsShiftKeyDown() then
|
||||
self:GetElement(self._tabPrevPath):SetFocused(true)
|
||||
elseif self._tabNextPath and not IsShiftKeyDown() then
|
||||
self:GetElement(self._tabNextPath):SetFocused(true)
|
||||
end
|
||||
end
|
||||
|
||||
function private.OnEnterPressed(self)
|
||||
local isValid, err = true, nil
|
||||
if self._validateFunc then
|
||||
local value = strtrim(self._editBox:GetText())
|
||||
isValid, err = self:_validateFunc(value, self._validateContext)
|
||||
end
|
||||
if not isValid and err then
|
||||
-- TODO: better way to show the error message?
|
||||
Log.PrintUser(err)
|
||||
end
|
||||
if isValid then
|
||||
self:SetFocused(false)
|
||||
self:HighlightText(0, 0)
|
||||
if self._onEnterPressedHandler then
|
||||
self:_onEnterPressedHandler()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function private.OnEditFocusGained(self)
|
||||
Delay.Cancel(self._lostFocusDelayName)
|
||||
self:Draw()
|
||||
self:HighlightText()
|
||||
end
|
||||
|
||||
function private.OnEditFocusLost(self)
|
||||
if self:_ShouldKeepFocus() then
|
||||
self:SetFocused(true)
|
||||
return
|
||||
end
|
||||
if self._isValid then
|
||||
self._escValue = self._value
|
||||
end
|
||||
self:HighlightText(0, 0)
|
||||
self:Draw()
|
||||
if not self._isValid then
|
||||
self._isValid = true
|
||||
self:_DrawBackgroundAndBorder()
|
||||
if self._onValidationChangedHandler then
|
||||
self:_onValidationChangedHandler()
|
||||
end
|
||||
end
|
||||
-- wait until the next frame before calling the handler
|
||||
Delay.AfterFrame(self._lostFocusDelayName, 0, private.OnFocusLost, nil, self)
|
||||
end
|
||||
|
||||
function private.OnFocusLost(self)
|
||||
if self:HasFocus() then
|
||||
return
|
||||
end
|
||||
if self._onFocusLostHandler then
|
||||
self:_onFocusLostHandler()
|
||||
end
|
||||
end
|
||||
|
||||
function private.OnTextChanged(self, isUserInput)
|
||||
if not isUserInput then
|
||||
return
|
||||
end
|
||||
local value = self._editBox:GetText()
|
||||
self:_SetValueHelper(value)
|
||||
self:_OnTextChanged(value)
|
||||
end
|
||||
|
||||
function private.OnUpdate(self)
|
||||
ScriptWrapper.Clear(self._editBox, "OnUpdate")
|
||||
local value = table.concat(self._pasteChars)
|
||||
wipe(self._pasteChars)
|
||||
self:_SetValueHelper(value)
|
||||
self:_OnTextChanged(value)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Built In Validate Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.CustomPriceValidateFunc(_, value, badSources)
|
||||
local isValid, err = CustomPrice.Validate(value, badSources)
|
||||
if not isValid then
|
||||
return false, L["Invalid custom price."].." "..err
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function private.NumberValidateFunc(input, value, range)
|
||||
local minValue, maxValue = strsplit(":", range)
|
||||
minValue = tonumber(minValue)
|
||||
maxValue = tonumber(maxValue)
|
||||
value = tonumber(value)
|
||||
if not value then
|
||||
return false, L["Invalid numeric value."]
|
||||
elseif value < minValue or value > maxValue then
|
||||
return false, format(L["Value must be between %d and %d."], minValue, maxValue)
|
||||
end
|
||||
return true
|
||||
end
|
||||
285
Core/UI/Elements/Button.lua
Normal file
285
Core/UI/Elements/Button.lua
Normal file
@@ -0,0 +1,285 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- Button UI Element Class.
|
||||
-- A button is a clickable element which has text drawn over top of it. It is a subclass of the @{Text} class.
|
||||
-- @classmod Button
|
||||
|
||||
local _, TSM = ...
|
||||
local Button = TSM.Include("LibTSMClass").DefineClass("Button", TSM.UI.Text)
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local ItemInfo = TSM.Include("Service.ItemInfo")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(Button)
|
||||
TSM.UI.Button = Button
|
||||
local ICON_SPACING = 4
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function Button.__init(self)
|
||||
local frame = UIElements.CreateFrame(self, "Button")
|
||||
|
||||
self.__super:__init(frame)
|
||||
|
||||
frame.backgroundTexture = frame:CreateTexture(nil, "BACKGROUND")
|
||||
|
||||
-- create the highlight
|
||||
frame.highlight = frame:CreateTexture(nil, "HIGHLIGHT")
|
||||
frame.highlight:SetAllPoints()
|
||||
frame.highlight:SetBlendMode("BLEND")
|
||||
frame:SetHighlightTexture(frame.highlight)
|
||||
|
||||
-- create the icon
|
||||
frame.icon = frame:CreateTexture(nil, "ARTWORK")
|
||||
|
||||
self._font = "BODY_BODY1"
|
||||
self._justifyH = "CENTER"
|
||||
self._background = nil
|
||||
self._iconTexturePack = nil
|
||||
self._iconPosition = nil
|
||||
self._highlightEnabled = false
|
||||
end
|
||||
|
||||
function Button.Acquire(self)
|
||||
self:_GetBaseFrame():Enable()
|
||||
self:_GetBaseFrame():RegisterForClicks("LeftButtonUp")
|
||||
self:_GetBaseFrame():SetHitRectInsets(0, 0, 0, 0)
|
||||
self.__super:Acquire()
|
||||
end
|
||||
|
||||
function Button.Release(self)
|
||||
local frame = self:_GetBaseFrame()
|
||||
frame:UnlockHighlight()
|
||||
self._background = nil
|
||||
self._iconTexturePack = nil
|
||||
self._iconPosition = nil
|
||||
self._highlightEnabled = false
|
||||
self.__super:Release()
|
||||
self._font = "BODY_BODY1"
|
||||
self._justifyH = "CENTER"
|
||||
end
|
||||
|
||||
--- Sets the background of the button.
|
||||
-- @tparam Button self The button object
|
||||
-- @tparam ?string|number|nil background Either a texture pack string, itemString, WoW file id, theme color key, or nil
|
||||
-- @treturn Button The button object
|
||||
function Button.SetBackground(self, background)
|
||||
assert(background == nil or type(background) == "string" or type(background) == "number")
|
||||
self._background = background
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the background and size of the button based on a texture pack string.
|
||||
-- @tparam Button self The button object
|
||||
-- @tparam string texturePack A texture pack string to set the background to and base the size on
|
||||
-- @treturn Button The button object
|
||||
function Button.SetBackgroundAndSize(self, texturePack)
|
||||
self:SetBackground(texturePack)
|
||||
self:SetSize(TSM.UI.TexturePacks.GetSize(texturePack))
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets whether or not the highlight is enabled.
|
||||
-- @tparam Button self The button object
|
||||
-- @tparam boolean enabled Whether or not the highlight is enabled
|
||||
-- @treturn Button The button object
|
||||
function Button.SetHighlightEnabled(self, enabled)
|
||||
self._highlightEnabled = enabled
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the icon that shows within the button.
|
||||
-- @tparam Button self The button object
|
||||
-- @tparam[opt=nil] string texturePack A texture pack string to set the icon and its size to
|
||||
-- @tparam[opt=nil] string position The positin of the icon
|
||||
-- @treturn Button The button object
|
||||
function Button.SetIcon(self, texturePack, position)
|
||||
if texturePack or position then
|
||||
assert(TSM.UI.TexturePacks.IsValid(texturePack))
|
||||
assert(position == "LEFT" or position == "LEFT_NO_TEXT" or position == "CENTER" or position == "RIGHT")
|
||||
self._iconTexturePack = texturePack
|
||||
self._iconPosition = position
|
||||
else
|
||||
self._iconTexturePack = nil
|
||||
self._iconPosition = nil
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether or not the button is disabled.
|
||||
-- @tparam Button self The button object
|
||||
-- @tparam boolean disabled Whether or not the button should be disabled
|
||||
-- @treturn Button The button object
|
||||
function Button.SetDisabled(self, disabled)
|
||||
if disabled then
|
||||
self:_GetBaseFrame():Disable()
|
||||
else
|
||||
self:_GetBaseFrame():Enable()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Registers the button for drag events.
|
||||
-- @tparam Button self The button object
|
||||
-- @tparam string button The mouse button to register for drag events from
|
||||
-- @treturn Button The button object
|
||||
function Button.RegisterForDrag(self, button)
|
||||
self:_GetBaseFrame():RegisterForDrag(button)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Click on the button.
|
||||
-- @tparam Button self The button object
|
||||
function Button.Click(self)
|
||||
self:_GetBaseFrame():Click()
|
||||
end
|
||||
|
||||
--- Enable right-click events for the button.
|
||||
-- @tparam Button self The button object
|
||||
-- @treturn Button The button object
|
||||
function Button.EnableRightClick(self)
|
||||
self:_GetBaseFrame():RegisterForClicks("LeftButtonUp", "RightButtonUp")
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the hit rectangle insets for the button.
|
||||
-- @tparam Button self The button object
|
||||
-- @tparam number left How much the left side of the hit rectangle is inset
|
||||
-- @tparam number right How much the right side of the hit rectangle is inset
|
||||
-- @tparam number top How much the top side of the hit rectangle is inset
|
||||
-- @tparam number bottom How much the bottom side of the hit rectangle is inset
|
||||
-- @treturn Button The button object
|
||||
function Button.SetHitRectInsets(self, left, right, top, bottom)
|
||||
self:_GetBaseFrame():SetHitRectInsets(left, right, top, bottom)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether or not to lock the button's highlight.
|
||||
-- @tparam Button self The action button object
|
||||
-- @tparam boolean locked Whether or not to lock the action button's highlight
|
||||
-- @treturn Button The action button object
|
||||
function Button.SetHighlightLocked(self, locked)
|
||||
if locked then
|
||||
self:_GetBaseFrame():LockHighlight()
|
||||
else
|
||||
self:_GetBaseFrame():UnlockHighlight()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function Button.Draw(self)
|
||||
local frame = self:_GetBaseFrame()
|
||||
frame.text:Show()
|
||||
self.__super:Draw()
|
||||
|
||||
frame.backgroundTexture:SetTexture(nil)
|
||||
frame.backgroundTexture:SetTexCoord(0, 1, 0, 1)
|
||||
frame.backgroundTexture:SetVertexColor(1, 1, 1, 1)
|
||||
|
||||
if self._background == nil then
|
||||
frame.backgroundTexture:Hide()
|
||||
elseif type(self._background) == "string" and TSM.UI.TexturePacks.IsValid(self._background) then
|
||||
-- this is a texture pack
|
||||
frame.backgroundTexture:Show()
|
||||
frame.backgroundTexture:ClearAllPoints()
|
||||
frame.backgroundTexture:SetPoint("CENTER")
|
||||
TSM.UI.TexturePacks.SetTextureAndSize(frame.backgroundTexture, self._background)
|
||||
elseif type(self._background) == "string" and strmatch(self._background, "^[ip]:%d+") then
|
||||
-- this is an itemString
|
||||
frame.backgroundTexture:Show()
|
||||
frame.backgroundTexture:ClearAllPoints()
|
||||
frame.backgroundTexture:SetAllPoints()
|
||||
frame.backgroundTexture:SetTexture(ItemInfo.GetTexture(self._background))
|
||||
elseif type(self._background) == "string" then
|
||||
-- this is a theme color key
|
||||
frame.backgroundTexture:Show()
|
||||
frame.backgroundTexture:ClearAllPoints()
|
||||
frame.backgroundTexture:SetAllPoints()
|
||||
frame.backgroundTexture:SetColorTexture(Theme.GetColor(self._background):GetFractionalRGBA())
|
||||
elseif type(self._background) == "number" then
|
||||
-- this is a wow file id
|
||||
frame.backgroundTexture:Show()
|
||||
frame.backgroundTexture:ClearAllPoints()
|
||||
frame.backgroundTexture:SetAllPoints()
|
||||
frame.backgroundTexture:SetTexture(self._background)
|
||||
else
|
||||
error("Invalid background: "..tostring(self._background))
|
||||
end
|
||||
|
||||
-- set the text color
|
||||
local textColor = frame:IsEnabled() and self:_GetTextColor() or Theme.GetColor("ACTIVE_BG_ALT")
|
||||
frame.text:SetTextColor(textColor:GetFractionalRGBA())
|
||||
|
||||
-- set the highlight texture
|
||||
if self._highlightEnabled then
|
||||
frame.highlight:SetColorTexture(Theme.GetColor(self._background):GetTint("+HOVER"):GetFractionalRGBA())
|
||||
else
|
||||
frame.highlight:SetColorTexture(0, 0, 0, 0)
|
||||
end
|
||||
|
||||
if self._iconTexturePack then
|
||||
TSM.UI.TexturePacks.SetTextureAndSize(frame.icon, self._iconTexturePack)
|
||||
frame.icon:Show()
|
||||
frame.icon:ClearAllPoints()
|
||||
frame.icon:SetVertexColor(textColor:GetFractionalRGBA())
|
||||
local iconWidth = TSM.UI.TexturePacks.GetWidth(self._iconTexturePack) + ICON_SPACING
|
||||
if self._iconPosition == "LEFT" then
|
||||
frame.icon:SetPoint("RIGHT", frame.text, "LEFT", -ICON_SPACING, 0)
|
||||
frame.text:ClearAllPoints()
|
||||
if self._justifyH == "CENTER" then
|
||||
local xOffset = iconWidth / 2
|
||||
frame.text:SetPoint("TOP", xOffset, -self:_GetPadding("TOP"))
|
||||
frame.text:SetPoint("BOTTOM", xOffset, self:_GetPadding("BOTTOM"))
|
||||
frame.text:SetWidth(frame.text:GetStringWidth())
|
||||
elseif self._justifyH == "LEFT" then
|
||||
frame.text:SetPoint("TOPLEFT", iconWidth + self:_GetPadding("LEFT"), -self:_GetPadding("TOP"))
|
||||
frame.text:SetPoint("BOTTOMRIGHT", -self:_GetPadding("RIGHT"), self:_GetPadding("BOTTOM"))
|
||||
else
|
||||
error("Unsupported justifyH: "..tostring(self._justifyH))
|
||||
end
|
||||
elseif self._iconPosition == "LEFT_NO_TEXT" then
|
||||
frame.icon:SetPoint("LEFT", self:_GetPadding("LEFT"), 0)
|
||||
frame.text:ClearAllPoints()
|
||||
frame.text:Hide()
|
||||
elseif self._iconPosition == "CENTER" then
|
||||
frame.icon:SetPoint("CENTER")
|
||||
frame.text:ClearAllPoints()
|
||||
frame.text:Hide()
|
||||
elseif self._iconPosition == "RIGHT" then
|
||||
frame.icon:SetPoint("RIGHT", -self:_GetPadding("RIGHT"), 0)
|
||||
local xOffset = iconWidth
|
||||
frame.text:ClearAllPoints()
|
||||
-- TODO: support non-left-aligned text
|
||||
frame.text:SetPoint("TOPLEFT", self:_GetPadding("LEFT"), -self:_GetPadding("TOP"))
|
||||
frame.text:SetPoint("BOTTOMRIGHT", -xOffset, self:_GetPadding("BOTTOM"))
|
||||
else
|
||||
error("Invalid iconPosition: "..tostring(self._iconPosition))
|
||||
end
|
||||
else
|
||||
frame.icon:Hide()
|
||||
frame.text:ClearAllPoints()
|
||||
frame.text:SetPoint("TOPLEFT", self:_GetPadding("LEFT"), -self:_GetPadding("TOP"))
|
||||
frame.text:SetPoint("BOTTOMRIGHT", -self:_GetPadding("RIGHT"), self:_GetPadding("BOTTOM"))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function Button._GetMinimumDimension(self, dimension)
|
||||
if dimension == "WIDTH" and self._autoWidth then
|
||||
return self:GetStringWidth() + (self._iconTexturePack and TSM.UI.TexturePacks.GetWidth(self._iconTexturePack) or 0)
|
||||
else
|
||||
return self.__super:_GetMinimumDimension(dimension)
|
||||
end
|
||||
end
|
||||
245
Core/UI/Elements/Checkbox.lua
Normal file
245
Core/UI/Elements/Checkbox.lua
Normal file
@@ -0,0 +1,245 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- Checkbox UI Element Class.
|
||||
-- This is a simple checkbox element with an attached description text. It is a subclass of the @{Text} class.
|
||||
-- @classmod Checkbox
|
||||
|
||||
local _, TSM = ...
|
||||
local ScriptWrapper = TSM.Include("Util.ScriptWrapper")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local Checkbox = TSM.Include("LibTSMClass").DefineClass("Checkbox", TSM.UI.Text)
|
||||
UIElements.Register(Checkbox)
|
||||
TSM.UI.Checkbox = Checkbox
|
||||
local private = {}
|
||||
local THEME_TEXTURES = {
|
||||
RADIO = {
|
||||
checked = "iconPack.Misc/Radio/Checked",
|
||||
unchecked = "iconPack.Misc/Radio/Unchecked",
|
||||
},
|
||||
CHECK = {
|
||||
checked = "iconPack.Misc/Checkbox/Checked",
|
||||
unchecked = "iconPack.Misc/Checkbox/Unchecked",
|
||||
},
|
||||
}
|
||||
local CHECKBOX_SPACING = 4
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function Checkbox.__init(self)
|
||||
local frame = UIElements.CreateFrame(self, "Button")
|
||||
self.__super:__init(frame)
|
||||
ScriptWrapper.Set(frame, "OnClick", private.FrameOnClick, self)
|
||||
|
||||
-- create the text and check texture
|
||||
frame.text = UIElements.CreateFontString(self, frame)
|
||||
frame.text:SetJustifyV("MIDDLE")
|
||||
frame.check = frame:CreateTexture()
|
||||
|
||||
self._position = "LEFT"
|
||||
self._theme = "CHECK"
|
||||
self._font = "BODY_BODY3"
|
||||
self._disabled = false
|
||||
self._value = false
|
||||
self._onValueChangedHandler = nil
|
||||
self._settingTable = nil
|
||||
self._settingKey = nil
|
||||
end
|
||||
|
||||
function Checkbox.Release(self)
|
||||
self._position = "LEFT"
|
||||
self._theme = "CHECK"
|
||||
self._disabled = false
|
||||
self._value = false
|
||||
self._onValueChangedHandler = nil
|
||||
self._settingTable = nil
|
||||
self._settingKey = nil
|
||||
self.__super:Release()
|
||||
self._font = "BODY_BODY3"
|
||||
end
|
||||
|
||||
--- Sets the position of the checkbox relative to the text.
|
||||
-- This method can be used to set the checkbox to be either on the left or right side of the text.
|
||||
-- @tparam Checkbox self The checkbox object
|
||||
-- @tparam string position The position of the checkbox relative to the text
|
||||
-- @treturn Checkbox The checkbox object
|
||||
function Checkbox.SetCheckboxPosition(self, position)
|
||||
if position == "LEFT" or position == "RIGHT" then
|
||||
self._position = position
|
||||
else
|
||||
error("Invalid checkbox position: "..tostring(position))
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the checkbox theme
|
||||
-- @tparam Checkbox self The checkbox object
|
||||
-- @tparam string theme Either "RADIO" or "CHECK"
|
||||
-- @treturn Checkbox The checkbox object
|
||||
function Checkbox.SetTheme(self, theme)
|
||||
assert(THEME_TEXTURES[theme])
|
||||
self._theme = theme
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets whether or not the checkbox is disabled.
|
||||
-- @tparam Checkbox self The checkbox object
|
||||
-- @tparam boolean disabled Whether or not the checkbox is disabled
|
||||
-- @treturn Checkbox The checkbox object
|
||||
function Checkbox.SetDisabled(self, disabled)
|
||||
self._disabled = disabled
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the text string.
|
||||
-- @tparam Checkbox self The checkbox object
|
||||
-- @tparam string text The text string to be displayed
|
||||
-- @treturn Checkbox The checkbox object
|
||||
function Checkbox.SetText(self, text)
|
||||
self._textStr = text
|
||||
return self
|
||||
end
|
||||
|
||||
--- Gets the text string.
|
||||
-- @tparam Checkbox self The checkbox object
|
||||
-- @treturn string The text string
|
||||
function Checkbox.GetText(self)
|
||||
return self._textStr
|
||||
end
|
||||
|
||||
--- Sets a formatted text string.
|
||||
-- @tparam Checkbox self The checkbox object
|
||||
-- @tparam vararg ... The format string and arguments
|
||||
-- @treturn Checkbox The checkbox object
|
||||
function Checkbox.SetFormattedText(self, ...)
|
||||
self._textStr = format(...)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets whether or not the checkbox is checked.
|
||||
-- @tparam Checkbox self The checkbox object
|
||||
-- @tparam boolean value Whether or not the checkbox is checked
|
||||
-- @tparam[opt=false] boolean silent If true, will not trigger the `OnValueChanged` script
|
||||
-- @treturn Checkbox The checkbox object
|
||||
function Checkbox.SetChecked(self, value, silent)
|
||||
self._value = value and true or false
|
||||
if self._onValueChangedHandler and not silent then
|
||||
self:_onValueChangedHandler(value)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the setting info.
|
||||
-- This method is used to have the state of the checkbox automatically correspond with the boolean state of a field in
|
||||
-- a table. This is useful for checkboxes which are tied directly to settings.
|
||||
-- @tparam Checkbox self The checkbox object
|
||||
-- @tparam table tbl The table which the field to set belongs to
|
||||
-- @tparam string key The key into the table to be set based on the checkbox state
|
||||
-- @treturn Checkbox The checkbox object
|
||||
function Checkbox.SetSettingInfo(self, tbl, key)
|
||||
self._settingTable = tbl
|
||||
self._settingKey = key
|
||||
self:SetChecked(tbl[key])
|
||||
return self
|
||||
end
|
||||
|
||||
--- Gets the checked state.
|
||||
-- @tparam Checkbox self The checkbox object
|
||||
-- @treturn boolean Whether or not the checkbox is checked
|
||||
function Checkbox.IsChecked(self)
|
||||
return self._value
|
||||
end
|
||||
|
||||
--- Registers a script handler.
|
||||
-- @tparam Checkbox self The checkbox object
|
||||
-- @tparam string script The script to register for (supported scripts: `OnValueChanged`)
|
||||
-- @tparam function handler The script handler which will be called with the checkbox object followed by any arguments
|
||||
-- to the script
|
||||
-- @treturn Checkbox The checkbox object
|
||||
function Checkbox.SetScript(self, script, handler)
|
||||
if script == "OnValueChanged" then
|
||||
self._onValueChangedHandler = handler
|
||||
elseif script == "OnEnter" or script == "OnLeave" then
|
||||
return self.__super:SetScript(script, handler)
|
||||
else
|
||||
error("Unknown Checkbox script: "..tostring(script))
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function Checkbox.Draw(self)
|
||||
self.__super:Draw()
|
||||
local frame = self:_GetBaseFrame()
|
||||
|
||||
if self._disabled then
|
||||
frame.text:SetTextColor(Theme.GetColor("TEXT_DISABLED"):GetFractionalRGBA())
|
||||
else
|
||||
frame.text:SetTextColor(self:_GetTextColor():GetFractionalRGBA())
|
||||
end
|
||||
TSM.UI.TexturePacks.SetTextureAndSize(frame.check, THEME_TEXTURES[self._theme][self._value and "checked" or "unchecked"])
|
||||
|
||||
frame.text:ClearAllPoints()
|
||||
frame.check:ClearAllPoints()
|
||||
if self._position == "LEFT" then
|
||||
frame.check:SetPoint("LEFT")
|
||||
frame.text:SetJustifyH("LEFT")
|
||||
frame.text:SetPoint("LEFT", frame.check, "RIGHT", CHECKBOX_SPACING, 0)
|
||||
frame.text:SetPoint("TOPRIGHT")
|
||||
frame.text:SetPoint("BOTTOMRIGHT")
|
||||
elseif self._position == "RIGHT" then
|
||||
frame.check:SetPoint("RIGHT")
|
||||
frame.text:SetJustifyH("RIGHT")
|
||||
frame.text:SetPoint("BOTTOMLEFT")
|
||||
frame.text:SetPoint("TOPLEFT")
|
||||
frame.text:SetPoint("RIGHT", frame.check, "LEFT", -CHECKBOX_SPACING, 0)
|
||||
else
|
||||
error("Invalid position: "..tostring(self._position))
|
||||
end
|
||||
if self._disabled then
|
||||
frame.check:SetAlpha(0.3)
|
||||
self:_GetBaseFrame():Disable()
|
||||
else
|
||||
frame.check:SetAlpha(1)
|
||||
self:_GetBaseFrame():Enable()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function Checkbox._GetMinimumDimension(self, dimension)
|
||||
if dimension == "WIDTH" and self._autoWidth then
|
||||
local checkboxWidth = TSM.UI.TexturePacks.GetWidth(THEME_TEXTURES[self._theme].checked)
|
||||
return self:GetStringWidth() + CHECKBOX_SPACING + checkboxWidth, nil
|
||||
else
|
||||
return self.__super:_GetMinimumDimension(dimension)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.FrameOnClick(self)
|
||||
local value = not self._value
|
||||
|
||||
if self._settingTable and self._settingKey then
|
||||
self._settingTable[self._settingKey] = value
|
||||
end
|
||||
|
||||
self:SetChecked(value)
|
||||
self:Draw()
|
||||
end
|
||||
135
Core/UI/Elements/CollapsibleContainer.lua
Normal file
135
Core/UI/Elements/CollapsibleContainer.lua
Normal file
@@ -0,0 +1,135 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- Collapsible Container UI Element Class.
|
||||
-- An collapsible container is a container which can be collapsed to a single heading line. It is a subclass of the @{Frame} class.
|
||||
-- @classmod CollapsibleContainer
|
||||
|
||||
local _, TSM = ...
|
||||
local CollapsibleContainer = TSM.Include("LibTSMClass").DefineClass("CollapsibleContainer", TSM.UI.Frame)
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(CollapsibleContainer)
|
||||
TSM.UI.CollapsibleContainer = CollapsibleContainer
|
||||
local private = {}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function CollapsibleContainer.__init(self)
|
||||
self.__super:__init()
|
||||
self._headingText = ""
|
||||
self._contextTbl = nil
|
||||
self._contextKey = nil
|
||||
end
|
||||
|
||||
function CollapsibleContainer.Acquire(self)
|
||||
self.__super:Acquire()
|
||||
self:SetBackgroundColor("PRIMARY_BG_ALT", true)
|
||||
self.__super:SetLayout("VERTICAL")
|
||||
self.__super:SetPadding(12, 12, 8, 8)
|
||||
self.__super:AddChild(UIElements.New("Frame", "heading")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:AddChild(UIElements.New("Button", "expander")
|
||||
:SetMargin(0, 4, 0, 0)
|
||||
:SetScript("OnClick", private.OnExpanderClick)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "text")
|
||||
:SetFont("BODY_BODY1_BOLD")
|
||||
)
|
||||
)
|
||||
self.__super:AddChild(UIElements.New("Frame", "content"))
|
||||
end
|
||||
|
||||
function CollapsibleContainer.Release(self)
|
||||
self._headingText = ""
|
||||
self._contextTbl = nil
|
||||
self._contextKey = nil
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
--- Sets the context table and key where to store the collapsed state.
|
||||
-- @tparam CollapsibleContainer self The collapsible container object
|
||||
-- @tparam table tbl The table
|
||||
-- @tparam string key The key
|
||||
-- @treturn CollapsibleContainer The collapsible container object
|
||||
function CollapsibleContainer.SetContextTable(self, tbl, key)
|
||||
assert(type(tbl) == "table" and type(key) == "string")
|
||||
self._contextTbl = tbl
|
||||
self._contextKey = key
|
||||
if self._contextTbl[self._contextKey] then
|
||||
self:GetElement("content"):Hide()
|
||||
else
|
||||
self:GetElement("content"):Show()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the heading text.
|
||||
-- @tparam CollapsibleContainer self The collapsible container object
|
||||
-- @tparam ?string|number headingText The heading text
|
||||
-- @treturn CollapsibleContainer The collapsible container object
|
||||
function CollapsibleContainer.SetHeadingText(self, headingText)
|
||||
assert(type(headingText) == "string" or type(headingText) == "number")
|
||||
self._headingText = headingText
|
||||
return self
|
||||
end
|
||||
|
||||
function CollapsibleContainer.SetPadding(self, left, right, top, bottom)
|
||||
error("CollapsibleContainer doesn't support this method")
|
||||
end
|
||||
|
||||
function CollapsibleContainer.SetLayout(self, layout)
|
||||
self:GetElement("content"):SetLayout(layout)
|
||||
return self
|
||||
end
|
||||
|
||||
function CollapsibleContainer.AddChild(self, child)
|
||||
self:GetElement("content"):AddChild(child)
|
||||
return self
|
||||
end
|
||||
|
||||
function CollapsibleContainer.AddChildIf(self, condition, child)
|
||||
self:GetElement("content"):AddChildIf(condition, child)
|
||||
return self
|
||||
end
|
||||
|
||||
function CollapsibleContainer.AddChildrenWithFunction(self, func, ...)
|
||||
self:GetElement("content"):AddChildrenWithFunction(func, ...)
|
||||
return self
|
||||
end
|
||||
|
||||
function CollapsibleContainer.AddChildBeforeById(self, beforeId, child)
|
||||
self:GetElement("content"):AddChildBeforeById(beforeId, child)
|
||||
return self
|
||||
end
|
||||
|
||||
function CollapsibleContainer.Draw(self)
|
||||
self:GetElement("heading.text"):SetText(self._headingText)
|
||||
self:GetElement("heading.expander"):SetBackgroundAndSize(self._contextTbl[self._contextKey] and "iconPack.18x18/Caret/Right" or "iconPack.18x18/Caret/Down")
|
||||
self.__super:Draw()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.OnExpanderClick(button)
|
||||
local self = button:GetParentElement():GetParentElement()
|
||||
self._contextTbl[self._contextKey] = not self._contextTbl[self._contextKey]
|
||||
if self._contextTbl[self._contextKey] then
|
||||
self:GetElement("content"):Hide()
|
||||
else
|
||||
self:GetElement("content"):Show()
|
||||
end
|
||||
-- TODO: is there a better way to notify the elements up the stack that our size has changed?
|
||||
self:GetBaseElement():Draw()
|
||||
end
|
||||
254
Core/UI/Elements/CommodityList.lua
Normal file
254
Core/UI/Elements/CommodityList.lua
Normal file
@@ -0,0 +1,254 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- Commodity List UI Element Class.
|
||||
-- The element used to show the details of a selected commodity in shopping. It is a subclass of the @{ScrollingTable} class.
|
||||
-- @classmod CommodityList
|
||||
|
||||
local _, TSM = ...
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local Money = TSM.Include("Util.Money")
|
||||
local Math = TSM.Include("Util.Math")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local CommodityList = TSM.Include("LibTSMClass").DefineClass("CommodityList", TSM.UI.ScrollingTable)
|
||||
UIElements.Register(CommodityList)
|
||||
TSM.UI.CommodityList = CommodityList
|
||||
local private = {}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function CommodityList.__init(self)
|
||||
self.__super:__init()
|
||||
self._row = nil
|
||||
self._marketValueFunc = nil
|
||||
self._alertThreshold = math.huge
|
||||
end
|
||||
|
||||
function CommodityList.Acquire(self)
|
||||
self._headerHidden = true
|
||||
self.__super:Acquire()
|
||||
self:GetScrollingTableInfo()
|
||||
:NewColumn("warning")
|
||||
:SetWidth(12)
|
||||
:SetIconSize(12)
|
||||
:SetIconHoverEnabled(true)
|
||||
:SetIconFunction(private.GetWarningIcon)
|
||||
:SetJustifyH("CENTER")
|
||||
:SetFont("BODY_BODY3")
|
||||
:Commit()
|
||||
:NewColumn("itemBuyout")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextFunction(private.GetItemBuyoutText)
|
||||
:DisableHiding()
|
||||
:Commit()
|
||||
:NewColumn("quantity")
|
||||
:SetWidth(60)
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextFunction(private.GetQuantityText)
|
||||
:DisableHiding()
|
||||
:Commit()
|
||||
:NewColumn("pct")
|
||||
:SetWidth(50)
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextFunction(private.GetPercentText)
|
||||
:DisableHiding()
|
||||
:Commit()
|
||||
:Commit()
|
||||
end
|
||||
|
||||
function CommodityList.Release(self)
|
||||
self._row = nil
|
||||
self._marketValueFunc = nil
|
||||
self._alertThreshold = math.huge
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
function CommodityList.GetTotalQuantity(self, maxIndex)
|
||||
local totalQuantity = 0
|
||||
for _, index in ipairs(self._data) do
|
||||
if index > maxIndex then
|
||||
break
|
||||
end
|
||||
local subRow = self:_GetSubRow(index)
|
||||
local _, numOwnerItems = subRow:GetOwnerInfo()
|
||||
local quantityAvailable = subRow:GetQuantities() - numOwnerItems
|
||||
totalQuantity = totalQuantity + quantityAvailable
|
||||
end
|
||||
return totalQuantity
|
||||
end
|
||||
|
||||
--- Sets the result row.
|
||||
-- @tparam CommodityList self The commodity list object
|
||||
-- @tparam table row The row to set
|
||||
-- @treturn CommodityList The commodity list object
|
||||
function CommodityList.SetData(self, row)
|
||||
self._row = row
|
||||
self:UpdateData()
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the selected quantity.
|
||||
-- @tparam CommodityList self The commodity list object
|
||||
-- @tparam number quantity The selected quantity
|
||||
-- @treturn CommodityList The commodity list object
|
||||
function CommodityList.SelectQuantity(self, quantity)
|
||||
local maxIndex = nil
|
||||
for _, index in ipairs(self._data) do
|
||||
local subRow = self:_GetSubRow(index)
|
||||
local _, numOwnerItems = subRow:GetOwnerInfo()
|
||||
local quantityAvailable = subRow:GetQuantities() - numOwnerItems
|
||||
maxIndex = index
|
||||
quantity = quantity - quantityAvailable
|
||||
if quantity <= 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
self:SetSelection(maxIndex)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the market value function.
|
||||
-- @tparam CommodityList self The commodity list object
|
||||
-- @tparam function func The function to call with the ResultSubRow to get the market value
|
||||
-- @treturn CommodityList The commodity list object
|
||||
function CommodityList.SetMarketValueFunction(self, func)
|
||||
self._marketValueFunc = func
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the alert threshold.
|
||||
-- @tparam CommodityList self The commodity list object
|
||||
-- @tparam number threshold The item buyout above which the alert icon should be shown
|
||||
-- @treturn CommodityList The commodity list object
|
||||
function CommodityList.SetAlertThreshold(self, threshold)
|
||||
self._alertThreshold = threshold or math.huge
|
||||
return self
|
||||
end
|
||||
|
||||
function CommodityList.SetSelection(self, selection)
|
||||
self.__super:SetSelection(selection and self:_SanitizeSelectionIndex(selection) or nil)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function CommodityList._HandleRowClick(self, data, mouseButton)
|
||||
local index = self:_SanitizeSelectionIndex(data)
|
||||
if not index then
|
||||
return
|
||||
end
|
||||
self.__super:_HandleRowClick(index, mouseButton)
|
||||
end
|
||||
|
||||
function CommodityList._SanitizeSelectionIndex(self, selectedIndex)
|
||||
-- select the highest subrow which isn't the player's auction and isn't above the selection
|
||||
local highestIndex = nil
|
||||
for index, subRow in self._row:SubRowIterator() do
|
||||
if subRow:GetQuantities() - select(2, subRow:GetOwnerInfo()) ~= 0 then
|
||||
highestIndex = index
|
||||
end
|
||||
if index == selectedIndex then
|
||||
break
|
||||
end
|
||||
end
|
||||
return highestIndex
|
||||
end
|
||||
|
||||
function CommodityList._GetSubRow(self, index)
|
||||
return self._row._subRows[index]
|
||||
end
|
||||
|
||||
function CommodityList._UpdateData(self)
|
||||
wipe(self._data)
|
||||
if not self._row then
|
||||
return
|
||||
end
|
||||
for index in self._row:SubRowIterator() do
|
||||
tinsert(self._data, index)
|
||||
end
|
||||
end
|
||||
|
||||
function CommodityList._IsSelected(self, data)
|
||||
if data > (self._selection or 0) then
|
||||
return false
|
||||
end
|
||||
local subRow = self:_GetSubRow(data)
|
||||
local _, numOwnerItems = subRow:GetOwnerInfo()
|
||||
local quantityAvailable = subRow:GetQuantities() - numOwnerItems
|
||||
return quantityAvailable > 0
|
||||
end
|
||||
|
||||
function CommodityList._GetMarketValuePct(self, row)
|
||||
assert(row:IsSubRow())
|
||||
if not self._marketValueFunc then
|
||||
-- no market value function was set
|
||||
return nil, nil
|
||||
end
|
||||
local marketValue = self._marketValueFunc(row) or 0
|
||||
if marketValue == 0 then
|
||||
-- this item doesn't have a market value
|
||||
return nil, nil
|
||||
end
|
||||
local _, itemBuyout = row:GetBuyouts()
|
||||
return itemBuyout > 0 and Math.Round(100 * itemBuyout / marketValue) or nil
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetWarningIcon(self, index)
|
||||
local subRow = self:_GetSubRow(index)
|
||||
assert(subRow)
|
||||
local _, itemBuyout = subRow:GetBuyouts()
|
||||
if itemBuyout < self._alertThreshold then
|
||||
return
|
||||
end
|
||||
return "iconPack.12x12/Attention", L["This price is above your confirmation alert threshold."]
|
||||
end
|
||||
|
||||
function private.GetItemBuyoutText(self, index)
|
||||
local _, itemBuyout = self:_GetSubRow(index):GetBuyouts()
|
||||
return Money.ToString(itemBuyout, nil, "OPT_83_NO_COPPER")
|
||||
end
|
||||
|
||||
function private.GetPercentText(self, index)
|
||||
local pct = self:_GetMarketValuePct(self:_GetSubRow(index))
|
||||
if not pct then
|
||||
return "---"
|
||||
end
|
||||
local pctColor = Theme.GetAuctionPercentColor(pct)
|
||||
if pct > 999 then
|
||||
pct = ">999"
|
||||
end
|
||||
return pctColor:ColorText(pct.."%")
|
||||
end
|
||||
|
||||
function private.GetQuantityText(self, index)
|
||||
local subRow = self:_GetSubRow(index)
|
||||
local _, numOwnerItems = subRow:GetOwnerInfo()
|
||||
local totalQuantity = subRow:GetQuantities()
|
||||
local quantityAvailable = totalQuantity - numOwnerItems
|
||||
if quantityAvailable == 0 then
|
||||
return Theme.GetColor("INDICATOR_ALT"):ColorText(totalQuantity)
|
||||
else
|
||||
return quantityAvailable
|
||||
end
|
||||
end
|
||||
207
Core/UI/Elements/Container.lua
Normal file
207
Core/UI/Elements/Container.lua
Normal file
@@ -0,0 +1,207 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- Container UI Element Class.
|
||||
-- A container is an abstract element class which simply contains other elements. It is a subclass of the @{Element} class.
|
||||
-- @classmod Container
|
||||
|
||||
local _, TSM = ...
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local Table = TSM.Include("Util.Table")
|
||||
local Container = TSM.Include("LibTSMClass").DefineClass("Container", TSM.UI.Element, "ABSTRACT")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(Container)
|
||||
TSM.UI.Container = Container
|
||||
local private = {}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function Container.__init(self, frame)
|
||||
self.__super:__init(frame)
|
||||
|
||||
self._children = {}
|
||||
self._layoutChildren = {}
|
||||
self._noLayoutChildren = {}
|
||||
end
|
||||
|
||||
function Container.Release(self)
|
||||
self:ReleaseAllChildren()
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
--- Release all child elements.
|
||||
-- @tparam Container self The container object
|
||||
function Container.ReleaseAllChildren(self)
|
||||
for _, child in ipairs(self._children) do
|
||||
child:Release()
|
||||
end
|
||||
wipe(self._children)
|
||||
wipe(self._layoutChildren)
|
||||
wipe(self._noLayoutChildren)
|
||||
end
|
||||
|
||||
--- Add a child element.
|
||||
-- @tparam Container self The container object
|
||||
-- @tparam Element child The child element
|
||||
-- @treturn Container The container object
|
||||
function Container.AddChild(self, child)
|
||||
self:_AddChildHelper(child, true)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a child element when the required condition is true.
|
||||
-- @tparam Container self The container object
|
||||
-- @tparam boolean condition The required condition
|
||||
-- @tparam Element child The child element
|
||||
-- @treturn Container The container object
|
||||
function Container.AddChildIf(self, condition, child)
|
||||
if not condition then
|
||||
child:Release()
|
||||
return self
|
||||
end
|
||||
self:_AddChildHelper(child, true)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a child element before another one.
|
||||
-- @tparam Container self The container object
|
||||
-- @tparam string beforeId The id of the child element to add this one before
|
||||
-- @tparam Element child The child element
|
||||
-- @treturn Container The container object
|
||||
function Container.AddChildBeforeById(self, beforeId, child)
|
||||
self:_AddChildHelper(child, true, beforeId)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add child elements using a function.
|
||||
-- @tparam Container self The container object
|
||||
-- @tparam function func The function to call and pass this container object
|
||||
-- @tparam vararg ... Additional arguments to pass to the function
|
||||
-- @treturn Container The container object
|
||||
function Container.AddChildrenWithFunction(self, func, ...)
|
||||
func(self, ...)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a child element which is not involved in layout.
|
||||
-- The layout of this child must be explicitly done by the application code.
|
||||
-- @tparam Container self The container object
|
||||
-- @tparam Element child The child element
|
||||
-- @treturn Container The container object
|
||||
function Container.AddChildNoLayout(self, child)
|
||||
self:_AddChildHelper(child, false)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove a child element.
|
||||
-- @tparam Container self The container object
|
||||
-- @tparam Element child The child element to remove
|
||||
function Container.RemoveChild(self, child)
|
||||
assert(child:__isa(TSM.UI.Element) and child:_GetBaseFrame():GetParent())
|
||||
child:_GetBaseFrame():SetParent(nil)
|
||||
Table.RemoveByValue(self._children, child)
|
||||
Table.RemoveByValue(self._layoutChildren, child)
|
||||
Table.RemoveByValue(self._noLayoutChildren, child)
|
||||
child:_SetParentElement(nil)
|
||||
end
|
||||
|
||||
function Container.HasChildById(self, childId)
|
||||
for _, child in ipairs(self._children) do
|
||||
if child._id == childId then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
--- Gets the number of child elements involved in layout.
|
||||
-- @tparam Container self The container object
|
||||
-- @treturn number The number of elements
|
||||
function Container.GetNumLayoutChildren(self)
|
||||
local count = 0
|
||||
for _ in self:LayoutChildrenIterator() do
|
||||
count = count + 1
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
--- Iterates through the child elements involved in layout.
|
||||
-- @tparam Container self The container object
|
||||
-- @return An iterator with the following fields: `index, child`
|
||||
function Container.LayoutChildrenIterator(self)
|
||||
local children = TempTable.Acquire()
|
||||
for _, child in ipairs(self._layoutChildren) do
|
||||
if child:IsVisible() then
|
||||
tinsert(children, child)
|
||||
end
|
||||
end
|
||||
return TempTable.Iterator(children)
|
||||
end
|
||||
|
||||
--- Shows all child elements.
|
||||
-- @tparam Container self The container object
|
||||
function Container.ShowAllChildren(self)
|
||||
for _, child in ipairs(self._layoutChildren) do
|
||||
if not child:IsVisible() then
|
||||
child:Show()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Container.Draw(self)
|
||||
self.__super:Draw()
|
||||
for _, child in ipairs(self._children) do
|
||||
child:Draw()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Container - Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function Container._AddChildHelper(self, child, layout, beforeId)
|
||||
assert(child:__isa(TSM.UI.Element) and not child:_GetBaseFrame():GetParent())
|
||||
child:_GetBaseFrame():SetParent(self:_GetBaseFrame())
|
||||
tinsert(self._children, private.GetElementInsertIndex(self._children, beforeId), child)
|
||||
if layout then
|
||||
tinsert(self._layoutChildren, private.GetElementInsertIndex(self._layoutChildren, beforeId), child)
|
||||
else
|
||||
tinsert(self._noLayoutChildren, private.GetElementInsertIndex(self._noLayoutChildren, beforeId), child)
|
||||
end
|
||||
child:_SetParentElement(self)
|
||||
child:Show()
|
||||
end
|
||||
|
||||
function Container._ClearBaseElementCache(self)
|
||||
self.__super:_ClearBaseElementCache()
|
||||
for _, child in ipairs(self._children) do
|
||||
child:_ClearBaseElementCache()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetElementInsertIndex(tbl, beforeId)
|
||||
if not beforeId then
|
||||
return #tbl + 1
|
||||
end
|
||||
for i, element in ipairs(tbl) do
|
||||
if element._id == beforeId then
|
||||
return i
|
||||
end
|
||||
end
|
||||
error("Invalid beforeId: "..tostring(beforeId))
|
||||
end
|
||||
141
Core/UI/Elements/CraftingMatList.lua
Normal file
141
Core/UI/Elements/CraftingMatList.lua
Normal file
@@ -0,0 +1,141 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- Crafting Mat List UI Element Class.
|
||||
-- The element used to show the mats for a specific craft in the Crafting UI. It is a subclass of the @{ScrollingTable} class.
|
||||
-- @classmod CraftingMatList
|
||||
|
||||
local _, TSM = ...
|
||||
local CraftingMatList = TSM.Include("LibTSMClass").DefineClass("CraftingMatList", TSM.UI.ScrollingTable)
|
||||
local ItemString = TSM.Include("Util.ItemString")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local Inventory = TSM.Include("Service.Inventory")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(CraftingMatList)
|
||||
TSM.UI.CraftingMatList = CraftingMatList
|
||||
local private = {}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function CraftingMatList.__init(self)
|
||||
self.__super:__init()
|
||||
self._spellId = nil
|
||||
self._rowHoverEnabled = false
|
||||
end
|
||||
|
||||
function CraftingMatList.Acquire(self)
|
||||
self._headerHidden = true
|
||||
self.__super:Acquire()
|
||||
self:SetSelectionDisabled(true)
|
||||
self:GetScrollingTableInfo()
|
||||
:NewColumn("check")
|
||||
:SetWidth(14)
|
||||
:SetIconSize(14)
|
||||
:SetIconFunction(private.GetCheck)
|
||||
:Commit()
|
||||
:NewColumn("item")
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetIconSize(12)
|
||||
:SetIconFunction(private.GetItemIcon)
|
||||
:SetTextFunction(private.GetItemText)
|
||||
:SetTooltipFunction(private.GetItemTooltip)
|
||||
:Commit()
|
||||
:NewColumn("qty")
|
||||
:SetAutoWidth()
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("CENTER")
|
||||
:SetTextFunction(private.GetQty)
|
||||
:Commit()
|
||||
:Commit()
|
||||
end
|
||||
|
||||
function CraftingMatList.Release(self)
|
||||
self._spellId = nil
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
function CraftingMatList.SetScript(self, script, handler)
|
||||
error("Unknown CraftingMatList script: "..tostring(script))
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the crafting recipe to display materials for.
|
||||
-- @tparam CraftingMatList self The crafting mat list object
|
||||
-- @tparam number spellId The spellId for the recipe
|
||||
-- @treturn CraftingMatList The crafting mat list object
|
||||
function CraftingMatList.SetRecipe(self, spellId)
|
||||
self._spellId = spellId
|
||||
self:_UpdateData()
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function CraftingMatList._UpdateData(self)
|
||||
wipe(self._data)
|
||||
if not self._spellId then
|
||||
return
|
||||
end
|
||||
for i = 1, TSM.Crafting.ProfessionUtil.GetNumMats(self._spellId) do
|
||||
tinsert(self._data, i)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetCheck(self, index)
|
||||
local itemLink, _, _, quantity = TSM.Crafting.ProfessionUtil.GetMatInfo(self._spellId, index)
|
||||
local itemString = ItemString.Get(itemLink)
|
||||
local bagQuantity = Inventory.GetBagQuantity(itemString)
|
||||
if not TSM.IsWowClassic() then
|
||||
bagQuantity = bagQuantity + Inventory.GetReagentBankQuantity(itemString) + Inventory.GetBankQuantity(itemString)
|
||||
end
|
||||
if bagQuantity >= quantity then
|
||||
return TSM.UI.TexturePacks.GetColoredKey("iconPack.14x14/Checkmark/Default", Theme.GetFeedbackColor("GREEN"))
|
||||
else
|
||||
return TSM.UI.TexturePacks.GetColoredKey("iconPack.14x14/Close/Default", Theme.GetFeedbackColor("RED"))
|
||||
end
|
||||
end
|
||||
|
||||
function private.GetItemIcon(self, index)
|
||||
local _, _, texture = TSM.Crafting.ProfessionUtil.GetMatInfo(self._spellId, index)
|
||||
return texture
|
||||
end
|
||||
|
||||
function private.GetItemText(self, index)
|
||||
local itemLink = TSM.Crafting.ProfessionUtil.GetMatInfo(self._spellId, index)
|
||||
local itemString = ItemString.Get(itemLink)
|
||||
return TSM.UI.GetColoredItemName(itemString) or Theme.GetFeedbackColor("RED"):ColorText("?")
|
||||
end
|
||||
|
||||
function private.GetItemTooltip(self, index)
|
||||
local itemLink = TSM.Crafting.ProfessionUtil.GetMatInfo(self._spellId, index)
|
||||
return ItemString.Get(itemLink)
|
||||
end
|
||||
|
||||
function private.GetQty(self, index)
|
||||
local itemLink, _, _, quantity = TSM.Crafting.ProfessionUtil.GetMatInfo(self._spellId, index)
|
||||
local itemString = ItemString.Get(itemLink)
|
||||
local bagQuantity = Inventory.GetBagQuantity(itemString)
|
||||
if not TSM.IsWowClassic() then
|
||||
bagQuantity = bagQuantity + Inventory.GetReagentBankQuantity(itemString) + Inventory.GetBankQuantity(itemString)
|
||||
end
|
||||
local color = bagQuantity >= quantity and Theme.GetFeedbackColor("GREEN") or Theme.GetFeedbackColor("RED")
|
||||
return color:ColorText(format("%d / %d", bagQuantity, quantity))
|
||||
end
|
||||
498
Core/UI/Elements/CraftingQueueList.lua
Normal file
498
Core/UI/Elements/CraftingQueueList.lua
Normal file
@@ -0,0 +1,498 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- Crafting Queue List UI Element Class.
|
||||
-- The element used to show the queue in the Crafting UI. It is a subclass of the @{ScrollingTable} class.
|
||||
-- @classmod CraftingQueueList
|
||||
|
||||
local _, TSM = ...
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local Money = TSM.Include("Util.Money")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local ScriptWrapper = TSM.Include("Util.ScriptWrapper")
|
||||
local ItemInfo = TSM.Include("Service.ItemInfo")
|
||||
local Inventory = TSM.Include("Service.Inventory")
|
||||
local CraftingQueueList = TSM.Include("LibTSMClass").DefineClass("CraftingQueueList", TSM.UI.ScrollingTable)
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(CraftingQueueList)
|
||||
TSM.UI.CraftingQueueList = CraftingQueueList
|
||||
local private = {
|
||||
categoryOrder = {},
|
||||
sortSelf = nil,
|
||||
sortProfitCache = {},
|
||||
}
|
||||
local CATEGORY_SEP = "\001"
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function CraftingQueueList.__init(self)
|
||||
self.__super:__init()
|
||||
self._collapsed = {}
|
||||
self._query = nil
|
||||
self._numCraftableCache = {}
|
||||
self._onRowMouseDownHandler = nil
|
||||
end
|
||||
|
||||
function CraftingQueueList.Acquire(self)
|
||||
self._headerHidden = true
|
||||
self.__super:Acquire()
|
||||
self:SetSelectionDisabled(true)
|
||||
self:GetScrollingTableInfo()
|
||||
:NewColumn("name")
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetIconSize(12)
|
||||
:SetExpanderStateFunction(private.GetExpanderState)
|
||||
:SetIconFunction(private.GetItemIcon)
|
||||
:SetIconHoverEnabled(true)
|
||||
:SetIconClickHandler(private.OnItemIconClick)
|
||||
:SetTextFunction(private.GetItemText)
|
||||
:SetTooltipFunction(private.GetItemTooltip)
|
||||
:SetActionIconInfo(1, 12, private.GetDeleteIcon, true)
|
||||
:SetActionIconClickHandler(private.OnDeleteIconClick)
|
||||
:Commit()
|
||||
:NewColumn("qty")
|
||||
:SetAutoWidth()
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("CENTER")
|
||||
:SetTextFunction(private.GetQty)
|
||||
:SetActionIconInfo(1, 12, private.GetEditIcon, true)
|
||||
:SetActionIconClickHandler(private.OnEditIconClick)
|
||||
:Commit()
|
||||
:Commit()
|
||||
end
|
||||
|
||||
function CraftingQueueList.Release(self)
|
||||
self._onRowMouseDownHandler = nil
|
||||
wipe(self._numCraftableCache)
|
||||
wipe(self._collapsed)
|
||||
if self._query then
|
||||
self._query:Release()
|
||||
self._query = nil
|
||||
end
|
||||
for _, row in ipairs(self._rows) do
|
||||
ScriptWrapper.Clear(row._frame, "OnDoubleClick")
|
||||
ScriptWrapper.Clear(row._frame, "OnMouseDown")
|
||||
for _, button in pairs(row._buttons) do
|
||||
ScriptWrapper.Clear(button, "OnMouseDown")
|
||||
end
|
||||
end
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
--- Gets the data of the first row.
|
||||
-- @tparam CraftingMatList self The crafting queue list object
|
||||
-- @treturn CraftingQueueList The crafting queue list object
|
||||
function CraftingQueueList.GetFirstData(self)
|
||||
for _, data in ipairs(self._data) do
|
||||
if type(data) ~= "string" then
|
||||
return data
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Registers a script handler.
|
||||
-- @tparam CraftingQueueList self The crafting queue list object
|
||||
-- @tparam string script The script to register for (supported scripts: `OnRowClick`, `OnRowMouseDown`)
|
||||
-- @tparam function handler The script handler which will be called with the crafting queue list object followed by any
|
||||
-- arguments to the script
|
||||
-- @treturn CraftingQueueList The crafting queue list object
|
||||
function CraftingQueueList.SetScript(self, script, handler)
|
||||
if script == "OnRowMouseDown" then
|
||||
self._onRowMouseDownHandler = handler
|
||||
else
|
||||
self.__super:SetScript(script, handler)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the @{DatabaseQuery} source for this list.
|
||||
-- This query is used to populate the entries in the crafting queue list.
|
||||
-- @tparam CraftingQueueList self The crafting queue list object
|
||||
-- @tparam DatabaseQuery query The query object
|
||||
-- @treturn CraftingQueueList The crafting queue list object
|
||||
function CraftingQueueList.SetQuery(self, query)
|
||||
if self._query then
|
||||
self._query:Release()
|
||||
end
|
||||
self._query = query
|
||||
self._query:SetUpdateCallback(private.QueryUpdateCallback, self)
|
||||
self:_UpdateData()
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function CraftingQueueList._GetTableRow(self, isHeader)
|
||||
local row = self.__super:_GetTableRow(isHeader)
|
||||
if not isHeader then
|
||||
ScriptWrapper.Set(row._frame, "OnMouseDown", private.RowOnMouseDown, row)
|
||||
ScriptWrapper.Set(row._frame, "OnDoubleClick", private.RowOnDoubleClick, row)
|
||||
for _, button in pairs(row._buttons) do
|
||||
ScriptWrapper.Set(button, "OnMouseDown", private.RowOnMouseDown, row)
|
||||
end
|
||||
end
|
||||
return row
|
||||
end
|
||||
|
||||
function CraftingQueueList._UpdateData(self)
|
||||
wipe(self._data)
|
||||
if not self._query then
|
||||
return
|
||||
end
|
||||
local categories = TempTable.Acquire()
|
||||
wipe(self._numCraftableCache)
|
||||
wipe(private.sortProfitCache)
|
||||
for _, row in self._query:Iterator() do
|
||||
local rawCategory = strjoin(CATEGORY_SEP, row:GetFields("profession", "players"))
|
||||
local category = strlower(rawCategory)
|
||||
if not categories[category] then
|
||||
tinsert(categories, category)
|
||||
end
|
||||
categories[category] = rawCategory
|
||||
if not self._collapsed[rawCategory] then
|
||||
local spellId = row:GetField("spellId")
|
||||
self._numCraftableCache[row] = TSM.Crafting.ProfessionUtil.GetNumCraftableFromDB(spellId)
|
||||
private.sortProfitCache[spellId] = TSM.Crafting.Cost.GetProfitBySpellId(spellId)
|
||||
tinsert(self._data, row)
|
||||
end
|
||||
end
|
||||
sort(categories, private.CategorySortComparator)
|
||||
wipe(private.categoryOrder)
|
||||
for i, category in ipairs(categories) do
|
||||
private.categoryOrder[category] = i
|
||||
tinsert(self._data, categories[category])
|
||||
end
|
||||
TempTable.Release(categories)
|
||||
private.sortSelf = self
|
||||
sort(self._data, private.DataSortComparator)
|
||||
private.sortSelf = nil
|
||||
end
|
||||
|
||||
function CraftingQueueList._SetCollapsed(self, data, collapsed)
|
||||
self._collapsed[data] = collapsed or nil
|
||||
end
|
||||
|
||||
function CraftingQueueList._HandleRowClick(self, data, mouseButton)
|
||||
if type(data) == "string" then
|
||||
self:_SetCollapsed(data, not self._collapsed[data])
|
||||
self:UpdateData(true)
|
||||
else
|
||||
local currentRow
|
||||
for _, row in ipairs(self._rows) do
|
||||
if row:GetData() == data then
|
||||
currentRow = row
|
||||
break
|
||||
end
|
||||
end
|
||||
if currentRow._texts.qty:IsMouseOver(0, 0, 0, 12) then
|
||||
private.OnEditIconClick(self, data, 1)
|
||||
else
|
||||
self.__super:_HandleRowClick(data, mouseButton)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.RowOnMouseDown(row, mouseButton)
|
||||
local data = row:GetData()
|
||||
if type(data) == "string" then
|
||||
return
|
||||
end
|
||||
local self = row._scrollingTable
|
||||
if self._onRowMouseDownHandler then
|
||||
self:_onRowMouseDownHandler(data, mouseButton)
|
||||
end
|
||||
end
|
||||
|
||||
function private.RowOnDoubleClick(row, mouseButton)
|
||||
local self = row._scrollingTable
|
||||
self:_HandleRowClick(row:GetData(), mouseButton)
|
||||
end
|
||||
|
||||
function private.GetExpanderState(self, data)
|
||||
if type(data) == "string" then
|
||||
return true, not self._collapsed[data], 0
|
||||
else
|
||||
return false, false, 0
|
||||
end
|
||||
end
|
||||
|
||||
function private.GetItemIcon(self, data)
|
||||
if type(data) == "string" then
|
||||
return
|
||||
end
|
||||
local spellId = data:GetField("spellId")
|
||||
local itemString = TSM.Crafting.GetItemString(spellId)
|
||||
local texture, tooltip = nil, nil
|
||||
if itemString then
|
||||
texture = ItemInfo.GetTexture(itemString)
|
||||
tooltip = itemString
|
||||
else
|
||||
texture = select(3, TSM.Crafting.ProfessionUtil.GetResultInfo(spellId))
|
||||
if TSM.Crafting.ProfessionState.IsClassicCrafting() then
|
||||
tooltip = "craft:"..(TSM.Crafting.ProfessionScanner.GetIndexBySpellId(spellId) or spellId)
|
||||
else
|
||||
tooltip = "enchant:"..spellId
|
||||
end
|
||||
return
|
||||
end
|
||||
return texture, tooltip
|
||||
end
|
||||
|
||||
function private.OnItemIconClick(self, data, mouseButton)
|
||||
self:_HandleRowClick(data, mouseButton)
|
||||
end
|
||||
|
||||
function private.GetItemText(self, data)
|
||||
if type(data) == "string" then
|
||||
local profession, players = strsplit(CATEGORY_SEP, data)
|
||||
local isValid = private.PlayersContains(players, UnitName("player")) and strlower(profession) == strlower(TSM.Crafting.ProfessionUtil.GetCurrentProfessionName() or "")
|
||||
local text = Theme.GetColor("INDICATOR"):ColorText(profession.." ("..players..")")
|
||||
if isValid then
|
||||
return text
|
||||
else
|
||||
return text.." "..TSM.UI.TexturePacks.GetTextureLink("iconPack.12x12/Attention")
|
||||
end
|
||||
else
|
||||
local spellId = data:GetField("spellId")
|
||||
local itemString = TSM.Crafting.GetItemString(spellId)
|
||||
return itemString and TSM.UI.GetColoredItemName(itemString) or GetSpellInfo(spellId) or "?"
|
||||
end
|
||||
end
|
||||
|
||||
function private.GetItemTooltip(self, data)
|
||||
if type(data) == "string" then
|
||||
local profession, players = strsplit(CATEGORY_SEP, data)
|
||||
if not private.PlayersContains(players, UnitName("player")) then
|
||||
return L["You are not on one of the listed characters."]
|
||||
elseif strlower(profession) ~= strlower(TSM.Crafting.ProfessionUtil.GetCurrentProfessionName() or "") then
|
||||
return L["This profession is not open."]
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local spellId = data:GetField("spellId")
|
||||
local numQueued = data:GetField("num")
|
||||
local itemString = TSM.Crafting.GetItemString(spellId)
|
||||
local name = itemString and TSM.UI.GetColoredItemName(itemString) or GetSpellInfo(spellId) or "?"
|
||||
local tooltipLines = TempTable.Acquire()
|
||||
tinsert(tooltipLines, name.." (x"..numQueued..")")
|
||||
local numResult = TSM.Crafting.GetNumResult(spellId)
|
||||
local profit = TSM.Crafting.Cost.GetProfitBySpellId(spellId)
|
||||
local profitStr = profit and Money.ToString(profit * numResult, Theme.GetFeedbackColor(profit >= 0 and "GREEN" or "RED"):GetTextColorPrefix()) or "---"
|
||||
local totalProfitStr = profit and Money.ToString(profit * numResult * numQueued, Theme.GetFeedbackColor(profit >= 0 and "GREEN" or "RED"):GetTextColorPrefix()) or "---"
|
||||
tinsert(tooltipLines, L["Profit (Total)"]..": "..profitStr.." ("..totalProfitStr..")")
|
||||
for _, matItemString, quantity in TSM.Crafting.MatIterator(spellId) do
|
||||
local numHave = Inventory.GetBagQuantity(matItemString)
|
||||
if not TSM.IsWowClassic() then
|
||||
numHave = numHave + Inventory.GetReagentBankQuantity(matItemString) + Inventory.GetBankQuantity(matItemString)
|
||||
end
|
||||
local numNeed = quantity * numQueued
|
||||
local color = Theme.GetFeedbackColor(numHave >= numNeed and "GREEN" or "RED")
|
||||
tinsert(tooltipLines, color:ColorText(numHave.."/"..numNeed).." - "..(ItemInfo.GetName(matItemString) or "?"))
|
||||
end
|
||||
if TSM.Crafting.ProfessionUtil.GetRemainingCooldown(spellId) then
|
||||
tinsert(tooltipLines, Theme.GetFeedbackColor("RED"):ColorText(L["On Cooldown"]))
|
||||
end
|
||||
return strjoin("\n", TempTable.UnpackAndRelease(tooltipLines)), true, true
|
||||
end
|
||||
|
||||
function private.GetDeleteIcon(self, data, iconIndex)
|
||||
assert(iconIndex == 1)
|
||||
if type(data) == "string" then
|
||||
return false
|
||||
end
|
||||
return true, "iconPack.12x12/Close/Default", true
|
||||
end
|
||||
|
||||
function private.OnDeleteIconClick(self, data, iconIndex)
|
||||
assert(iconIndex == 1 and type(data) ~= "string")
|
||||
TSM.Crafting.Queue.SetNum(data:GetField("spellId"), 0)
|
||||
end
|
||||
|
||||
function private.GetEditIcon(self, data, iconIndex)
|
||||
assert(iconIndex == 1)
|
||||
if type(data) == "string" then
|
||||
return false
|
||||
end
|
||||
return true, "iconPack.12x12/Edit", true
|
||||
end
|
||||
|
||||
function private.OnEditIconClick(self, data, iconIndex)
|
||||
assert(iconIndex == 1 and type(data) ~= "string")
|
||||
local currentRow = nil
|
||||
for _, row in ipairs(self._rows) do
|
||||
if row:GetData() == data then
|
||||
currentRow = row
|
||||
break
|
||||
end
|
||||
end
|
||||
local name = private.GetItemText(self, data)
|
||||
local texture, tooltip = private.GetItemIcon(self, data)
|
||||
local dialogFrame = UIElements.New("Frame", "qty")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:AddAnchor("LEFT", currentRow._frame, Theme.GetColSpacing() / 2, 0)
|
||||
:AddAnchor("RIGHT", currentRow._frame, -Theme.GetColSpacing(), 0)
|
||||
:SetHeight(20)
|
||||
:SetContext(self)
|
||||
:SetBackgroundColor("PRIMARY_BG")
|
||||
:SetScript("OnHide", private.DialogOnHide)
|
||||
:AddChild(UIElements.New("Button", "icon")
|
||||
:SetSize(12, 12)
|
||||
:SetMargin(16, 4, 0, 0)
|
||||
:SetBackground(texture)
|
||||
:SetTooltip(tooltip)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "name")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetText(name)
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
:AddChild(UIElements.New("Input", "input")
|
||||
:SetWidth(75)
|
||||
:SetBackgroundColor("ACTIVE_BG")
|
||||
:SetJustifyH("CENTER")
|
||||
:SetContext(currentRow:GetData():GetField("spellId"))
|
||||
:SetSubAddEnabled(true)
|
||||
:SetValidateFunc("NUMBER", "1:9999")
|
||||
:SetValue(currentRow:GetData():GetField("num"))
|
||||
:SetScript("OnFocusLost", private.QtyInputOnFocusLost)
|
||||
)
|
||||
local baseFrame = self:GetBaseElement()
|
||||
baseFrame:ShowDialogFrame(dialogFrame)
|
||||
dialogFrame:GetElement("input"):SetFocused(true)
|
||||
end
|
||||
|
||||
function private.DialogOnHide(frame)
|
||||
local input = frame:GetElement("input")
|
||||
TSM.Crafting.Queue.SetNum(input:GetContext(), tonumber(input:GetValue()))
|
||||
frame:GetContext():Draw()
|
||||
end
|
||||
|
||||
function private.QtyInputOnFocusLost(input)
|
||||
input:GetBaseElement():HideDialog()
|
||||
end
|
||||
|
||||
function private.GetQty(self, data)
|
||||
if type(data) == "string" then
|
||||
return ""
|
||||
end
|
||||
local numQueued = data:GetFields("num")
|
||||
local numCraftable = min(self._numCraftableCache[data], numQueued)
|
||||
local onCooldown = TSM.Crafting.ProfessionUtil.GetRemainingCooldown(data:GetField("spellId"))
|
||||
local color = Theme.GetFeedbackColor(((numCraftable == 0 or onCooldown) and "RED") or (numCraftable < numQueued and "YELLOW") or "GREEN")
|
||||
return color:ColorText(format("%s / %s", numCraftable, numQueued))
|
||||
end
|
||||
|
||||
function private.PlayersContains(players, player)
|
||||
players = strlower(players)
|
||||
player = strlower(player)
|
||||
return players == player or strmatch(players, "^"..player..",") or strmatch(players, ","..player..",") or strmatch(players, ","..player.."$")
|
||||
end
|
||||
|
||||
function private.CategorySortComparator(a, b)
|
||||
local aProfession, aPlayers = strsplit(CATEGORY_SEP, a)
|
||||
local bProfession, bPlayers = strsplit(CATEGORY_SEP, b)
|
||||
if aProfession ~= bProfession then
|
||||
local currentProfession = TSM.Crafting.ProfessionUtil.GetCurrentProfessionName()
|
||||
currentProfession = strlower(currentProfession or "")
|
||||
if aProfession == currentProfession then
|
||||
return true
|
||||
elseif bProfession == currentProfession then
|
||||
return false
|
||||
else
|
||||
return aProfession < bProfession
|
||||
end
|
||||
end
|
||||
local playerName = UnitName("player")
|
||||
local aContainsPlayer = private.PlayersContains(aPlayers, playerName)
|
||||
local bContainsPlayer = private.PlayersContains(bPlayers, playerName)
|
||||
if aContainsPlayer and not bContainsPlayer then
|
||||
return true
|
||||
elseif bContainsPlayer and not aContainsPlayer then
|
||||
return false
|
||||
else
|
||||
return aPlayers < bPlayers
|
||||
end
|
||||
end
|
||||
|
||||
function private.DataSortComparator(a, b)
|
||||
-- sort by category
|
||||
local aCategory, bCategory = nil, nil
|
||||
if type(a) == "string" and type(b) == "string" then
|
||||
return private.categoryOrder[strlower(a)] < private.categoryOrder[strlower(b)]
|
||||
elseif type(a) == "string" then
|
||||
aCategory = strlower(a)
|
||||
bCategory = strlower(strjoin(CATEGORY_SEP, b:GetFields("profession", "players")))
|
||||
if aCategory == bCategory then
|
||||
return true
|
||||
end
|
||||
elseif type(b) == "string" then
|
||||
aCategory = strlower(strjoin(CATEGORY_SEP, a:GetFields("profession", "players")))
|
||||
bCategory = strlower(b)
|
||||
if aCategory == bCategory then
|
||||
return false
|
||||
end
|
||||
else
|
||||
aCategory = strlower(strjoin(CATEGORY_SEP, a:GetFields("profession", "players")))
|
||||
bCategory = strlower(strjoin(CATEGORY_SEP, b:GetFields("profession", "players")))
|
||||
end
|
||||
if aCategory ~= bCategory then
|
||||
return private.categoryOrder[aCategory] < private.categoryOrder[bCategory]
|
||||
end
|
||||
-- sort spells within a category
|
||||
local aSpellId = a:GetField("spellId")
|
||||
local bSpellId = b:GetField("spellId")
|
||||
local aNumCraftable = private.sortSelf._numCraftableCache[a]
|
||||
local bNumCraftable = private.sortSelf._numCraftableCache[b]
|
||||
local aNumQueued = a:GetField("num")
|
||||
local bNumQueued = b:GetField("num")
|
||||
local aCanCraftAll = aNumCraftable >= aNumQueued
|
||||
local bCanCraftAll = bNumCraftable >= bNumQueued
|
||||
if aCanCraftAll and not bCanCraftAll then
|
||||
return true
|
||||
elseif not aCanCraftAll and bCanCraftAll then
|
||||
return false
|
||||
end
|
||||
local aCanCraftSome = aNumCraftable > 0
|
||||
local bCanCraftSome = bNumCraftable > 0
|
||||
if aCanCraftSome and not bCanCraftSome then
|
||||
return true
|
||||
elseif not aCanCraftSome and bCanCraftSome then
|
||||
return false
|
||||
end
|
||||
local aProfit = private.sortProfitCache[aSpellId]
|
||||
local bProfit = private.sortProfitCache[bSpellId]
|
||||
if aProfit and not bProfit then
|
||||
return true
|
||||
elseif not aProfit and bProfit then
|
||||
return false
|
||||
end
|
||||
if aProfit ~= bProfit then
|
||||
return aProfit > bProfit
|
||||
end
|
||||
return aSpellId < bSpellId
|
||||
end
|
||||
|
||||
function private.QueryUpdateCallback(_, _, self)
|
||||
self:UpdateData(true)
|
||||
end
|
||||
281
Core/UI/Elements/DividedContainer.lua
Normal file
281
Core/UI/Elements/DividedContainer.lua
Normal file
@@ -0,0 +1,281 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- Divided Container UI Element Class.
|
||||
-- A divided container is a container with two children with a divider between them. It is a subclass of the @{Frame} class.
|
||||
-- @classmod DividedContainer
|
||||
|
||||
local _, TSM = ...
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local DividedContainer = TSM.Include("LibTSMClass").DefineClass("DividedContainer", TSM.UI.Frame)
|
||||
UIElements.Register(DividedContainer)
|
||||
TSM.UI.DividedContainer = DividedContainer
|
||||
local private = {}
|
||||
local DIVIDER_SIZE = 2
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function DividedContainer.__init(self)
|
||||
self.__super:__init()
|
||||
self._leftChild = nil
|
||||
self._rightChild = nil
|
||||
self._resizeStartX = nil
|
||||
self._resizeOffset = 0
|
||||
self._contextTable = nil
|
||||
self._defaultContextTable = nil
|
||||
self._minLeftWidth = nil
|
||||
self._minRightWidth = nil
|
||||
end
|
||||
|
||||
function DividedContainer.Acquire(self)
|
||||
self.__super:AddChildNoLayout(UIElements.New("Frame", "leftEmpty")
|
||||
:AddAnchor("TOPLEFT")
|
||||
:AddAnchor("BOTTOMRIGHT", "divider", "BOTTOMLEFT")
|
||||
)
|
||||
self.__super:AddChild(UIElements.New("Button", "divider")
|
||||
:SetSize(DIVIDER_SIZE, nil)
|
||||
:SetHitRectInsets(-2, -2, 0, 0)
|
||||
:SetRelativeLevel(2)
|
||||
:EnableRightClick()
|
||||
:SetScript("OnMouseDown", private.HandleOnMouseDown)
|
||||
:SetScript("OnMouseUp", private.HandleOnMouseUp)
|
||||
:SetScript("OnClick", private.HandleOnClick)
|
||||
:SetScript("OnUpdate", private.HandleOnUpdate)
|
||||
)
|
||||
self.__super:AddChildNoLayout(UIElements.New("Frame", "rightEmpty")
|
||||
:AddAnchor("TOPLEFT", "divider", "TOPRIGHT")
|
||||
:AddAnchor("BOTTOMRIGHT")
|
||||
)
|
||||
self.__super:Acquire()
|
||||
self.__super:SetLayout("HORIZONTAL")
|
||||
end
|
||||
|
||||
function DividedContainer.Release(self)
|
||||
self._isVertical = false
|
||||
self._leftChild = nil
|
||||
self._rightChild = nil
|
||||
self._resizeStartX = nil
|
||||
self._resizeOffset = 0
|
||||
self._contextTable = nil
|
||||
self._defaultContextTable = nil
|
||||
self._minLeftWidth = nil
|
||||
self._minRightWidth = nil
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
function DividedContainer.SetVertical(self)
|
||||
assert(not self._leftChild and not self._rightChild and not self._isVertical)
|
||||
self._isVertical = true
|
||||
self:GetElement("leftEmpty")
|
||||
:WipeAnchors()
|
||||
:AddAnchor("TOPLEFT")
|
||||
:AddAnchor("BOTTOMRIGHT", "divider", "TOPRIGHT")
|
||||
self:GetElement("divider")
|
||||
:SetSize(nil, DIVIDER_SIZE)
|
||||
:SetHitRectInsets(0, 0, -2, -2)
|
||||
self:GetElement("rightEmpty")
|
||||
:WipeAnchors()
|
||||
:AddAnchor("TOPLEFT", "divider", "BOTTOMLEFT")
|
||||
:AddAnchor("BOTTOMRIGHT")
|
||||
self.__super:SetLayout("VERTICAL")
|
||||
return self
|
||||
end
|
||||
|
||||
function DividedContainer.SetLayout(self, layout)
|
||||
error("DividedContainer doesn't support this method")
|
||||
end
|
||||
|
||||
function DividedContainer.AddChild(self, child)
|
||||
error("DividedContainer doesn't support this method")
|
||||
end
|
||||
|
||||
function DividedContainer.AddChildBeforeById(self, beforeId, child)
|
||||
error("DividedContainer doesn't support this method")
|
||||
end
|
||||
|
||||
--- Sets the context table.
|
||||
-- This table can be used to preserve the divider position across lifecycles of the divided container and even WoW
|
||||
-- sessions if it's within the settings DB. The position is stored as the width of the left child element.
|
||||
-- @tparam DividedContainer self The divided container object
|
||||
-- @tparam table tbl The context table
|
||||
-- @tparam table defaultTbl The default table (required fields: `leftWidth`)
|
||||
-- @treturn DividedContainer The divided container object
|
||||
function DividedContainer.SetContextTable(self, tbl, defaultTbl)
|
||||
assert(defaultTbl.leftWidth > 0)
|
||||
tbl.leftWidth = tbl.leftWidth or defaultTbl.leftWidth
|
||||
self._contextTable = tbl
|
||||
self._defaultContextTable = defaultTbl
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the context table from a settings object.
|
||||
-- @tparam DividedContainer self The divided container object
|
||||
-- @tparam Settings settings The settings object
|
||||
-- @tparam string key The setting key
|
||||
-- @treturn DividedContainer The divided container object
|
||||
function DividedContainer.SetSettingsContext(self, settings, key)
|
||||
return self:SetContextTable(settings[key], settings:GetDefaultReadOnly(key))
|
||||
end
|
||||
|
||||
--- Sets the minimum width of the child element.
|
||||
-- @tparam DividedContainer self The divided container object
|
||||
-- @tparam number minLeftWidth The minimum width of the left child element
|
||||
-- @tparam number minRightWidth The minimum width of the right child element
|
||||
-- @treturn DividedContainer The divided container object
|
||||
function DividedContainer.SetMinWidth(self, minLeftWidth, minRightWidth)
|
||||
self._minLeftWidth = minLeftWidth
|
||||
self._minRightWidth = minRightWidth
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the left child element.
|
||||
-- @tparam DividedContainer self The divided container object
|
||||
-- @tparam Element child The left child element
|
||||
-- @treturn DividedContainer The divided container object
|
||||
function DividedContainer.SetLeftChild(self, child)
|
||||
assert(not self._isVertical and not self._leftChild and child)
|
||||
self._leftChild = child
|
||||
self.__super:AddChildBeforeById("divider", child)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the right child element.
|
||||
-- @tparam DividedContainer self The divided container object
|
||||
-- @tparam Element child The right child element
|
||||
-- @treturn DividedContainer The divided container object
|
||||
function DividedContainer.SetRightChild(self, child)
|
||||
assert(not self._isVertical and not self._rightChild and child)
|
||||
self._rightChild = child
|
||||
self.__super:AddChild(child)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the top child element in vertical mode.
|
||||
-- @tparam DividedContainer self The divided container object
|
||||
-- @tparam Element child The top child element
|
||||
-- @treturn DividedContainer The divided container object
|
||||
function DividedContainer.SetTopChild(self, child)
|
||||
assert(self._isVertical and not self._leftChild and child)
|
||||
self._leftChild = child
|
||||
self.__super:AddChildBeforeById("divider", child)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the bottom child element in vertical mode.
|
||||
-- @tparam DividedContainer self The divided container object
|
||||
-- @tparam Element child The bottom child element
|
||||
-- @treturn DividedContainer The divided container object
|
||||
function DividedContainer.SetBottomChild(self, child)
|
||||
assert(self._isVertical and not self._rightChild and child)
|
||||
self._rightChild = child
|
||||
self.__super:AddChild(child)
|
||||
return self
|
||||
end
|
||||
|
||||
function DividedContainer.Draw(self)
|
||||
assert(self._contextTable and self._minLeftWidth and self._minRightWidth)
|
||||
self.__super.__super.__super:Draw()
|
||||
self:GetElement("divider")
|
||||
:SetBackground("ACTIVE_BG")
|
||||
:SetHighlightEnabled(true)
|
||||
|
||||
local width = self:_GetDimension(self._isVertical and "HEIGHT" or "WIDTH") - DIVIDER_SIZE
|
||||
local leftWidth = self._contextTable.leftWidth + self._resizeOffset
|
||||
local rightWidth = width - leftWidth
|
||||
if rightWidth < self._minRightWidth then
|
||||
leftWidth = width - self._minRightWidth
|
||||
assert(leftWidth >= self._minLeftWidth)
|
||||
elseif leftWidth < self._minLeftWidth then
|
||||
leftWidth = self._minLeftWidth
|
||||
end
|
||||
self._contextTable.leftWidth = leftWidth - self._resizeOffset
|
||||
|
||||
local leftChild = self._leftChild
|
||||
local rightChild = self._rightChild
|
||||
local leftEmpty = self:GetElement("leftEmpty")
|
||||
local rightEmpty = self:GetElement("rightEmpty")
|
||||
if self._isVertical then
|
||||
leftEmpty:SetHeight(leftWidth)
|
||||
leftChild:SetHeight(leftWidth)
|
||||
else
|
||||
leftEmpty:SetWidth(leftWidth)
|
||||
leftChild:SetWidth(leftWidth)
|
||||
end
|
||||
if self._resizeStartX then
|
||||
leftChild:_GetBaseFrame():SetAlpha(0)
|
||||
leftChild:_GetBaseFrame():SetFrameStrata("LOW")
|
||||
rightChild:_GetBaseFrame():SetAlpha(0)
|
||||
rightChild:_GetBaseFrame():SetFrameStrata("LOW")
|
||||
leftEmpty:Show()
|
||||
rightEmpty:Show()
|
||||
else
|
||||
leftChild:_GetBaseFrame():SetAlpha(1)
|
||||
leftChild:_GetBaseFrame():SetFrameStrata(self:_GetBaseFrame():GetFrameStrata())
|
||||
rightChild:_GetBaseFrame():SetAlpha(1)
|
||||
rightChild:_GetBaseFrame():SetFrameStrata(self:_GetBaseFrame():GetFrameStrata())
|
||||
leftEmpty:Hide()
|
||||
rightEmpty:Hide()
|
||||
end
|
||||
|
||||
self.__super:Draw()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.HandleOnUpdate(handle)
|
||||
local self = handle:GetParentElement()
|
||||
if self._resizeStartX then
|
||||
if self._isVertical then
|
||||
local currY = select(2, GetCursorPosition()) / self:_GetBaseFrame():GetEffectiveScale()
|
||||
self._resizeOffset = self._resizeStartX - currY
|
||||
else
|
||||
local currX = GetCursorPosition() / self:_GetBaseFrame():GetEffectiveScale()
|
||||
self._resizeOffset = currX - self._resizeStartX
|
||||
end
|
||||
self:Draw()
|
||||
end
|
||||
end
|
||||
|
||||
function private.HandleOnMouseDown(handle, mouseButton)
|
||||
if mouseButton ~= "LeftButton" then
|
||||
return
|
||||
end
|
||||
local self = handle:GetParentElement()
|
||||
if self._isVertical then
|
||||
self._resizeStartX = select(2, GetCursorPosition()) / self:_GetBaseFrame():GetEffectiveScale()
|
||||
else
|
||||
self._resizeStartX = GetCursorPosition() / self:_GetBaseFrame():GetEffectiveScale()
|
||||
end
|
||||
self._resizeOffset = 0
|
||||
end
|
||||
|
||||
function private.HandleOnMouseUp(handle, mouseButton)
|
||||
if mouseButton ~= "LeftButton" then
|
||||
return
|
||||
end
|
||||
local self = handle:GetParentElement()
|
||||
self._contextTable.leftWidth = max(self._contextTable.leftWidth + self._resizeOffset, self._minLeftWidth)
|
||||
self._resizeOffset = 0
|
||||
self._resizeStartX = nil
|
||||
self:Draw()
|
||||
end
|
||||
|
||||
function private.HandleOnClick(handle, mouseButton)
|
||||
if mouseButton ~= "RightButton" then
|
||||
return
|
||||
end
|
||||
local self = handle:GetParentElement()
|
||||
self._contextTable.leftWidth = self._defaultContextTable.leftWidth
|
||||
self:Draw()
|
||||
end
|
||||
195
Core/UI/Elements/DropdownList.lua
Normal file
195
Core/UI/Elements/DropdownList.lua
Normal file
@@ -0,0 +1,195 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Color = TSM.Include("Util.Color")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local DropdownList = TSM.Include("LibTSMClass").DefineClass("DropdownList", TSM.UI.ScrollingTable)
|
||||
UIElements.Register(DropdownList)
|
||||
TSM.UI.DropdownList = DropdownList
|
||||
local private = {}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function DropdownList.__init(self)
|
||||
self.__super:__init()
|
||||
self._selectedItems = {}
|
||||
self._multiselect = false
|
||||
self._onSelectionChangedHandler = nil
|
||||
end
|
||||
|
||||
function DropdownList.Acquire(self)
|
||||
self._backgroundColor = "ACTIVE_BG"
|
||||
self._headerHidden = true
|
||||
self.__super:Acquire()
|
||||
self:SetSelectionDisabled(true)
|
||||
self:GetScrollingTableInfo()
|
||||
:NewColumn("text")
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextFunction(private.GetText)
|
||||
:SetIconSize(12)
|
||||
:SetIconFunction(private.GetIcon)
|
||||
:DisableHiding()
|
||||
:Commit()
|
||||
:Commit()
|
||||
end
|
||||
|
||||
function DropdownList.Release(self)
|
||||
wipe(self._selectedItems)
|
||||
self._multiselect = false
|
||||
self._onSelectionChangedHandler = nil
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
function DropdownList.SetMultiselect(self, multiselect)
|
||||
self._multiselect = multiselect
|
||||
return self
|
||||
end
|
||||
|
||||
function DropdownList.SetItems(self, items, selection, redraw)
|
||||
wipe(self._data)
|
||||
for _, item in ipairs(items) do
|
||||
tinsert(self._data, item)
|
||||
end
|
||||
self:_SetSelectionHelper(selection)
|
||||
|
||||
if redraw then
|
||||
self:Draw()
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function DropdownList.ItemIterator(self)
|
||||
return private.ItemIterator, self, 0
|
||||
end
|
||||
|
||||
function DropdownList.SetSelection(self, selection)
|
||||
self:_SetSelectionHelper(selection)
|
||||
if self._onSelectionChangedHandler then
|
||||
self:_onSelectionChangedHandler(self._multiselect and self._selectedItems or selection)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function DropdownList.GetSelection(self)
|
||||
if self._multiselect then
|
||||
return self._selectedItems
|
||||
else
|
||||
local selectedItem = next(self._selectedItems)
|
||||
return selectedItem
|
||||
end
|
||||
end
|
||||
|
||||
function DropdownList.SelectAll(self)
|
||||
assert(self._multiselect)
|
||||
for _, data in ipairs(self._data) do
|
||||
self._selectedItems[data] = true
|
||||
end
|
||||
if self._onSelectionChangedHandler then
|
||||
self:_onSelectionChangedHandler(self._selectedItems)
|
||||
end
|
||||
self:Draw()
|
||||
end
|
||||
|
||||
function DropdownList.DeselectAll(self)
|
||||
assert(self._multiselect)
|
||||
wipe(self._selectedItems)
|
||||
if self._onSelectionChangedHandler then
|
||||
self:_onSelectionChangedHandler(self._selectedItems)
|
||||
end
|
||||
self:Draw()
|
||||
end
|
||||
|
||||
function DropdownList.SetScript(self, script, handler)
|
||||
if script == "OnSelectionChanged" then
|
||||
self._onSelectionChangedHandler = handler
|
||||
else
|
||||
error("Invalid DropdownList script: "..tostring(script))
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function DropdownList.Draw(self)
|
||||
self.__super:Draw()
|
||||
|
||||
local textColor = nil
|
||||
|
||||
local color = Theme.GetColor(self._backgroundColor)
|
||||
-- the text color should have maximum contrast with the background color, so set it to white/black based on the background color
|
||||
if color:IsLight() then
|
||||
-- the background is light, so set the text to black
|
||||
textColor = Color.GetFullBlack()
|
||||
else
|
||||
-- the background is dark, so set the text to white
|
||||
textColor = Color.GetFullWhite()
|
||||
end
|
||||
for _, row in ipairs(self._rows) do
|
||||
row:SetTextColor(textColor)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function DropdownList._SetSelectionHelper(self, selection)
|
||||
wipe(self._selectedItems)
|
||||
if selection then
|
||||
if self._multiselect then
|
||||
assert(type(selection) == "table")
|
||||
for item, selected in pairs(selection) do
|
||||
self._selectedItems[item] = selected
|
||||
end
|
||||
else
|
||||
assert(type(selection) == "string" or type(selection) == "number")
|
||||
self._selectedItems[selection] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function DropdownList._HandleRowClick(self, data)
|
||||
if self._multiselect then
|
||||
self._selectedItems[data] = not self._selectedItems[data] or nil
|
||||
if self._onSelectionChangedHandler then
|
||||
self:_onSelectionChangedHandler(self._selectedItems)
|
||||
end
|
||||
self:Draw()
|
||||
else
|
||||
self:SetSelection(data)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetText(self, data)
|
||||
return data
|
||||
end
|
||||
|
||||
function private.GetIcon(self, data)
|
||||
return self._multiselect and self._selectedItems[data] and "iconPack.12x12/Checkmark/Default" or ""
|
||||
end
|
||||
|
||||
function private.ItemIterator(self, index)
|
||||
index = index + 1
|
||||
local item = self._data[index]
|
||||
if not item then
|
||||
return
|
||||
end
|
||||
return index, item, self._selectedItems[item]
|
||||
end
|
||||
179
Core/UI/Elements/EditableText.lua
Normal file
179
Core/UI/Elements/EditableText.lua
Normal file
@@ -0,0 +1,179 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- EditableText UI Element Class.
|
||||
-- A text element which has an editing state. It is a subclass of the @{Text} class.
|
||||
-- @classmod EditableText
|
||||
|
||||
local _, TSM = ...
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local ScriptWrapper = TSM.Include("Util.ScriptWrapper")
|
||||
local ItemLinked = TSM.Include("Service.ItemLinked")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local EditableText = TSM.Include("LibTSMClass").DefineClass("EditableText", TSM.UI.Text)
|
||||
UIElements.Register(EditableText)
|
||||
TSM.UI.EditableText = EditableText
|
||||
local private = {}
|
||||
local STRING_RIGHT_PADDING = 16
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function EditableText.__init(self)
|
||||
local frame = UIElements.CreateFrame(self, "EditBox")
|
||||
|
||||
self.__super:__init(frame)
|
||||
|
||||
frame:SetShadowColor(0, 0, 0, 0)
|
||||
frame:SetAutoFocus(false)
|
||||
ScriptWrapper.Set(frame, "OnEscapePressed", private.OnEscapePressed, self)
|
||||
ScriptWrapper.Set(frame, "OnEnterPressed", private.OnEnterPressed, self)
|
||||
ScriptWrapper.Set(frame, "OnEditFocusLost", private.OnEditFocusLost, self)
|
||||
|
||||
frame.text = UIElements.CreateFontString(self, frame)
|
||||
frame.text:SetAllPoints()
|
||||
|
||||
|
||||
local function ItemLinkedCallback(name, link)
|
||||
if self._allowItemInsert == nil or not self:IsVisible() or not self._editing then
|
||||
return
|
||||
end
|
||||
if self._allowItemInsert == true then
|
||||
frame:Insert(link)
|
||||
else
|
||||
frame:Insert(name)
|
||||
end
|
||||
return true
|
||||
end
|
||||
ItemLinked.RegisterCallback(ItemLinkedCallback, -1)
|
||||
|
||||
self._editing = false
|
||||
self._allowItemInsert = nil
|
||||
self._onValueChangedHandler = nil
|
||||
self._onEditingChangedHandler = nil
|
||||
end
|
||||
|
||||
function EditableText.Release(self)
|
||||
self:_GetBaseFrame():ClearFocus()
|
||||
self:_GetBaseFrame():Disable()
|
||||
self._editing = false
|
||||
self._allowItemInsert = nil
|
||||
self._onValueChangedHandler = nil
|
||||
self._onEditingChangedHandler = nil
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
--- Registers a script handler.
|
||||
-- @tparam EditableText self The editable text object
|
||||
-- @tparam string script The script to register for (supported scripts: `OnValueChanged`, `OnEditingChanged`)
|
||||
-- @tparam function handler The script handler which will be called with the editable text object followed by any
|
||||
-- arguments to the script
|
||||
-- @treturn EditableText The editable text object
|
||||
function EditableText.SetScript(self, script, handler)
|
||||
if script == "OnValueChanged" then
|
||||
self._onValueChangedHandler = handler
|
||||
elseif script == "OnEditingChanged" then
|
||||
self._onEditingChangedHandler = handler
|
||||
elseif script == "OnEnter" or script == "OnLeave" or script == "OnMouseDown" then
|
||||
self.__super:SetScript(script, handler)
|
||||
else
|
||||
error("Unknown EditableText script: "..tostring(script))
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets whether or not the text is currently being edited.
|
||||
-- @tparam EditableText self The editable text object
|
||||
-- @tparam boolean editing The editing state to set
|
||||
-- @treturn EditableText The editable text object
|
||||
function EditableText.SetEditing(self, editing)
|
||||
self._editing = editing
|
||||
if self._onEditingChangedHandler then
|
||||
self:_onEditingChangedHandler(editing)
|
||||
end
|
||||
if self._autoWidth then
|
||||
self:GetParentElement():Draw()
|
||||
else
|
||||
self:Draw()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Allows inserting an item into the editable text by linking it while the editable text has focus.
|
||||
-- @tparam EditableText self The editable text object
|
||||
-- @tparam[opt=false] boolean insertLink Insert the link instead of the item name
|
||||
-- @treturn EditableText The editable text object
|
||||
function EditableText.AllowItemInsert(self, insertLink)
|
||||
assert(insertLink == true or insertLink == false or insertLink == nil)
|
||||
self._allowItemInsert = insertLink or false
|
||||
return self
|
||||
end
|
||||
|
||||
function EditableText.Draw(self)
|
||||
self.__super:Draw()
|
||||
local frame = self:_GetBaseFrame()
|
||||
|
||||
-- set the editbox font
|
||||
frame:SetFont(Theme.GetFont(self._font):GetWowFont())
|
||||
|
||||
-- set the justification
|
||||
frame:SetJustifyH(self._justifyH)
|
||||
frame:SetJustifyV(self._justifyV)
|
||||
|
||||
-- set the text color
|
||||
frame:SetTextColor(self:_GetTextColor():GetFractionalRGBA())
|
||||
|
||||
if self._editing then
|
||||
frame:Enable()
|
||||
frame:SetText(self._textStr)
|
||||
frame:SetFocus()
|
||||
frame:HighlightText(0, -1)
|
||||
frame.text:Hide()
|
||||
else
|
||||
frame:SetText("")
|
||||
frame:ClearFocus()
|
||||
frame:HighlightText(0, 0)
|
||||
frame:Disable()
|
||||
frame.text:Show()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function EditableText._GetPreferredDimension(self, dimension)
|
||||
if dimension == "WIDTH" and self._autoWidth and not self._editing then
|
||||
return self:GetStringWidth() + STRING_RIGHT_PADDING
|
||||
else
|
||||
return self.__super.__super:_GetPreferredDimension(dimension)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.OnEscapePressed(self)
|
||||
self:SetEditing(false)
|
||||
end
|
||||
|
||||
function private.OnEnterPressed(self)
|
||||
local newText = self:_GetBaseFrame():GetText()
|
||||
self:SetEditing(false)
|
||||
self:_onValueChangedHandler(newText)
|
||||
end
|
||||
|
||||
function private.OnEditFocusLost(self)
|
||||
self:SetEditing(false)
|
||||
end
|
||||
575
Core/UI/Elements/Element.lua
Normal file
575
Core/UI/Elements/Element.lua
Normal file
@@ -0,0 +1,575 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- Base UI Element Class.
|
||||
-- This the base class for all other UI element classes.
|
||||
-- @classmod Element
|
||||
|
||||
local _, TSM = ...
|
||||
local Element = TSM.Include("LibTSMClass").DefineClass("Element", nil, "ABSTRACT")
|
||||
local ScriptWrapper = TSM.Include("Util.ScriptWrapper")
|
||||
local Analytics = TSM.Include("Util.Analytics")
|
||||
local Tooltip = TSM.Include("UI.Tooltip")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(Element)
|
||||
TSM.UI.Element = Element
|
||||
local private = {}
|
||||
local ANCHOR_REL_PARENT = newproxy()
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function Element.__init(self, frame)
|
||||
self._tags = {}
|
||||
self._frame = frame
|
||||
self._scripts = {}
|
||||
self._baseElementCache = nil
|
||||
self._parent = nil
|
||||
self._context = nil
|
||||
self._acquired = nil
|
||||
self._tooltip = nil
|
||||
self._width = nil
|
||||
self._height = nil
|
||||
self._margin = { left = 0, right = 0, top = 0, bottom = 0 }
|
||||
self._padding = { left = 0, right = 0, top = 0, bottom = 0 }
|
||||
self._relativeLevel = nil
|
||||
self._anchors = {}
|
||||
end
|
||||
|
||||
function Element.__tostring(self)
|
||||
local parentId = self._parent and self._parent._id
|
||||
return self.__class.__name..":"..(parentId and (parentId..".") or "")..(self._id or "?")
|
||||
end
|
||||
|
||||
function Element.SetId(self, id)
|
||||
-- should only be called by core UI code before acquiring the element
|
||||
assert(not self._acquired)
|
||||
self._id = id or tostring(self)
|
||||
end
|
||||
|
||||
function Element.SetTags(self, ...)
|
||||
-- should only be called by core UI code before acquiring the element
|
||||
assert(not self._acquired)
|
||||
assert(#self._tags == 0)
|
||||
for i = 1, select("#", ...) do
|
||||
local tag = select(i, ...)
|
||||
tinsert(self._tags, tag)
|
||||
end
|
||||
end
|
||||
|
||||
function Element.Acquire(self)
|
||||
assert(not self._acquired)
|
||||
self._acquired = true
|
||||
self:Show()
|
||||
end
|
||||
|
||||
function Element.Release(self)
|
||||
assert(self._acquired)
|
||||
local frame = self:_GetBaseFrame()
|
||||
|
||||
-- clear the OnLeave script before hiding the frame (otherwise it'll get called)
|
||||
if self._scripts.OnLeave then
|
||||
frame:SetScript("OnLeave", nil)
|
||||
self._scripts.OnLeave = nil
|
||||
end
|
||||
|
||||
if self._tooltip and Tooltip.IsVisible(frame) then
|
||||
-- hide the tooltip
|
||||
Tooltip.Hide()
|
||||
end
|
||||
|
||||
self:Hide()
|
||||
frame:ClearAllPoints()
|
||||
frame:SetParent(nil)
|
||||
frame:SetScale(1)
|
||||
-- clear scripts
|
||||
for script in pairs(self._scripts) do
|
||||
frame:SetScript(script, nil)
|
||||
end
|
||||
|
||||
wipe(self._tags)
|
||||
wipe(self._scripts)
|
||||
self._baseElementCache = nil
|
||||
self._parent = nil
|
||||
self._context = nil
|
||||
self._acquired = nil
|
||||
self._tooltip = nil
|
||||
self._width = nil
|
||||
self._height = nil
|
||||
self._margin.left = 0
|
||||
self._margin.right = 0
|
||||
self._margin.top = 0
|
||||
self._margin.bottom = 0
|
||||
self._padding.left = 0
|
||||
self._padding.right = 0
|
||||
self._padding.top = 0
|
||||
self._padding.bottom = 0
|
||||
self._relativeLevel = nil
|
||||
wipe(self._anchors)
|
||||
|
||||
UIElements.Recycle(self)
|
||||
end
|
||||
|
||||
--- Shows the element.
|
||||
-- @tparam Element self The element object
|
||||
function Element.Show(self)
|
||||
self:_GetBaseFrame():Show()
|
||||
return self
|
||||
end
|
||||
|
||||
--- Hides the element.
|
||||
-- @tparam Element self The element object
|
||||
function Element.Hide(self)
|
||||
self:_GetBaseFrame():Hide()
|
||||
return self
|
||||
end
|
||||
|
||||
--- Returns whether or not the element is visible.
|
||||
-- @tparam Element self The element object
|
||||
-- @treturn boolean Whether or not the element is currently visible
|
||||
function Element.IsVisible(self)
|
||||
return self:_GetBaseFrame():IsVisible()
|
||||
end
|
||||
|
||||
--- Sets the width of the element.
|
||||
-- @tparam Element self The element object
|
||||
-- @tparam ?number width The width of the element, or nil to have an undefined width
|
||||
-- @treturn Element The element object
|
||||
function Element.SetWidth(self, width)
|
||||
assert(width == nil or type(width) == "number")
|
||||
self._width = width
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the height of the element.
|
||||
-- @tparam Element self The element object
|
||||
-- @tparam ?number height The height of the element, or nil to have an undefined height
|
||||
-- @treturn Element The element object
|
||||
function Element.SetHeight(self, height)
|
||||
assert(height == nil or type(height) == "number")
|
||||
self._height = height
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the width and height of the element.
|
||||
-- @tparam Element self The element object
|
||||
-- @tparam ?number width The width of the element, or nil to have an undefined width
|
||||
-- @tparam ?number height The height of the element, or nil to have an undefined height
|
||||
-- @treturn Element The element object
|
||||
function Element.SetSize(self, width, height)
|
||||
self:SetWidth(width)
|
||||
self:SetHeight(height)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the padding of the element.
|
||||
-- @tparam Element self The element object
|
||||
-- @tparam number left The left padding value if all arguments are passed or the value of all sides if a single argument is passed
|
||||
-- @tparam[opt] number right The right padding value if all arguments are passed
|
||||
-- @tparam[opt] number top The top padding value if all arguments are passed
|
||||
-- @tparam[opt] number bottom The bottom padding value if all arguments are passed
|
||||
-- @treturn Element The element object
|
||||
function Element.SetPadding(self, left, right, top, bottom)
|
||||
if not right and not top and not bottom then
|
||||
right = left
|
||||
top = left
|
||||
bottom = left
|
||||
end
|
||||
assert(type(left) == "number" and type(right) == "number" and type(top) == "number" and type(bottom) == "number")
|
||||
self._padding.left = left
|
||||
self._padding.right = right
|
||||
self._padding.top = top
|
||||
self._padding.bottom = bottom
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the margin of the element.
|
||||
-- @tparam Element self The element object
|
||||
-- @tparam number left The left margin value if all arguments are passed or the value of all sides if a single argument is passed
|
||||
-- @tparam[opt] number right The right margin value if all arguments are passed
|
||||
-- @tparam[opt] number top The top margin value if all arguments are passed
|
||||
-- @tparam[opt] number bottom The bottom margin value if all arguments are passed
|
||||
-- @treturn Element The element object
|
||||
function Element.SetMargin(self, left, right, top, bottom)
|
||||
if not right and not top and not bottom then
|
||||
right = left
|
||||
top = left
|
||||
bottom = left
|
||||
end
|
||||
assert(type(left) == "number" and type(right) == "number" and type(top) == "number" and type(bottom) == "number")
|
||||
self._margin.left = left
|
||||
self._margin.right = right
|
||||
self._margin.top = top
|
||||
self._margin.bottom = bottom
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the relative level of this element with regards to its parent.
|
||||
-- @tparam Element self The element object
|
||||
-- @tparam number level The relative level of this element
|
||||
-- @treturn Element The element object
|
||||
function Element.SetRelativeLevel(self, level)
|
||||
self._relativeLevel = level
|
||||
return self
|
||||
end
|
||||
|
||||
--- Wipes the element's anchors.
|
||||
-- @treturn Element The element object
|
||||
function Element.WipeAnchors(self)
|
||||
wipe(self._anchors)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Adds an anchor to the element.
|
||||
-- @tparam Element self The element object
|
||||
-- @param ... The anchor arguments (following WoW's SetPoint() arguments)
|
||||
-- @treturn Element The element object
|
||||
function Element.AddAnchor(self, ...)
|
||||
local numArgs = select("#", ...)
|
||||
local point, relFrame, relPoint, x, y = nil, nil, nil, nil, nil
|
||||
if numArgs == 1 then
|
||||
point = ...
|
||||
elseif numArgs == 2 then
|
||||
point, relFrame = ...
|
||||
elseif numArgs == 3 then
|
||||
local arg2 = select(2, ...)
|
||||
if type(arg2) == "number" then
|
||||
point, x, y = ...
|
||||
else
|
||||
point, relFrame, relPoint = ...
|
||||
end
|
||||
elseif numArgs == 4 then
|
||||
point, relFrame, x, y = ...
|
||||
elseif numArgs == 5 then
|
||||
point, relFrame, relPoint, x, y = ...
|
||||
else
|
||||
error("Invalid anchor")
|
||||
end
|
||||
tinsert(self._anchors, point)
|
||||
tinsert(self._anchors, relFrame or ANCHOR_REL_PARENT)
|
||||
tinsert(self._anchors, relPoint or point)
|
||||
tinsert(self._anchors, x or 0)
|
||||
tinsert(self._anchors, y or 0)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Gets the top-most element in the tree.
|
||||
-- @tparam Element self The element object
|
||||
-- @treturn Element The top-most element object
|
||||
function Element.GetBaseElement(self)
|
||||
if not self._baseElementCache then
|
||||
local element = self
|
||||
local parent = element:GetParentElement()
|
||||
while parent do
|
||||
local temp = element
|
||||
element = parent
|
||||
parent = temp:GetParentElement()
|
||||
end
|
||||
self._baseElementCache = element
|
||||
end
|
||||
return self._baseElementCache
|
||||
end
|
||||
|
||||
--- Gets the parent element's base frame.
|
||||
-- @tparam Element self The element object
|
||||
-- @treturn Element The parent element's base frame
|
||||
function Element.GetParent(self)
|
||||
return self:GetParentElement():_GetBaseFrame()
|
||||
end
|
||||
|
||||
--- Gets the parent element.
|
||||
-- @tparam Element self The element object
|
||||
-- @treturn Element The parent element object
|
||||
function Element.GetParentElement(self)
|
||||
return self._parent
|
||||
end
|
||||
|
||||
--- Gets another element in the tree by relative path.
|
||||
-- The path consists of element ids separated by `.`. `__parent` may also be used to indicate the parent element.
|
||||
-- @tparam Element self The element object
|
||||
-- @tparam string path The relative path to the element
|
||||
-- @treturn Element The desired element
|
||||
function Element.GetElement(self, path)
|
||||
-- First try to find the element as a child of self
|
||||
local result = private.GetElementHelper(self, path)
|
||||
if not result then
|
||||
Analytics.Action("GET_ELEMENT_FAIL", tostring(self), path)
|
||||
end
|
||||
-- TODO: is this needed?
|
||||
result = result or private.GetElementHelper(self:GetBaseElement(), path)
|
||||
return result
|
||||
end
|
||||
|
||||
--- Sets the tooltip of the element.
|
||||
-- @tparam Element self The element object
|
||||
-- @param tooltip The value passed to @{Tooltip.Show} when the user hovers over the element, or nil to clear it
|
||||
-- @treturn Element The element object
|
||||
function Element.SetTooltip(self, tooltip)
|
||||
self._tooltip = tooltip
|
||||
if tooltip then
|
||||
-- setting OnEnter/OnLeave will implicitly enable the mouse, so make sure it's previously been enabled
|
||||
assert(self:_GetBaseFrame():IsMouseEnabled())
|
||||
self:SetScript("OnEnter", private.OnEnter)
|
||||
self:SetScript("OnLeave", private.OnLeave)
|
||||
else
|
||||
self:SetScript("OnEnter", nil)
|
||||
self:SetScript("OnLeave", nil)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Shows a tooltip on the element.
|
||||
-- @tparam Element self The element object
|
||||
-- @param tooltip The value passed to @{Tooltip.Show} when the user hovers over the element
|
||||
-- @tparam ?boolean noWrapping Disables wrapping of text lines
|
||||
-- @tparam[opt=0] number xOffset An extra x offset to apply to the anchor of the tooltip
|
||||
-- @treturn Element The element object
|
||||
function Element.ShowTooltip(self, tooltip, noWrapping, xOffset)
|
||||
Tooltip.Show(self:_GetBaseFrame(), tooltip, noWrapping, xOffset)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the context value of the element.
|
||||
-- @tparam Element self The element object
|
||||
-- @param context The context value
|
||||
-- @treturn Element The element object
|
||||
function Element.SetContext(self, context)
|
||||
self._context = context
|
||||
return self
|
||||
end
|
||||
|
||||
--- Gets the context value from the element.
|
||||
-- @tparam Element self The element object
|
||||
-- @return The context value
|
||||
function Element.GetContext(self)
|
||||
return self._context
|
||||
end
|
||||
|
||||
--- Registers a script handler.
|
||||
-- @tparam Element self The element object
|
||||
-- @tparam string script The script to register for
|
||||
-- @tparam function handler The script handler which will be called with the element object followed by any arguments to
|
||||
-- the script
|
||||
-- @treturn Element The element object
|
||||
function Element.SetScript(self, script, handler)
|
||||
self._scripts[script] = handler
|
||||
if handler then
|
||||
ScriptWrapper.Set(self:_GetBaseFrame(), script, handler, self)
|
||||
else
|
||||
ScriptWrapper.Clear(self:_GetBaseFrame(), script)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets a script to propagate to the parent element.
|
||||
-- @tparam Element self The element object
|
||||
-- @tparam string script The script to propagate
|
||||
-- @treturn Element The element object
|
||||
function Element.PropagateScript(self, script)
|
||||
self._scripts[script] = "__PROPAGATE"
|
||||
ScriptWrapper.SetPropagate(self:_GetBaseFrame(), script, self)
|
||||
return self
|
||||
end
|
||||
|
||||
function Element.Draw(self)
|
||||
assert(self._acquired)
|
||||
local frame = self:_GetBaseFrame()
|
||||
local numAnchors = self:_GetNumAnchors()
|
||||
if numAnchors > 0 then
|
||||
frame:ClearAllPoints()
|
||||
for i = 1, numAnchors do
|
||||
local point, relFrame, relPoint, x, y = self:_GetAnchor(i)
|
||||
if relFrame == ANCHOR_REL_PARENT then
|
||||
relFrame = frame:GetParent()
|
||||
elseif type(relFrame) == "string" then
|
||||
-- this is a relative element
|
||||
relFrame = self:GetParentElement():GetElement(relFrame):_GetBaseFrame()
|
||||
end
|
||||
frame:SetPoint(point, relFrame, relPoint, x, y)
|
||||
end
|
||||
end
|
||||
local width = self._width
|
||||
if width then
|
||||
self:_SetDimension("WIDTH", width)
|
||||
end
|
||||
local height = self._height
|
||||
if height then
|
||||
self:_SetDimension("HEIGHT", height)
|
||||
end
|
||||
local relativeLevel = self._relativeLevel
|
||||
if relativeLevel then
|
||||
frame:SetFrameLevel(frame:GetParent():GetFrameLevel() + relativeLevel)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function Element._GetNumAnchors(self)
|
||||
assert(#self._anchors % 5 == 0)
|
||||
return #self._anchors / 5
|
||||
end
|
||||
|
||||
function Element._GetAnchor(self, index)
|
||||
index = (index - 1) * 5 + 1
|
||||
assert(index < #self._anchors)
|
||||
return unpack(self._anchors, index, index + 4)
|
||||
end
|
||||
|
||||
function Element._SetParentElement(self, parent)
|
||||
self._parent = parent
|
||||
self:_ClearBaseElementCache()
|
||||
end
|
||||
|
||||
function Element._ClearBaseElementCache(self)
|
||||
self._baseElementCache = nil
|
||||
end
|
||||
|
||||
function Element._GetMinimumDimension(self, dimension)
|
||||
if dimension == "WIDTH" then
|
||||
local width = self._width
|
||||
return width or 0, width == nil
|
||||
elseif dimension == "HEIGHT" then
|
||||
local height = self._height
|
||||
return height or 0, height == nil
|
||||
else
|
||||
error("Invalid dimension: " .. tostring(dimension))
|
||||
end
|
||||
end
|
||||
|
||||
function Element._GetPreferredDimension(self, dimension)
|
||||
if dimension == "WIDTH" then
|
||||
return nil
|
||||
elseif dimension == "HEIGHT" then
|
||||
return nil
|
||||
else
|
||||
error("Invalid dimension: " .. tostring(dimension))
|
||||
end
|
||||
end
|
||||
|
||||
function Element._GetDimension(self, dimension)
|
||||
if dimension == "WIDTH" then
|
||||
return self:_GetBaseFrame():GetWidth()
|
||||
elseif dimension == "HEIGHT" then
|
||||
return self:_GetBaseFrame():GetHeight()
|
||||
else
|
||||
error("Invalid dimension: " .. tostring(dimension))
|
||||
end
|
||||
end
|
||||
|
||||
function Element._SetDimension(self, dimension, ...)
|
||||
if dimension == "WIDTH" then
|
||||
self:_GetBaseFrame():SetWidth(...)
|
||||
elseif dimension == "HEIGHT" then
|
||||
self:_GetBaseFrame():SetHeight(...)
|
||||
else
|
||||
error("Invalid dimension: " .. tostring(dimension))
|
||||
end
|
||||
end
|
||||
|
||||
function Element._GetBaseFrame(self)
|
||||
return self._frame
|
||||
end
|
||||
|
||||
function Element._GetPadding(self, side)
|
||||
return self._padding[strlower(side)]
|
||||
end
|
||||
|
||||
function Element._GetPaddingAnchorOffsets(self, anchor)
|
||||
local xPart, yPart = private.SplitAnchor(anchor)
|
||||
local x = xPart and ((xPart == "LEFT" and 1 or -1) * self:_GetPadding(xPart)) or 0
|
||||
local y = yPart and ((yPart == "BOTTOM" and 1 or -1) * self:_GetPadding(yPart)) or 0
|
||||
return x, y
|
||||
end
|
||||
|
||||
function Element._GetMargin(self, side)
|
||||
return self._margin[strlower(side)]
|
||||
end
|
||||
|
||||
function Element._GetMarginAnchorOffsets(self, anchor)
|
||||
local xPart, yPart = private.SplitAnchor(anchor)
|
||||
local x = xPart and ((xPart == "LEFT" and 1 or -1) * self:_GetMargin(xPart)) or 0
|
||||
local y = yPart and ((yPart == "BOTTOM" and 1 or -1) * self:_GetMargin(yPart)) or 0
|
||||
return x, y
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetElementHelper(element, path)
|
||||
local numParts = select("#", strsplit(".", path))
|
||||
local partIndex = 1
|
||||
while partIndex <= numParts do
|
||||
local part = select(partIndex, strsplit(".", path))
|
||||
if part == "__parent" then
|
||||
local parentElement = element:GetParentElement()
|
||||
if not parentElement then
|
||||
error(format("Element (%s) has no parent", tostring(element._id)))
|
||||
end
|
||||
element = parentElement
|
||||
elseif part == "__base" then
|
||||
local baseElement = element:GetBaseElement()
|
||||
if not baseElement then
|
||||
error(format("Element (%s) has no base element", tostring(element._id)))
|
||||
end
|
||||
element = baseElement
|
||||
else
|
||||
local found = false
|
||||
for _, child in ipairs(element._children) do
|
||||
if child._id == part then
|
||||
element = child
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not found then
|
||||
element = nil
|
||||
break
|
||||
end
|
||||
end
|
||||
partIndex = partIndex + 1
|
||||
end
|
||||
return element
|
||||
end
|
||||
|
||||
function private.SplitAnchor(anchor)
|
||||
if anchor == "BOTTOMLEFT" then
|
||||
return "LEFT", "BOTTOM"
|
||||
elseif anchor == "BOTTOM" then
|
||||
return nil, "BOTTOM"
|
||||
elseif anchor == "BOTTOMRIGHT" then
|
||||
return "RIGHT", "BOTTOM"
|
||||
elseif anchor == "RIGHT" then
|
||||
return "RIGHT", nil
|
||||
elseif anchor == "TOPRIGHT" then
|
||||
return "RIGHT", "TOP"
|
||||
elseif anchor == "TOP" then
|
||||
return nil, "TOP"
|
||||
elseif anchor == "TOPLEFT" then
|
||||
return "LEFT", "TOP"
|
||||
elseif anchor == "LEFT" then
|
||||
return "LEFT", nil
|
||||
else
|
||||
error("Invalid anchor: "..tostring(anchor))
|
||||
end
|
||||
end
|
||||
|
||||
function private.OnEnter(element)
|
||||
element:ShowTooltip(element._tooltip)
|
||||
end
|
||||
|
||||
function private.OnLeave(element)
|
||||
Tooltip.Hide()
|
||||
end
|
||||
456
Core/UI/Elements/Frame.lua
Normal file
456
Core/UI/Elements/Frame.lua
Normal file
@@ -0,0 +1,456 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- Frame UI Element Class.
|
||||
-- A frame is a container which supports automated layout of its children. It also supports being the base element of a UI and anchoring/parenting directly to a WoW frame. It is a subclass of the @{Container} class.
|
||||
-- @classmod Frame
|
||||
|
||||
local _, TSM = ...
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local Table = TSM.Include("Util.Table")
|
||||
local Color = TSM.Include("Util.Color")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local NineSlice = TSM.Include("Util.NineSlice")
|
||||
local VALID_LAYOUTS = {
|
||||
NONE = true,
|
||||
HORIZONTAL = true,
|
||||
VERTICAL = true,
|
||||
FLOW = true,
|
||||
}
|
||||
local LAYOUT_CONTEXT = {
|
||||
VERTICAL = {
|
||||
primaryDimension = "HEIGHT",
|
||||
secondaryDimension = "WIDTH",
|
||||
sides = { primary = { "TOP", "BOTTOM" }, secondary = { "LEFT", "RIGHT" } },
|
||||
},
|
||||
HORIZONTAL = {
|
||||
primaryDimension = "WIDTH",
|
||||
secondaryDimension = "HEIGHT",
|
||||
sides = { primary = { "LEFT", "RIGHT" }, secondary = { "TOP", "BOTTOM" } },
|
||||
},
|
||||
}
|
||||
local Frame = TSM.Include("LibTSMClass").DefineClass("Frame", TSM.UI.Container)
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(Frame)
|
||||
TSM.UI.Frame = Frame
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function Frame.__init(self)
|
||||
local frame = UIElements.CreateFrame(self, "Frame")
|
||||
|
||||
self.__super:__init(frame)
|
||||
|
||||
self._borderNineSlice = NineSlice.New(frame)
|
||||
self._borderNineSlice:Hide()
|
||||
|
||||
self._backgroundNineSlice = NineSlice.New(frame, 1)
|
||||
self._backgroundNineSlice:Hide()
|
||||
|
||||
self._layout = "NONE"
|
||||
self._backgroundColor = nil
|
||||
self._roundedCorners = false
|
||||
self._borderColor = nil
|
||||
self._borderSize = nil
|
||||
self._expandWidth = false
|
||||
self._strata = nil
|
||||
self._scale = 1
|
||||
end
|
||||
|
||||
function Frame.Release(self)
|
||||
self._layout = "NONE"
|
||||
self._backgroundColor = nil
|
||||
self._roundedCorners = false
|
||||
self._borderColor = nil
|
||||
self._borderSize = nil
|
||||
self._expandWidth = false
|
||||
self._strata = nil
|
||||
self._scale = 1
|
||||
self._borderNineSlice:Hide()
|
||||
self._backgroundNineSlice:Hide()
|
||||
local frame = self:_GetBaseFrame()
|
||||
frame:RegisterForDrag(nil)
|
||||
frame:EnableMouse(false)
|
||||
frame:SetMovable(false)
|
||||
frame:EnableMouseWheel(false)
|
||||
frame:SetHitRectInsets(0, 0, 0, 0)
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
--- Sets the background of the frame.
|
||||
-- @tparam Frame self The frame object
|
||||
-- @tparam ?string|Color|nil color The background color as a theme color key, Color object, or nil
|
||||
-- @tparam[opt=false] boolean roundedCorners Whether or not the corners should be rounded
|
||||
-- @treturn Frame The frame object
|
||||
function Frame.SetBackgroundColor(self, color, roundedCorners)
|
||||
assert(color == nil or Color.IsInstance(color) or Theme.GetColor(color))
|
||||
self._backgroundColor = color
|
||||
self._roundedCorners = roundedCorners
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the border color of the frame.
|
||||
-- @tparam Frame self The frame object
|
||||
-- @tparam ?string|nil color The border color as a theme color key or nil
|
||||
-- @tparam[opt=1] ?number borderSize The border size
|
||||
-- @treturn Frame The frame object
|
||||
function Frame.SetBorderColor(self, color, borderSize)
|
||||
assert(color == nil or Color.IsInstance(color) or Theme.GetColor(color))
|
||||
self._borderColor = color
|
||||
self._borderSize = borderSize or 1
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the width of the frame.
|
||||
-- @tparam Frame self The frame object
|
||||
-- @tparam ?number|string width The width of the frame, "EXPAND" to set the width to expand to be
|
||||
-- as large as possible, or nil to have an undefined width
|
||||
-- @treturn Frame The frame object
|
||||
function Frame.SetWidth(self, width)
|
||||
if width == "EXPAND" then
|
||||
self._expandWidth = true
|
||||
else
|
||||
self.__super:SetWidth(width)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the parent frame.
|
||||
-- @tparam Frame self The frame object
|
||||
-- @tparam frame parent The WoW frame to parent to
|
||||
-- @treturn Frame The frame object
|
||||
function Frame.SetParent(self, parent)
|
||||
self:_GetBaseFrame():SetParent(parent)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the level of the frame.
|
||||
-- @tparam Frame self The frame object
|
||||
-- @tparam number level The frame level
|
||||
-- @treturn Frame The frame object
|
||||
function Frame.SetFrameLevel(self, level)
|
||||
self:_GetBaseFrame():SetFrameLevel(level)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the strata of the frame.
|
||||
-- @tparam Frame self The frame object
|
||||
-- @tparam string strata The frame strata
|
||||
-- @treturn Frame The frame object
|
||||
function Frame.SetStrata(self, strata)
|
||||
self._strata = strata
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the scale of the frame.
|
||||
-- @tparam Frame self The frame object
|
||||
-- @tparam string scale The frame scale
|
||||
-- @treturn Frame The frame object
|
||||
function Frame.SetScale(self, scale)
|
||||
self._scale = scale
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the layout of the frame.
|
||||
-- @tparam Frame self The frame object
|
||||
-- @tparam string layout The frame layout (`NONE`, `HORIZONTAL`, `VERTICAL`, or `FLOW`)
|
||||
-- @treturn Frame The frame object
|
||||
function Frame.SetLayout(self, layout)
|
||||
assert(VALID_LAYOUTS[layout], format("Invalid layout (%s)", tostring(layout)))
|
||||
self._layout = layout
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets whether mouse interaction is enabled.
|
||||
-- @tparam Frame self The frame object
|
||||
-- @tparam boolean enabled Whether mouse interaction is enabled
|
||||
-- @treturn Frame The frame object
|
||||
function Frame.SetMouseEnabled(self, enabled)
|
||||
self:_GetBaseFrame():EnableMouse(enabled)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets whether mouse wheel interaction is enabled.
|
||||
-- @tparam Frame self The frame object
|
||||
-- @tparam boolean enabled Whether mouse wheel interaction is enabled
|
||||
-- @treturn Frame The frame object
|
||||
function Frame.SetMouseWheelEnabled(self, enabled)
|
||||
self:_GetBaseFrame():EnableMouseWheel(enabled)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Allows dragging of the frame.
|
||||
-- @tparam Frame self The frame object
|
||||
-- @tparam string button The button to support dragging with
|
||||
-- @treturn Frame The frame object
|
||||
function Frame.RegisterForDrag(self, button)
|
||||
self:SetMouseEnabled(button and true or false)
|
||||
self:_GetBaseFrame():RegisterForDrag(button)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Gets whether the mouse is currently over the frame.
|
||||
-- @tparam Frame self The frame object
|
||||
-- @treturn boolean Whether or not the mouse is over the frame
|
||||
function Frame.IsMouseOver(self)
|
||||
return self:_GetBaseFrame():IsMouseOver()
|
||||
end
|
||||
|
||||
--- Sets the hit rectangle insets.
|
||||
-- @tparam Frame self The frame object
|
||||
-- @tparam number left The left hit rectangle inset
|
||||
-- @tparam number right The right hit rectangle inset
|
||||
-- @tparam number top The top hit rectangle inset
|
||||
-- @tparam number bottom The bottom hit rectangle inset
|
||||
-- @treturn Frame The frame object
|
||||
function Frame.SetHitRectInsets(self, left, right, top, bottom)
|
||||
self:_GetBaseFrame():SetHitRectInsets(left, right, top, bottom)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Makes the element movable and starts moving it.
|
||||
-- @tparam Frame self The element object
|
||||
function Frame.StartMoving(self)
|
||||
self:_GetBaseFrame():SetMovable(true)
|
||||
self:_GetBaseFrame():StartMoving()
|
||||
return self
|
||||
end
|
||||
|
||||
--- Stops moving the element, and makes it unmovable.
|
||||
-- @tparam Frame self The element object
|
||||
function Frame.StopMovingOrSizing(self)
|
||||
self:_GetBaseFrame():StopMovingOrSizing()
|
||||
self:_GetBaseFrame():SetMovable(false)
|
||||
return self
|
||||
end
|
||||
|
||||
function Frame.Draw(self)
|
||||
local layout = self._layout
|
||||
self.__super.__super:Draw()
|
||||
local frame = self:_GetBaseFrame()
|
||||
|
||||
if self._backgroundColor then
|
||||
self._backgroundNineSlice:SetStyle(self._roundedCorners and "rounded" or "solid", self._borderColor and self._borderSize or nil)
|
||||
local color = Color.IsInstance(self._backgroundColor) and self._backgroundColor or Theme.GetColor(self._backgroundColor)
|
||||
self._backgroundNineSlice:SetVertexColor(color:GetFractionalRGBA())
|
||||
else
|
||||
assert(not self._borderColor)
|
||||
self._backgroundNineSlice:Hide()
|
||||
end
|
||||
if self._borderColor then
|
||||
assert(self._backgroundColor)
|
||||
self._borderNineSlice:SetStyle(self._roundedCorners and "rounded" or "solid")
|
||||
local color = Color.IsInstance(self._borderColor) and self._borderColor or Theme.GetColor(self._borderColor)
|
||||
self._borderNineSlice:SetVertexColor(color:GetFractionalRGBA())
|
||||
else
|
||||
self._borderNineSlice:Hide()
|
||||
end
|
||||
|
||||
frame:SetScale(self._scale)
|
||||
|
||||
local strata = self._strata
|
||||
if strata then
|
||||
frame:SetFrameStrata(strata)
|
||||
end
|
||||
|
||||
if layout == "NONE" then
|
||||
-- pass
|
||||
elseif layout == "FLOW" then
|
||||
local width = self:_GetDimension("WIDTH")
|
||||
local height = self:_GetDimension("HEIGHT") - self:_GetPadding("TOP") - self:_GetPadding("BOTTOM")
|
||||
local rowHeight = 0
|
||||
for _, child in self:LayoutChildrenIterator() do
|
||||
child:_GetBaseFrame():ClearAllPoints()
|
||||
local childPrimary = child:_GetMinimumDimension("WIDTH")
|
||||
child:_SetDimension("WIDTH", childPrimary)
|
||||
local childSecondary = child:_GetMinimumDimension("HEIGHT")
|
||||
rowHeight = childSecondary + child:_GetMargin("BOTTOM") + child:_GetMargin("TOP")
|
||||
child:_SetDimension("HEIGHT", childSecondary)
|
||||
end
|
||||
local xOffset = self:_GetPadding("LEFT")
|
||||
-- calculate the Y offset to properly position stuff with the padding of this frame taken into account
|
||||
local yOffset = -self:_GetPadding("TOP")
|
||||
for _, child in self:LayoutChildrenIterator() do
|
||||
local childFrame = child:_GetBaseFrame()
|
||||
local childWidth = childFrame:GetWidth() + child:_GetMargin("LEFT") + child:_GetMargin("RIGHT")
|
||||
if xOffset + childWidth + self:_GetPadding("RIGHT") > width then
|
||||
-- move to the next row
|
||||
xOffset = self:_GetPadding("LEFT")
|
||||
yOffset = yOffset - rowHeight
|
||||
end
|
||||
local childYOffset = yOffset + (height - childFrame:GetHeight()) / 2 - child:_GetMargin("TOP")
|
||||
childFrame:SetPoint("LEFT", xOffset + child:_GetMargin("LEFT"), childYOffset)
|
||||
xOffset = xOffset + childWidth
|
||||
end
|
||||
else
|
||||
local context = LAYOUT_CONTEXT[layout]
|
||||
assert(context)
|
||||
local primary = self:_GetDimension(context.primaryDimension) - self:_GetPadding(context.sides.primary[1]) - self:_GetPadding(context.sides.primary[2])
|
||||
local secondary = self:_GetDimension(context.secondaryDimension) - self:_GetPadding(context.sides.secondary[1]) - self:_GetPadding(context.sides.secondary[2])
|
||||
|
||||
local expandChildren = TempTable.Acquire()
|
||||
local preferredChildren = TempTable.Acquire()
|
||||
for _, child in self:LayoutChildrenIterator() do
|
||||
child:_GetBaseFrame():ClearAllPoints()
|
||||
local childPrimary, childPrimaryCanExpand = child:_GetMinimumDimension(context.primaryDimension)
|
||||
if childPrimaryCanExpand then
|
||||
local childPreferredPrimary = child:_GetPreferredDimension(context.primaryDimension)
|
||||
if childPreferredPrimary then
|
||||
assert(childPreferredPrimary > childPrimary, "Invalid preferred dimension")
|
||||
preferredChildren[child] = childPreferredPrimary
|
||||
else
|
||||
expandChildren[child] = childPrimary
|
||||
end
|
||||
else
|
||||
child:_SetDimension(context.primaryDimension, childPrimary)
|
||||
end
|
||||
primary = primary - childPrimary - child:_GetMargin(context.sides.primary[1]) - child:_GetMargin(context.sides.primary[2])
|
||||
local childSecondary, childSecondaryCanExpand = child:_GetMinimumDimension(context.secondaryDimension)
|
||||
childSecondary = min(childSecondary, secondary)
|
||||
if childSecondaryCanExpand and childSecondary < secondary then
|
||||
childSecondary = secondary
|
||||
end
|
||||
child:_SetDimension(context.secondaryDimension, childSecondary - child:_GetMargin(context.sides.secondary[1]) - child:_GetMargin(context.sides.secondary[2]))
|
||||
end
|
||||
for child, preferredPrimary in pairs(preferredChildren) do
|
||||
local childPrimary = min(primary, preferredPrimary)
|
||||
child:_SetDimension(context.primaryDimension, childPrimary)
|
||||
primary = primary - (childPrimary - child:_GetMinimumDimension(context.primaryDimension))
|
||||
end
|
||||
local numExpandChildren = Table.Count(expandChildren)
|
||||
for child, childPrimary in pairs(expandChildren) do
|
||||
childPrimary = max(childPrimary, childPrimary + primary / numExpandChildren)
|
||||
child:_SetDimension(context.primaryDimension, childPrimary)
|
||||
end
|
||||
TempTable.Release(expandChildren)
|
||||
TempTable.Release(preferredChildren)
|
||||
if layout == "HORIZONTAL" then
|
||||
local xOffset = self:_GetPadding("LEFT")
|
||||
-- calculate the Y offset to properly position stuff with the padding of this frame taken into account
|
||||
local yOffset = (self:_GetPadding("BOTTOM") - self:_GetPadding("TOP")) / 2
|
||||
for _, child in self:LayoutChildrenIterator() do
|
||||
local childFrame = child:_GetBaseFrame()
|
||||
xOffset = xOffset + child:_GetMargin("LEFT")
|
||||
local childYOffset = (child:_GetMargin("BOTTOM") - child:_GetMargin("TOP")) / 2
|
||||
childFrame:SetPoint("LEFT", xOffset, childYOffset + yOffset)
|
||||
xOffset = xOffset + childFrame:GetWidth() + child:_GetMargin("RIGHT")
|
||||
end
|
||||
elseif layout == "VERTICAL" then
|
||||
local yOffset = -self:_GetPadding("TOP")
|
||||
-- calculate the X offset to properly position stuff with the padding of this frame taken into account
|
||||
local xOffset = (self:_GetPadding("LEFT") - self:_GetPadding("RIGHT")) / 2
|
||||
for _, child in self:LayoutChildrenIterator() do
|
||||
local childFrame = child:_GetBaseFrame()
|
||||
yOffset = yOffset - child:_GetMargin("TOP")
|
||||
local childXOffset = (child:_GetMargin("LEFT") - child:_GetMargin("RIGHT")) / 2
|
||||
childFrame:SetPoint("TOP", childXOffset + xOffset, yOffset)
|
||||
yOffset = yOffset - childFrame:GetHeight() - child:_GetMargin("BOTTOM")
|
||||
end
|
||||
else
|
||||
error()
|
||||
end
|
||||
end
|
||||
for _, child in self:LayoutChildrenIterator() do
|
||||
child:Draw()
|
||||
end
|
||||
for _, child in ipairs(self._noLayoutChildren) do
|
||||
child:Draw()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function Frame._GetMinimumDimension(self, dimension)
|
||||
assert(dimension == "WIDTH" or dimension == "HEIGHT")
|
||||
local styleResult = nil
|
||||
if dimension == "WIDTH" then
|
||||
styleResult = self._width
|
||||
elseif dimension == "HEIGHT" then
|
||||
styleResult = self._height
|
||||
else
|
||||
error("Invalid dimension: "..tostring(dimension))
|
||||
end
|
||||
local layout = self._layout
|
||||
local context = LAYOUT_CONTEXT[layout]
|
||||
if styleResult then
|
||||
return styleResult, false
|
||||
elseif self:GetNumLayoutChildren() == 0 or layout == "NONE" then
|
||||
return 0, true
|
||||
elseif layout == "FLOW" then
|
||||
-- calculate our minimum width which is the largest of the widths of the children
|
||||
local minWidth = 0
|
||||
for _, child in self:LayoutChildrenIterator() do
|
||||
local childMin = child:_GetMinimumDimension("WIDTH")
|
||||
childMin = childMin + child:_GetMargin("LEFT") + child:_GetMargin("RIGHT")
|
||||
minWidth = max(minWidth, childMin)
|
||||
end
|
||||
minWidth = minWidth + self:_GetPadding("LEFT") + self:_GetPadding("RIGHT")
|
||||
if dimension == "WIDTH" then
|
||||
return minWidth, true
|
||||
end
|
||||
|
||||
-- calculate the row height (all children should be the exact same height)
|
||||
local rowHeight = nil
|
||||
for _, child in self:LayoutChildrenIterator() do
|
||||
local childMin, childCanExpand = child:_GetMinimumDimension("HEIGHT")
|
||||
childMin = childMin + child:_GetMargin("TOP") + child:_GetMargin("BOTTOM")
|
||||
rowHeight = rowHeight or childMin
|
||||
assert(childMin == rowHeight and not childCanExpand)
|
||||
end
|
||||
rowHeight = rowHeight or 0
|
||||
|
||||
local parentElement = self:GetParentElement()
|
||||
local parentWidth = parentElement:_GetDimension("WIDTH") - parentElement:_GetPadding("LEFT") - parentElement:_GetPadding("RIGHT")
|
||||
if minWidth > parentWidth then
|
||||
-- we won't fit, so just pretend we're a single row
|
||||
return rowHeight, false
|
||||
end
|
||||
|
||||
-- calculate our height based on our parent's width
|
||||
local height = rowHeight
|
||||
local currentRowWidth = 0
|
||||
for _, child in self:LayoutChildrenIterator() do
|
||||
local childWidth = child:_GetMinimumDimension("WIDTH") + child:_GetMargin("LEFT") + child:_GetMargin("RIGHT")
|
||||
if currentRowWidth + childWidth > parentWidth then
|
||||
-- this child will go on the next row
|
||||
height = height + rowHeight
|
||||
currentRowWidth = childWidth
|
||||
else
|
||||
-- this child fits on the current row
|
||||
currentRowWidth = currentRowWidth + childWidth
|
||||
end
|
||||
end
|
||||
|
||||
return height, false
|
||||
elseif context then
|
||||
-- calculate the dimension based on the children
|
||||
local sides = (dimension == context.primaryDimension) and context.sides.primary or context.sides.secondary
|
||||
local result = 0
|
||||
local canExpand = false
|
||||
for _, child in self:LayoutChildrenIterator() do
|
||||
local childMin, childCanExpand = child:_GetMinimumDimension(dimension)
|
||||
childMin = childMin + child:_GetMargin(sides[1]) + child:_GetMargin(sides[2])
|
||||
canExpand = canExpand or childCanExpand
|
||||
if dimension == context.primaryDimension then
|
||||
result = result + childMin
|
||||
else
|
||||
result = max(result, childMin)
|
||||
end
|
||||
end
|
||||
result = result + self:_GetPadding(sides[1]) + self:_GetPadding(sides[2])
|
||||
return result, self._expandWidth or canExpand
|
||||
else
|
||||
error(format("Invalid layout (%s)", tostring(layout)))
|
||||
end
|
||||
end
|
||||
615
Core/UI/Elements/Graph.lua
Normal file
615
Core/UI/Elements/Graph.lua
Normal file
@@ -0,0 +1,615 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- Graph UI Element Class.
|
||||
-- The graph element allows for generating line graphs. It is a subclass of the @{Element} class.
|
||||
-- @classmod Graph
|
||||
|
||||
local _, TSM = ...
|
||||
local Math = TSM.Include("Util.Math")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local ScriptWrapper = TSM.Include("Util.ScriptWrapper")
|
||||
local Graph = TSM.Include("LibTSMClass").DefineClass("Graph", TSM.UI.Element)
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(Graph)
|
||||
TSM.UI.Graph = Graph
|
||||
local private = {}
|
||||
local PLOT_X_LABEL_WIDTH = 48
|
||||
local PLOT_X_LABEL_HEIGHT = 16
|
||||
local PLOT_X_LABEL_MARGIN = 6
|
||||
local PLOT_Y_LABEL_WIDTH = 48
|
||||
local PLOT_Y_LABEL_HEIGHT = 16
|
||||
local PLOT_Y_LABEL_MARGIN = 4
|
||||
local PLOT_HIGHLIGHT_TEXT_WIDTH = 80
|
||||
local PLOT_HIGHLIGHT_TEXT_HEIGHT = 16
|
||||
local PLOT_X_EXTRA_HIT_RECT = 4
|
||||
local PLOT_Y_MARGIN = 4
|
||||
local LINE_THICKNESS = 1
|
||||
local LINE_THICKNESS_RATIO = 16
|
||||
local PLOT_MIN_X_LINE_SPACING = PLOT_X_LABEL_WIDTH * 1.5 + 8
|
||||
local PLOT_MIN_Y_LINE_SPACING = PLOT_Y_LABEL_HEIGHT * 1.5 + 8
|
||||
local HOVER_LINE_THICKNESS = 1
|
||||
local MAX_FILL_ALPHA = 0.5
|
||||
local SELECTION_ALPHA = 0.2
|
||||
local MAX_PLOT_POINTS = 300
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function Graph.__init(self)
|
||||
local frame = UIElements.CreateFrame(self, "Frame", nil, nil, TSM.IsShadowlands() and "BackdropTemplate" or nil)
|
||||
|
||||
self.__super:__init(frame)
|
||||
|
||||
frame:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8" })
|
||||
|
||||
frame.plot = CreateFrame("Frame", nil, frame, nil)
|
||||
frame.plot:SetPoint("BOTTOMLEFT", PLOT_Y_LABEL_WIDTH, PLOT_X_LABEL_HEIGHT)
|
||||
frame.plot:SetPoint("TOPRIGHT", -PLOT_X_EXTRA_HIT_RECT, -PLOT_HIGHLIGHT_TEXT_HEIGHT - PLOT_Y_MARGIN)
|
||||
frame.plot:SetHitRectInsets(-PLOT_X_EXTRA_HIT_RECT, -PLOT_X_EXTRA_HIT_RECT, 0, 0)
|
||||
frame.plot:EnableMouse(true)
|
||||
ScriptWrapper.Set(frame.plot, "OnEnter", private.PlotFrameOnEnter, self)
|
||||
ScriptWrapper.Set(frame.plot, "OnLeave", private.PlotFrameOnLeave, self)
|
||||
ScriptWrapper.Set(frame.plot, "OnMouseDown", private.PlotFrameOnMouseDown, self)
|
||||
ScriptWrapper.Set(frame.plot, "OnMouseUp", private.PlotFrameOnMouseUp, self)
|
||||
|
||||
frame.plot.dot = frame.plot:CreateTexture(nil, "ARTWORK", nil, 3)
|
||||
TSM.UI.TexturePacks.SetTextureAndSize(frame.plot.dot, "uiFrames.HighlightDot")
|
||||
|
||||
frame.plot.hoverLine = frame.plot:CreateTexture(nil, "ARTWORK", nil, 2)
|
||||
frame.plot.hoverLine:SetWidth(HOVER_LINE_THICKNESS)
|
||||
frame.plot.hoverLine:Hide()
|
||||
|
||||
frame.plot.hoverText = frame.plot:CreateFontString()
|
||||
frame.plot.hoverText:SetSize(PLOT_HIGHLIGHT_TEXT_WIDTH, PLOT_HIGHLIGHT_TEXT_HEIGHT)
|
||||
frame.plot.hoverText:Hide()
|
||||
|
||||
frame.plot.selectionBox = frame.plot:CreateTexture(nil, "ARTWORK", nil, 2)
|
||||
frame.plot.selectionBox:Hide()
|
||||
|
||||
self._usedTextures = {}
|
||||
self._freeTextures = {}
|
||||
self._usedFontStrings = {}
|
||||
self._freeFontStrings = {}
|
||||
self._xValuesFiltered = {}
|
||||
self._yLookup = {}
|
||||
self._yValueFunc = nil
|
||||
self._xFormatFunc = nil
|
||||
self._yFormatFunc = nil
|
||||
self._xStepFunc = nil
|
||||
self._yStepFunc = nil
|
||||
self._xMin = nil
|
||||
self._xMax = nil
|
||||
self._yMin = nil
|
||||
self._yMax = nil
|
||||
self._isMouseOver = false
|
||||
self._selectionStartX = nil
|
||||
self._zoomStart = nil
|
||||
self._zoomEnd = nil
|
||||
self._onZoomChanged = nil
|
||||
self._onHoverUpdate = nil
|
||||
end
|
||||
|
||||
function Graph.Release(self)
|
||||
self:_ReleaseAllTextures()
|
||||
self:_ReleaseAllFontStrings()
|
||||
wipe(self._xValuesFiltered)
|
||||
wipe(self._yLookup)
|
||||
self._yValueFunc = nil
|
||||
self._xFormatFunc = nil
|
||||
self._yFormatFunc = nil
|
||||
self._xStepFunc = nil
|
||||
self._yStepFunc = nil
|
||||
self._xMin = nil
|
||||
self._xMax = nil
|
||||
self._yMin = nil
|
||||
self._yMax = nil
|
||||
self._isMouseOver = false
|
||||
self._selectionStartX = nil
|
||||
self._zoomStart = nil
|
||||
self._zoomEnd = nil
|
||||
self._onZoomChanged = nil
|
||||
self._onHoverUpdate = nil
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
--- Sets the step size of the axes.
|
||||
-- @tparam Graph self The graph object
|
||||
-- @tparam function x A function which gets the next x-axis step value
|
||||
-- @tparam function y A function which gets the next y-axis step value
|
||||
-- @treturn Graph The graph object
|
||||
function Graph.SetAxisStepFunctions(self, x, y)
|
||||
self._xStepFunc = x
|
||||
self._yStepFunc = y
|
||||
return self
|
||||
end
|
||||
|
||||
function Graph.SetXRange(self, xMin, xMax, stepInterval)
|
||||
assert(xMin <= xMax)
|
||||
self._xMin = xMin
|
||||
self._xMax = xMax
|
||||
self._xStepInterval = stepInterval
|
||||
self._zoomStart = xMin
|
||||
self._zoomEnd = xMax
|
||||
return self
|
||||
end
|
||||
|
||||
function Graph.SetZoom(self, zoomStart, zoomEnd)
|
||||
self._zoomStart = zoomStart
|
||||
self._zoomEnd = zoomEnd
|
||||
return self
|
||||
end
|
||||
|
||||
function Graph.GetZoom(self)
|
||||
return self._zoomStart, self._zoomEnd
|
||||
end
|
||||
|
||||
function Graph.GetXRange(self)
|
||||
local yMin, yMax = nil, nil
|
||||
for _, x in ipairs(self._xValuesFiltered) do
|
||||
local y = self._yValueFunc(x)
|
||||
yMin = min(yMin or math.huge, y)
|
||||
yMax = max(yMax or -math.huge, y)
|
||||
end
|
||||
return self._xMin, self._xMax
|
||||
end
|
||||
|
||||
function Graph.GetYRange(self)
|
||||
local yMin, yMax = nil, nil
|
||||
for _, x in ipairs(self._xValuesFiltered) do
|
||||
local y = self._yValueFunc(x)
|
||||
yMin = min(yMin or math.huge, y)
|
||||
yMax = max(yMax or -math.huge, y)
|
||||
end
|
||||
return yMin, yMax
|
||||
end
|
||||
|
||||
function Graph.SetYValueFunction(self, func)
|
||||
self._yValueFunc = func
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets functions for formatting values.
|
||||
-- @tparam Graph self The graph object
|
||||
-- @tparam function xFormatFunc A function which is passed an x value and returns a formatted string
|
||||
-- @tparam function yFormatFunc A function which is passed a y value and returns a formatted string
|
||||
-- @treturn Graph The graph object
|
||||
function Graph.SetFormatFunctions(self, xFormatFunc, yFormatFunc)
|
||||
self._xFormatFunc = xFormatFunc
|
||||
self._yFormatFunc = yFormatFunc
|
||||
return self
|
||||
end
|
||||
|
||||
--- Registers a script handler.
|
||||
-- @tparam ScrollingTable self The graph object
|
||||
-- @tparam string script The script to register for (supported scripts: `OnZoomChanged`)
|
||||
-- @tparam function handler The script handler which will be called with the graph object followed by any
|
||||
-- arguments to the script
|
||||
-- @treturn Graph The graph object
|
||||
function Graph.SetScript(self, script, handler)
|
||||
if script == "OnZoomChanged" then
|
||||
self._onZoomChanged = handler
|
||||
elseif script == "OnHoverUpdate" then
|
||||
self._onHoverUpdate = handler
|
||||
else
|
||||
error("Unknown Graph script: "..tostring(script))
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function Graph.Draw(self)
|
||||
self.__super:Draw()
|
||||
self:_ReleaseAllTextures()
|
||||
self:_ReleaseAllFontStrings()
|
||||
local frame = self:_GetBaseFrame()
|
||||
frame:SetBackdropColor(Theme.GetColor("PRIMARY_BG"):GetFractionalRGBA())
|
||||
local plot = frame.plot
|
||||
plot.hoverText:SetFont(Theme.GetFont("TABLE_TABLE1"):GetWowFont())
|
||||
plot.hoverText:SetTextColor(Theme.GetColor("INDICATOR_ALT"):GetFractionalRGBA())
|
||||
|
||||
local plotWidth = plot:GetWidth()
|
||||
local plotHeight = plot:GetHeight()
|
||||
|
||||
-- update the filtered set of x values to show and the bounds of the plot data
|
||||
self:_PopulateFilteredData(plotWidth)
|
||||
|
||||
-- calculate the min and max y values which should be shown
|
||||
self._yMin, self._yMax = self._yStepFunc("RANGE", self._yMin, self._yMax, floor(plotHeight / PLOT_MIN_Y_LINE_SPACING))
|
||||
if Math.IsNan(self._yMax) then
|
||||
-- this happens when we're resizing the application frame
|
||||
return
|
||||
end
|
||||
|
||||
-- draw the y axis lines and labels
|
||||
local prevYAxisOffset = -math.huge
|
||||
local yAxisValue = self._yMin
|
||||
while yAxisValue <= self._yMax do
|
||||
local yAxisOffset = Math.Scale(yAxisValue, self._yMin, self._yMax, 0, plotHeight)
|
||||
if not prevYAxisOffset or (yAxisOffset - prevYAxisOffset) >= PLOT_MIN_Y_LINE_SPACING then
|
||||
self:_DrawYAxisLine(yAxisOffset, yAxisValue, plotWidth, plotHeight)
|
||||
prevYAxisOffset = yAxisOffset
|
||||
end
|
||||
yAxisValue = self._yStepFunc("NEXT", yAxisValue, self._yMax)
|
||||
end
|
||||
|
||||
-- draw the x axis lines and labels
|
||||
local xSuggestedStep = Math.Scale(PLOT_MIN_X_LINE_SPACING, 0, plotWidth, 0, self._zoomEnd - self._zoomStart)
|
||||
local prevXAxisOffset = -math.huge
|
||||
local xAxisValue = self._xStepFunc(self._zoomStart, xSuggestedStep)
|
||||
while xAxisValue <= self._zoomEnd do
|
||||
local xAxisOffset = Math.Scale(xAxisValue, self._zoomStart, self._zoomEnd, 0, plotWidth)
|
||||
if not prevXAxisOffset or (xAxisOffset - prevXAxisOffset) > PLOT_MIN_X_LINE_SPACING then
|
||||
self:_DrawXAxisLine(xAxisOffset, xAxisValue, plotWidth, plotHeight, xSuggestedStep)
|
||||
prevXAxisOffset = xAxisOffset
|
||||
end
|
||||
xAxisValue = self._xStepFunc(xAxisValue, xSuggestedStep)
|
||||
end
|
||||
|
||||
-- draw all the lines
|
||||
local color = nil
|
||||
if self._isMouseOver or self._selectionStartX then
|
||||
color = Theme.GetColor("INDICATOR_ALT")
|
||||
elseif self._yLookup[self._xValuesFiltered[1]] <= self._yLookup[self._xValuesFiltered[#self._xValuesFiltered]] then
|
||||
color = Theme.GetFeedbackColor("GREEN")
|
||||
else
|
||||
color = Theme.GetFeedbackColor("RED")
|
||||
end
|
||||
local xPrev, yPrev = nil, nil
|
||||
for _, x in ipairs(self._xValuesFiltered) do
|
||||
local y = self._yLookup[x]
|
||||
local xCoord = Math.Scale(x, self._zoomStart, self._zoomEnd, 0, plotWidth)
|
||||
local yCoord = Math.Scale(y, self._yMin, self._yMax, 0, plotHeight)
|
||||
if xPrev then
|
||||
self:_DrawFillLine(xPrev, yPrev, xCoord, yCoord, LINE_THICKNESS, plotHeight, color)
|
||||
end
|
||||
xPrev = xCoord
|
||||
yPrev = yCoord
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function Graph._PopulateFilteredData(self, plotWidth)
|
||||
wipe(self._xValuesFiltered)
|
||||
wipe(self._yLookup)
|
||||
self._yMin = math.huge
|
||||
self._yMax = -math.huge
|
||||
local minStep = Math.Ceil((self._zoomEnd - self._zoomStart) / min(plotWidth / 3, MAX_PLOT_POINTS), self._xStepInterval)
|
||||
local x = self._zoomStart
|
||||
while x <= self._zoomEnd do
|
||||
local prevX = self._xValuesFiltered[#self._xValuesFiltered]
|
||||
if not prevX or x == self._zoomEnd or (x - prevX > minStep and self._zoomEnd - x > minStep) then
|
||||
-- this is either the first / last point or a middle point which is sufficiently far from the previous and last points
|
||||
tinsert(self._xValuesFiltered, x)
|
||||
local y = self._yValueFunc(x)
|
||||
self._yMin = min(self._yMin, y)
|
||||
self._yMax = max(self._yMax, y)
|
||||
self._yLookup[x] = y
|
||||
end
|
||||
if x == self._zoomEnd then
|
||||
break
|
||||
end
|
||||
x = min(x + minStep, self._zoomEnd)
|
||||
end
|
||||
end
|
||||
|
||||
function Graph._DrawYAxisLine(self, yOffset, yValue, plotWidth, plotHeight, ySuggestedStep)
|
||||
local line = self:_AcquireLine("ARTWORK")
|
||||
local thickness = LINE_THICKNESS
|
||||
local textureHeight = thickness * LINE_THICKNESS_RATIO
|
||||
-- trim the texture a bit on the left/right since it's not completely filled to the edges which is noticeable on long lines
|
||||
line:SetTexCoord(0.1, 1, 0.1, 0, 0.9, 1, 0.9, 0)
|
||||
line:SetPoint("BOTTOMLEFT", 0 - thickness / 2, yOffset - textureHeight / 2)
|
||||
line:SetPoint("TOPRIGHT", line:GetParent(), "BOTTOMLEFT", plotWidth + thickness / 2, yOffset + textureHeight / 2)
|
||||
line:SetVertexColor(Theme.GetColor("ACTIVE_BG"):GetFractionalRGBA())
|
||||
line:SetDrawLayer("BACKGROUND", 0)
|
||||
local text = self:_AcquireFontString(Theme.GetFont("TABLE_TABLE1"))
|
||||
text:SetJustifyH("RIGHT")
|
||||
local textYOffset = 0
|
||||
if PLOT_Y_LABEL_HEIGHT / 2 > yOffset then
|
||||
text:SetJustifyV("BOTTOM")
|
||||
textYOffset = max(PLOT_Y_LABEL_HEIGHT / 2 - yOffset, 0)
|
||||
elseif yOffset + PLOT_Y_LABEL_HEIGHT / 2 > plotHeight then
|
||||
text:SetJustifyV("TOP")
|
||||
textYOffset = plotHeight - yOffset - PLOT_Y_LABEL_HEIGHT / 2
|
||||
else
|
||||
text:SetJustifyV("MIDDLE")
|
||||
end
|
||||
text:SetPoint("RIGHT", line, "LEFT", -PLOT_Y_LABEL_MARGIN, textYOffset)
|
||||
text:SetSize(PLOT_Y_LABEL_WIDTH, PLOT_Y_LABEL_HEIGHT)
|
||||
text:SetText(self._yFormatFunc(yValue, ySuggestedStep))
|
||||
end
|
||||
|
||||
function Graph._DrawXAxisLine(self, xOffset, xValue, plotWidth, plotHeight, xSuggestedStep)
|
||||
local line = self:_AcquireLine("ARTWORK")
|
||||
local thickness = LINE_THICKNESS
|
||||
local textureHeight = thickness * LINE_THICKNESS_RATIO
|
||||
-- trim the texture a bit on the left/right since it's not completely filled to the edges which is noticeable on long lines
|
||||
line:SetTexCoord(0.9, 1, 0.1, 1, 0.9, 0, 0.1, 0)
|
||||
line:SetPoint("BOTTOMLEFT", xOffset - textureHeight / 2, thickness / 2)
|
||||
line:SetPoint("TOPRIGHT", line:GetParent(), "BOTTOMLEFT", xOffset + textureHeight / 2, plotHeight + thickness / 2)
|
||||
line:SetVertexColor(Theme.GetColor("ACTIVE_BG"):GetFractionalRGBA())
|
||||
line:SetDrawLayer("BACKGROUND", 0)
|
||||
local text = self:_AcquireFontString(Theme.GetFont("BODY_BODY3_MEDIUM"))
|
||||
text:ClearAllPoints()
|
||||
text:SetJustifyV("TOP")
|
||||
local textXOffset = 0
|
||||
if PLOT_X_LABEL_WIDTH / 2 > xOffset then
|
||||
text:SetJustifyH("LEFT")
|
||||
textXOffset = max(PLOT_X_LABEL_WIDTH / 2 - xOffset, 0)
|
||||
elseif xOffset + PLOT_X_LABEL_WIDTH / 2 > plotWidth then
|
||||
text:SetJustifyH("RIGHT")
|
||||
textXOffset = plotWidth - xOffset - PLOT_X_LABEL_WIDTH / 2
|
||||
else
|
||||
text:SetJustifyH("CENTER")
|
||||
end
|
||||
text:SetPoint("TOP", line, "BOTTOM", textXOffset, -PLOT_X_LABEL_MARGIN)
|
||||
text:SetSize(PLOT_X_LABEL_WIDTH, PLOT_X_LABEL_HEIGHT)
|
||||
text:SetText(self._xFormatFunc(xValue, xSuggestedStep))
|
||||
end
|
||||
|
||||
function Graph._DrawFillLine(self, xFrom, yFrom, xTo, yTo, thickness, plotHeight, color)
|
||||
assert(xFrom <= xTo)
|
||||
local line = self:_AcquireLine("ARTWORK")
|
||||
local textureHeight = thickness * LINE_THICKNESS_RATIO
|
||||
local xDiff = xTo - xFrom
|
||||
local yDiff = yTo - yFrom
|
||||
local length = sqrt(xDiff * xDiff + yDiff * yDiff)
|
||||
local sinValue = -yDiff / length
|
||||
local cosValue = xDiff / length
|
||||
local sinCosValue = sinValue * cosValue
|
||||
local aspectRatio = length / textureHeight
|
||||
local invAspectRatio = textureHeight / length
|
||||
|
||||
-- calculate and set tex coords
|
||||
local LLx, LLy, ULx, ULy, URx, URy, LRx, LRy = nil, nil, nil, nil, nil, nil, nil, nil
|
||||
if yDiff >= 0 then
|
||||
LLx = invAspectRatio * sinCosValue
|
||||
LLy = sinValue * sinValue
|
||||
LRy = aspectRatio * sinCosValue
|
||||
LRx = 1 - LLy
|
||||
ULx = LLy
|
||||
ULy = 1 - LRy
|
||||
URx = 1 - LLx
|
||||
URy = LRx
|
||||
else
|
||||
LLx = sinValue * sinValue
|
||||
LLy = -aspectRatio * sinCosValue
|
||||
LRx = 1 + invAspectRatio * sinCosValue
|
||||
LRy = LLx
|
||||
ULx = 1 - LRx
|
||||
ULy = 1 - LLx
|
||||
URy = 1 - LLy
|
||||
URx = ULy
|
||||
end
|
||||
line:SetTexCoord(ULx, ULy, LLx, LLy, URx, URy, LRx, LRy)
|
||||
|
||||
-- calculate and set texture anchors
|
||||
local xCenter = (xFrom + xTo) / 2
|
||||
local yCenter = (yFrom + yTo) / 2
|
||||
local halfWidth = (xDiff + invAspectRatio * abs(yDiff) + thickness) / 2
|
||||
local halfHeight = (abs(yDiff) + invAspectRatio * xDiff + thickness) / 2
|
||||
line:SetPoint("BOTTOMLEFT", xCenter - halfWidth, yCenter - halfHeight)
|
||||
line:SetPoint("TOPRIGHT", line:GetParent(), "BOTTOMLEFT", xCenter + halfWidth, yCenter + halfHeight)
|
||||
|
||||
local minY = min(yFrom, yTo)
|
||||
local maxY = max(yFrom, yTo)
|
||||
local r, g, b, a = color:GetFractionalRGBA()
|
||||
local barMaxAlpha = Math.Scale(minY, 0, plotHeight, 0, MAX_FILL_ALPHA * a)
|
||||
local topMaxAlpha = Math.Scale(maxY, 0, plotHeight, 0, MAX_FILL_ALPHA * a)
|
||||
line:SetVertexColor(r, g, b, a)
|
||||
|
||||
local fillTop = self:_AcquireTexture("ARTWORK", -1)
|
||||
fillTop:SetTexture("Interface\\AddOns\\TradeSkillMaster\\Media\\triangle")
|
||||
if yFrom < yTo then
|
||||
fillTop:SetTexCoord(0, 0, 0, 1, 1, 0, 1, 1)
|
||||
else
|
||||
fillTop:SetTexCoord(1, 0, 1, 1, 0, 0, 0, 1)
|
||||
end
|
||||
fillTop:SetGradientAlpha("VERTICAL", r, g, b, barMaxAlpha, r, g, b, topMaxAlpha)
|
||||
fillTop:SetPoint("BOTTOMLEFT", xFrom, minY)
|
||||
fillTop:SetPoint("TOPRIGHT", fillTop:GetParent(), "BOTTOMLEFT", xTo, maxY)
|
||||
|
||||
local fillBar = self:_AcquireTexture("ARTWORK", -1)
|
||||
fillBar:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||||
fillBar:SetGradientAlpha("VERTICAL", r, g, b, 0, r, g, b, barMaxAlpha)
|
||||
fillBar:SetPoint("BOTTOMLEFT", xFrom, 0)
|
||||
fillBar:SetPoint("TOPRIGHT", fillBar:GetParent(), "BOTTOMLEFT", xTo, minY)
|
||||
|
||||
return line
|
||||
end
|
||||
|
||||
function Graph._AcquireLine(self, layer, subLayer)
|
||||
local line = self:_AcquireTexture(layer, subLayer)
|
||||
line:SetTexture("Interface\\AddOns\\TradeSkillMaster\\Media\\line.tga")
|
||||
return line
|
||||
end
|
||||
|
||||
function Graph._AcquireTexture(self, layer, subLayer)
|
||||
local plot = self:_GetBaseFrame().plot
|
||||
local result = tremove(self._freeTextures) or plot:CreateTexture()
|
||||
tinsert(self._usedTextures, result)
|
||||
result:SetParent(plot)
|
||||
result:Show()
|
||||
result:SetDrawLayer(layer, subLayer)
|
||||
return result
|
||||
end
|
||||
|
||||
function Graph._ReleaseAllTextures(self)
|
||||
while #self._usedTextures > 0 do
|
||||
local texture = tremove(self._usedTextures)
|
||||
texture:SetTexture(nil)
|
||||
texture:SetVertexColor(0, 0, 0, 0)
|
||||
texture:SetTexCoord(0, 0, 0, 1, 1, 0, 1, 1)
|
||||
texture:SetWidth(0)
|
||||
texture:SetHeight(0)
|
||||
texture:ClearAllPoints()
|
||||
texture:Hide()
|
||||
tinsert(self._freeTextures, texture)
|
||||
end
|
||||
end
|
||||
|
||||
function Graph._AcquireFontString(self, font)
|
||||
local plot = self:_GetBaseFrame().plot
|
||||
local result = tremove(self._freeFontStrings) or plot:CreateFontString()
|
||||
tinsert(self._usedFontStrings, result)
|
||||
result:SetParent(plot)
|
||||
result:Show()
|
||||
result:SetFont(font:GetWowFont())
|
||||
result:SetTextColor(Theme.GetColor("TEXT"):GetFractionalRGBA())
|
||||
return result
|
||||
end
|
||||
|
||||
function Graph._ReleaseAllFontStrings(self)
|
||||
while #self._usedFontStrings > 0 do
|
||||
local fontString = tremove(self._usedFontStrings)
|
||||
fontString:SetWidth(0)
|
||||
fontString:SetHeight(0)
|
||||
fontString:ClearAllPoints()
|
||||
fontString:Hide()
|
||||
tinsert(self._freeFontStrings, fontString)
|
||||
end
|
||||
end
|
||||
|
||||
function Graph._GetCursorClosestPoint(self)
|
||||
local plotFrame = self:_GetBaseFrame().plot
|
||||
local xPos = GetCursorPosition() / plotFrame:GetEffectiveScale()
|
||||
local fromMin = plotFrame:GetLeft()
|
||||
local fromMax = plotFrame:GetRight()
|
||||
-- Convert the cursor position to be relative to the plotted x values
|
||||
xPos = Math.Scale(Math.Bound(xPos, fromMin, fromMax), fromMin, fromMax, self._zoomStart, self._zoomEnd)
|
||||
-- Find the closest point to the cursor (based on the x distance)
|
||||
local closestX, closestY = nil, nil
|
||||
for _, x in ipairs(self._xValuesFiltered) do
|
||||
local y = self._yLookup[x]
|
||||
local xDist = abs(x - xPos)
|
||||
if not closestX or xDist < abs(closestX - xPos) then
|
||||
closestX = x
|
||||
closestY = y
|
||||
end
|
||||
end
|
||||
assert(closestY)
|
||||
return closestX, closestY
|
||||
end
|
||||
|
||||
function Graph._XValueToPlotCoord(self, xValue)
|
||||
local plotFrame = self:_GetBaseFrame().plot
|
||||
return Math.Scale(xValue, self._zoomStart, self._zoomEnd, 0, plotFrame:GetWidth())
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.PlotFrameOnEnter(self)
|
||||
self._isMouseOver = true
|
||||
self:Draw()
|
||||
local plotFrame = self:_GetBaseFrame().plot
|
||||
ScriptWrapper.Set(plotFrame, "OnUpdate", private.PlotFrameOnUpdate, self)
|
||||
end
|
||||
|
||||
function private.PlotFrameOnLeave(self)
|
||||
self._isMouseOver = false
|
||||
end
|
||||
|
||||
function private.PlotFrameOnUpdate(self)
|
||||
local plotFrame = self:_GetBaseFrame().plot
|
||||
local closestX, closestY = self:_GetCursorClosestPoint()
|
||||
local xCoord = self:_XValueToPlotCoord(closestX)
|
||||
local yCoord = Math.Scale(closestY, self._yMin, self._yMax, 0, plotFrame:GetHeight())
|
||||
|
||||
if self._isMouseOver then
|
||||
plotFrame.dot:Show()
|
||||
plotFrame.dot:ClearAllPoints()
|
||||
plotFrame.dot:SetPoint("CENTER", plotFrame, "BOTTOMLEFT", xCoord, yCoord)
|
||||
|
||||
plotFrame.hoverLine:Show()
|
||||
plotFrame.hoverLine:SetColorTexture(Theme.GetColor("INDICATOR_ALT"):GetFractionalRGBA())
|
||||
plotFrame.hoverLine:ClearAllPoints()
|
||||
plotFrame.hoverLine:SetPoint("TOP", plotFrame, "TOPLEFT", xCoord, 0)
|
||||
plotFrame.hoverLine:SetPoint("BOTTOM", plotFrame, "BOTTOMLEFT", xCoord, 0)
|
||||
|
||||
plotFrame.hoverText:Show()
|
||||
plotFrame.hoverText:SetWidth(1000)
|
||||
plotFrame.hoverText:SetText(self._yFormatFunc(closestY, nil, true))
|
||||
local textWidth = plotFrame.hoverText:GetStringWidth()
|
||||
plotFrame.hoverText:SetWidth(textWidth)
|
||||
plotFrame.hoverText:ClearAllPoints()
|
||||
if xCoord - textWidth / 2 < 0 then
|
||||
plotFrame.hoverText:SetPoint("BOTTOMLEFT", plotFrame, "TOPLEFT", 0, PLOT_Y_MARGIN)
|
||||
elseif textWidth / 2 + xCoord > plotFrame:GetWidth() then
|
||||
plotFrame.hoverText:SetPoint("BOTTOMRIGHT", plotFrame, "TOPRIGHT", 0, PLOT_Y_MARGIN)
|
||||
else
|
||||
plotFrame.hoverText:SetPoint("BOTTOM", plotFrame, "TOPLEFT", xCoord, PLOT_Y_MARGIN)
|
||||
end
|
||||
else
|
||||
plotFrame.dot:Hide()
|
||||
plotFrame.hoverLine:Hide()
|
||||
plotFrame.hoverText:Hide()
|
||||
end
|
||||
|
||||
if self._selectionStartX then
|
||||
local startXCoord = self:_XValueToPlotCoord(self._selectionStartX)
|
||||
local selectionMinX = min(startXCoord, xCoord)
|
||||
local selectionMaxX = max(startXCoord, xCoord)
|
||||
plotFrame.selectionBox:Show()
|
||||
local r, g, b, a = Theme.GetColor("INDICATOR_ALT"):GetFractionalRGBA()
|
||||
assert(a == 1)
|
||||
plotFrame.selectionBox:SetColorTexture(r, g, b, SELECTION_ALPHA)
|
||||
plotFrame.selectionBox:ClearAllPoints()
|
||||
plotFrame.selectionBox:SetPoint("TOPLEFT", plotFrame, selectionMinX, 0)
|
||||
plotFrame.selectionBox:SetPoint("BOTTOMRIGHT", plotFrame, "BOTTOMLEFT", selectionMaxX, 0)
|
||||
else
|
||||
plotFrame.selectionBox:Hide()
|
||||
end
|
||||
|
||||
local isHovered = self._isMouseOver or self._selectionStartX
|
||||
if not isHovered then
|
||||
self:Draw()
|
||||
ScriptWrapper.Clear(plotFrame, "OnUpdate")
|
||||
end
|
||||
if self._onHoverUpdate then
|
||||
self:_onHoverUpdate(isHovered and closestX or nil)
|
||||
end
|
||||
end
|
||||
|
||||
function private.PlotFrameOnMouseDown(self, mouseButton)
|
||||
if mouseButton ~= "LeftButton" then
|
||||
return
|
||||
end
|
||||
assert(self._isMouseOver)
|
||||
self._selectionStartX = self:_GetCursorClosestPoint()
|
||||
end
|
||||
|
||||
function private.PlotFrameOnMouseUp(self, mouseButton)
|
||||
if mouseButton ~= "LeftButton" then
|
||||
return
|
||||
end
|
||||
local currentX = self:_GetCursorClosestPoint()
|
||||
local startX = min(self._selectionStartX, currentX)
|
||||
local endX = max(self._selectionStartX, currentX)
|
||||
self._selectionStartX = nil
|
||||
local plotFrame = self:_GetBaseFrame().plot
|
||||
plotFrame.selectionBox:Hide()
|
||||
|
||||
if startX ~= endX and (startX ~= self._zoomStart or endX ~= self._zoomEnd) then
|
||||
self._zoomStart = startX
|
||||
self._zoomEnd = endX
|
||||
self:Draw()
|
||||
if self._onZoomChanged then
|
||||
self:_onZoomChanged()
|
||||
end
|
||||
end
|
||||
end
|
||||
360
Core/UI/Elements/GroupSelector.lua
Normal file
360
Core/UI/Elements/GroupSelector.lua
Normal file
@@ -0,0 +1,360 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- Group Selector UI Element Class.
|
||||
-- A group selector is an element which can be used to prompt the user to select a list of groups, usually for
|
||||
-- filtering. It is a subclass of the @{Element} class.
|
||||
-- @classmod GroupSelector
|
||||
|
||||
local _, TSM = ...
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local Table = TSM.Include("Util.Table")
|
||||
local Analytics = TSM.Include("Util.Analytics")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local NineSlice = TSM.Include("Util.NineSlice")
|
||||
local ScriptWrapper = TSM.Include("Util.ScriptWrapper")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local GroupSelector = TSM.Include("LibTSMClass").DefineClass("GroupSelector", TSM.UI.Element)
|
||||
UIElements.Register(GroupSelector)
|
||||
TSM.UI.GroupSelector = GroupSelector
|
||||
local private = {}
|
||||
local TEXT_MARGIN = 8
|
||||
local ICON_MARGIN = 8
|
||||
local DEFAULT_CONTEXT = { selected = {}, collapsed = {} }
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function GroupSelector.__init(self)
|
||||
local frame = UIElements.CreateFrame(self, "Button")
|
||||
ScriptWrapper.Set(frame, "OnClick", private.OnClick, self)
|
||||
self.__super:__init(frame)
|
||||
|
||||
frame.text = UIElements.CreateFontString(self, frame)
|
||||
frame.text:SetPoint("TOPLEFT", TEXT_MARGIN, 0)
|
||||
frame.text:SetPoint("BOTTOMRIGHT", -ICON_MARGIN - TSM.UI.TexturePacks.GetWidth("iconPack.18x18/Add/Default") - TEXT_MARGIN, 0)
|
||||
frame.text:SetJustifyH("LEFT")
|
||||
frame.text:SetJustifyV("MIDDLE")
|
||||
|
||||
frame.icon = frame:CreateTexture(nil, "ARTWORK")
|
||||
frame.icon:SetPoint("RIGHT", -ICON_MARGIN, 0)
|
||||
|
||||
frame.iconBtn = CreateFrame("Button", nil, frame)
|
||||
frame.iconBtn:SetAllPoints(frame.icon)
|
||||
ScriptWrapper.Set(frame.iconBtn, "OnClick", private.OnIconClick, self)
|
||||
|
||||
self._nineSlice = NineSlice.New(frame)
|
||||
|
||||
self._groupTreeContext = CopyTable(DEFAULT_CONTEXT)
|
||||
self._hintText = ""
|
||||
self._selectedText = L["%d groups"]
|
||||
self._singleSelection = nil
|
||||
self._onSelectionChanged = nil
|
||||
self._customQueryFunc = nil
|
||||
self._showCreateNew = false
|
||||
end
|
||||
|
||||
function GroupSelector.Release(self)
|
||||
wipe(self._groupTreeContext.collapsed)
|
||||
wipe(self._groupTreeContext.selected)
|
||||
self._hintText = ""
|
||||
self._selectedText = L["%d groups"]
|
||||
self._singleSelection = nil
|
||||
self._onSelectionChanged = nil
|
||||
self._customQueryFunc = nil
|
||||
self._showCreateNew = false
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
--- Sets the hint text.
|
||||
-- @tparam GroupSelector self The group selector object
|
||||
-- @tparam string text The hint text
|
||||
-- @treturn GroupSelector The group selector object
|
||||
function GroupSelector.SetHintText(self, text)
|
||||
assert(type(text) == "string")
|
||||
self._hintText = text
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the selected text.
|
||||
-- @tparam GroupSelector self The group selector object
|
||||
-- @tparam string text The selected text (with a %d formatter for the number of groups)
|
||||
-- @treturn GroupSelector The group selector object
|
||||
function GroupSelector.SetSelectedText(self, text)
|
||||
assert(type(text) == "string" and strmatch(text, "%%d"))
|
||||
self._selectedText = text
|
||||
return self
|
||||
end
|
||||
|
||||
--- Registers a script handler.
|
||||
-- @tparam GroupSelector self The group selector object
|
||||
-- @tparam string script The script to register for (supported scripts: `OnSelectionChanged`)
|
||||
-- @tparam function handler The script handler which will be called with the group selector object followed by any
|
||||
-- arguments to the script
|
||||
-- @treturn GroupSelector The group selector object
|
||||
function GroupSelector.SetScript(self, script, handler)
|
||||
if script == "OnSelectionChanged" then
|
||||
self._onSelectionChanged = handler
|
||||
else
|
||||
error("Unknown GroupSelector script: "..tostring(script))
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets a function to generate a custom query to use for the group tree
|
||||
-- @tparam GroupSelector self The group selector object
|
||||
-- @tparam function func A function to call to create the custom query (gets auto-released by the GroupTree)
|
||||
-- @treturn GroupSelector The group selector object
|
||||
function GroupSelector.SetCustomQueryFunc(self, func)
|
||||
self._customQueryFunc = func
|
||||
return self
|
||||
end
|
||||
|
||||
--- Adds the "Create New Group" option to the group tree
|
||||
-- @tparam GroupSelector self The group selector object
|
||||
-- @treturn GroupSelector The group selector object
|
||||
function GroupSelector.AddCreateNew(self)
|
||||
self._showCreateNew = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the selection to only handle single selection.
|
||||
-- @tparam GroupSelector self The group selector object
|
||||
-- @tparam boolean enabled The state of the single selection
|
||||
-- @treturn GroupSelector The group selector object
|
||||
function GroupSelector.SetSingleSelection(self, enabled)
|
||||
self._singleSelection = enabled
|
||||
return self
|
||||
end
|
||||
|
||||
--- Returns the single selected group path.
|
||||
-- @tparam GroupSelector self The group selector object
|
||||
function GroupSelector.GetSelection(self)
|
||||
assert(self._singleSelection)
|
||||
return next(self._groupTreeContext.selected)
|
||||
end
|
||||
|
||||
--- Sets the single selected group path.
|
||||
-- @tparam GroupSelector self The group selector object
|
||||
-- @tparam string|table selection The selected group(s) or nil if nothing should be selected
|
||||
-- @treturn GroupSelector The group selector object
|
||||
function GroupSelector.SetSelection(self, selection)
|
||||
wipe(self._groupTreeContext.selected)
|
||||
if not selection then
|
||||
return self
|
||||
end
|
||||
if self._singleSelection then
|
||||
self._groupTreeContext.selected[selection] = true
|
||||
else
|
||||
for groupPath in pairs(selection) do
|
||||
self._groupTreeContext.selected[groupPath] = true
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Returns an iterator for all selected groups.
|
||||
-- @tparam GroupSelector self The group selector object
|
||||
-- @return An iterator which iterates over the selected groups and has the following values: `groupPath`
|
||||
function GroupSelector.SelectedGroupIterator(self)
|
||||
return pairs(self._groupTreeContext.selected)
|
||||
end
|
||||
|
||||
--- Clears all selected groups.
|
||||
-- @tparam GroupSelector self The group selector object
|
||||
-- @tparam boolean silent Don't call the selection changed callback
|
||||
-- @treturn GroupSelector The group selector object
|
||||
function GroupSelector.ClearSelectedGroups(self, silent)
|
||||
wipe(self._groupTreeContext.selected)
|
||||
if not silent and self._onSelectionChanged then
|
||||
self:_onSelectionChanged()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function GroupSelector.Draw(self)
|
||||
self.__super:Draw()
|
||||
local frame = self:_GetBaseFrame()
|
||||
|
||||
frame.text:SetFont(Theme.GetFont("BODY_BODY2"):GetWowFont())
|
||||
local numGroups = Table.Count(self._groupTreeContext.selected)
|
||||
frame.text:SetText(numGroups == 0 and self._hintText or (self._singleSelection and TSM.Groups.Path.Format(next(self._groupTreeContext.selected)) or format(self._selectedText, numGroups)))
|
||||
|
||||
TSM.UI.TexturePacks.SetTextureAndSize(frame.icon, numGroups == 0 and "iconPack.18x18/Add/Default" or "iconPack.18x18/Close/Default")
|
||||
|
||||
self._nineSlice:SetStyle("rounded")
|
||||
self._nineSlice:SetVertexColor(Theme.GetColor("ACTIVE_BG"):GetFractionalRGBA())
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function GroupSelector._CreateQuery(self)
|
||||
local query = nil
|
||||
if self._customQueryFunc then
|
||||
query = self._customQueryFunc()
|
||||
else
|
||||
query = TSM.Groups.CreateQuery()
|
||||
end
|
||||
if self._singleSelection then
|
||||
query:NotEqual("groupPath", TSM.CONST.ROOT_GROUP_PATH)
|
||||
end
|
||||
return query
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.OnClick(self)
|
||||
self:GetBaseElement():ShowDialogFrame(UIElements.New("Frame", "frame", "DIALOG")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetSize(464, 500)
|
||||
:SetPadding(8)
|
||||
:AddAnchor("CENTER")
|
||||
:SetBackgroundColor("FRAME_BG", true)
|
||||
:SetMouseEnabled(true)
|
||||
:AddChild(UIElements.New("Frame", "header")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, 0, 8)
|
||||
:AddChild(UIElements.New("Text", "title")
|
||||
:SetMargin(32, 8, 0, 0)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetJustifyH("CENTER")
|
||||
:SetText(L["Select Group"])
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "closeBtn")
|
||||
:SetBackgroundAndSize("iconPack.24x24/Close/Default")
|
||||
:SetScript("OnClick", private.DialogCloseBtnOnClick)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "container")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetPadding(2)
|
||||
:SetBackgroundColor("PRIMARY_BG")
|
||||
:SetBorderColor("ACTIVE_BG")
|
||||
:AddChild(UIElements.New("Frame", "header")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:SetMargin(8)
|
||||
:AddChild(UIElements.New("Input", "input")
|
||||
:AllowItemInsert(true)
|
||||
:SetIconTexture("iconPack.18x18/Search")
|
||||
:SetClearButtonEnabled(true)
|
||||
:SetHintText(L["Search Groups"])
|
||||
:SetScript("OnValueChanged", private.DialogFilterOnValueChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "expandAllBtn")
|
||||
:SetSize(24, 24)
|
||||
:SetMargin(8, 0, 0, 0)
|
||||
:SetBackground("iconPack.18x18/Expand All")
|
||||
:SetScript("OnClick", private.ExpandAllGroupsOnClick)
|
||||
:SetTooltip(L["Expand / Collapse All Groups"])
|
||||
)
|
||||
:AddChildIf(not self._singleSelection, UIElements.New("Button", "selectAllBtn")
|
||||
:SetSize(24, 24)
|
||||
:SetMargin(8, 0, 0, 0)
|
||||
:SetBackground("iconPack.18x18/Select All")
|
||||
:SetScript("OnClick", private.SelectAllGroupsOnClick)
|
||||
:SetTooltip(L["Select / Deselect All Groups"])
|
||||
)
|
||||
)
|
||||
:AddChildIf(self._showCreateNew, UIElements.New("Button", "createGroup")
|
||||
:SetHeight(24)
|
||||
:SetMargin(8, 8, 0, 0)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetIcon("iconPack.14x14/Add/Circle", "LEFT")
|
||||
:SetText(L["Create New Group"])
|
||||
:SetScript("OnClick", private.CreateGroupOnClick)
|
||||
)
|
||||
:AddChild(UIElements.New(self._singleSelection and "SelectionGroupTree" or "ApplicationGroupTree", "groupTree")
|
||||
:SetContext(self)
|
||||
:SetContextTable(self._groupTreeContext, DEFAULT_CONTEXT)
|
||||
:SetQuery(self:_CreateQuery())
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("ActionButton", "groupBtn")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, 8, 0)
|
||||
:SetContext(self)
|
||||
:SetText(L["Select Group"])
|
||||
:SetScript("OnClick", private.DialogSelectOnClick)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
function private.OnIconClick(self)
|
||||
if Table.Count(self._groupTreeContext.selected) > 0 then
|
||||
self:ClearSelectedGroups()
|
||||
self:Draw()
|
||||
if self._onSelectionChanged then
|
||||
self:_onSelectionChanged()
|
||||
end
|
||||
else
|
||||
private.OnClick(self)
|
||||
end
|
||||
end
|
||||
|
||||
function private.DialogCloseBtnOnClick(button)
|
||||
local self = button:GetElement("__parent.__parent.groupBtn"):GetContext()
|
||||
button:GetBaseElement():HideDialog()
|
||||
self:Draw()
|
||||
if self._onSelectionChanged then
|
||||
self:_onSelectionChanged()
|
||||
end
|
||||
end
|
||||
|
||||
function private.DialogFilterOnValueChanged(input)
|
||||
input:GetElement("__parent.__parent.groupTree")
|
||||
:SetSearchString(strlower(input:GetValue()))
|
||||
:Draw()
|
||||
end
|
||||
|
||||
function private.ExpandAllGroupsOnClick(button)
|
||||
button:GetElement("__parent.__parent.groupTree")
|
||||
:ToggleExpandAll()
|
||||
end
|
||||
|
||||
function private.SelectAllGroupsOnClick(button)
|
||||
button:GetElement("__parent.__parent.groupTree")
|
||||
:ToggleSelectAll()
|
||||
end
|
||||
|
||||
function private.DialogSelectOnClick(button)
|
||||
local self = button:GetContext()
|
||||
button:GetBaseElement():HideDialog()
|
||||
self:Draw()
|
||||
if self._onSelectionChanged then
|
||||
self:_onSelectionChanged()
|
||||
end
|
||||
end
|
||||
|
||||
function private.CreateGroupOnClick(button)
|
||||
local newGroupPath = L["New Group"]
|
||||
if TSM.Groups.Exists(newGroupPath) then
|
||||
local num = 1
|
||||
while TSM.Groups.Exists(newGroupPath.." "..num) do
|
||||
num = num + 1
|
||||
end
|
||||
newGroupPath = newGroupPath.." "..num
|
||||
end
|
||||
TSM.Groups.Create(newGroupPath)
|
||||
Analytics.Action("CREATED_GROUP", newGroupPath)
|
||||
button:GetElement("__parent.groupTree")
|
||||
:UpdateData()
|
||||
:SetSelection(newGroupPath)
|
||||
:Draw()
|
||||
end
|
||||
345
Core/UI/Elements/GroupTree.lua
Normal file
345
Core/UI/Elements/GroupTree.lua
Normal file
@@ -0,0 +1,345 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- GroupTree UI Element Class.
|
||||
-- A group tree is an abstract element which displays TSM groups. It is a subclass of the @{ScrollingTable} class.
|
||||
-- @classmod GroupTree
|
||||
|
||||
local _, TSM = ...
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local String = TSM.Include("Util.String")
|
||||
local ScriptWrapper = TSM.Include("Util.ScriptWrapper")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local GroupTree = TSM.Include("LibTSMClass").DefineClass("GroupTree", TSM.UI.ScrollingTable, "ABSTRACT")
|
||||
UIElements.Register(GroupTree)
|
||||
TSM.UI.GroupTree = GroupTree
|
||||
local private = {}
|
||||
local EXPANDER_SPACING = 2
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function GroupTree.__init(self)
|
||||
self.__super:__init()
|
||||
self:SetRowHeight(24)
|
||||
|
||||
self._allData = {}
|
||||
self._contextTable = nil
|
||||
self._defaultContextTable = nil
|
||||
self._hasChildrenLookup = {}
|
||||
self._query = nil
|
||||
self._searchStr = ""
|
||||
self._moduleOperationFilter = nil
|
||||
end
|
||||
|
||||
function GroupTree.Acquire(self)
|
||||
self._headerHidden = true
|
||||
self.__super:Acquire()
|
||||
self:GetScrollingTableInfo()
|
||||
:NewColumn("group")
|
||||
:SetFont("BODY_BODY2")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextFunction(private.GetGroupText)
|
||||
:SetExpanderStateFunction(private.GetExpanderState)
|
||||
:SetFlagStateFunction(private.GetFlagState)
|
||||
:SetTooltipFunction(private.GetTooltip)
|
||||
:Commit()
|
||||
:Commit()
|
||||
end
|
||||
|
||||
function GroupTree.Release(self)
|
||||
wipe(self._allData)
|
||||
if self._query then
|
||||
self._query:Release()
|
||||
self._query = nil
|
||||
end
|
||||
self._searchStr = ""
|
||||
self._moduleOperationFilter = nil
|
||||
self._contextTable = nil
|
||||
self._defaultContextTable = nil
|
||||
wipe(self._hasChildrenLookup)
|
||||
for _, row in ipairs(self._rows) do
|
||||
ScriptWrapper.Clear(row._frame, "OnDoubleClick")
|
||||
end
|
||||
self.__super:Release()
|
||||
self:SetRowHeight(24)
|
||||
end
|
||||
|
||||
--- Sets the context table.
|
||||
-- This table can be used to preserve collapsed state across lifecycles of the group tree and even WoW sessions if it's
|
||||
-- within the settings DB.
|
||||
-- @tparam GroupTree self The group tree object
|
||||
-- @tparam table tbl The context table
|
||||
-- @tparam table defaultTbl The default table (required fields: `collapsed`)
|
||||
-- @treturn GroupTree The group tree object
|
||||
function GroupTree.SetContextTable(self, tbl, defaultTbl)
|
||||
assert(type(defaultTbl.collapsed) == "table")
|
||||
tbl.collapsed = tbl.collapsed or CopyTable(defaultTbl.collapsed)
|
||||
self._contextTable = tbl
|
||||
self._defaultContextTable = defaultTbl
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the context table from a settings object.
|
||||
-- @tparam GroupTree self The group tree object
|
||||
-- @tparam Settings settings The settings object
|
||||
-- @tparam string key The setting key
|
||||
-- @treturn GroupTree The group tree object
|
||||
function GroupTree.SetSettingsContext(self, settings, key)
|
||||
return self:SetContextTable(settings[key], settings:GetDefaultReadOnly(key))
|
||||
end
|
||||
|
||||
--- Sets the query used to populate the group tree.
|
||||
-- @tparam GroupTree self The group tree object
|
||||
-- @tparam DatabaseQuery query The database query object
|
||||
-- @tparam[opt=nil] string moduleName The name of the module to filter visible groups to only ones with operations
|
||||
-- @treturn GroupTree The group tree object
|
||||
function GroupTree.SetQuery(self, query, moduleName)
|
||||
assert(query)
|
||||
if self._query then
|
||||
self._query:Release()
|
||||
end
|
||||
self._query = query
|
||||
self._query:SetUpdateCallback(private.QueryUpdateCallback, self)
|
||||
self._moduleOperationFilter = moduleName
|
||||
self:UpdateData()
|
||||
return self
|
||||
end
|
||||
|
||||
function GroupTree.SetScript(self, script, handler)
|
||||
-- GroupTree doesn't support any scripts
|
||||
error("Unknown GroupTree script: "..tostring(script))
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the search string.
|
||||
-- This search string is used to filter the groups which are displayed in the group tree.
|
||||
-- @tparam GroupTree self The group tree object
|
||||
-- @tparam string searchStr The search string which filters the displayed groups
|
||||
-- @treturn GroupTree The group tree object
|
||||
function GroupTree.SetSearchString(self, searchStr)
|
||||
self._searchStr = String.Escape(searchStr)
|
||||
self:UpdateData()
|
||||
return self
|
||||
end
|
||||
|
||||
--- Expand every group.
|
||||
-- @tparam GroupTree self The application group tree object
|
||||
-- @treturn GroupTree The application group tree object
|
||||
function GroupTree.ExpandAll(self)
|
||||
for _, groupPath in ipairs(self._allData) do
|
||||
if groupPath ~= TSM.CONST.ROOT_GROUP_PATH and self._hasChildrenLookup[groupPath] and self._contextTable.collapsed[groupPath] then
|
||||
self:_SetCollapsed(groupPath, false)
|
||||
end
|
||||
end
|
||||
self:UpdateData(true)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Collapse every group.
|
||||
-- @tparam GroupTree self The application group tree object
|
||||
-- @treturn GroupTree The application group tree object
|
||||
function GroupTree.CollapseAll(self)
|
||||
for _, groupPath in ipairs(self._allData) do
|
||||
if groupPath ~= TSM.CONST.ROOT_GROUP_PATH and self._hasChildrenLookup[groupPath] and not self._contextTable.collapsed[groupPath] then
|
||||
self:_SetCollapsed(groupPath, true)
|
||||
end
|
||||
end
|
||||
self:UpdateData(true)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Toggle the expand/collapse all state of the group tree.
|
||||
-- @tparam GroupTree self The application group tree object
|
||||
-- @treturn GroupTree The application group tree object
|
||||
function GroupTree.ToggleExpandAll(self)
|
||||
if next(self._contextTable.collapsed) then
|
||||
-- at least one group is collapsed, so expand everything
|
||||
self:ExpandAll()
|
||||
else
|
||||
-- nothing is collapsed, so collapse everything
|
||||
self:CollapseAll()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function GroupTree._GetTableRow(self, isHeader)
|
||||
local row = self.__super:_GetTableRow(isHeader)
|
||||
if not isHeader then
|
||||
ScriptWrapper.Set(row._frame, "OnDoubleClick", private.RowOnDoubleClick, row)
|
||||
end
|
||||
return row
|
||||
end
|
||||
|
||||
function GroupTree._CanResizeCols(self)
|
||||
return false
|
||||
end
|
||||
|
||||
function GroupTree._UpdateData(self)
|
||||
-- update our groups list
|
||||
wipe(self._hasChildrenLookup)
|
||||
wipe(self._allData)
|
||||
wipe(self._data)
|
||||
local groups = TempTable.Acquire()
|
||||
if self._moduleOperationFilter then
|
||||
local shouldKeep = TempTable.Acquire()
|
||||
for _, row in self._query:Iterator() do
|
||||
local groupPath = row:GetField("groupPath")
|
||||
shouldKeep[groupPath] = row:GetField("has"..self._moduleOperationFilter.."Operation")
|
||||
if shouldKeep[groupPath] then
|
||||
shouldKeep[TSM.CONST.ROOT_GROUP_PATH] = true
|
||||
-- add all parent groups to the keep table as well
|
||||
local checkPath = TSM.Groups.Path.GetParent(groupPath)
|
||||
while checkPath and checkPath ~= TSM.CONST.ROOT_GROUP_PATH do
|
||||
shouldKeep[checkPath] = true
|
||||
checkPath = TSM.Groups.Path.GetParent(checkPath)
|
||||
end
|
||||
end
|
||||
end
|
||||
for _, row in self._query:Iterator() do
|
||||
local groupPath = row:GetField("groupPath")
|
||||
if shouldKeep[groupPath] then
|
||||
tinsert(groups, groupPath)
|
||||
end
|
||||
end
|
||||
TempTable.Release(shouldKeep)
|
||||
else
|
||||
for _, row in self._query:Iterator() do
|
||||
tinsert(groups, row:GetField("groupPath"))
|
||||
end
|
||||
end
|
||||
|
||||
-- remove collapsed state for any groups which no longer exist or no longer have children
|
||||
local pathExists = TempTable.Acquire()
|
||||
for i, groupPath in ipairs(groups) do
|
||||
pathExists[groupPath] = true
|
||||
local nextGroupPath = groups[i + 1]
|
||||
self._hasChildrenLookup[groupPath] = nextGroupPath and TSM.Groups.Path.IsChild(nextGroupPath, groupPath) or nil
|
||||
end
|
||||
for groupPath in pairs(self._contextTable.collapsed) do
|
||||
if groupPath == TSM.CONST.ROOT_GROUP_PATH or not pathExists[groupPath] or not self._hasChildrenLookup[groupPath] then
|
||||
self._contextTable.collapsed[groupPath] = nil
|
||||
end
|
||||
end
|
||||
TempTable.Release(pathExists)
|
||||
|
||||
for _, groupPath in ipairs(groups) do
|
||||
tinsert(self._allData, groupPath)
|
||||
if self._searchStr ~= "" or not self:_IsGroupHidden(groupPath) then
|
||||
local groupName = groupPath == TSM.CONST.ROOT_GROUP_PATH and L["Base Group"] or TSM.Groups.Path.GetName(groupPath)
|
||||
if strmatch(strlower(groupName), self._searchStr) and (self._searchStr == "" or groupPath ~= TSM.CONST.ROOT_GROUP_PATH) then
|
||||
tinsert(self._data, groupPath)
|
||||
end
|
||||
end
|
||||
end
|
||||
TempTable.Release(groups)
|
||||
end
|
||||
|
||||
function GroupTree._IsGroupHidden(self, data)
|
||||
if data == TSM.CONST.ROOT_GROUP_PATH then
|
||||
return false
|
||||
elseif self._contextTable.collapsed[TSM.CONST.ROOT_GROUP_PATH] then
|
||||
return true
|
||||
end
|
||||
local parent = TSM.Groups.Path.GetParent(data)
|
||||
while parent and parent ~= TSM.CONST.ROOT_GROUP_PATH do
|
||||
if self._contextTable.collapsed[parent] then
|
||||
return true
|
||||
end
|
||||
parent = TSM.Groups.Path.GetParent(parent)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function GroupTree._SetCollapsed(self, data, collapsed)
|
||||
self._contextTable.collapsed[data] = collapsed or nil
|
||||
end
|
||||
|
||||
function GroupTree._IsSelected(self, data)
|
||||
return false
|
||||
end
|
||||
|
||||
function GroupTree._HandleRowClick(self, data, mouseButton)
|
||||
if mouseButton == "RightButton" and self._searchStr == "" and data ~= TSM.CONST.ROOT_GROUP_PATH and self._hasChildrenLookup[data] then
|
||||
self:_SetCollapsed(data, not self._contextTable.collapsed[data])
|
||||
self:UpdateData(true)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetGroupText(self, data)
|
||||
local groupName = data == TSM.CONST.ROOT_GROUP_PATH and L["Base Group"] or TSM.Groups.Path.GetName(data)
|
||||
if data ~= TSM.CONST.ROOT_GROUP_PATH then
|
||||
groupName = Theme.GetGroupColor(select('#', strsplit(TSM.CONST.GROUP_SEP, data))):ColorText(groupName)
|
||||
end
|
||||
return groupName
|
||||
end
|
||||
|
||||
function private.GetExpanderState(self, data)
|
||||
local indentWidth = nil
|
||||
local searchIsActive = self._searchStr ~= ""
|
||||
if data == TSM.CONST.ROOT_GROUP_PATH then
|
||||
indentWidth = -TSM.UI.TexturePacks.GetWidth("iconPack.14x14/Caret/Right") + EXPANDER_SPACING
|
||||
else
|
||||
local level = select('#', strsplit(TSM.CONST.GROUP_SEP, data))
|
||||
indentWidth = (searchIsActive and 0 or (level - 1)) * (TSM.UI.TexturePacks.GetWidth("iconPack.14x14/Caret/Right") + EXPANDER_SPACING)
|
||||
end
|
||||
return not searchIsActive and data ~= TSM.CONST.ROOT_GROUP_PATH and self._hasChildrenLookup[data], not self._contextTable.collapsed[data], nil, indentWidth, EXPANDER_SPACING, true
|
||||
end
|
||||
|
||||
function private.GetFlagState(self, data, isMouseOver)
|
||||
if data == TSM.CONST.ROOT_GROUP_PATH then
|
||||
return true, Theme.GetColor("TEXT")
|
||||
end
|
||||
local level = select('#', strsplit(TSM.CONST.GROUP_SEP, data))
|
||||
local levelColor = Theme.GetGroupColor(level)
|
||||
local color = (self:_IsSelected(data) or isMouseOver) and levelColor or Theme.GetColor("PRIMARY_BG_ALT")
|
||||
return true, color
|
||||
end
|
||||
|
||||
function private.GetTooltip(self, data)
|
||||
if self._searchStr == "" then
|
||||
return nil
|
||||
end
|
||||
return TSM.Groups.Path.Format(data), true
|
||||
end
|
||||
|
||||
function private.QueryUpdateCallback(_, _, self)
|
||||
self:UpdateData(true)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.RowOnDoubleClick(row, mouseButton)
|
||||
if mouseButton ~= "LeftButton" then
|
||||
return
|
||||
end
|
||||
local data = row:GetData()
|
||||
local self = row._scrollingTable
|
||||
assert(self._searchStr == "" and data ~= TSM.CONST.ROOT_GROUP_PATH and self._hasChildrenLookup[data])
|
||||
self:_SetCollapsed(data, not self._contextTable.collapsed[data])
|
||||
self:UpdateData(true)
|
||||
end
|
||||
351
Core/UI/Elements/Input.lua
Normal file
351
Core/UI/Elements/Input.lua
Normal file
@@ -0,0 +1,351 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- Input UI Element Class.
|
||||
-- The input element allows the user to enter text. It is a subclass of the @{BaseInput} class.
|
||||
-- @classmod Input
|
||||
|
||||
local _, TSM = ...
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local ScriptWrapper = TSM.Include("Util.ScriptWrapper")
|
||||
local ItemLinked = TSM.Include("Service.ItemLinked")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local Input = TSM.Include("LibTSMClass").DefineClass("Input", TSM.UI.BaseInput)
|
||||
UIElements.Register(Input)
|
||||
TSM.UI.Input = Input
|
||||
local private = {}
|
||||
local PADDING_LEFT = 8
|
||||
local PADDING_RIGHT = 8
|
||||
local PADDING_TOP_BOTTOM = 4
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function Input.__init(self)
|
||||
local frame = UIElements.CreateFrame(self, "EditBox")
|
||||
self._editBox = frame
|
||||
self.__super:__init(frame)
|
||||
|
||||
self._hintText = UIElements.CreateFontString(self, frame)
|
||||
self._hintText:SetFont(Theme.GetFont("BODY_BODY3"):GetWowFont())
|
||||
self._hintText:SetJustifyH("LEFT")
|
||||
self._hintText:SetJustifyV("MIDDLE")
|
||||
self._hintText:SetPoint("TOPLEFT", PADDING_LEFT, 0)
|
||||
self._hintText:SetPoint("BOTTOMRIGHT", -PADDING_RIGHT, 0)
|
||||
|
||||
self._icon = frame:CreateTexture(nil, "ARTWORK")
|
||||
self._icon:SetPoint("RIGHT", -PADDING_RIGHT / 2, 0)
|
||||
|
||||
self._clearBtn = CreateFrame("Button", nil, frame)
|
||||
self._clearBtn:SetAllPoints(self._icon)
|
||||
ScriptWrapper.Set(self._clearBtn, "OnClick", private.ClearBtnOnClick, self)
|
||||
|
||||
self._subIcon = frame:CreateTexture(nil, "ARTWORK")
|
||||
self._subIcon:SetPoint("LEFT", PADDING_LEFT / 2, 0)
|
||||
TSM.UI.TexturePacks.SetTextureAndSize(self._subIcon, "iconPack.14x14/Subtract/Default")
|
||||
|
||||
self._subBtn = CreateFrame("Button", nil, frame)
|
||||
self._subBtn:SetAllPoints(self._subIcon)
|
||||
ScriptWrapper.Set(self._subBtn, "OnClick", private.SubBtnOnClick, self)
|
||||
ScriptWrapper.SetPropagate(self._subBtn, "OnEnter")
|
||||
ScriptWrapper.SetPropagate(self._subBtn, "OnLeave")
|
||||
|
||||
self._addIcon = frame:CreateTexture(nil, "ARTWORK")
|
||||
self._addIcon:SetPoint("RIGHT", -PADDING_RIGHT / 2, 0)
|
||||
TSM.UI.TexturePacks.SetTextureAndSize(self._addIcon, "iconPack.14x14/Add/Default")
|
||||
|
||||
self._addBtn = CreateFrame("Button", nil, frame)
|
||||
self._addBtn:SetAllPoints(self._addIcon)
|
||||
ScriptWrapper.Set(self._addBtn, "OnClick", private.AddBtnOnClick, self)
|
||||
ScriptWrapper.SetPropagate(self._addBtn, "OnEnter")
|
||||
ScriptWrapper.SetPropagate(self._addBtn, "OnLeave")
|
||||
|
||||
ScriptWrapper.Set(frame, "OnEnter", private.OnEnter, self)
|
||||
ScriptWrapper.Set(frame, "OnLeave", private.OnLeave, self)
|
||||
|
||||
local function ItemLinkedCallback(name, link)
|
||||
if self._allowItemInsert == nil or not self:IsVisible() or not self:HasFocus() then
|
||||
return
|
||||
end
|
||||
if self._allowItemInsert == true then
|
||||
self._editBox:Insert(link)
|
||||
else
|
||||
self._editBox:Insert(name)
|
||||
end
|
||||
return true
|
||||
end
|
||||
ItemLinked.RegisterCallback(ItemLinkedCallback, -1)
|
||||
|
||||
self._clearEnabled = false
|
||||
self._subAddEnabled = false
|
||||
self._iconTexture = nil
|
||||
self._autoComplete = nil
|
||||
self._allowItemInsert = nil
|
||||
self._lostFocusOnButton = false
|
||||
end
|
||||
|
||||
function Input.Release(self)
|
||||
self._clearEnabled = false
|
||||
self._subAddEnabled = false
|
||||
self._iconTexture = nil
|
||||
self._autoComplete = nil
|
||||
self._allowItemInsert = nil
|
||||
self._lostFocusOnButton = false
|
||||
self._hintText:SetText("")
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
--- Sets the horizontal justification of the hint text.
|
||||
-- @tparam Input self The input object
|
||||
-- @tparam string justifyH The horizontal justification (either "LEFT", "CENTER" or "RIGHT")
|
||||
-- @treturn Input The input object
|
||||
function Input.SetHintJustifyH(self, justifyH)
|
||||
assert(justifyH == "LEFT" or justifyH == "CENTER" or justifyH == "RIGHT")
|
||||
self._hintText:SetJustifyH(justifyH)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the vertical justification of the hint text.
|
||||
-- @tparam Input self The input object
|
||||
-- @tparam string justifyV The vertical justification (either "TOP", "MIDDLE" or "BOTTOM")
|
||||
-- @treturn Input The input object
|
||||
function Input.SetHintJustifyV(self, justifyV)
|
||||
assert(justifyV == "TOP" or justifyV == "MIDDLE" or justifyV == "BOTTOM")
|
||||
self._hintText:SetJustifyV(justifyV)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the auto complete table.
|
||||
-- @tparam Input self The input object
|
||||
-- @tparam table tbl A list of strings to auto-complete to
|
||||
-- @treturn Input The input object
|
||||
function Input.SetAutoComplete(self, tbl)
|
||||
assert(type(tbl) == "table")
|
||||
self._autoComplete = tbl
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the hint text.
|
||||
-- The hint text is shown when there's no other text in the input.
|
||||
-- @tparam Input self The input object
|
||||
-- @tparam string text The hint text
|
||||
-- @treturn Input The input object
|
||||
function Input.SetHintText(self, text)
|
||||
self._hintText:SetText(text)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets whether or not the clear button is enabled.
|
||||
-- @tparam Input self The input object
|
||||
-- @tparam boolean enabled Whether or not the clear button is enabled
|
||||
-- @treturn Input The input object
|
||||
function Input.SetClearButtonEnabled(self, enabled)
|
||||
assert(type(enabled) == "boolean")
|
||||
assert(not self._subAddEnabled)
|
||||
self._clearEnabled = enabled
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets whether or not the sub/add buttons are enabled.
|
||||
-- @tparam Input self The input object
|
||||
-- @tparam boolean enabled Whether or not the sub/add buttons are enabled
|
||||
-- @treturn Input The input object
|
||||
function Input.SetSubAddEnabled(self, enabled)
|
||||
assert(type(enabled) == "boolean")
|
||||
assert(not self._clearEnabled and not self._iconTexture)
|
||||
self._subAddEnabled = enabled
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the icon texture.
|
||||
-- @tparam Input self The input object
|
||||
-- @tparam[opt=nil] string iconTexture The texture string to use for the icon texture
|
||||
-- @treturn Input The input object
|
||||
function Input.SetIconTexture(self, iconTexture)
|
||||
assert(iconTexture == nil or TSM.UI.TexturePacks.IsValid(iconTexture))
|
||||
assert(not self._subAddEnabled)
|
||||
self._iconTexture = iconTexture
|
||||
return self
|
||||
end
|
||||
|
||||
--- Allows inserting an item into the input by linking it while the input has focus.
|
||||
-- @tparam Input self The input object
|
||||
-- @tparam[opt=false] boolean insertLink Insert the link instead of the item name
|
||||
-- @treturn Input The input object
|
||||
function Input.AllowItemInsert(self, insertLink)
|
||||
assert(insertLink == true or insertLink == false or insertLink == nil)
|
||||
self._allowItemInsert = insertLink or false
|
||||
return self
|
||||
end
|
||||
|
||||
function Input.Draw(self)
|
||||
self.__super:Draw()
|
||||
self:_UpdateIconsForValue(self._value)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function Input._GetHintTextColor(self)
|
||||
local color = Theme.GetColor(self._disabled and "PRIMARY_BG_ALT" or self._backgroundColor)
|
||||
if color:IsLight() then
|
||||
return self:_GetTextColor("+HOVER")
|
||||
else
|
||||
return self:_GetTextColor("-HOVER")
|
||||
end
|
||||
end
|
||||
|
||||
function Input._UpdateIconsForValue(self, value)
|
||||
local frame = self:_GetBaseFrame()
|
||||
local leftPadding, rightPadding = PADDING_LEFT, PADDING_RIGHT
|
||||
|
||||
-- set the hint text
|
||||
if value == "" and self._hintText:GetText() ~= "" then
|
||||
self._hintText:SetFont(Theme.GetFont(self._font):GetWowFont())
|
||||
self._hintText:SetTextColor(self:_GetHintTextColor():GetFractionalRGBA())
|
||||
self._hintText:Show()
|
||||
else
|
||||
self._hintText:Hide()
|
||||
end
|
||||
|
||||
local showSubAdd = self._subAddEnabled and (frame:IsMouseOver() or frame:HasFocus())
|
||||
if showSubAdd then
|
||||
self._subIcon:Show()
|
||||
self._subBtn:Show()
|
||||
self._addIcon:Show()
|
||||
self._addBtn:Show()
|
||||
else
|
||||
self._subIcon:Hide()
|
||||
self._subBtn:Hide()
|
||||
self._addIcon:Hide()
|
||||
self._addBtn:Hide()
|
||||
end
|
||||
|
||||
-- set the icon
|
||||
local iconTexture = nil
|
||||
if self._clearEnabled and value ~= "" then
|
||||
self._clearBtn:Show()
|
||||
iconTexture = TSM.UI.TexturePacks.GetColoredKey("iconPack.18x18/Close/Default", self:_GetTextColor())
|
||||
else
|
||||
self._clearBtn:Hide()
|
||||
iconTexture = not frame:HasFocus() and self._iconTexture and TSM.UI.TexturePacks.GetColoredKey(self._iconTexture, self:_GetTextColor()) or nil
|
||||
end
|
||||
if iconTexture then
|
||||
assert(not showSubAdd)
|
||||
self._icon:Show()
|
||||
TSM.UI.TexturePacks.SetTextureAndSize(self._icon, iconTexture)
|
||||
rightPadding = rightPadding + TSM.UI.TexturePacks.GetWidth(iconTexture)
|
||||
else
|
||||
self._icon:Hide()
|
||||
end
|
||||
frame:SetTextInsets(leftPadding, rightPadding, PADDING_TOP_BOTTOM, PADDING_TOP_BOTTOM)
|
||||
-- for some reason the text insets don't take effect right away, so on the next frame, we call GetTextInsets() which seems to fix things
|
||||
ScriptWrapper.Set(frame, "OnUpdate", private.OnUpdate, self)
|
||||
end
|
||||
|
||||
function Input._OnTextChanged(self, value)
|
||||
self:_UpdateIconsForValue(value)
|
||||
end
|
||||
|
||||
function Input._ShouldKeepFocus(self)
|
||||
if not IsMouseButtonDown("LeftButton") then
|
||||
return false
|
||||
end
|
||||
if self._clearBtn:IsVisible() and self._clearBtn:IsMouseOver() then
|
||||
return true
|
||||
elseif self._subBtn:IsVisible() and self._subBtn:IsMouseOver() then
|
||||
return true
|
||||
elseif self._addBtn:IsVisible() and self._addBtn:IsMouseOver() then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function Input._OnChar(self, c)
|
||||
self.__super:_OnChar(c)
|
||||
if not self._autoComplete then
|
||||
return
|
||||
end
|
||||
local frame = self:_GetBaseFrame()
|
||||
local text = frame:GetText()
|
||||
local match = nil
|
||||
for _, k in ipairs(self._autoComplete) do
|
||||
local start, ending = strfind(strlower(k), strlower(text), 1, true)
|
||||
if start == 1 and ending and ending == #text then
|
||||
match = k
|
||||
break
|
||||
end
|
||||
end
|
||||
if match and not IsControlKeyDown() then
|
||||
local compStart = #text
|
||||
frame:SetText(match)
|
||||
self:HighlightText(compStart, #match)
|
||||
frame:GetScript("OnTextChanged")(frame, true)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.OnUpdate(self)
|
||||
local frame = self:_GetBaseFrame()
|
||||
ScriptWrapper.Clear(frame, "OnUpdate")
|
||||
frame:GetTextInsets()
|
||||
end
|
||||
|
||||
function private.ClearBtnOnClick(self)
|
||||
assert(self:_SetValueHelper(""))
|
||||
self._escValue = ""
|
||||
self._editBox:SetText(self._value)
|
||||
self:Draw()
|
||||
end
|
||||
|
||||
function private.SubBtnOnClick(self)
|
||||
local minVal = self._validateContext and strsplit(":", self._validateContext)
|
||||
local value = tostring(max(tonumber(self:GetValue()) - (IsShiftKeyDown() and 10 or 1), minVal or -math.huge))
|
||||
if self:_SetValueHelper(value) then
|
||||
self._escValue = self._value
|
||||
self:_GetBaseFrame():SetText(value)
|
||||
self:_UpdateIconsForValue(value)
|
||||
end
|
||||
end
|
||||
|
||||
function private.AddBtnOnClick(self)
|
||||
local _, maxVal = nil, nil
|
||||
if self._validateContext then
|
||||
_, maxVal = strsplit(":", self._validateContext)
|
||||
end
|
||||
local value = tostring(min(tonumber(self:GetValue()) + (IsShiftKeyDown() and 10 or 1), maxVal or math.huge))
|
||||
if self:_SetValueHelper(value) then
|
||||
self._escValue = self._value
|
||||
self:_GetBaseFrame():SetText(value)
|
||||
self:_UpdateIconsForValue(value)
|
||||
end
|
||||
end
|
||||
|
||||
function private.OnEnter(self)
|
||||
self:_UpdateIconsForValue(self._value)
|
||||
end
|
||||
|
||||
function private.OnLeave(self)
|
||||
self:_UpdateIconsForValue(self._value)
|
||||
end
|
||||
287
Core/UI/Elements/ItemList.lua
Normal file
287
Core/UI/Elements/ItemList.lua
Normal file
@@ -0,0 +1,287 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- ItemList UI Element Class.
|
||||
-- This element is used for the item lists in the group UI. It is a subclass of the @{ScrollingTable} class.
|
||||
-- @classmod ItemList
|
||||
|
||||
local _, TSM = ...
|
||||
local ItemString = TSM.Include("Util.ItemString")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local ScriptWrapper = TSM.Include("Util.ScriptWrapper")
|
||||
local ItemInfo = TSM.Include("Service.ItemInfo")
|
||||
local ItemList = TSM.Include("LibTSMClass").DefineClass("ItemList", TSM.UI.ScrollingTable)
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(ItemList)
|
||||
TSM.UI.ItemList = ItemList
|
||||
local private = {}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function ItemList.__init(self)
|
||||
self.__super:__init()
|
||||
|
||||
self._rightClickToggle = true
|
||||
self._allData = {}
|
||||
self._selectedItems = {}
|
||||
self._category = {}
|
||||
self._categoryCollapsed = {}
|
||||
self._filterFunc = nil
|
||||
self._onSelectionChangedHandler = nil
|
||||
end
|
||||
|
||||
function ItemList.Acquire(self)
|
||||
self._headerHidden = true
|
||||
self.__super:Acquire()
|
||||
self:SetSelectionDisabled(true)
|
||||
self:GetScrollingTableInfo()
|
||||
:NewColumn("item")
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetIconSize(12)
|
||||
:SetExpanderStateFunction(private.GetExpanderState)
|
||||
:SetCheckStateFunction(private.GetCheckState)
|
||||
:SetIconFunction(private.GetItemIcon)
|
||||
:SetTextFunction(private.GetItemText)
|
||||
:SetTooltipFunction(private.GetItemTooltip)
|
||||
:Commit()
|
||||
:Commit()
|
||||
end
|
||||
|
||||
function ItemList.Release(self)
|
||||
wipe(self._allData)
|
||||
wipe(self._selectedItems)
|
||||
wipe(self._category)
|
||||
wipe(self._categoryCollapsed)
|
||||
self._filterFunc = nil
|
||||
self._onSelectionChangedHandler = nil
|
||||
for _, row in ipairs(self._rows) do
|
||||
ScriptWrapper.Clear(row._frame, "OnDoubleClick")
|
||||
end
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
--- Sets the items.
|
||||
-- @tparam ItemList self The item list object
|
||||
-- @tparam table items Either a list of items or list of tables with a `header` field and sub-list of items
|
||||
-- @tparam boolean redraw Whether or not to redraw the item list
|
||||
-- @treturn ItemList The item list object
|
||||
function ItemList.SetItems(self, items, redraw)
|
||||
wipe(self._allData)
|
||||
wipe(self._category)
|
||||
wipe(self._categoryCollapsed)
|
||||
|
||||
for _, item in ipairs(items) do
|
||||
if type(item) == "table" and next(item) then
|
||||
assert(item.header)
|
||||
tinsert(self._allData, item.header)
|
||||
for _, subItem in ipairs(item) do
|
||||
tinsert(self._allData, subItem)
|
||||
self._category[subItem] = item.header
|
||||
end
|
||||
elseif type(item) ~= "table" then
|
||||
tinsert(self._allData, item)
|
||||
self._category[item] = ""
|
||||
end
|
||||
end
|
||||
self:_UpdateData()
|
||||
|
||||
wipe(self._selectedItems)
|
||||
if self._onSelectionChangedHandler then
|
||||
self:_onSelectionChangedHandler()
|
||||
end
|
||||
|
||||
if redraw then
|
||||
-- scroll up to the top
|
||||
self._vScrollbar:SetValue(0)
|
||||
self:Draw()
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets a filter function.
|
||||
-- @tparam ItemList self The item list object
|
||||
-- @tparam function func A function which is passed an item and returns true if it should be filtered (not shown)
|
||||
-- @treturn ItemList The item list object
|
||||
function ItemList.SetFilterFunction(self, func)
|
||||
self._filterFunc = func
|
||||
self:_UpdateData()
|
||||
return self
|
||||
end
|
||||
|
||||
--- Gets whether or not an item is selected.
|
||||
-- @tparam ItemList self The item list object
|
||||
-- @tparam string item The item
|
||||
-- @treturn boolean Whether or not the item is selected
|
||||
function ItemList.IsItemSelected(self, item)
|
||||
return tContains(self._data, item) and self._selectedItems[item]
|
||||
end
|
||||
|
||||
--- Selects all items.
|
||||
-- @tparam ItemList self The item list object
|
||||
function ItemList.SelectAll(self)
|
||||
for _, item in ipairs(self._data) do
|
||||
if self._category[item] then
|
||||
self._selectedItems[item] = true
|
||||
end
|
||||
end
|
||||
if self._onSelectionChangedHandler then
|
||||
self:_onSelectionChangedHandler()
|
||||
end
|
||||
self:Draw()
|
||||
end
|
||||
|
||||
--- Deselects all items.
|
||||
-- @tparam ItemList self The item list object
|
||||
function ItemList.ClearSelection(self)
|
||||
wipe(self._selectedItems)
|
||||
if self._onSelectionChangedHandler then
|
||||
self:_onSelectionChangedHandler()
|
||||
end
|
||||
self:Draw()
|
||||
end
|
||||
|
||||
--- Toggle the selection state of the item list.
|
||||
-- @tparam ItemList self The item list object
|
||||
-- @treturn ItemList The item list object
|
||||
function ItemList.ToggleSelectAll(self)
|
||||
if self:GetNumSelected() == 0 then
|
||||
self:SelectAll()
|
||||
else
|
||||
self:ClearSelection()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Registers a script handler.
|
||||
-- @tparam ItemList self The item list object
|
||||
-- @tparam string script The script to register for (supported scripts: `OnSelectionChanged`)
|
||||
-- @tparam function handler The script handler which will be called with the item list object followed by any arguments
|
||||
-- to the script
|
||||
-- @treturn ItemList The item list object
|
||||
function ItemList.SetScript(self, script, handler)
|
||||
if script == "OnSelectionChanged" then
|
||||
self._onSelectionChangedHandler = handler
|
||||
else
|
||||
error("Unknown ItemList script: "..tostring(script))
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Gets the number of selected items.
|
||||
-- @tparam ItemList self The item list object
|
||||
-- @treturn number The number of selected items
|
||||
function ItemList.GetNumSelected(self)
|
||||
local num = 0
|
||||
for _, item in ipairs(self._data) do
|
||||
if self._selectedItems[item] then
|
||||
num = num + 1
|
||||
end
|
||||
end
|
||||
return num
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function ItemList._UpdateData(self)
|
||||
wipe(self._data)
|
||||
for _, data in ipairs(self._allData) do
|
||||
if not self:_IsDataHidden(data) then
|
||||
tinsert(self._data, data)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ItemList._IsDataHidden(self, data)
|
||||
if not self._category[data] then
|
||||
return false
|
||||
end
|
||||
if self._categoryCollapsed[self._category[data]] then
|
||||
return true
|
||||
end
|
||||
if self._filterFunc then
|
||||
return self._filterFunc(data)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function ItemList._GetTableRow(self, isHeader)
|
||||
local row = self.__super:_GetTableRow(isHeader)
|
||||
if not isHeader then
|
||||
ScriptWrapper.Set(row._frame, "OnDoubleClick", private.RowOnDoubleClick, row)
|
||||
end
|
||||
return row
|
||||
end
|
||||
|
||||
function ItemList._HandleRowClick(self, data)
|
||||
if self._category[data] then
|
||||
self._selectedItems[data] = not self._selectedItems[data]
|
||||
else
|
||||
if IsMouseButtonDown("RightButton") then
|
||||
return
|
||||
end
|
||||
self._categoryCollapsed[data] = not self._categoryCollapsed[data]
|
||||
self:_UpdateData()
|
||||
end
|
||||
if self._onSelectionChangedHandler then
|
||||
self:_onSelectionChangedHandler()
|
||||
end
|
||||
self:Draw()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetExpanderState(self, data)
|
||||
local isHeading = not self._category[data]
|
||||
return isHeading, not self._categoryCollapsed[data], isHeading and 0 or 1
|
||||
end
|
||||
|
||||
function private.GetCheckState(self, data)
|
||||
return self._category[data] and self._selectedItems[data]
|
||||
end
|
||||
|
||||
function private.GetItemIcon(self, data)
|
||||
if not self._category[data] then
|
||||
return
|
||||
end
|
||||
return ItemInfo.GetTexture(data)
|
||||
end
|
||||
|
||||
function private.GetItemText(self, data)
|
||||
if self._category[data] then
|
||||
return TSM.UI.GetColoredItemName(data) or Theme.GetFeedbackColor("RED"):ColorText("?")
|
||||
else
|
||||
return data
|
||||
end
|
||||
end
|
||||
|
||||
function private.GetItemTooltip(self, data)
|
||||
if not self._category[data] then
|
||||
return nil
|
||||
end
|
||||
return ItemString.Get(data)
|
||||
end
|
||||
|
||||
function private.RowOnDoubleClick(row, mouseButton)
|
||||
if mouseButton ~= "LeftButton" then
|
||||
return
|
||||
end
|
||||
local self = row._scrollingTable
|
||||
self:_HandleRowClick(row:GetData())
|
||||
end
|
||||
204
Core/UI/Elements/LargeApplicationFrame.lua
Normal file
204
Core/UI/Elements/LargeApplicationFrame.lua
Normal file
@@ -0,0 +1,204 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- LargeApplicationFrame UI Element Class.
|
||||
-- This is the base frame of the large TSM windows which have tabs along the top (i.e. MainUI, AuctionUI, CraftingUI).
|
||||
-- It is a subclass of the @{ApplicationFrame} class.
|
||||
-- @classmod LargeApplicationFrame
|
||||
|
||||
local _, TSM = ...
|
||||
local LargeApplicationFrame = TSM.Include("LibTSMClass").DefineClass("LargeApplicationFrame", TSM.UI.ApplicationFrame)
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(LargeApplicationFrame)
|
||||
TSM.UI.LargeApplicationFrame = LargeApplicationFrame
|
||||
local private = {}
|
||||
local NAV_BAR_SPACING = 16
|
||||
local NAV_BAR_HEIGHT = 24
|
||||
local NAV_BAR_RELATIVE_LEVEL = 21
|
||||
local NAV_BAR_TOP_OFFSET = -8
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Meta Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function LargeApplicationFrame.__init(self)
|
||||
self.__super:__init()
|
||||
|
||||
self._buttons = {}
|
||||
self._selectedButton = nil
|
||||
self._buttonIndex = {}
|
||||
end
|
||||
|
||||
function LargeApplicationFrame.Acquire(self)
|
||||
self:SetContentFrame(UIElements.New("Frame", "content")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetBackgroundColor("FRAME_BG")
|
||||
)
|
||||
self.__super:Acquire()
|
||||
end
|
||||
|
||||
function LargeApplicationFrame.Release(self)
|
||||
wipe(self._buttons)
|
||||
wipe(self._buttonIndex)
|
||||
self._selectedButton = nil
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
--- Sets the context table.
|
||||
-- This table can be used to preserve position, size, and current page information across lifecycles of the frame and
|
||||
-- even WoW sessions if it's within the settings DB.
|
||||
-- @see ApplicationFrame.SetContextTable
|
||||
-- @tparam LargeApplicationFrame self The large application frame object
|
||||
-- @tparam table tbl The context table
|
||||
-- @tparam table defaultTbl Default values (see @{ApplicationFrame.SetContextTable} for fields)
|
||||
-- @treturn LargeApplicationFrame The large application frame object
|
||||
function LargeApplicationFrame.SetContextTable(self, tbl, defaultTbl)
|
||||
assert(defaultTbl.page)
|
||||
tbl.page = tbl.page or defaultTbl.page
|
||||
self.__super:SetContextTable(tbl, defaultTbl)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Adds a top-level navigation button.
|
||||
-- @tparam LargeApplicationFrame self The large application frame object
|
||||
-- @tparam string text The button text
|
||||
-- @tparam function drawCallback The function called when the button is clicked to get the corresponding content
|
||||
-- @treturn LargeApplicationFrame The large application frame object
|
||||
function LargeApplicationFrame.AddNavButton(self, text, drawCallback)
|
||||
local button = UIElements.New("AlphaAnimatedFrame", "NavBar_"..text)
|
||||
:SetRange(1, 0.3)
|
||||
:SetDuration(1)
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetRelativeLevel(NAV_BAR_RELATIVE_LEVEL)
|
||||
:SetContext(drawCallback)
|
||||
:AddChild(UIElements.New("Button", "button")
|
||||
:SetText(text)
|
||||
:SetScript("OnEnter", private.OnNavBarButtonEnter)
|
||||
:SetScript("OnLeave", private.OnNavBarButtonLeave)
|
||||
:SetScript("OnClick", private.OnNavBarButtonClicked)
|
||||
)
|
||||
self:AddChildNoLayout(button)
|
||||
tinsert(self._buttons, button)
|
||||
self._buttonIndex[text] = #self._buttons
|
||||
if self._buttonIndex[text] == self._contextTable.page then
|
||||
self:SetSelectedNavButton(text)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the selected nav button.
|
||||
-- @tparam LargeApplicationFrame self The large application frame object
|
||||
-- @tparam string buttonText The button text
|
||||
-- @tparam boolean redraw Whether or not to redraw the frame
|
||||
function LargeApplicationFrame.SetSelectedNavButton(self, buttonText, redraw)
|
||||
if buttonText == self._selectedButton then
|
||||
return
|
||||
end
|
||||
local index = self._buttonIndex[buttonText]
|
||||
self._contextTable.page = index
|
||||
self._selectedButton = buttonText
|
||||
self._contentFrame:ReleaseAllChildren()
|
||||
self._contentFrame:AddChild(self._buttons[index]:GetContext()(self))
|
||||
if redraw then
|
||||
self:Draw()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the selected nav button.
|
||||
-- @tparam LargeApplicationFrame self The large application frame object
|
||||
-- @treturn string The text of the selected button
|
||||
function LargeApplicationFrame.GetSelectedNavButton(self)
|
||||
return self._selectedButton
|
||||
end
|
||||
|
||||
--- Sets which nav button is pulsing.
|
||||
-- @tparam LargeApplicationFrame self The large application frame object
|
||||
-- @tparam ?string buttonText The button text or nil if no nav button should be pulsing
|
||||
function LargeApplicationFrame.SetPulsingNavButton(self, buttonText)
|
||||
local index = buttonText and self._buttonIndex[buttonText]
|
||||
for i, button in ipairs(self._buttons) do
|
||||
if not index or i ~= index then
|
||||
button:SetPlaying(false)
|
||||
elseif not button:IsPlaying() then
|
||||
button:SetPlaying(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function LargeApplicationFrame.Draw(self)
|
||||
self.__super:Draw()
|
||||
for i, buttonFrame in ipairs(self._buttons) do
|
||||
local button = buttonFrame:GetElement("button")
|
||||
button:SetFont("BODY_BODY1_BOLD")
|
||||
button:SetTextColor(i == self._contextTable.page and "INDICATOR" or "TEXT_ALT")
|
||||
button:Draw()
|
||||
buttonFrame:SetSize(button:GetStringWidth(), NAV_BAR_HEIGHT)
|
||||
end
|
||||
|
||||
local offsetX = 104
|
||||
for _, buttonFrame in ipairs(self._buttons) do
|
||||
local buttonWidth = buttonFrame:GetElement("button"):GetStringWidth()
|
||||
buttonFrame:SetSize(buttonWidth, NAV_BAR_HEIGHT)
|
||||
buttonFrame:WipeAnchors()
|
||||
buttonFrame:AddAnchor("TOPLEFT", offsetX, NAV_BAR_TOP_OFFSET)
|
||||
offsetX = offsetX + buttonWidth + NAV_BAR_SPACING
|
||||
-- draw the buttons again now that we know their dimensions
|
||||
buttonFrame:Draw()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function LargeApplicationFrame._SetResizing(self, resizing)
|
||||
for _, button in ipairs(self._buttons) do
|
||||
if resizing then
|
||||
button:Hide()
|
||||
else
|
||||
button:Show()
|
||||
end
|
||||
end
|
||||
self.__super:_SetResizing(resizing)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.OnNavBarButtonEnter(button)
|
||||
if button:GetBaseElement():GetSelectedNavButton() == button:GetText() then
|
||||
return
|
||||
end
|
||||
button:SetTextColor("TEXT")
|
||||
:Draw()
|
||||
end
|
||||
|
||||
function private.OnNavBarButtonLeave(button)
|
||||
if button:GetBaseElement():GetSelectedNavButton() == button:GetText() then
|
||||
return
|
||||
end
|
||||
button:SetTextColor("TEXT_ALT")
|
||||
:Draw()
|
||||
end
|
||||
|
||||
function private.OnNavBarButtonClicked(button)
|
||||
local self = button:GetParentElement():GetParentElement()
|
||||
self:SetSelectedNavButton(button:GetText(), true)
|
||||
end
|
||||
301
Core/UI/Elements/ManagementGroupTree.lua
Normal file
301
Core/UI/Elements/ManagementGroupTree.lua
Normal file
@@ -0,0 +1,301 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- ManagementGroupTree UI Element Class.
|
||||
-- The management group tree allows for moving, adding, and deleting groups. It also only allows for a single group to
|
||||
-- be selected. It is a subclass of the @{GroupTree} class.
|
||||
-- @classmod ManagementGroupTree
|
||||
|
||||
local _, TSM = ...
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local Analytics = TSM.Include("Util.Analytics")
|
||||
local String = TSM.Include("Util.String")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local ScriptWrapper = TSM.Include("Util.ScriptWrapper")
|
||||
local ManagementGroupTree = TSM.Include("LibTSMClass").DefineClass("ManagementGroupTree", TSM.UI.GroupTree)
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(ManagementGroupTree)
|
||||
TSM.UI.ManagementGroupTree = ManagementGroupTree
|
||||
local private = {}
|
||||
local DRAG_SCROLL_SPEED_FACTOR = 12
|
||||
local MOVE_FRAME_PADDING = 8
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function ManagementGroupTree.__init(self)
|
||||
self.__super:__init()
|
||||
|
||||
self._moveFrame = nil
|
||||
self._selectedGroup = nil
|
||||
self._onGroupSelectedHandler = nil
|
||||
self._onNewGroupHandler = nil
|
||||
self._scrollAmount = 0
|
||||
end
|
||||
|
||||
function ManagementGroupTree.Acquire(self)
|
||||
self._moveFrame = UIElements.New("Frame", self._id.."_MoveFrame")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(20)
|
||||
:SetStrata("TOOLTIP")
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT", true)
|
||||
:SetBorderColor("INDICATOR")
|
||||
:SetContext(self)
|
||||
:AddChild(UIElements.New("Text", "text")
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetJustifyH("CENTER")
|
||||
)
|
||||
self._moveFrame:SetParent(self:_GetBaseFrame())
|
||||
self._moveFrame:Hide()
|
||||
self._moveFrame:SetScript("OnShow", private.MoveFrameOnShow)
|
||||
self._moveFrame:SetScript("OnUpdate", private.MoveFrameOnUpdate)
|
||||
|
||||
self.__super:Acquire()
|
||||
|
||||
self:GetScrollingTableInfo()
|
||||
:GetColById("group")
|
||||
:SetActionIconInfo(2, 14, private.GetActionIcon, true)
|
||||
:SetActionIconClickHandler(private.OnActionIconClick)
|
||||
:Commit()
|
||||
:Commit()
|
||||
end
|
||||
|
||||
function ManagementGroupTree.Release(self)
|
||||
self._selectedGroup = nil
|
||||
self._onGroupSelectedHandler = nil
|
||||
self._onNewGroupHandler = nil
|
||||
self._moveFrame:Release()
|
||||
self._moveFrame = nil
|
||||
for _, row in ipairs(self._rows) do
|
||||
row._frame:RegisterForDrag()
|
||||
ScriptWrapper.Clear(row._frame, "OnDragStart")
|
||||
ScriptWrapper.Clear(row._frame, "OnDragStop")
|
||||
for _, button in pairs(row._buttons) do
|
||||
button:RegisterForDrag()
|
||||
ScriptWrapper.Clear(button, "OnDragStart")
|
||||
ScriptWrapper.Clear(button, "OnDragStop")
|
||||
end
|
||||
end
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
--- Sets the selected group.
|
||||
-- @tparam ManagementGroupTree self The management group tree object
|
||||
-- @tparam string groupPath The selected group's path
|
||||
-- @tparam boolean redraw Whether or not to redraw the management group tree
|
||||
-- @treturn ManagementGroupTree The management group tree object
|
||||
function ManagementGroupTree.SetSelectedGroup(self, groupPath, redraw)
|
||||
self._selectedGroup = groupPath
|
||||
if self._onGroupSelectedHandler then
|
||||
self:_onGroupSelectedHandler(groupPath)
|
||||
end
|
||||
if redraw then
|
||||
-- make sure this group is visible (its parent is expanded)
|
||||
local parent = TSM.Groups.Path.GetParent(groupPath)
|
||||
self._contextTable.collapsed[TSM.CONST.ROOT_GROUP_PATH] = nil
|
||||
while parent and parent ~= TSM.CONST.ROOT_GROUP_PATH do
|
||||
self._contextTable.collapsed[parent] = nil
|
||||
parent = TSM.Groups.Path.GetParent(parent)
|
||||
end
|
||||
self:UpdateData(true)
|
||||
self:_ScrollToData(self._selectedGroup)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Registers a script handler.
|
||||
-- @tparam ManagementGroupTree self The management group tree object
|
||||
-- @tparam string script The script to register for (supported scripts: `OnGroupSelected`)
|
||||
-- @tparam function handler The script handler which will be called with the management group tree object followed by
|
||||
-- any arguments to the script
|
||||
-- @treturn ManagementGroupTree The management group tree object
|
||||
function ManagementGroupTree.SetScript(self, script, handler)
|
||||
if script == "OnGroupSelected" then
|
||||
self._onGroupSelectedHandler = handler
|
||||
elseif script == "OnNewGroup" then
|
||||
self._onNewGroupHandler = handler
|
||||
else
|
||||
error("Unknown ManagementGroupTree script: "..tostring(script))
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function ManagementGroupTree._GetTableRow(self, isHeader)
|
||||
local row = self.__super:_GetTableRow(isHeader)
|
||||
if not isHeader then
|
||||
row._frame:RegisterForDrag("LeftButton")
|
||||
ScriptWrapper.Set(row._frame, "OnDragStart", private.RowOnDragStart, row)
|
||||
ScriptWrapper.Set(row._frame, "OnDragStop", private.RowOnDragStop, row)
|
||||
for _, button in pairs(row._buttons) do
|
||||
button:RegisterForDrag("LeftButton")
|
||||
ScriptWrapper.Set(button, "OnDragStart", private.RowOnDragStart, row)
|
||||
ScriptWrapper.Set(button, "OnDragStop", private.RowOnDragStop, row)
|
||||
end
|
||||
end
|
||||
return row
|
||||
end
|
||||
|
||||
function ManagementGroupTree._SetCollapsed(self, data, collapsed)
|
||||
self.__super:_SetCollapsed(data, collapsed)
|
||||
if collapsed and self._selectedGroup ~= data and strmatch(self._selectedGroup, "^"..String.Escape(data)) then
|
||||
-- we collapsed a parent of the selected group, so select the group we just collapsed instead
|
||||
self:SetSelectedGroup(data, true)
|
||||
end
|
||||
end
|
||||
|
||||
function ManagementGroupTree._IsSelected(self, data)
|
||||
return data == self._selectedGroup
|
||||
end
|
||||
|
||||
function ManagementGroupTree._HandleRowClick(self, data, mouseButton)
|
||||
if mouseButton == "RightButton" then
|
||||
self.__super:_HandleRowClick(data, mouseButton)
|
||||
return
|
||||
end
|
||||
self:SetSelectedGroup(data, true)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetActionIcon(self, data, iconIndex, isMouseOver)
|
||||
if iconIndex == 1 then
|
||||
local texturePack = "iconPack.14x14/Add/Circle"
|
||||
return true, isMouseOver and TSM.UI.TexturePacks.GetColoredKey(texturePack, Theme.GetColor("INDICATOR")) or texturePack
|
||||
elseif iconIndex == 2 then
|
||||
if data ~= TSM.CONST.ROOT_GROUP_PATH then
|
||||
local texturePack = "iconPack.14x14/Delete"
|
||||
return true, isMouseOver and TSM.UI.TexturePacks.GetColoredKey(texturePack, Theme.GetColor("INDICATOR")) or texturePack
|
||||
else
|
||||
return false, nil
|
||||
end
|
||||
else
|
||||
error("Invalid index: "..tostring(iconIndex))
|
||||
end
|
||||
end
|
||||
|
||||
function private.OnActionIconClick(self, data, iconIndex)
|
||||
if iconIndex == 1 then
|
||||
local newGroupPath = TSM.Groups.Path.Join(data, L["New Group"])
|
||||
if TSM.Groups.Exists(newGroupPath) then
|
||||
local num = 1
|
||||
while TSM.Groups.Exists(newGroupPath.." "..num) do
|
||||
num = num + 1
|
||||
end
|
||||
newGroupPath = newGroupPath.." "..num
|
||||
end
|
||||
TSM.Groups.Create(newGroupPath)
|
||||
Analytics.Action("CREATED_GROUP", newGroupPath)
|
||||
self:SetSelectedGroup(newGroupPath, true)
|
||||
if self._onNewGroupHandler then
|
||||
self:_onNewGroupHandler()
|
||||
end
|
||||
elseif iconIndex == 2 then
|
||||
local groupColor = Theme.GetGroupColor(select('#', strsplit(TSM.CONST.GROUP_SEP, data)))
|
||||
self:GetBaseElement():ShowConfirmationDialog(L["Delete Group?"], format(L["Deleting this group (%s) will also remove any sub-groups attached to this group."], groupColor:ColorText(TSM.Groups.Path.GetName(data))), private.DeleteConfirmed, self, data)
|
||||
else
|
||||
error("Invalid index: "..tostring(iconIndex))
|
||||
end
|
||||
end
|
||||
|
||||
function private.DeleteConfirmed(self, data)
|
||||
TSM.Groups.Delete(data)
|
||||
Analytics.Action("DELETED_GROUP", data)
|
||||
self:SetSelectedGroup(TSM.CONST.ROOT_GROUP_PATH, true)
|
||||
end
|
||||
|
||||
function private.MoveFrameOnShow(frame)
|
||||
local self = frame:GetContext()
|
||||
self._scrollAmount = 0
|
||||
end
|
||||
|
||||
function private.MoveFrameOnUpdate(frame)
|
||||
local self = frame:GetContext()
|
||||
local uiScale = UIParent:GetEffectiveScale()
|
||||
local x, y = GetCursorPosition()
|
||||
x = x / uiScale
|
||||
y = y / uiScale
|
||||
frame:_GetBaseFrame():SetPoint("CENTER", UIParent, "BOTTOMLEFT", x, y)
|
||||
|
||||
-- figure out if we're above or below the frame for scrolling while dragging
|
||||
local top = self:_GetBaseFrame():GetTop()
|
||||
local bottom = self:_GetBaseFrame():GetBottom()
|
||||
if y > top then
|
||||
self._scrollAmount = top - y
|
||||
elseif y < bottom then
|
||||
self._scrollAmount = bottom - y
|
||||
else
|
||||
self._scrollAmount = 0
|
||||
end
|
||||
|
||||
self._vScrollbar:SetValue(self._vScrollbar:GetValue() + self._scrollAmount / DRAG_SCROLL_SPEED_FACTOR)
|
||||
end
|
||||
|
||||
function private.RowOnDragStart(row)
|
||||
local self = row._scrollingTable
|
||||
local groupPath = row:GetData()
|
||||
if groupPath == TSM.CONST.ROOT_GROUP_PATH then
|
||||
-- don't do anything for the root group
|
||||
return
|
||||
end
|
||||
local level = select('#', strsplit(TSM.CONST.GROUP_SEP, groupPath))
|
||||
local levelColor = Theme.GetGroupColor(level)
|
||||
self._dragGroupPath = groupPath
|
||||
self._moveFrame:Show()
|
||||
self._moveFrame:SetHeight(self._rowHeight)
|
||||
local moveFrameText = self._moveFrame:GetElement("text")
|
||||
moveFrameText:SetTextColor(levelColor)
|
||||
moveFrameText:SetText(TSM.Groups.Path.GetName(groupPath))
|
||||
moveFrameText:SetWidth(1000)
|
||||
moveFrameText:Draw()
|
||||
self._moveFrame:SetWidth(moveFrameText:GetStringWidth() + MOVE_FRAME_PADDING * 2)
|
||||
self._moveFrame:Draw()
|
||||
end
|
||||
|
||||
function private.RowOnDragStop(row)
|
||||
local self = row._scrollingTable
|
||||
local groupPath = row:GetData()
|
||||
if groupPath == TSM.CONST.ROOT_GROUP_PATH then
|
||||
-- don't do anything for the root group
|
||||
return
|
||||
end
|
||||
self._moveFrame:Hide()
|
||||
|
||||
local destPath = nil
|
||||
for _, targetRow in ipairs(self._rows) do
|
||||
if targetRow:IsMouseOver() then
|
||||
destPath = targetRow:GetData()
|
||||
break
|
||||
end
|
||||
end
|
||||
local oldPath = self._dragGroupPath
|
||||
self._dragGroupPath = nil
|
||||
if not destPath or destPath == oldPath or TSM.Groups.Path.IsChild(destPath, oldPath) then
|
||||
return
|
||||
end
|
||||
local newPath = TSM.Groups.Path.Join(destPath, TSM.Groups.Path.GetName(oldPath))
|
||||
if oldPath == newPath then
|
||||
return
|
||||
elseif TSM.Groups.Exists(newPath) then
|
||||
return
|
||||
end
|
||||
|
||||
TSM.Groups.Move(oldPath, newPath)
|
||||
Analytics.Action("MOVED_GROUP", oldPath, newPath)
|
||||
self:SetSelectedGroup(newPath, true)
|
||||
end
|
||||
170
Core/UI/Elements/MultiLineInput.lua
Normal file
170
Core/UI/Elements/MultiLineInput.lua
Normal file
@@ -0,0 +1,170 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- MultiLineInput UI Element Class.
|
||||
-- The input element allows the user to enter text. It is a subclass of the @{BaseInput} class.
|
||||
-- @classmod MultiLineInput
|
||||
|
||||
local _, TSM = ...
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local ScriptWrapper = TSM.Include("Util.ScriptWrapper")
|
||||
local MultiLineInput = TSM.Include("LibTSMClass").DefineClass("MultiLineInput", TSM.UI.BaseInput)
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(MultiLineInput)
|
||||
TSM.UI.MultiLineInput = MultiLineInput
|
||||
local private = {}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function MultiLineInput.__init(self)
|
||||
local frame = UIElements.CreateFrame(self, "ScrollFrame")
|
||||
self._editBox = CreateFrame("EditBox", nil, frame)
|
||||
self.__super:__init(frame)
|
||||
|
||||
frame:EnableMouseWheel(true)
|
||||
frame:SetClipsChildren(true)
|
||||
ScriptWrapper.Set(frame, "OnUpdate", private.FrameOnUpdate, self)
|
||||
ScriptWrapper.Set(frame, "OnMouseWheel", private.FrameOnMouseWheel, self)
|
||||
ScriptWrapper.Set(frame, "OnMouseUp", private.FrameOnMouseUp, self)
|
||||
|
||||
self._scrollbar = TSM.UI.Scrollbar.Create(frame)
|
||||
ScriptWrapper.Set(self._scrollbar, "OnValueChanged", private.OnScrollbarValueChanged, self)
|
||||
|
||||
self._editBox:SetSpacing(4)
|
||||
self._editBox:SetMultiLine(true)
|
||||
self._editBox:SetTextInsets(8, 8, 4, 4)
|
||||
frame:SetScrollChild(self._editBox)
|
||||
|
||||
ScriptWrapper.Set(self._editBox, "OnCursorChanged", private.OnCursorChanged, self)
|
||||
ScriptWrapper.Set(self._editBox, "OnSizeChanged", private.OnSizeChanged, self)
|
||||
|
||||
self._scrollValue = 0
|
||||
self._ignoreEnter = false
|
||||
end
|
||||
|
||||
function MultiLineInput.Acquire(self)
|
||||
self:SetBackgroundColor("ACTIVE_BG")
|
||||
self:SetJustifyH("LEFT")
|
||||
self:SetJustifyV("TOP")
|
||||
self.__super:Acquire()
|
||||
self._scrollValue = 0
|
||||
self._ignoreEnter = false
|
||||
self._scrollbar:SetValue(0)
|
||||
end
|
||||
|
||||
function MultiLineInput.Draw(self)
|
||||
self._editBox:SetWidth(self:_GetBaseFrame():GetWidth())
|
||||
|
||||
self.__super:Draw()
|
||||
|
||||
local maxScroll = self:_GetMaxScroll()
|
||||
self._scrollbar:SetMinMaxValues(0, maxScroll)
|
||||
self._scrollbar:SetValue(min(self._scrollValue, maxScroll))
|
||||
self._scrollbar.thumb:SetHeight(TSM.UI.Scrollbar.GetLength(self._editBox:GetHeight(), self:_GetDimension("HEIGHT")))
|
||||
end
|
||||
|
||||
--- Sets to ignore enter pressed scripts for the input multi-line input.
|
||||
-- @tparam MultiLineInput self The multi-line input object
|
||||
-- @treturn MultiLineInput The multi-line input object
|
||||
function MultiLineInput.SetIgnoreEnter(self)
|
||||
ScriptWrapper.Clear(self._editBox, "OnEnterPressed")
|
||||
self._ignoreEnter = true
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function MultiLineInput._OnScrollValueChanged(self, value)
|
||||
self:_GetBaseFrame():SetVerticalScroll(value)
|
||||
self._scrollValue = value
|
||||
end
|
||||
|
||||
function MultiLineInput._GetMaxScroll(self)
|
||||
return max(self._editBox:GetHeight() - self:_GetDimension("HEIGHT"), 0)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.OnCursorChanged(self, _, y, _, lineHeight)
|
||||
y = abs(y)
|
||||
local offset = self._scrollValue - y
|
||||
if offset > 0 or offset < self:_GetDimension("HEIGHT") + lineHeight then
|
||||
self._scrollbar:SetValue(y)
|
||||
end
|
||||
end
|
||||
|
||||
function private.OnSizeChanged(self, _, height)
|
||||
local maxScroll = self:_GetMaxScroll()
|
||||
self._scrollbar:SetMinMaxValues(0, maxScroll)
|
||||
self._scrollbar:SetValue(min(self._scrollValue, maxScroll))
|
||||
self._scrollbar.thumb:SetHeight(TSM.UI.Scrollbar.GetLength(self._editBox:GetHeight(), self:_GetDimension("HEIGHT")))
|
||||
end
|
||||
|
||||
function private.OnScrollbarValueChanged(self, value)
|
||||
value = max(min(value, self:_GetMaxScroll()), 0)
|
||||
self:_OnScrollValueChanged(value)
|
||||
end
|
||||
|
||||
function private.FrameOnUpdate(self)
|
||||
if (self:_GetBaseFrame():IsMouseOver() and self:_GetMaxScroll() > 0) or self._scrollbar.dragging then
|
||||
self._scrollbar:Show()
|
||||
else
|
||||
self._scrollbar:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
function private.FrameOnMouseWheel(self, direction)
|
||||
local parentScroll = nil
|
||||
local parent = self:GetParentElement()
|
||||
while parent do
|
||||
if parent:__isa(TSM.UI.ScrollFrame) then
|
||||
parentScroll = parent
|
||||
break
|
||||
else
|
||||
parent = parent:GetParentElement()
|
||||
end
|
||||
end
|
||||
|
||||
if parentScroll then
|
||||
local minValue, maxValue = self._scrollbar:GetMinMaxValues()
|
||||
if direction > 0 then
|
||||
if self._scrollbar:GetValue() == minValue then
|
||||
local scrollAmount = min(parentScroll:_GetDimension("HEIGHT") / 3, Theme.GetMouseWheelScrollAmount())
|
||||
parentScroll._scrollbar:SetValue(parentScroll._scrollbar:GetValue() + -1 * direction * scrollAmount)
|
||||
else
|
||||
local scrollAmount = min(self:_GetDimension("HEIGHT") / 3, Theme.GetMouseWheelScrollAmount())
|
||||
self._scrollbar:SetValue(self._scrollbar:GetValue() + -1 * direction * scrollAmount)
|
||||
end
|
||||
else
|
||||
if self._scrollbar:GetValue() == maxValue then
|
||||
local scrollAmount = min(parentScroll:_GetDimension("HEIGHT") / 3, Theme.GetMouseWheelScrollAmount())
|
||||
parentScroll._scrollbar:SetValue(parentScroll._scrollbar:GetValue() + -1 * direction * scrollAmount)
|
||||
else
|
||||
local scrollAmount = min(self:_GetDimension("HEIGHT") / 3, Theme.GetMouseWheelScrollAmount())
|
||||
self._scrollbar:SetValue(self._scrollbar:GetValue() + -1 * direction * scrollAmount)
|
||||
end
|
||||
end
|
||||
else
|
||||
local scrollAmount = min(self:_GetDimension("HEIGHT") / 3, Theme.GetMouseWheelScrollAmount())
|
||||
self._scrollbar:SetValue(self._scrollbar:GetValue() + -1 * direction * scrollAmount)
|
||||
end
|
||||
end
|
||||
|
||||
function private.FrameOnMouseUp(self)
|
||||
self:SetFocused(true)
|
||||
end
|
||||
292
Core/UI/Elements/MultiselectionDropdown.lua
Normal file
292
Core/UI/Elements/MultiselectionDropdown.lua
Normal file
@@ -0,0 +1,292 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- Multiselection Dropdown UI Element Class.
|
||||
-- A dropdown element allows the user to select from a dialog list. It is a subclass of the @{BaseDropdown} class.
|
||||
-- @classmod MultiselectionDropdown
|
||||
|
||||
local _, TSM = ...
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local Table = TSM.Include("Util.Table")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local MultiselectionDropdown = TSM.Include("LibTSMClass").DefineClass("MultiselectionDropdown", TSM.UI.BaseDropdown)
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(MultiselectionDropdown)
|
||||
TSM.UI.MultiselectionDropdown = MultiselectionDropdown
|
||||
local private = {}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Meta Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function MultiselectionDropdown.__init(self)
|
||||
self.__super:__init()
|
||||
self._itemIsSelected = {}
|
||||
self._settingTableDirect = nil
|
||||
self._text = self:_GetBaseFrame():CreateFontString()
|
||||
self._text:SetFont(Theme.GetFont("BODY_BODY3"):GetWowFont())
|
||||
self._text:Hide()
|
||||
self._noneSelectionText = L["None Selected"]
|
||||
self._partialSelectionText = L["%d Selected"]
|
||||
self._allSelectionText = L["All Selected"]
|
||||
end
|
||||
|
||||
function MultiselectionDropdown.Release(self)
|
||||
wipe(self._itemIsSelected)
|
||||
self._settingTableDirect = nil
|
||||
self._noneSelectionText = L["None Selected"]
|
||||
self._partialSelectionText = L["%d Selected"]
|
||||
self._allSelectionText = L["All Selected"]
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
--- Set whether the item is selected.
|
||||
-- @tparam MultiselectionDropdown self The dropdown object
|
||||
-- @tparam string item The item
|
||||
-- @tparam boolean selected Whether or not the item should be selected
|
||||
-- @treturn MultiselectionDropdown The dropdown object
|
||||
function MultiselectionDropdown.SetItemSelected(self, item, selected)
|
||||
self:_SetItemSelectedHelper(item, selected)
|
||||
if self._onSelectionChangedHandler then
|
||||
self:_onSelectionChangedHandler()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether the item is selected by key.
|
||||
-- @tparam MultiselectionDropdown self The dropdown object
|
||||
-- @tparam string itemKey The key for the item
|
||||
-- @tparam boolean selected Whether or not the item should be selected
|
||||
-- @treturn MultiselectionDropdown The dropdown object
|
||||
function MultiselectionDropdown.SetItemSelectedByKey(self, itemKey, selected)
|
||||
self:SetItemSelected(Table.GetDistinctKey(self._itemKeyLookup, itemKey), selected)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the selected items.
|
||||
-- @tparam MultiselectionDropdown self The dropdown object
|
||||
-- @tparam table selected A table where the keys are the items to be selected
|
||||
-- @treturn MultiselectionDropdown The dropdown object
|
||||
function MultiselectionDropdown.SetSelectedItems(self, selected)
|
||||
wipe(self._itemIsSelected)
|
||||
if self._settingTableDirect then
|
||||
wipe(self._settingTableDirect)
|
||||
end
|
||||
for _, item in ipairs(self._items) do
|
||||
self:_SetItemSelectedHelper(item, selected[item])
|
||||
end
|
||||
if self._onSelectionChangedHandler then
|
||||
self:_onSelectionChangedHandler()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the selected items.
|
||||
-- @tparam MultiselectionDropdown self The dropdown object
|
||||
-- @tparam table selected A table where the keys are the items to be selected
|
||||
-- @treturn MultiselectionDropdown The dropdown object
|
||||
function MultiselectionDropdown.SetSelectedItemKeys(self, selected)
|
||||
wipe(self._itemIsSelected)
|
||||
if self._settingTableDirect then
|
||||
wipe(self._settingTableDirect)
|
||||
end
|
||||
for _, item in ipairs(self._items) do
|
||||
self:_SetItemSelectedHelper(item, selected[self._itemKeyLookup[item]])
|
||||
end
|
||||
if self._onSelectionChangedHandler then
|
||||
self:_onSelectionChangedHandler()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the unselected items.
|
||||
-- @tparam MultiselectionDropdown self The dropdown object
|
||||
-- @tparam table unselected A table where the keys are the items which aren't selected
|
||||
-- @treturn MultiselectionDropdown The dropdown object
|
||||
function MultiselectionDropdown.SetUnselectedItemKeys(self, unselected)
|
||||
wipe(self._itemIsSelected)
|
||||
if self._settingTableDirect then
|
||||
wipe(self._settingTableDirect)
|
||||
end
|
||||
for _, item in ipairs(self._items) do
|
||||
self:_SetItemSelectedHelper(item, not unselected[self._itemKeyLookup[item]])
|
||||
end
|
||||
if self._onSelectionChangedHandler then
|
||||
self:_onSelectionChangedHandler()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the currently selected item.
|
||||
-- @tparam MultiselectionDropdown self The dropdown object
|
||||
-- @tparam string item The item
|
||||
-- @treturn ?string The selected item
|
||||
function MultiselectionDropdown.ItemIsSelected(self, item)
|
||||
return self._itemIsSelected[item]
|
||||
end
|
||||
|
||||
--- Get the currently selected item.
|
||||
-- @tparam MultiselectionDropdown self The dropdown object
|
||||
-- @tparam string|number itemKey The key for the item
|
||||
-- @treturn boolean Whether or not the item is selected
|
||||
function MultiselectionDropdown.ItemIsSelectedByKey(self, itemKey)
|
||||
return self:ItemIsSelected(Table.GetDistinctKey(self._itemKeyLookup, itemKey))
|
||||
end
|
||||
|
||||
--- Sets the setting info.
|
||||
-- This method is used to have the selected keys of the dropdown automatically correspond with the value of a field in a
|
||||
-- table. This is useful for dropdowns which are tied directly to settings.
|
||||
-- @tparam MultiselectionDropdown self The dropdown object
|
||||
-- @tparam table tbl The table which the field to set belongs to
|
||||
-- @tparam string key The key into the table to be set based on the dropdown state
|
||||
-- @treturn MultiselectionDropdown The dropdown object
|
||||
function MultiselectionDropdown.SetSettingInfo(self, tbl, key)
|
||||
local directTbl = tbl[key]
|
||||
assert(type(directTbl) == "table")
|
||||
-- this function wipes our settingTable, so set the selected items first
|
||||
self:SetSelectedItemKeys(directTbl)
|
||||
self._settingTableDirect = directTbl
|
||||
return self
|
||||
end
|
||||
|
||||
--- Populate the specified table with a list of selected items
|
||||
-- @tparam MultiselectionDropdown self The dropdown object
|
||||
-- @tparam table resultTbl The table to populate
|
||||
function MultiselectionDropdown.GetSelectedItems(self, resultTbl)
|
||||
for _, item in ipairs(self._items) do
|
||||
if self:ItemIsSelected(item) then
|
||||
tinsert(resultTbl, item)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Sets the selection text which is shown to summarize the current value.
|
||||
-- @tparam BaseDropdown self The dropdown object
|
||||
-- @tparam string noneText The selection text string when none are selected
|
||||
-- @tparam string partialText The selection text string for a partial selection
|
||||
-- @tparam string allText The selection text string when all are selected
|
||||
-- @treturn BaseDropdown The dropdown object
|
||||
function MultiselectionDropdown.SetSelectionText(self, noneText, partialText, allText)
|
||||
assert(type(partialText) == "string" and type(partialText) == "string" and type(allText) == "string")
|
||||
self._noneSelectionText = noneText
|
||||
self._partialSelectionText = partialText
|
||||
self._allSelectionText = allText
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function MultiselectionDropdown._GetDialogSize(self)
|
||||
local width, height = self.__super:_GetDialogSize()
|
||||
width = max(width + 12, 200) -- check icon, and big enough for select all / deselect all buttons
|
||||
height = height + 26 -- header + line
|
||||
return width, height
|
||||
end
|
||||
|
||||
function MultiselectionDropdown._GetNumSelected(self)
|
||||
local num = 0
|
||||
for _, item in ipairs(self._items) do
|
||||
if self:ItemIsSelected(item) then
|
||||
num = num + 1
|
||||
end
|
||||
end
|
||||
return num
|
||||
end
|
||||
|
||||
function MultiselectionDropdown._AddDialogChildren(self, frame)
|
||||
local numSelected = self:_GetNumSelected()
|
||||
frame:AddChild(UIElements.New("Frame", "header")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetPadding(8, 8, 2, 2)
|
||||
:SetHeight(24)
|
||||
:AddChild(UIElements.New("Button", "selectAll")
|
||||
:SetWidth("AUTO")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetFont("BODY_BODY2_BOLD")
|
||||
:SetTextColor(numSelected == #self._items and "ACTIVE_BG_ALT" or "TEXT")
|
||||
:SetDisabled(numSelected == #self._items)
|
||||
:SetText(L["Select All"])
|
||||
:SetScript("OnClick", private.SelectAllOnClick)
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "deselectAll")
|
||||
:SetWidth("AUTO")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetFont("BODY_BODY2_BOLD")
|
||||
:SetTextColor(numSelected == 0 and "ACTIVE_BG_ALT" or "TEXT")
|
||||
:SetDisabled(numSelected == 0)
|
||||
:SetText(L["Deselect All"])
|
||||
:SetScript("OnClick", private.DeselectAllOnClick)
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
)
|
||||
frame:AddChild(UIElements.New("Texture", "line")
|
||||
:SetHeight(2)
|
||||
:SetTexture("ACTIVE_BG_ALT")
|
||||
)
|
||||
frame:AddChild(UIElements.New("DropdownList", "list")
|
||||
:SetMultiselect(true)
|
||||
:SetItems(self._items, self._itemIsSelected)
|
||||
)
|
||||
end
|
||||
|
||||
function MultiselectionDropdown._GetCurrentSelectionString(self)
|
||||
local numSelected = self:_GetNumSelected()
|
||||
local result = nil
|
||||
if numSelected == 0 then
|
||||
result = self._hintText ~= "" and self._hintText or self._noneSelectionText
|
||||
elseif numSelected == #self._items then
|
||||
result = self._allSelectionText.." ("..numSelected..")"
|
||||
else
|
||||
result = format(self._partialSelectionText, numSelected)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function MultiselectionDropdown._OnListSelectionChanged(self, dropdownList, selection)
|
||||
self:SetSelectedItems(selection)
|
||||
local numSelected = self:_GetNumSelected()
|
||||
dropdownList:GetElement("__parent.header.selectAll")
|
||||
:SetTextColor(numSelected == #self._items and "ACTIVE_BG_ALT" or "TEXT")
|
||||
:SetDisabled(numSelected == #self._items)
|
||||
:Draw()
|
||||
dropdownList:GetElement("__parent.header.deselectAll")
|
||||
:SetTextColor(numSelected == 0 and "ACTIVE_BG_ALT" or "TEXT")
|
||||
:SetDisabled(numSelected == 0)
|
||||
:Draw()
|
||||
end
|
||||
|
||||
function MultiselectionDropdown._SetItemSelectedHelper(self, item, selected)
|
||||
self._itemIsSelected[item] = selected and true or nil
|
||||
if self._settingTableDirect then
|
||||
self._settingTableDirect[self._itemKeyLookup[item]] = self._itemIsSelected[item]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.SelectAllOnClick(button)
|
||||
button:GetElement("__parent.__parent.list"):SelectAll()
|
||||
end
|
||||
|
||||
function private.DeselectAllOnClick(button)
|
||||
button:GetElement("__parent.__parent.list"):DeselectAll()
|
||||
end
|
||||
183
Core/UI/Elements/MyAuctionsScrollingTable.lua
Normal file
183
Core/UI/Elements/MyAuctionsScrollingTable.lua
Normal file
@@ -0,0 +1,183 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- My Auctions Scrolling Table Class.
|
||||
-- A scrolling table containing the player's auctions. It is a subclass of the @{QueryScrollingTable} class.
|
||||
-- @classmod MyAuctionsScrollingTable
|
||||
|
||||
local _, TSM = ...
|
||||
local MyAuctionsScrollingTable = TSM.Include("LibTSMClass").DefineClass("MyAuctionsScrollingTable", TSM.UI.QueryScrollingTable)
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(MyAuctionsScrollingTable)
|
||||
TSM.UI.MyAuctionsScrollingTable = MyAuctionsScrollingTable
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function MyAuctionsScrollingTable.__init(self)
|
||||
self.__super:__init()
|
||||
self._selectionSortValue = nil
|
||||
self._selectionAuctionId = nil
|
||||
end
|
||||
|
||||
function MyAuctionsScrollingTable.Release(self)
|
||||
self._selectionSortValue = nil
|
||||
self._selectionAuctionId = nil
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
--- Sets the selected record.
|
||||
-- @tparam MyAuctionsScrollingTable self The my auctions scrolling table object
|
||||
-- @param selection The selected record or nil to clear the selection
|
||||
-- @tparam[opt=false] bool redraw Whether or not to redraw the scrolling table
|
||||
-- @treturn MyAuctionsScrollingTable The my auctions scrolling table object
|
||||
function MyAuctionsScrollingTable.SetSelection(self, selection, redraw)
|
||||
self.__super:SetSelection(selection, redraw)
|
||||
if self._selection then
|
||||
local selectedRow = self:GetSelection()
|
||||
local sortField = TSM.IsWowClassic() and "index" or self._tableInfo:_GetSortFieldById(self._sortCol)
|
||||
self._selectionSortValue = selectedRow:GetField(sortField)
|
||||
if type(self._selectionSortValue) == "string" then
|
||||
self._selectionSortValue = strlower(self._selectionSortValue)
|
||||
end
|
||||
self._selectionAuctionId = selectedRow:GetField("auctionId")
|
||||
else
|
||||
self._selectionSortValue = nil
|
||||
self._selectionAuctionId = nil
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Selects the next row.
|
||||
-- @tparam MyAuctionsScrollingTable self The my auctions scrolling table object
|
||||
-- @treturn MyAuctionsScrollingTable The my auctions scrolling table object
|
||||
function MyAuctionsScrollingTable.SelectNextRow(self)
|
||||
local newSelection = nil
|
||||
for i = 1, #self._data - 1 do
|
||||
if self._data[i] == self._selection then
|
||||
for j = i + 1, #self._data do
|
||||
if not self._selectionValidator or self:_selectionValidator(self._query:GetResultRowByUUID(self._data[j])) then
|
||||
newSelection = self._data[j]
|
||||
break
|
||||
end
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
self:SetSelection(newSelection, true)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function MyAuctionsScrollingTable._UpdateSortFromQuery(self)
|
||||
if self._tableInfo:_IsSortEnabled() then
|
||||
assert(not TSM.IsWowClassic())
|
||||
local sortField, sortAscending = self._query:GetOrderBy(2)
|
||||
if sortField then
|
||||
self._sortCol = self._tableInfo:_GetIdBySortField(sortField)
|
||||
self._sortAscending = sortAscending
|
||||
else
|
||||
self._sortCol = nil
|
||||
self._sortAscending = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function MyAuctionsScrollingTable._UpdateData(self)
|
||||
-- we need to fix up the data within the rows updated to avoid errors with trying to access old DatabaseQueryResultRows
|
||||
local prevRowIndex = TempTable.Acquire()
|
||||
local newRowData = TempTable.Acquire()
|
||||
for i, row in ipairs(self._rows) do
|
||||
if row:IsVisible() then
|
||||
prevRowIndex[row:GetData()] = i
|
||||
end
|
||||
end
|
||||
local prevSelection = self._selection
|
||||
wipe(self._data)
|
||||
self._selection = nil
|
||||
for _, uuid in self._query:UUIDIterator() do
|
||||
local row = self._query:GetResultRowByUUID(uuid)
|
||||
if (uuid == prevSelection or (row:GetField("auctionId") == self._selectionAuctionId)) and not row:GetField("isPending") then
|
||||
self._selection = uuid
|
||||
end
|
||||
if prevRowIndex[uuid] then
|
||||
newRowData[prevRowIndex[uuid]] = uuid
|
||||
end
|
||||
tinsert(self._data, uuid)
|
||||
end
|
||||
for i, row in ipairs(self._rows) do
|
||||
if row:IsVisible() then
|
||||
if newRowData[i] then
|
||||
row:SetData(newRowData[i])
|
||||
else
|
||||
row:ClearData()
|
||||
end
|
||||
end
|
||||
end
|
||||
TempTable.Release(prevRowIndex)
|
||||
TempTable.Release(newRowData)
|
||||
if prevSelection and not self._selection then
|
||||
local newSelection = nil
|
||||
-- try to select the next row based on the sorting
|
||||
local sortField = TSM.IsWowClassic() and "index" or self._tableInfo:_GetSortFieldById(self._sortCol)
|
||||
local sortAscending = not TSM.IsWowClassic() and self._sortAscending
|
||||
for _, uuid in ipairs(self._data) do
|
||||
local row = self._query:GetResultRowByUUID(uuid)
|
||||
local sortValue = row:GetField(sortField)
|
||||
if type(sortValue) == "string" then
|
||||
sortValue = strlower(sortValue)
|
||||
end
|
||||
if (sortAscending and sortValue > self._selectionSortValue) or (not sortAscending and sortValue < self._selectionSortValue) then
|
||||
if not self._selectionValidator or self:_selectionValidator(row) then
|
||||
newSelection = uuid
|
||||
break
|
||||
end
|
||||
elseif not TSM.IsWowClassic() and sortValue == self._selectionSortValue and row:GetField("auctionId") > self._selectionAuctionId then
|
||||
if not self._selectionValidator or self:_selectionValidator(row) then
|
||||
newSelection = uuid
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
-- select either the next row
|
||||
self:SetSelection(newSelection)
|
||||
end
|
||||
if self._onDataUpdated then
|
||||
self:_onDataUpdated()
|
||||
end
|
||||
end
|
||||
|
||||
function MyAuctionsScrollingTable._ToggleSort(self, id)
|
||||
local sortField = self._tableInfo:_GetSortFieldById(id)
|
||||
if not self._sortCol or not self._query or not sortField then
|
||||
-- sorting disabled so ignore
|
||||
return
|
||||
end
|
||||
|
||||
if id == self._sortCol then
|
||||
self._sortAscending = not self._sortAscending
|
||||
else
|
||||
self._sortCol = id
|
||||
self._sortAscending = true
|
||||
end
|
||||
|
||||
assert(not TSM.IsWowClassic())
|
||||
self._query:ResetOrderBy()
|
||||
:OrderBy("saleStatus", false)
|
||||
:OrderBy(sortField, self._sortAscending)
|
||||
:OrderBy("auctionId", true)
|
||||
self:_UpdateData()
|
||||
self:Draw()
|
||||
end
|
||||
288
Core/UI/Elements/OperationTree.lua
Normal file
288
Core/UI/Elements/OperationTree.lua
Normal file
@@ -0,0 +1,288 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- OperationTree UI Element Class.
|
||||
-- The operation tree is used to display operations grouped by module and allows for adding, duplicating, and deleting
|
||||
-- them. Only one module is allowed to be expanded at a time. It is a subclass of the @{ScrollingTable} class.
|
||||
-- @classmod OperationTree
|
||||
|
||||
local _, TSM = ...
|
||||
local OperationTree = TSM.Include("LibTSMClass").DefineClass("OperationTree", TSM.UI.ScrollingTable)
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local ScriptWrapper = TSM.Include("Util.ScriptWrapper")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(OperationTree)
|
||||
TSM.UI.OperationTree = OperationTree
|
||||
local private = {}
|
||||
local DATA_SEP = "\001"
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function OperationTree.__init(self)
|
||||
self.__super:__init()
|
||||
self:SetRowHeight(28)
|
||||
|
||||
self._operationNameFilter = ""
|
||||
self._selected = nil
|
||||
self._expandedModule = nil
|
||||
self._selectedOperation = nil
|
||||
self._prevSelectedOperation = nil
|
||||
self._onOperationSelectedHandler = nil
|
||||
self._onOperationAddedHandler = nil
|
||||
self._onOperationDeletedHandler = nil
|
||||
end
|
||||
|
||||
function OperationTree.Acquire(self)
|
||||
self._backgroundColor = "PRIMARY_BG_ALT"
|
||||
self._headerHidden = true
|
||||
self.__super:Acquire()
|
||||
self:GetScrollingTableInfo()
|
||||
:NewColumn("text")
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextFunction(private.GetText)
|
||||
:SetExpanderStateFunction(private.GetExpanderState)
|
||||
:SetActionIconInfo(2, 14, private.GetActionIcon)
|
||||
:SetActionIconClickHandler(private.OnActionIconClick)
|
||||
:DisableHiding()
|
||||
:Commit()
|
||||
:Commit()
|
||||
self:UpdateData()
|
||||
end
|
||||
|
||||
function OperationTree.Release(self)
|
||||
for _, row in ipairs(self._rows) do
|
||||
ScriptWrapper.Clear(row._frame, "OnDoubleClick")
|
||||
end
|
||||
self._selected = nil
|
||||
self._operationNameFilter = ""
|
||||
self._expandedModule = nil
|
||||
self._selectedOperation = nil
|
||||
self._prevSelectedOperation = nil
|
||||
self._onOperationSelectedHandler = nil
|
||||
self._onOperationAddedHandler = nil
|
||||
self._onOperationDeletedHandler = nil
|
||||
self.__super:Release()
|
||||
self:SetRowHeight(28)
|
||||
end
|
||||
|
||||
--- Sets the operation name filter.
|
||||
-- @tparam OperationTree self The operation tree object
|
||||
-- @tparam string filter The filter string (any operations which don't match this are hidden)
|
||||
function OperationTree.SetOperationNameFilter(self, filter)
|
||||
self._operationNameFilter = filter
|
||||
if filter == "" and self._prevSelectedOperation and not self._selectedOperation then
|
||||
-- restore any previous selection if we don't have something selected
|
||||
self:SetSelectedOperation(self:_SplitOperationKey(self._prevSelectedOperation))
|
||||
self._prevSelectedOperation = nil
|
||||
elseif filter ~= "" and self._selectedOperation then
|
||||
local _, operationName = self:_SplitOperationKey(self._selectedOperation)
|
||||
if not operationName or not strmatch(strlower(operationName), filter) then
|
||||
-- save the current selection to restore after the filter is cleared and then clear the current selection
|
||||
self._prevSelectedOperation = self._selectedOperation
|
||||
self:SetSelectedOperation()
|
||||
end
|
||||
end
|
||||
self:UpdateData(true)
|
||||
end
|
||||
|
||||
--- Registers a script handler.
|
||||
-- @tparam OperationTree self The operation tree object
|
||||
-- @tparam string script The script to register for (supported scripts: `OnOperationSelected`, `OnOperationAdded`,
|
||||
-- `OnOperationDeleted`)
|
||||
-- @tparam function handler The script handler which will be called with the operation tree object followed by any
|
||||
-- arguments to the script
|
||||
-- @treturn OperationTree The operation tree object
|
||||
function OperationTree.SetScript(self, script, handler)
|
||||
if script == "OnOperationSelected" then
|
||||
self._onOperationSelectedHandler = handler
|
||||
elseif script == "OnOperationAdded" then
|
||||
self._onOperationAddedHandler = handler
|
||||
elseif script == "OnOperationDeleted" then
|
||||
self._onOperationDeletedHandler = handler
|
||||
else
|
||||
error("Unknown OperationTree script: "..tostring(script))
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the selected operation.
|
||||
-- @tparam OperationTree self The operation tree object
|
||||
-- @tparam string moduleName The name of the module which the operation belongs to
|
||||
-- @tparam string operationName The name of the operation
|
||||
-- @treturn OperationTree The operation tree object
|
||||
function OperationTree.SetSelectedOperation(self, moduleName, operationName)
|
||||
if moduleName and operationName then
|
||||
self._selectedOperation = moduleName..DATA_SEP..operationName
|
||||
self._expandedModule = moduleName
|
||||
elseif moduleName then
|
||||
self._selectedOperation = moduleName
|
||||
self._expandedModule = moduleName
|
||||
else
|
||||
self._selectedOperation = nil
|
||||
self._expandedModule = nil
|
||||
end
|
||||
self:UpdateData()
|
||||
self.__super:SetSelection(self._selectedOperation, true)
|
||||
if self._onOperationSelectedHandler then
|
||||
self:_onOperationSelectedHandler(moduleName, operationName)
|
||||
end
|
||||
self:_ForceLastDataUpdate()
|
||||
self:UpdateData(true)
|
||||
return self
|
||||
end
|
||||
|
||||
function OperationTree.SetSelection(self, data)
|
||||
self:SetSelectedOperation(self:_SplitOperationKey(data))
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function OperationTree._GetTableRow(self, isHeader)
|
||||
local row = self.__super:_GetTableRow(isHeader)
|
||||
if not isHeader then
|
||||
ScriptWrapper.Set(row._frame, "OnDoubleClick", private.RowOnDoubleClick, row)
|
||||
end
|
||||
return row
|
||||
end
|
||||
|
||||
function OperationTree._IsDataHidden(self, data)
|
||||
local moduleName, operationName = self:_SplitOperationKey(data)
|
||||
if operationName and not strmatch(strlower(operationName), self._operationNameFilter) then
|
||||
return true
|
||||
elseif operationName and moduleName ~= self._expandedModule then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function OperationTree._SplitOperationKey(self, data)
|
||||
local moduleName, operationName = strmatch(data, "([^"..DATA_SEP.."]+)"..DATA_SEP.."?(.*)")
|
||||
operationName = operationName ~= "" and operationName or nil
|
||||
return moduleName, operationName
|
||||
end
|
||||
|
||||
function OperationTree._UpdateData(self)
|
||||
wipe(self._data)
|
||||
for _, moduleName in TSM.Operations.ModuleIterator() do
|
||||
if not self:_IsDataHidden(moduleName) then
|
||||
tinsert(self._data, moduleName)
|
||||
end
|
||||
for _, operationName in TSM.Operations.OperationIterator(moduleName) do
|
||||
local data = moduleName..DATA_SEP..operationName
|
||||
if not self:_IsDataHidden(data) then
|
||||
tinsert(self._data, data)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function OperationTree._HandleRowClick(self)
|
||||
self:Draw()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetText(self, data)
|
||||
local moduleName, operationName = self:_SplitOperationKey(data)
|
||||
local color = Theme.GetColor(operationName and "TEXT" or "INDICATOR")
|
||||
return color:ColorText(operationName or moduleName.." "..L["Operations"])
|
||||
end
|
||||
|
||||
function private.GetExpanderState(self, data)
|
||||
local moduleName, operationName = self:_SplitOperationKey(data)
|
||||
return not operationName, self._expandedModule == moduleName, operationName and 1 or 0
|
||||
end
|
||||
|
||||
function private.GetActionIcon(self, data, iconIndex, isMouseOver)
|
||||
local _, operationName = self:_SplitOperationKey(data)
|
||||
if iconIndex == 1 then
|
||||
if operationName and data == self._selectedOperation then
|
||||
local texturePack = "iconPack.14x14/Duplicate"
|
||||
return true, isMouseOver and TSM.UI.TexturePacks.GetColoredKey(texturePack, Theme.GetColor("INDICATOR")) or texturePack
|
||||
elseif operationName then
|
||||
return false, nil
|
||||
else
|
||||
local texturePack = "iconPack.14x14/Add/Circle"
|
||||
return true, isMouseOver and TSM.UI.TexturePacks.GetColoredKey(texturePack, Theme.GetColor("INDICATOR")) or texturePack
|
||||
end
|
||||
elseif iconIndex == 2 then
|
||||
if operationName and data == self._selectedOperation then
|
||||
local texturePack = "iconPack.14x14/Delete"
|
||||
return true, isMouseOver and TSM.UI.TexturePacks.GetColoredKey(texturePack, Theme.GetColor("INDICATOR")) or texturePack
|
||||
else
|
||||
return false, nil
|
||||
end
|
||||
else
|
||||
error("Invalid index: "..tostring(iconIndex))
|
||||
end
|
||||
end
|
||||
|
||||
function private.OnActionIconClick(self, data, iconIndex)
|
||||
local moduleName, operationName = self:_SplitOperationKey(data)
|
||||
if iconIndex == 1 then
|
||||
if operationName then
|
||||
-- duplicate
|
||||
local num = 1
|
||||
while TSM.Operations.Exists(moduleName, operationName.." "..num) do
|
||||
num = num + 1
|
||||
end
|
||||
local newOperationName = operationName.." "..num
|
||||
self:_onOperationAddedHandler(moduleName, newOperationName, operationName)
|
||||
self:UpdateData()
|
||||
self:SetSelectedOperation(moduleName, newOperationName)
|
||||
else
|
||||
-- add
|
||||
operationName = "New Operation"
|
||||
local num = 1
|
||||
while TSM.Operations.Exists(moduleName, operationName.." "..num) do
|
||||
num = num + 1
|
||||
end
|
||||
operationName = operationName .. " " .. num
|
||||
self._expandedModule = moduleName
|
||||
self:_onOperationAddedHandler(moduleName, operationName)
|
||||
self:UpdateData()
|
||||
self:SetSelectedOperation(moduleName, operationName)
|
||||
end
|
||||
self:Draw()
|
||||
elseif iconIndex == 2 then
|
||||
assert(operationName)
|
||||
-- delete
|
||||
self:_onOperationDeletedHandler(moduleName, operationName)
|
||||
self:UpdateData(true)
|
||||
else
|
||||
error("Invalid index: "..tostring(iconIndex))
|
||||
end
|
||||
end
|
||||
|
||||
function private.RowOnDoubleClick(row, mouseButton)
|
||||
if mouseButton ~= "LeftButton" then
|
||||
return
|
||||
end
|
||||
local self = row._scrollingTable
|
||||
local data = row:GetData()
|
||||
local moduleName, operationName = self:_SplitOperationKey(data)
|
||||
if operationName then
|
||||
return
|
||||
end
|
||||
if moduleName == self._selectedOperation then
|
||||
self:SetSelectedOperation()
|
||||
else
|
||||
self:SetSelectedOperation(moduleName, operationName)
|
||||
end
|
||||
end
|
||||
186
Core/UI/Elements/OverlayApplicationFrame.lua
Normal file
186
Core/UI/Elements/OverlayApplicationFrame.lua
Normal file
@@ -0,0 +1,186 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- OverlayApplicationFrame UI Element Class.
|
||||
-- The overlay application frame is currently just used for the TaskListUI. It is a subclass of the @{Frame} class.
|
||||
-- @classmod OverlayApplicationFrame
|
||||
|
||||
local _, TSM = ...
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local OverlayApplicationFrame = TSM.Include("LibTSMClass").DefineClass("OverlayApplicationFrame", TSM.UI.Frame)
|
||||
UIElements.Register(OverlayApplicationFrame)
|
||||
TSM.UI.OverlayApplicationFrame = OverlayApplicationFrame
|
||||
local private = {}
|
||||
local TITLE_HEIGHT = 40
|
||||
local CONTENT_PADDING_BOTTOM = 16
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function OverlayApplicationFrame.__init(self)
|
||||
self.__super:__init()
|
||||
self._contentFrame = nil
|
||||
self._contextTable = nil
|
||||
self._defaultContextTable = nil
|
||||
Theme.RegisterChangeCallback(function()
|
||||
if self:IsVisible() then
|
||||
self:Draw()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function OverlayApplicationFrame.Acquire(self)
|
||||
local frame = self:_GetBaseFrame()
|
||||
frame:EnableMouse(true)
|
||||
frame:SetMovable(true)
|
||||
frame:RegisterForDrag("LeftButton")
|
||||
self:AddChildNoLayout(UIElements.New("Button", "closeBtn")
|
||||
:AddAnchor("TOPRIGHT", -8, -11)
|
||||
:SetBackgroundAndSize("iconPack.18x18/Close/Circle")
|
||||
:SetScript("OnClick", private.CloseButtonOnClick)
|
||||
)
|
||||
self:AddChildNoLayout(UIElements.New("Button", "minimizeBtn")
|
||||
:AddAnchor("TOPRIGHT", -26, -11)
|
||||
:SetBackgroundAndSize("iconPack.18x18/Subtract/Circle")
|
||||
:SetScript("OnClick", private.MinimizeBtnOnClick)
|
||||
)
|
||||
self:AddChildNoLayout(UIElements.New("Text", "title")
|
||||
:SetHeight(24)
|
||||
:SetFont("BODY_BODY1_BOLD")
|
||||
:AddAnchor("TOPLEFT", 8, -8)
|
||||
:AddAnchor("TOPRIGHT", -52, -8)
|
||||
)
|
||||
self:SetScript("OnDragStart", private.FrameOnDragStart)
|
||||
self:SetScript("OnDragStop", private.FrameOnDragStop)
|
||||
|
||||
self.__super:Acquire()
|
||||
end
|
||||
|
||||
function OverlayApplicationFrame.Release(self)
|
||||
self._contentFrame = nil
|
||||
self._contextTable = nil
|
||||
self._defaultContextTable = nil
|
||||
self:_GetBaseFrame():SetMinResize(0, 0)
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
--- Sets the title text.
|
||||
-- @tparam OverlayApplicationFrame self The overlay application frame object
|
||||
-- @tparam string title The title text
|
||||
-- @treturn OverlayApplicationFrame The overlay application frame object
|
||||
function OverlayApplicationFrame.SetTitle(self, title)
|
||||
self:GetElement("title"):SetText(title)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the content frame.
|
||||
-- @tparam OverlayApplicationFrame self The overlay application frame object
|
||||
-- @tparam Element frame The content frame
|
||||
-- @treturn OverlayApplicationFrame The overlay application frame object
|
||||
function OverlayApplicationFrame.SetContentFrame(self, frame)
|
||||
frame:WipeAnchors()
|
||||
frame:AddAnchor("TOPLEFT", 0, -TITLE_HEIGHT)
|
||||
frame:AddAnchor("BOTTOMRIGHT", 0, CONTENT_PADDING_BOTTOM)
|
||||
self._contentFrame = frame
|
||||
self:AddChildNoLayout(frame)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the context table.
|
||||
-- This table can be used to preserve position information across lifecycles of the frame and even WoW sessions if it's
|
||||
-- within the settings DB.
|
||||
-- @tparam OverlayApplicationFrame self The overlay application frame object
|
||||
-- @tparam table tbl The context table
|
||||
-- @tparam table defaultTbl The default values (required fields: `minimized`, `topRightX`, `topRightY`)
|
||||
-- @treturn OverlayApplicationFrame The overlay application frame object
|
||||
function OverlayApplicationFrame.SetContextTable(self, tbl, defaultTbl)
|
||||
assert(defaultTbl.minimized ~= nil and defaultTbl.topRightX and defaultTbl.topRightY)
|
||||
if tbl.minimized == nil then
|
||||
tbl.minimized = defaultTbl.minimized
|
||||
end
|
||||
tbl.topRightX = tbl.topRightX or defaultTbl.topRightX
|
||||
tbl.topRightY = tbl.topRightY or defaultTbl.topRightY
|
||||
self._contextTable = tbl
|
||||
self._defaultContextTable = defaultTbl
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the context table from a settings object.
|
||||
-- @tparam OverlayApplicationFrame self The overlay application frame object
|
||||
-- @tparam Settings settings The settings object
|
||||
-- @tparam string key The setting key
|
||||
-- @treturn OverlayApplicationFrame The overlay application frame object
|
||||
function OverlayApplicationFrame.SetSettingsContext(self, settings, key)
|
||||
return self:SetContextTable(settings[key], settings:GetDefaultReadOnly(key))
|
||||
end
|
||||
|
||||
function OverlayApplicationFrame.Draw(self)
|
||||
if self._contextTable.minimized then
|
||||
self:GetElement("minimizeBtn"):SetBackgroundAndSize("iconPack.18x18/Add/Circle")
|
||||
self:GetElement("content"):Hide()
|
||||
self:SetHeight(TITLE_HEIGHT)
|
||||
else
|
||||
self:GetElement("minimizeBtn"):SetBackgroundAndSize("iconPack.18x18/Subtract/Circle")
|
||||
self:GetElement("content"):Show()
|
||||
-- set the height of the frame based on the height of the children
|
||||
local contentHeight, contentHeightExpandable = self:GetElement("content"):_GetMinimumDimension("HEIGHT")
|
||||
assert(not contentHeightExpandable)
|
||||
self:SetHeight(contentHeight + TITLE_HEIGHT + CONTENT_PADDING_BOTTOM)
|
||||
end
|
||||
|
||||
-- make sure the frame is on the screen
|
||||
self._contextTable.topRightX = max(min(self._contextTable.topRightX, 0), -UIParent:GetWidth() + 100)
|
||||
self._contextTable.topRightY = max(min(self._contextTable.topRightY, 0), -UIParent:GetHeight() + 100)
|
||||
|
||||
-- set the frame position from the contextTable
|
||||
self:WipeAnchors()
|
||||
self:AddAnchor("TOPRIGHT", self._contextTable.topRightX, self._contextTable.topRightY)
|
||||
|
||||
self.__super:Draw()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function OverlayApplicationFrame._SavePosition(self)
|
||||
local frame = self:_GetBaseFrame()
|
||||
local parentFrame = frame:GetParent()
|
||||
self._contextTable.topRightX = frame:GetRight() - parentFrame:GetRight()
|
||||
self._contextTable.topRightY = frame:GetTop() - parentFrame:GetTop()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.FrameOnDragStart(self)
|
||||
self:_GetBaseFrame():StartMoving()
|
||||
end
|
||||
|
||||
function private.FrameOnDragStop(self)
|
||||
local frame = self:_GetBaseFrame()
|
||||
frame:StopMovingOrSizing()
|
||||
self:_SavePosition()
|
||||
end
|
||||
|
||||
function private.CloseButtonOnClick(button)
|
||||
button:GetParentElement():Hide()
|
||||
end
|
||||
|
||||
function private.MinimizeBtnOnClick(button)
|
||||
local self = button:GetParentElement()
|
||||
self._contextTable.minimized = not self._contextTable.minimized
|
||||
self:Draw()
|
||||
end
|
||||
103
Core/UI/Elements/PlayerGoldText.lua
Normal file
103
Core/UI/Elements/PlayerGoldText.lua
Normal file
@@ -0,0 +1,103 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- PlayerGoldText UI Element Class.
|
||||
-- A text element which contains player gold info which automatically updates when the player's gold amount changes. It
|
||||
-- is a subclass of the @{Text} class.
|
||||
-- @classmod PlayerGoldText
|
||||
|
||||
local _, TSM = ...
|
||||
local PlayerGoldText = TSM.Include("LibTSMClass").DefineClass("PlayerGoldText", TSM.UI.Text)
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local Event = TSM.Include("Util.Event")
|
||||
local Money = TSM.Include("Util.Money")
|
||||
local Settings = TSM.Include("Service.Settings")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(PlayerGoldText)
|
||||
TSM.UI.PlayerGoldText = PlayerGoldText
|
||||
local private = {
|
||||
registered = false,
|
||||
elements = {},
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Meta Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function PlayerGoldText.__init(self)
|
||||
self.__super:__init()
|
||||
self:_GetBaseFrame():EnableMouse(true)
|
||||
|
||||
if not private.registered then
|
||||
Event.Register("PLAYER_MONEY", private.MoneyOnUpdate)
|
||||
private.registered = true
|
||||
end
|
||||
|
||||
self._justifyH = "RIGHT"
|
||||
self._font = "TABLE_TABLE1"
|
||||
end
|
||||
|
||||
function PlayerGoldText.Acquire(self)
|
||||
private.elements[self] = true
|
||||
self.__super:Acquire()
|
||||
self:SetText(Money.ToString(TSM.db.global.appearanceOptions.showTotalMoney and private.GetTotalMoney() or GetMoney()))
|
||||
self:SetTooltip(private.MoneyTooltipFunc)
|
||||
end
|
||||
|
||||
function PlayerGoldText.Release(self)
|
||||
private.elements[self] = nil
|
||||
self.__super:Release()
|
||||
self._justifyH = "RIGHT"
|
||||
self._font = "TABLE_TABLE1"
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.MoneyOnUpdate()
|
||||
for element in pairs(private.elements) do
|
||||
element:SetText(Money.ToString(TSM.db.global.appearanceOptions.showTotalMoney and private.GetTotalMoney() or GetMoney()))
|
||||
element:Draw()
|
||||
end
|
||||
end
|
||||
|
||||
function private.MoneyTooltipFunc()
|
||||
local tooltipLines = TempTable.Acquire()
|
||||
local playerMoney = TSM.db.sync.internalData.money
|
||||
local total = playerMoney
|
||||
tinsert(tooltipLines, strjoin(TSM.CONST.TOOLTIP_SEP, UnitName("player")..":", Money.ToString(playerMoney)))
|
||||
local numPosted, numSold, postedGold, soldGold = TSM.MyAuctions.GetAuctionInfo()
|
||||
if numPosted then
|
||||
tinsert(tooltipLines, " "..strjoin(TSM.CONST.TOOLTIP_SEP, format(L["%s Sold Auctions"], numSold)..":", Money.ToString(soldGold)))
|
||||
tinsert(tooltipLines, " "..strjoin(TSM.CONST.TOOLTIP_SEP, format(L["%s Posted Auctions"], numPosted)..":", Money.ToString(postedGold)))
|
||||
end
|
||||
for _, _, character, syncScopeKey in Settings.ConnectedFactionrealmAltCharacterIterator() do
|
||||
local money = Settings.Get("sync", syncScopeKey, "internalData", "money")
|
||||
if money > 0 then
|
||||
tinsert(tooltipLines, strjoin(TSM.CONST.TOOLTIP_SEP, character..":", Money.ToString(money)))
|
||||
total = total + money
|
||||
end
|
||||
end
|
||||
tinsert(tooltipLines, 1, strjoin(TSM.CONST.TOOLTIP_SEP, L["Total Gold"]..":", Money.ToString(total)))
|
||||
return strjoin("\n", TempTable.UnpackAndRelease(tooltipLines))
|
||||
end
|
||||
|
||||
function private.GetTotalMoney()
|
||||
local total = TSM.db.sync.internalData.money
|
||||
for _, _, _, syncScopeKey in Settings.ConnectedFactionrealmAltCharacterIterator() do
|
||||
local money = Settings.Get("sync", syncScopeKey, "internalData", "money")
|
||||
if money > 0 then
|
||||
total = total + money
|
||||
end
|
||||
end
|
||||
return total
|
||||
end
|
||||
35
Core/UI/Elements/PopupFrame.lua
Normal file
35
Core/UI/Elements/PopupFrame.lua
Normal file
@@ -0,0 +1,35 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- PopupFrame UI Element Class.
|
||||
-- A popup frame which shows when clicking on a "more" button.
|
||||
-- @classmod PopupFrame
|
||||
|
||||
local _, TSM = ...
|
||||
local NineSlice = TSM.Include("Util.NineSlice")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local PopupFrame = TSM.Include("LibTSMClass").DefineClass("PopupFrame", TSM.UI.Frame)
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(PopupFrame)
|
||||
TSM.UI.PopupFrame = PopupFrame
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function PopupFrame.__init(self)
|
||||
self.__super:__init()
|
||||
self._nineSlice = NineSlice.New(self:_GetBaseFrame())
|
||||
end
|
||||
|
||||
function PopupFrame.Draw(self)
|
||||
self.__super:Draw()
|
||||
self._nineSlice:SetStyle("popup")
|
||||
-- TOOD: fix the texture color properly
|
||||
self._nineSlice:SetPartVertexColor("center", Theme.GetColor("PRIMARY_BG_ALT", "duskwood"):GetFractionalRGBA())
|
||||
end
|
||||
615
Core/UI/Elements/ProfessionScrollingTable.lua
Normal file
615
Core/UI/Elements/ProfessionScrollingTable.lua
Normal file
@@ -0,0 +1,615 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- ProfessionScrollingTable UI Element Class.
|
||||
-- This is used to display the crafts within the currently-selected profession in the CraftingUI. It is a subclass of
|
||||
-- the @{ScrollingTable} class.
|
||||
-- @classmod ProfessionScrollingTable
|
||||
|
||||
local _, TSM = ...
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local Money = TSM.Include("Util.Money")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local Log = TSM.Include("Util.Log")
|
||||
local ScriptWrapper = TSM.Include("Util.ScriptWrapper")
|
||||
local Event = TSM.Include("Util.Event")
|
||||
local ItemInfo = TSM.Include("Service.ItemInfo")
|
||||
local Tooltip = TSM.Include("UI.Tooltip")
|
||||
local ProfessionScrollingTable = TSM.Include("LibTSMClass").DefineClass("ProfessionScrollingTable", TSM.UI.ScrollingTable)
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(ProfessionScrollingTable)
|
||||
TSM.UI.ProfessionScrollingTable = ProfessionScrollingTable
|
||||
local private = {
|
||||
activeElements = {},
|
||||
categoryInfoCache = {
|
||||
parent = {},
|
||||
numIndents = {},
|
||||
name = {},
|
||||
currentSkillLevel = {},
|
||||
maxSkillLevel = {},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function ProfessionScrollingTable.__init(self)
|
||||
self.__super:__init()
|
||||
self._query = nil
|
||||
self._isSpellId = {}
|
||||
self._favoritesContextTable = nil
|
||||
end
|
||||
|
||||
function ProfessionScrollingTable.Acquire(self)
|
||||
self.__super:Acquire()
|
||||
self:GetScrollingTableInfo()
|
||||
:SetMenuInfo(private.MenuIterator, private.MenuClickHandler)
|
||||
:NewColumn("name")
|
||||
:SetTitle(L["Recipe Name"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextFunction(private.GetNameCellText)
|
||||
:SetExpanderStateFunction(private.GetExpanderState)
|
||||
:SetActionIconInfo(1, 14, private.GetFavoriteIcon, true)
|
||||
:SetActionIconClickHandler(private.OnFavoriteIconClick)
|
||||
:DisableHiding()
|
||||
:Commit()
|
||||
:NewColumn("qty")
|
||||
:SetTitle(L["Craft"])
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetJustifyH("CENTER")
|
||||
:SetTextFunction(private.GetQtyCellText)
|
||||
:Commit()
|
||||
if not TSM.IsWowClassic() then
|
||||
self:GetScrollingTableInfo()
|
||||
:NewColumn("rank")
|
||||
:SetTitle(RANK)
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetJustifyH("CENTER")
|
||||
:SetTextFunction(private.GetRankCellText)
|
||||
:Commit()
|
||||
end
|
||||
self:GetScrollingTableInfo()
|
||||
:NewColumn("craftingCost")
|
||||
:SetTitle(L["Crafting Cost"])
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextFunction(private.GetCraftingCostCellText)
|
||||
:Commit()
|
||||
:NewColumn("itemValue")
|
||||
:SetTitle(L["Item Value"])
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextFunction(private.GetItemValueCellIndex)
|
||||
:Commit()
|
||||
:NewColumn("profit")
|
||||
:SetTitle(L["Profit"])
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextFunction(private.GetProfitCellText)
|
||||
:Commit()
|
||||
:NewColumn("profitPct")
|
||||
:SetTitle("%")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextFunction(private.GetProfitPctCellText)
|
||||
:Commit()
|
||||
:NewColumn("saleRate")
|
||||
:SetTitleIcon("iconPack.14x14/SaleRate")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextFunction(private.GetSaleRateCellText)
|
||||
:Commit()
|
||||
:Commit()
|
||||
if not next(private.activeElements) then
|
||||
Event.Register("CHAT_MSG_SKILL", private.OnChatMsgSkill)
|
||||
end
|
||||
private.activeElements[self] = true
|
||||
end
|
||||
|
||||
function ProfessionScrollingTable.Release(self)
|
||||
private.activeElements[self] = nil
|
||||
if not next(private.activeElements) then
|
||||
Event.Unregister("CHAT_MSG_SKILL", private.OnChatMsgSkill)
|
||||
end
|
||||
if self._query then
|
||||
self._query:SetUpdateCallback()
|
||||
self._query = nil
|
||||
end
|
||||
wipe(self._isSpellId)
|
||||
self._favoritesContextTable = nil
|
||||
for _, row in ipairs(self._rows) do
|
||||
ScriptWrapper.Clear(row._frame, "OnDoubleClick")
|
||||
end
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
--- Sets the @{DatabaseQuery} source for this table.
|
||||
-- This query is used to populate the entries in the profession scrolling table.
|
||||
-- @tparam ProfessionScrollingTable self The profession scrolling table object
|
||||
-- @tparam DatabaseQuery query The query object
|
||||
-- @tparam[opt=false] bool redraw Whether or not to redraw the scrolling table
|
||||
-- @treturn ProfessionScrollingTable The profession scrolling table object
|
||||
function ProfessionScrollingTable.SetQuery(self, query, redraw)
|
||||
if query == self._query and not redraw then
|
||||
return self
|
||||
end
|
||||
if self._query then
|
||||
self._query:SetUpdateCallback()
|
||||
end
|
||||
self._query = query
|
||||
self._query:SetUpdateCallback(private.QueryUpdateCallback, self)
|
||||
|
||||
self:_ForceLastDataUpdate()
|
||||
self:UpdateData(redraw)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the context table to use to store favorite craft information.
|
||||
-- @tparam ProfessionScrollingTable self The profession scrolling table object
|
||||
-- @tparam table tbl The context table
|
||||
-- @treturn ProfessionScrollingTable The profession scrolling table object
|
||||
function ProfessionScrollingTable.SetFavoritesContext(self, tbl)
|
||||
assert(type(tbl) == "table")
|
||||
self._favoritesContextTable = tbl
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the context table.
|
||||
-- @tparam ProfessionScrollingTable self The profession scrolling table object
|
||||
-- @tparam table tbl The context table
|
||||
-- @tparam table defaultTbl The default table (required fields: `colWidth`, `colHidden`, `collapsed`)
|
||||
-- @treturn ProfessionScrollingTable The profession scrolling table object
|
||||
function ProfessionScrollingTable.SetContextTable(self, tbl, defaultTbl)
|
||||
assert(type(defaultTbl.collapsed) == "table")
|
||||
tbl.collapsed = tbl.collapsed or CopyTable(defaultTbl.collapsed)
|
||||
self.__super:SetContextTable(tbl, defaultTbl)
|
||||
return self
|
||||
end
|
||||
|
||||
function ProfessionScrollingTable.IsSpellIdVisible(self, spellId)
|
||||
if not self._isSpellId[spellId] then
|
||||
-- this spellId isn't included in the query
|
||||
return false
|
||||
end
|
||||
local categoryId = TSM.Crafting.ProfessionScanner.GetCategoryIdBySpellId(spellId)
|
||||
return not self:_IsCategoryHidden(categoryId) and not self._contextTable.collapsed[categoryId]
|
||||
end
|
||||
|
||||
function ProfessionScrollingTable.Draw(self)
|
||||
if self._lastDataUpdate == nil then
|
||||
self:_IgnoreLastDataUpdate()
|
||||
end
|
||||
self.__super:Draw()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function ProfessionScrollingTable._ToggleCollapsed(self, categoryId)
|
||||
self._contextTable.collapsed[categoryId] = not self._contextTable.collapsed[categoryId] or nil
|
||||
if self._selection and not self:IsSpellIdVisible(self._selection) then
|
||||
self:SetSelection(nil, true)
|
||||
end
|
||||
end
|
||||
|
||||
function ProfessionScrollingTable._GetTableRow(self, isHeader)
|
||||
local row = self.__super:_GetTableRow(isHeader)
|
||||
if not isHeader then
|
||||
ScriptWrapper.Set(row._frame, "OnClick", private.RowOnClick, row)
|
||||
ScriptWrapper.Set(row._frame, "OnDoubleClick", private.RowOnClick, row)
|
||||
local rankBtn = row:_GetButton()
|
||||
rankBtn:SetAllPoints(row._texts.rank)
|
||||
ScriptWrapper.SetPropagate(rankBtn, "OnClick")
|
||||
ScriptWrapper.Set(rankBtn, "OnEnter", private.RankOnEnter, row)
|
||||
ScriptWrapper.Set(rankBtn, "OnLeave", private.RankOnLeave, row)
|
||||
row._buttons.rank = rankBtn
|
||||
end
|
||||
return row
|
||||
end
|
||||
|
||||
function ProfessionScrollingTable._UpdateData(self)
|
||||
local currentCategoryPath = TempTable.Acquire()
|
||||
local foundSelection = false
|
||||
-- populate the data
|
||||
wipe(self._data)
|
||||
wipe(self._isSpellId)
|
||||
for _, spellId in self._query:Iterator() do
|
||||
if self._favoritesContextTable[spellId] then
|
||||
local categoryId = -1
|
||||
if categoryId ~= currentCategoryPath[#currentCategoryPath] then
|
||||
-- this is a new category
|
||||
local newCategoryPath = TempTable.Acquire()
|
||||
tinsert(newCategoryPath, 1, categoryId)
|
||||
-- create new category headers
|
||||
if currentCategoryPath[1] ~= categoryId then
|
||||
if not self:_IsCategoryHidden(categoryId) then
|
||||
tinsert(self._data, categoryId)
|
||||
end
|
||||
end
|
||||
TempTable.Release(currentCategoryPath)
|
||||
currentCategoryPath = newCategoryPath
|
||||
end
|
||||
foundSelection = foundSelection or spellId == self:GetSelection()
|
||||
if not self._contextTable.collapsed[categoryId] and not self:_IsCategoryHidden(categoryId) then
|
||||
tinsert(self._data, spellId)
|
||||
self._isSpellId[spellId] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
for _, spellId, categoryId in self._query:Iterator() do
|
||||
if not self._favoritesContextTable[spellId] then
|
||||
if categoryId ~= currentCategoryPath[#currentCategoryPath] then
|
||||
-- this is a new category
|
||||
local newCategoryPath = TempTable.Acquire()
|
||||
local currentCategoryId = categoryId
|
||||
while currentCategoryId do
|
||||
tinsert(newCategoryPath, 1, currentCategoryId)
|
||||
currentCategoryId = private.CategoryGetParentCategoryId(currentCategoryId)
|
||||
end
|
||||
-- create new category headers
|
||||
for i = 1, #newCategoryPath do
|
||||
local newCategoryId = newCategoryPath[i]
|
||||
if currentCategoryPath[i] ~= newCategoryId then
|
||||
if not self:_IsCategoryHidden(newCategoryId) then
|
||||
tinsert(self._data, newCategoryId)
|
||||
end
|
||||
end
|
||||
end
|
||||
TempTable.Release(currentCategoryPath)
|
||||
currentCategoryPath = newCategoryPath
|
||||
end
|
||||
foundSelection = foundSelection or spellId == self:GetSelection()
|
||||
if not self._contextTable.collapsed[categoryId] and not self:_IsCategoryHidden(categoryId) then
|
||||
tinsert(self._data, spellId)
|
||||
self._isSpellId[spellId] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
TempTable.Release(currentCategoryPath)
|
||||
if not foundSelection then
|
||||
-- try to select the first visible spellId
|
||||
local newSelection = nil
|
||||
for _, data in ipairs(self._data) do
|
||||
if not newSelection and self._isSpellId[data] then
|
||||
newSelection = data
|
||||
end
|
||||
end
|
||||
self:SetSelection(newSelection, true)
|
||||
end
|
||||
end
|
||||
|
||||
function ProfessionScrollingTable._IsCategoryHidden(self, categoryId)
|
||||
if private.IsFavoriteCategory(categoryId) then
|
||||
return false
|
||||
end
|
||||
local parent = private.CategoryGetParentCategoryId(categoryId)
|
||||
while parent do
|
||||
if self._contextTable.collapsed[parent] then
|
||||
return true
|
||||
end
|
||||
parent = private.CategoryGetParentCategoryId(parent)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function ProfessionScrollingTable._SetRowData(self, row, data)
|
||||
local rank = self._isSpellId[data] and TSM.Crafting.ProfessionScanner.GetRankBySpellId(data) or -1
|
||||
if rank == -1 then
|
||||
row._buttons.rank:Hide()
|
||||
else
|
||||
row._buttons.rank:Show()
|
||||
end
|
||||
self.__super:_SetRowData(row, data)
|
||||
end
|
||||
|
||||
function ProfessionScrollingTable._ToggleSort(self, id)
|
||||
-- do nothing
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.PopulateCategoryInfoCache(categoryId)
|
||||
-- numIndents always gets set, so use that to know whether or not this category is already cached
|
||||
if not private.categoryInfoCache.numIndents[categoryId] then
|
||||
local name, numIndents, parentCategoryId, currentSkillLevel, maxSkillLevel = TSM.Crafting.ProfessionUtil.GetCategoryInfo(categoryId)
|
||||
private.categoryInfoCache.name[categoryId] = name
|
||||
private.categoryInfoCache.numIndents[categoryId] = numIndents
|
||||
private.categoryInfoCache.parent[categoryId] = parentCategoryId
|
||||
private.categoryInfoCache.currentSkillLevel[categoryId] = currentSkillLevel
|
||||
private.categoryInfoCache.maxSkillLevel[categoryId] = maxSkillLevel
|
||||
end
|
||||
end
|
||||
|
||||
function private.CategoryGetParentCategoryId(categoryId)
|
||||
private.PopulateCategoryInfoCache(categoryId)
|
||||
return private.categoryInfoCache.parent[categoryId]
|
||||
end
|
||||
|
||||
function private.CategoryGetNumIndents(categoryId)
|
||||
private.PopulateCategoryInfoCache(categoryId)
|
||||
return private.categoryInfoCache.numIndents[categoryId]
|
||||
end
|
||||
|
||||
function private.CategoryGetName(categoryId)
|
||||
private.PopulateCategoryInfoCache(categoryId)
|
||||
return private.categoryInfoCache.name[categoryId]
|
||||
end
|
||||
|
||||
function private.CategoryGetSkillLevel(categoryId)
|
||||
private.PopulateCategoryInfoCache(categoryId)
|
||||
return private.categoryInfoCache.currentSkillLevel[categoryId], private.categoryInfoCache.maxSkillLevel[categoryId]
|
||||
end
|
||||
|
||||
function private.IsFavoriteCategory(categoryId)
|
||||
return categoryId == -1
|
||||
end
|
||||
|
||||
function private.QueryUpdateCallback(_, _, self)
|
||||
self:_ForceLastDataUpdate()
|
||||
self:UpdateData(true)
|
||||
end
|
||||
|
||||
function private.MenuIterator(self, prevIndex)
|
||||
if prevIndex == "CREATE_GROUPS" then
|
||||
-- we're done
|
||||
return
|
||||
else
|
||||
return "CREATE_GROUPS", L["Create Groups from Table"]
|
||||
end
|
||||
end
|
||||
|
||||
function private.MenuClickHandler(self, index1, index2)
|
||||
if index1 == "CREATE_GROUPS" then
|
||||
assert(not index2)
|
||||
self:GetBaseElement():HideDialog()
|
||||
local numCreated, numAdded = 0, 0
|
||||
for _, spellId in self._query:Iterator() do
|
||||
local itemString = TSM.Crafting.GetItemString(spellId)
|
||||
if itemString then
|
||||
local groupPath = private.GetCategoryGroupPath(TSM.Crafting.ProfessionScanner.GetCategoryIdBySpellId(spellId))
|
||||
if not TSM.Groups.Exists(groupPath) then
|
||||
TSM.Groups.Create(groupPath)
|
||||
numCreated = numCreated + 1
|
||||
end
|
||||
if not TSM.Groups.IsItemInGroup(itemString) and not ItemInfo.IsSoulbound(itemString) then
|
||||
TSM.Groups.SetItemGroup(itemString, groupPath)
|
||||
numAdded = numAdded + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
Log.PrintfUser(L["%d groups were created and %d items were added from the table."], numCreated, numAdded)
|
||||
else
|
||||
error("Unexpected index1: "..tostring(index1))
|
||||
end
|
||||
end
|
||||
|
||||
function private.GetCategoryGroupPath(categoryId)
|
||||
local parts = TempTable.Acquire()
|
||||
while categoryId do
|
||||
tinsert(parts, 1, private.categoryInfoCache.name[categoryId])
|
||||
categoryId = private.categoryInfoCache.parent[categoryId]
|
||||
end
|
||||
tinsert(parts, 1, TSM.Crafting.ProfessionUtil.GetCurrentProfessionName())
|
||||
return TSM.Groups.Path.Join(TempTable.UnpackAndRelease(parts))
|
||||
end
|
||||
|
||||
function private.GetNameCellText(self, data)
|
||||
if self._isSpellId[data] then
|
||||
local name = TSM.Crafting.ProfessionScanner.GetNameBySpellId(data)
|
||||
local color = nil
|
||||
if TSM.Crafting.ProfessionUtil.IsGuildProfession() then
|
||||
color = Theme.GetProfessionDifficultyColor("easy")
|
||||
elseif TSM.Crafting.ProfessionUtil.IsNPCProfession() then
|
||||
color = Theme.GetProfessionDifficultyColor("nodifficulty")
|
||||
else
|
||||
local difficulty = TSM.Crafting.ProfessionScanner.GetDifficultyBySpellId(data)
|
||||
color = Theme.GetProfessionDifficultyColor(difficulty)
|
||||
end
|
||||
return color:ColorText(name)
|
||||
else
|
||||
-- this is a category
|
||||
local name = nil
|
||||
if private.IsFavoriteCategory(data) then
|
||||
name = L["Favorited Patterns"]
|
||||
else
|
||||
local currentSkillLevel, maxSkillLevel = private.CategoryGetSkillLevel(data)
|
||||
name = private.CategoryGetName(data)
|
||||
if name and currentSkillLevel and maxSkillLevel then
|
||||
name = name.." ("..currentSkillLevel.."/"..maxSkillLevel..")"
|
||||
end
|
||||
end
|
||||
if not name then
|
||||
-- happens if we're switching to another profession
|
||||
return "?"
|
||||
end
|
||||
if private.IsFavoriteCategory(data) or private.CategoryGetNumIndents(data) == 0 then
|
||||
return Theme.GetColor("INDICATOR"):ColorText(name)
|
||||
else
|
||||
return Theme.GetColor("INDICATOR_ALT"):ColorText(name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function private.GetExpanderState(self, data)
|
||||
local indentLevel = 0
|
||||
if self._isSpellId[data] then
|
||||
indentLevel = 2
|
||||
elseif not private.IsFavoriteCategory(data) then
|
||||
indentLevel = private.CategoryGetNumIndents(data) * 2
|
||||
end
|
||||
return not self._isSpellId[data], not self._contextTable.collapsed[data], -indentLevel
|
||||
end
|
||||
|
||||
function private.GetFavoriteIcon(self, data, iconIndex, isMouseOver)
|
||||
if iconIndex == 1 then
|
||||
if not self._isSpellId[data] then
|
||||
return false, nil, true
|
||||
else
|
||||
return true, self._favoritesContextTable[data] and "iconPack.12x12/Star/Filled" or "iconPack.12x12/Star/Unfilled", true
|
||||
end
|
||||
else
|
||||
error("Invalid index: "..tostring(iconIndex))
|
||||
end
|
||||
end
|
||||
|
||||
function private.OnFavoriteIconClick(self, data, iconIndex)
|
||||
if iconIndex == 1 then
|
||||
if self._isSpellId[data] and private.IsPlayerProfession() then
|
||||
self._favoritesContextTable[data] = not self._favoritesContextTable[data] or nil
|
||||
self:_ForceLastDataUpdate()
|
||||
self:UpdateData(true)
|
||||
end
|
||||
else
|
||||
error("Invalid index: "..tostring(iconIndex))
|
||||
end
|
||||
end
|
||||
|
||||
function private.GetQtyCellText(self, data)
|
||||
if not self._isSpellId[data] then
|
||||
return ""
|
||||
end
|
||||
local num, numAll = TSM.Crafting.ProfessionUtil.GetNumCraftable(data)
|
||||
if num == numAll then
|
||||
if num > 0 then
|
||||
return Theme.GetFeedbackColor("GREEN"):ColorText(num)
|
||||
end
|
||||
return tostring(num)
|
||||
else
|
||||
if num > 0 then
|
||||
return Theme.GetFeedbackColor("GREEN"):ColorText(num.."-"..numAll)
|
||||
elseif numAll > 0 then
|
||||
return Theme.GetFeedbackColor("YELLOW"):ColorText(num.."-"..numAll)
|
||||
else
|
||||
return num.."-"..numAll
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function private.GetRankCellText(self, data)
|
||||
local rank = self._isSpellId[data] and TSM.Crafting.ProfessionScanner.GetRankBySpellId(data) or -1
|
||||
if rank == -1 then
|
||||
return ""
|
||||
end
|
||||
local filled = TSM.UI.TexturePacks.GetTextureLink("iconPack.14x14/Star/Filled")
|
||||
local unfilled = TSM.UI.TexturePacks.GetTextureLink("iconPack.14x14/Star/Unfilled")
|
||||
assert(rank >= 1 and rank <= 3)
|
||||
return strrep(filled, rank)..strrep(unfilled, 3 - rank)
|
||||
end
|
||||
|
||||
function private.GetCraftingCostCellText(self, data)
|
||||
if not self._isSpellId[data] then
|
||||
return ""
|
||||
end
|
||||
local craftingCost = TSM.Crafting.Cost.GetCostsBySpellId(data)
|
||||
return craftingCost and Money.ToString(craftingCost) or ""
|
||||
end
|
||||
|
||||
function private.GetItemValueCellIndex(self, data)
|
||||
if not self._isSpellId[data] then
|
||||
return ""
|
||||
end
|
||||
local _, craftedItemValue = TSM.Crafting.Cost.GetCostsBySpellId(data)
|
||||
return craftedItemValue and Money.ToString(craftedItemValue) or ""
|
||||
end
|
||||
|
||||
function private.GetProfitCellText(self, data, currentTitleIndex)
|
||||
if not self._isSpellId[data] then
|
||||
return ""
|
||||
end
|
||||
local _, _, profit = TSM.Crafting.Cost.GetCostsBySpellId(data)
|
||||
local color = profit and Theme.GetFeedbackColor(profit >= 0 and "GREEN" or "RED")
|
||||
return profit and Money.ToString(profit, color:GetTextColorPrefix()) or ""
|
||||
end
|
||||
|
||||
function private.GetProfitPctCellText(self, data, currentTitleIndex)
|
||||
if not self._isSpellId[data] then
|
||||
return ""
|
||||
end
|
||||
local craftingCost, _, profit = TSM.Crafting.Cost.GetCostsBySpellId(data)
|
||||
local color = profit and Theme.GetFeedbackColor(profit >= 0 and "GREEN" or "RED")
|
||||
return profit and color:ColorText(floor(profit * 100 / craftingCost).."%") or ""
|
||||
end
|
||||
|
||||
function private.GetSaleRateCellText(self, data)
|
||||
return self._isSpellId[data] and TSM.Crafting.Cost.GetSaleRateBySpellId(data) or ""
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.RowOnClick(row, mouseButton)
|
||||
local scrollingTable = row._scrollingTable
|
||||
local data = row:GetData()
|
||||
if mouseButton == "LeftButton" then
|
||||
if scrollingTable._isSpellId[data] then
|
||||
scrollingTable:SetSelection(data)
|
||||
else
|
||||
scrollingTable:_ToggleCollapsed(data)
|
||||
end
|
||||
scrollingTable:UpdateData(true)
|
||||
|
||||
if scrollingTable._isSpellId[data] then
|
||||
row:SetHighlightState("selectedHover")
|
||||
else
|
||||
row:SetHighlightState("hover")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function private.RankOnEnter(row)
|
||||
local data = row:GetData()
|
||||
local rank = row._scrollingTable._isSpellId[data] and TSM.Crafting.ProfessionScanner.GetRankBySpellId(data) or -1
|
||||
if rank > 0 and not TSM.IsWowClassic() then
|
||||
assert(not Tooltip.IsVisible())
|
||||
GameTooltip:SetOwner(row._buttons.rank, "ANCHOR_PRESERVE")
|
||||
GameTooltip:ClearAllPoints()
|
||||
GameTooltip:SetPoint("LEFT", row._buttons.rank, "RIGHT")
|
||||
GameTooltip:SetRecipeRankInfo(data, rank)
|
||||
GameTooltip:Show()
|
||||
end
|
||||
row._frame:GetScript("OnEnter")(row._frame)
|
||||
end
|
||||
|
||||
function private.RankOnLeave(row)
|
||||
Tooltip.Hide()
|
||||
row._frame:GetScript("OnLeave")(row._frame)
|
||||
end
|
||||
|
||||
function private.IsPlayerProfession()
|
||||
return not (TSM.Crafting.ProfessionUtil.IsNPCProfession() or TSM.Crafting.ProfessionUtil.IsLinkedProfession() or TSM.Crafting.ProfessionUtil.IsGuildProfession())
|
||||
end
|
||||
|
||||
function private.OnChatMsgSkill(_, msg)
|
||||
if not strmatch(msg, TSM.Crafting.ProfessionUtil.GetCurrentProfessionName()) then
|
||||
return
|
||||
end
|
||||
for self in pairs(private.activeElements) do
|
||||
wipe(private.categoryInfoCache.numIndents)
|
||||
self:_ForceLastDataUpdate()
|
||||
self:_UpdateData(true)
|
||||
end
|
||||
end
|
||||
168
Core/UI/Elements/ProgressBar.lua
Normal file
168
Core/UI/Elements/ProgressBar.lua
Normal file
@@ -0,0 +1,168 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- ProgressBar UI Element Class.
|
||||
-- The progress bar element is a left-to-right progress bar with an anaimated progress indicator and text. It is a
|
||||
-- subclass of the @{Text} class.
|
||||
-- @classmod ProgressBar
|
||||
|
||||
local _, TSM = ...
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local ProgressBar = TSM.Include("LibTSMClass").DefineClass("ProgressBar", TSM.UI.Text)
|
||||
UIElements.Register(ProgressBar)
|
||||
TSM.UI.ProgressBar = ProgressBar
|
||||
local PROGRESS_PADDING = 2
|
||||
local PROGRESS_ICON_PADDING = 4
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function ProgressBar.__init(self)
|
||||
local frame = UIElements.CreateFrame(self, "Frame")
|
||||
|
||||
self.__super:__init(frame)
|
||||
|
||||
self._bgLeft = frame:CreateTexture(nil, "BACKGROUND")
|
||||
self._bgLeft:SetPoint("TOPLEFT")
|
||||
self._bgLeft:SetPoint("BOTTOMLEFT")
|
||||
TSM.UI.TexturePacks.SetTextureAndWidth(self._bgLeft, "uiFrames.LoadingBarLeft")
|
||||
|
||||
self._bgRight = frame:CreateTexture(nil, "BACKGROUND")
|
||||
self._bgRight:SetPoint("TOPRIGHT")
|
||||
self._bgRight:SetPoint("BOTTOMRIGHT")
|
||||
TSM.UI.TexturePacks.SetTextureAndWidth(self._bgRight, "uiFrames.LoadingBarRight")
|
||||
|
||||
self._bgMiddle = frame:CreateTexture(nil, "BACKGROUND")
|
||||
self._bgMiddle:SetPoint("TOPLEFT", self._bgLeft, "TOPRIGHT")
|
||||
self._bgMiddle:SetPoint("BOTTOMRIGHT", self._bgRight, "BOTTOMLEFT")
|
||||
TSM.UI.TexturePacks.SetTexture(self._bgMiddle, "uiFrames.LoadingBarMiddle")
|
||||
|
||||
-- create the progress textures
|
||||
self._progressLeft = frame:CreateTexture(nil, "ARTWORK")
|
||||
self._progressLeft:SetPoint("TOPLEFT", PROGRESS_PADDING, -PROGRESS_PADDING)
|
||||
self._progressLeft:SetPoint("BOTTOMLEFT", PROGRESS_PADDING, PROGRESS_PADDING)
|
||||
self._progressLeft:SetBlendMode("BLEND")
|
||||
TSM.UI.TexturePacks.SetTexture(self._progressLeft, "uiFrames.LoadingBarLeft")
|
||||
|
||||
self._progressMiddle = frame:CreateTexture(nil, "ARTWORK")
|
||||
self._progressMiddle:SetPoint("TOPLEFT", self._progressLeft, "TOPRIGHT")
|
||||
self._progressMiddle:SetPoint("BOTTOMLEFT", self._progressLeft, "BOTTOMRIGHT")
|
||||
self._progressMiddle:SetBlendMode("BLEND")
|
||||
TSM.UI.TexturePacks.SetTexture(self._progressMiddle, "uiFrames.LoadingBarMiddle")
|
||||
|
||||
self._progressRight = frame:CreateTexture(nil, "ARTWORK")
|
||||
self._progressRight:SetPoint("TOPLEFT", self._progressMiddle, "TOPRIGHT")
|
||||
self._progressRight:SetPoint("BOTTOMLEFT", self._progressMiddle, "BOTTOMRIGHT")
|
||||
self._progressRight:SetBlendMode("BLEND")
|
||||
TSM.UI.TexturePacks.SetTexture(self._progressRight, "uiFrames.LoadingBarRight")
|
||||
|
||||
-- create the progress icon
|
||||
frame.progressIcon = frame:CreateTexture(nil, "OVERLAY")
|
||||
frame.progressIcon:SetPoint("RIGHT", frame.text, "LEFT", -PROGRESS_ICON_PADDING, 0)
|
||||
frame.progressIcon:Hide()
|
||||
|
||||
frame.progressIcon.ag = frame.progressIcon:CreateAnimationGroup()
|
||||
local spin = frame.progressIcon.ag:CreateAnimation("Rotation")
|
||||
spin:SetDuration(2)
|
||||
spin:SetDegrees(360)
|
||||
frame.progressIcon.ag:SetLooping("REPEAT")
|
||||
|
||||
self._progress = 0
|
||||
self._progressIconHidden = false
|
||||
self._justifyH = "CENTER"
|
||||
self._font = "BODY_BODY2_MEDIUM"
|
||||
self._textColor = "INDICATOR"
|
||||
end
|
||||
|
||||
function ProgressBar.Release(self)
|
||||
self._progress = 0
|
||||
self._progressIconHidden = false
|
||||
self:_GetBaseFrame().progressIcon.ag:Stop()
|
||||
self:_GetBaseFrame().progressIcon:Hide()
|
||||
self.__super:Release()
|
||||
self._justifyH = "CENTER"
|
||||
self._font = "BODY_BODY2_MEDIUM"
|
||||
self._textColor = "INDICATOR"
|
||||
end
|
||||
|
||||
--- Sets the progress.
|
||||
-- @tparam ProgressBar self The progress bar object
|
||||
-- @tparam number progress The progress from a value of 0 to 1 (inclusive)
|
||||
-- @tparam boolean isDone Whether or not the progress is finished
|
||||
-- @treturn ProgressBar The progress bar object
|
||||
function ProgressBar.SetProgress(self, progress, isDone)
|
||||
self._progress = progress
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets whether or not the progress indicator is hidden.
|
||||
-- @tparam ProgressBar self The progress bar object
|
||||
-- @tparam boolean hidden Whether or not the progress indicator is hidden
|
||||
-- @treturn ProgressBar The progress bar object
|
||||
function ProgressBar.SetProgressIconHidden(self, hidden)
|
||||
self._progressIconHidden = hidden
|
||||
return self
|
||||
end
|
||||
|
||||
function ProgressBar.Draw(self)
|
||||
self.__super:Draw()
|
||||
local frame = self:_GetBaseFrame()
|
||||
|
||||
self._bgLeft:SetVertexColor(Theme.GetColor("PRIMARY_BG"):GetFractionalRGBA())
|
||||
self._bgMiddle:SetVertexColor(Theme.GetColor("PRIMARY_BG"):GetFractionalRGBA())
|
||||
self._bgRight:SetVertexColor(Theme.GetColor("PRIMARY_BG"):GetFractionalRGBA())
|
||||
|
||||
local text = frame.text
|
||||
text:ClearAllPoints()
|
||||
text:SetWidth(self:_GetDimension("WIDTH"))
|
||||
text:SetWidth(frame.text:GetStringWidth())
|
||||
text:SetHeight(self:_GetDimension("HEIGHT"))
|
||||
text:SetPoint("CENTER", self._progressIconHidden and 0 or ((TSM.UI.TexturePacks.GetWidth("iconPack.18x18/Running") + PROGRESS_ICON_PADDING) / 2), 0)
|
||||
|
||||
TSM.UI.TexturePacks.SetTextureAndSize(frame.progressIcon, "iconPack.18x18/Running")
|
||||
frame.progressIcon:SetVertexColor(self:_GetTextColor():GetFractionalRGBA())
|
||||
if self._progressIconHidden and frame.progressIcon:IsVisible() then
|
||||
frame.progressIcon.ag:Stop()
|
||||
frame.progressIcon:Hide()
|
||||
elseif not self._progressIconHidden and not frame.progressIcon:IsVisible() then
|
||||
frame.progressIcon:Show()
|
||||
frame.progressIcon.ag:Play()
|
||||
end
|
||||
|
||||
if self._progress == 0 then
|
||||
self._progressLeft:Hide()
|
||||
self._progressMiddle:Hide()
|
||||
self._progressRight:Hide()
|
||||
else
|
||||
self._progressLeft:Show()
|
||||
self._progressMiddle:Show()
|
||||
self._progressRight:Show()
|
||||
self._progressLeft:SetVertexColor(Theme.GetColor("ACTIVE_BG"):GetFractionalRGBA())
|
||||
self._progressMiddle:SetVertexColor(Theme.GetColor("ACTIVE_BG"):GetFractionalRGBA())
|
||||
self._progressRight:SetVertexColor(Theme.GetColor("ACTIVE_BG"):GetFractionalRGBA())
|
||||
local leftTextureWidth = TSM.UI.TexturePacks.GetWidth("uiFrames.LoadingBarLeft")
|
||||
local rightTextureWidth = TSM.UI.TexturePacks.GetWidth("uiFrames.LoadingBarRight")
|
||||
local maxProgressWidth = self:_GetDimension("WIDTH") - PROGRESS_PADDING * 2
|
||||
local progressWidth = maxProgressWidth * self._progress
|
||||
if progressWidth <= leftTextureWidth then
|
||||
self._progressLeft:SetWidth(progressWidth)
|
||||
self._progressMiddle:Hide()
|
||||
self._progressRight:Hide()
|
||||
elseif progressWidth < maxProgressWidth - rightTextureWidth then
|
||||
self._progressLeft:SetWidth(leftTextureWidth)
|
||||
self._progressMiddle:SetWidth(progressWidth - leftTextureWidth)
|
||||
self._progressRight:Hide()
|
||||
else
|
||||
self._progressLeft:SetWidth(leftTextureWidth)
|
||||
self._progressMiddle:SetWidth(progressWidth - leftTextureWidth - rightTextureWidth)
|
||||
self._progressRight:SetWidth(rightTextureWidth)
|
||||
end
|
||||
end
|
||||
end
|
||||
258
Core/UI/Elements/QueryScrollingTable.lua
Normal file
258
Core/UI/Elements/QueryScrollingTable.lua
Normal file
@@ -0,0 +1,258 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- Query Scrolling Table UI ScrollingTable Class.
|
||||
-- A query scrolling table contains a scrollable list of rows with a fixed set of columns. It is a subclass of the
|
||||
-- @{ScrollingTable} class.
|
||||
-- @classmod QueryScrollingTable
|
||||
|
||||
local _, TSM = ...
|
||||
local QueryScrollingTable = TSM.Include("LibTSMClass").DefineClass("QueryScrollingTable", TSM.UI.ScrollingTable)
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local Table = TSM.Include("Util.Table")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(QueryScrollingTable)
|
||||
TSM.UI.QueryScrollingTable = QueryScrollingTable
|
||||
local private = {}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function QueryScrollingTable.__init(self)
|
||||
self.__super:__init()
|
||||
self._query = nil
|
||||
self._sortCol = nil
|
||||
self._sortAscending = nil
|
||||
self._autoReleaseQuery = false
|
||||
end
|
||||
|
||||
function QueryScrollingTable.Release(self)
|
||||
if self._query then
|
||||
self._query:SetUpdateCallback()
|
||||
if self._autoReleaseQuery then
|
||||
self._query:Release()
|
||||
end
|
||||
self._query = nil
|
||||
end
|
||||
self._sortCol = nil
|
||||
self._sortAscending = nil
|
||||
self._autoReleaseQuery = false
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
--- Sets the @{DatabaseQuery} source for this table.
|
||||
-- This query is used to populate the entries in the query scrolling table.
|
||||
-- @tparam QueryScrollingTable self The query scrolling table object
|
||||
-- @tparam DatabaseQuery query The query object
|
||||
-- @tparam[opt=false] bool redraw Whether or not to redraw the scrolling table
|
||||
-- @treturn QueryScrollingTable The query scrolling table object
|
||||
function QueryScrollingTable.SetQuery(self, query, redraw)
|
||||
if query == self._query and not redraw then
|
||||
return self
|
||||
end
|
||||
if self._query then
|
||||
self._query:SetUpdateCallback()
|
||||
end
|
||||
self._query = query
|
||||
self._query:SetUpdateCallback(private.QueryUpdateCallback, self)
|
||||
|
||||
self:_UpdateSortFromQuery()
|
||||
self:_ForceLastDataUpdate()
|
||||
self:UpdateData(redraw)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets whether or not the @{DatabaseQuery} is automatically released.
|
||||
-- @tparam QueryScrollingTable self The query scrolling table object
|
||||
-- @tparam bool autoRelease Whether or not to auto-release the query
|
||||
-- @treturn QueryScrollingTable The query scrolling table object
|
||||
function QueryScrollingTable.SetAutoReleaseQuery(self, autoRelease)
|
||||
self._autoReleaseQuery = autoRelease
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the selected record.
|
||||
-- @tparam QueryScrollingTable self The query scrolling table object
|
||||
-- @param selection The selected record or nil to clear the selection
|
||||
-- @tparam[opt=false] bool redraw Whether or not to redraw the scrolling table
|
||||
-- @treturn QueryScrollingTable The query scrolling table object
|
||||
function QueryScrollingTable.SetSelection(self, selection, redraw)
|
||||
if selection == self._selection then
|
||||
return self
|
||||
elseif selection and self._selectionValidator and not self:_selectionValidator(self._query:GetResultRowByUUID(selection)) then
|
||||
return self
|
||||
end
|
||||
local index = nil
|
||||
if selection then
|
||||
index = Table.KeyByValue(self._data, selection)
|
||||
assert(index)
|
||||
end
|
||||
self:_IgnoreLastDataUpdate()
|
||||
self._selection = selection
|
||||
if selection then
|
||||
-- set the scroll so that the selection is visible if necessary
|
||||
local rowHeight = self._rowHeight
|
||||
local firstVisibleIndex = ceil(self._vScrollValue / rowHeight) + 1
|
||||
local lastVisibleIndex = floor((self._vScrollValue + self:_GetDimension("HEIGHT")) / rowHeight)
|
||||
if lastVisibleIndex > firstVisibleIndex and (index < firstVisibleIndex or index > lastVisibleIndex) then
|
||||
self:_OnScrollValueChanged(min((index - 1) * rowHeight, self:_GetMaxScroll()))
|
||||
end
|
||||
end
|
||||
for _, row in ipairs(self._rows) do
|
||||
if not row:IsMouseOver() and row:IsVisible() and row:GetData() ~= selection then
|
||||
row:SetHighlightState(nil)
|
||||
elseif row:IsMouseOver() and row:IsVisible() then
|
||||
row:SetHighlightState(row:GetData() == selection and "selectedHover" or "hover")
|
||||
elseif row:IsVisible() and row:GetData() == selection then
|
||||
row:SetHighlightState("selected")
|
||||
end
|
||||
end
|
||||
if self._onSelectionChangedHandler then
|
||||
self:_onSelectionChangedHandler()
|
||||
end
|
||||
if redraw then
|
||||
self:Draw()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Gets the currently selected row.
|
||||
-- @tparam QueryScrollingTable self The scrolling table object
|
||||
-- @return The selected row or nil if there's nothing selected
|
||||
function QueryScrollingTable.GetSelection(self)
|
||||
return self._selection and self._query:GetResultRowByUUID(self._selection) or nil
|
||||
end
|
||||
|
||||
--- Registers a script handler.
|
||||
-- @tparam QueryScrollingTable self The scrolling table object
|
||||
-- @tparam string script The script to register for (supported scripts: `OnDataUpdated`, `OnSelectionChanged`, `OnRowClick`)
|
||||
-- @tparam function handler The script handler which will be called with the scrolling table object followed by any
|
||||
-- arguments to the script
|
||||
-- @treturn QueryScrollingTable The scrolling table object
|
||||
function QueryScrollingTable.SetScript(self, script, handler)
|
||||
if script == "OnDataUpdated" then
|
||||
self._onDataUpdated = handler
|
||||
else
|
||||
self.__super:SetScript(script, handler)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function QueryScrollingTable.Draw(self)
|
||||
self._query:SetUpdatesPaused(true)
|
||||
if self._lastDataUpdate == nil then
|
||||
self:_IgnoreLastDataUpdate()
|
||||
end
|
||||
self.__super:Draw()
|
||||
self._header:SetSort(self._sortCol, self._sortAscending)
|
||||
self._query:SetUpdatesPaused(false)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function QueryScrollingTable._CreateScrollingTableInfo(self)
|
||||
return TSM.UI.Util.QueryScrollingTableInfo()
|
||||
end
|
||||
|
||||
function QueryScrollingTable._UpdateSortFromQuery(self)
|
||||
if self._tableInfo:_IsSortEnabled() then
|
||||
local sortField, sortAscending = self._query:GetLastOrderBy()
|
||||
if sortField then
|
||||
self._sortCol = self._tableInfo:_GetIdBySortField(sortField)
|
||||
self._sortAscending = sortAscending
|
||||
else
|
||||
self._sortCol = nil
|
||||
self._sortAscending = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function QueryScrollingTable._UpdateData(self)
|
||||
-- we need to fix up the data within the rows updated to avoid errors with trying to access old DatabaseQueryResultRows
|
||||
local prevRowIndex = TempTable.Acquire()
|
||||
local newRowData = TempTable.Acquire()
|
||||
for i, row in ipairs(self._rows) do
|
||||
if row:IsVisible() then
|
||||
prevRowIndex[row:GetData()] = i
|
||||
end
|
||||
end
|
||||
local prevSelection = self._selection
|
||||
wipe(self._data)
|
||||
self._selection = nil
|
||||
for _, uuid in self._query:UUIDIterator() do
|
||||
if uuid == prevSelection then
|
||||
self._selection = uuid
|
||||
end
|
||||
if prevRowIndex[uuid] then
|
||||
newRowData[prevRowIndex[uuid]] = uuid
|
||||
end
|
||||
tinsert(self._data, uuid)
|
||||
end
|
||||
for i, row in ipairs(self._rows) do
|
||||
if row:IsVisible() then
|
||||
if newRowData[i] then
|
||||
row:SetData(newRowData[i])
|
||||
else
|
||||
row:ClearData()
|
||||
end
|
||||
end
|
||||
end
|
||||
TempTable.Release(prevRowIndex)
|
||||
TempTable.Release(newRowData)
|
||||
if prevSelection and not self._selection then
|
||||
-- select the first row since we weren't able to find the previously-selected row
|
||||
self:SetSelection(self._data[1])
|
||||
end
|
||||
if self._onDataUpdated then
|
||||
self:_onDataUpdated()
|
||||
end
|
||||
end
|
||||
|
||||
function QueryScrollingTable._ToggleSort(self, id)
|
||||
local sortField = self._tableInfo:_GetSortFieldById(id)
|
||||
if not self._sortCol or not self._query or not sortField then
|
||||
-- sorting disabled so ignore
|
||||
return
|
||||
end
|
||||
|
||||
if id == self._sortCol then
|
||||
self._sortAscending = not self._sortAscending
|
||||
else
|
||||
self._sortCol = id
|
||||
self._sortAscending = true
|
||||
end
|
||||
|
||||
self._query:UpdateLastOrderBy(sortField, self._sortAscending)
|
||||
self:_UpdateData()
|
||||
self:Draw()
|
||||
end
|
||||
|
||||
function QueryScrollingTable._HandleRowClick(self, uuid, mouseButton)
|
||||
if self._onRowClickHandler then
|
||||
self:_onRowClickHandler(self._query:GetResultRowByUUID(uuid), mouseButton)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.QueryUpdateCallback(_, uuid, self)
|
||||
self:_SetLastDataUpdate(uuid)
|
||||
if not uuid then
|
||||
self:_UpdateData()
|
||||
end
|
||||
self:Draw()
|
||||
end
|
||||
237
Core/UI/Elements/ScrollFrame.lua
Normal file
237
Core/UI/Elements/ScrollFrame.lua
Normal file
@@ -0,0 +1,237 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- ScrollFrame UI Element Class.
|
||||
-- A scroll frame is a container which allows the content to be of unlimited (but fixed/static) height within a
|
||||
-- scrollable window. It is a subclass of the @{Container} class.
|
||||
-- @classmod ScrollFrame
|
||||
|
||||
local _, TSM = ...
|
||||
local ScriptWrapper = TSM.Include("Util.ScriptWrapper")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local NineSlice = TSM.Include("Util.NineSlice")
|
||||
local ScrollFrame = TSM.Include("LibTSMClass").DefineClass("ScrollFrame", TSM.UI.Container)
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(ScrollFrame)
|
||||
TSM.UI.ScrollFrame = ScrollFrame
|
||||
local private = {}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function ScrollFrame.__init(self)
|
||||
local frame = UIElements.CreateFrame(self, "ScrollFrame")
|
||||
|
||||
self.__super:__init(frame)
|
||||
|
||||
self._backgroundNineSlice = NineSlice.New(frame, 1)
|
||||
self._backgroundNineSlice:Hide()
|
||||
|
||||
frame:EnableMouseWheel(true)
|
||||
frame:SetClipsChildren(true)
|
||||
ScriptWrapper.Set(frame, "OnUpdate", private.FrameOnUpdate, self)
|
||||
ScriptWrapper.Set(frame, "OnMouseWheel", private.FrameOnMouseWheel, self)
|
||||
|
||||
self._scrollbar = TSM.UI.Scrollbar.Create(frame)
|
||||
ScriptWrapper.Set(self._scrollbar, "OnValueChanged", private.OnScrollbarValueChanged, self)
|
||||
|
||||
self._content = CreateFrame("Frame", nil, frame)
|
||||
self._content:SetPoint("TOPLEFT")
|
||||
self._content:SetPoint("TOPRIGHT")
|
||||
frame:SetScrollChild(self._content)
|
||||
|
||||
self._scrollValue = 0
|
||||
self._onUpdateHandler = nil
|
||||
self._backgroundColor = nil
|
||||
end
|
||||
|
||||
function ScrollFrame.Acquire(self)
|
||||
self.__super:Acquire()
|
||||
self._scrollValue = 0
|
||||
self._scrollbar:SetValue(0)
|
||||
end
|
||||
|
||||
function ScrollFrame.Release(self)
|
||||
self._onUpdateHandler = nil
|
||||
self._backgroundColor = nil
|
||||
self._backgroundNineSlice:Hide()
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
--- Sets the background of the scroll frame.
|
||||
-- @tparam ScrollFrame self The scroll frame object
|
||||
-- @tparam ?string|nil color The background color as a theme color key or nil
|
||||
-- @treturn ScrollFrame The scroll frame object
|
||||
function ScrollFrame.SetBackgroundColor(self, color)
|
||||
assert(color == nil or Theme.GetColor(color))
|
||||
self._backgroundColor = color
|
||||
return self
|
||||
end
|
||||
|
||||
function ScrollFrame.SetScript(self, script, handler)
|
||||
if script == "OnUpdate" then
|
||||
self._onUpdateHandler = handler
|
||||
else
|
||||
self.__super:SetScript(script, handler)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function ScrollFrame.Draw(self)
|
||||
self.__super.__super:Draw()
|
||||
|
||||
if self._backgroundColor then
|
||||
self._backgroundNineSlice:SetStyle("solid")
|
||||
self._backgroundNineSlice:SetVertexColor(Theme.GetColor(self._backgroundColor):GetFractionalRGBA())
|
||||
else
|
||||
self._backgroundNineSlice:Hide()
|
||||
end
|
||||
|
||||
local width = self:_GetDimension("WIDTH")
|
||||
self._content:SetWidth(width)
|
||||
width = width - self:_GetPadding("LEFT") - self:_GetPadding("RIGHT")
|
||||
|
||||
local totalHeight = self:_GetPadding("TOP") + self:_GetPadding("BOTTOM")
|
||||
for _, child in self:LayoutChildrenIterator() do
|
||||
child:_GetBaseFrame():SetParent(self._content)
|
||||
child:_GetBaseFrame():ClearAllPoints()
|
||||
|
||||
-- set the height
|
||||
local childHeight, childHeightCanExpand = child:_GetMinimumDimension("HEIGHT")
|
||||
assert(not childHeightCanExpand, "Invalid height for child: "..tostring(child._id))
|
||||
child:_SetDimension("HEIGHT", childHeight)
|
||||
totalHeight = totalHeight + childHeight + child:_GetMargin("TOP") + child:_GetMargin("BOTTOM")
|
||||
|
||||
-- set the width
|
||||
local childWidth, childWidthCanExpand = child:_GetMinimumDimension("WIDTH")
|
||||
if childWidthCanExpand then
|
||||
childWidth = max(childWidth, width - child:_GetMargin("LEFT") - child:_GetMargin("RIGHT"))
|
||||
end
|
||||
child:_SetDimension("WIDTH", childWidth)
|
||||
end
|
||||
self._content:SetHeight(totalHeight)
|
||||
local maxScroll = self:_GetMaxScroll()
|
||||
self._scrollbar:SetMinMaxValues(0, maxScroll)
|
||||
self._scrollbar:SetValue(min(self._scrollValue, maxScroll))
|
||||
self._scrollbar.thumb:SetHeight(TSM.UI.Scrollbar.GetLength(totalHeight, self:_GetDimension("HEIGHT")))
|
||||
|
||||
local yOffset = -1 * self:_GetPadding("TOP")
|
||||
for _, child in self:LayoutChildrenIterator() do
|
||||
local childFrame = child:_GetBaseFrame()
|
||||
yOffset = yOffset - child:_GetMargin("TOP")
|
||||
childFrame:SetPoint("TOPLEFT", child:_GetMargin("LEFT") + self:_GetPadding("LEFT"), yOffset)
|
||||
yOffset = yOffset - childFrame:GetHeight() - child:_GetMargin("BOTTOM")
|
||||
end
|
||||
|
||||
self.__super:Draw()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function ScrollFrame._OnScrollValueChanged(self, value)
|
||||
self:_GetBaseFrame():SetVerticalScroll(value)
|
||||
self._scrollValue = value
|
||||
end
|
||||
|
||||
function ScrollFrame._GetMaxScroll(self)
|
||||
return max(self._content:GetHeight() - self:_GetDimension("HEIGHT"), 0)
|
||||
end
|
||||
|
||||
function ScrollFrame._GetMinimumDimension(self, dimension)
|
||||
local styleResult = nil
|
||||
if dimension == "WIDTH" then
|
||||
styleResult = self._width
|
||||
elseif dimension == "HEIGHT" then
|
||||
styleResult = self._height
|
||||
else
|
||||
error("Invalid dimension: "..tostring(dimension))
|
||||
end
|
||||
if styleResult then
|
||||
return styleResult, false
|
||||
elseif dimension == "HEIGHT" or self:GetNumLayoutChildren() == 0 then
|
||||
-- regarding the first condition for this if statment, a scrollframe can be any height (including greater than
|
||||
-- the height of the content if no scrolling is needed), so has no minimum and can always expand
|
||||
return 0, true
|
||||
else
|
||||
-- we're trying to determine the width based on the max width of any of the children
|
||||
local result = 0
|
||||
local canExpand = false
|
||||
for _, child in self:LayoutChildrenIterator() do
|
||||
local childMin, childCanExpand = child:_GetMinimumDimension(dimension)
|
||||
childMin = childMin + child:_GetMargin("LEFT") + child:_GetMargin("RIGHT")
|
||||
canExpand = canExpand or childCanExpand
|
||||
result = max(result, childMin)
|
||||
end
|
||||
result = result + self:_GetPadding("LEFT") + self:_GetPadding("RIGHT")
|
||||
return result, canExpand
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.OnScrollbarValueChanged(self, value)
|
||||
value = max(min(value, self:_GetMaxScroll()), 0)
|
||||
self:_OnScrollValueChanged(value)
|
||||
end
|
||||
|
||||
function private.FrameOnUpdate(self)
|
||||
if (self:_GetBaseFrame():IsMouseOver() and self:_GetMaxScroll() > 0) or self._scrollbar.dragging then
|
||||
self._scrollbar:Show()
|
||||
else
|
||||
self._scrollbar:Hide()
|
||||
end
|
||||
if self._onUpdateHandler then
|
||||
self:_onUpdateHandler()
|
||||
end
|
||||
end
|
||||
|
||||
function private.FrameOnMouseWheel(self, direction)
|
||||
local parentScroll = nil
|
||||
local parent = self:GetParentElement()
|
||||
while parent do
|
||||
if parent:__isa(ScrollFrame) then
|
||||
parentScroll = parent
|
||||
break
|
||||
else
|
||||
parent = parent:GetParentElement()
|
||||
end
|
||||
end
|
||||
|
||||
if parentScroll then
|
||||
local minValue, maxValue = self._scrollbar:GetMinMaxValues()
|
||||
if direction > 0 then
|
||||
if self._scrollbar:GetValue() == minValue then
|
||||
local scrollAmount = min(parentScroll:_GetDimension("HEIGHT") / 3, Theme.GetMouseWheelScrollAmount())
|
||||
parentScroll._scrollbar:SetValue(parentScroll._scrollbar:GetValue() + -1 * direction * scrollAmount)
|
||||
else
|
||||
local scrollAmount = min(self:_GetDimension("HEIGHT") / 3, Theme.GetMouseWheelScrollAmount())
|
||||
self._scrollbar:SetValue(self._scrollbar:GetValue() + -1 * direction * scrollAmount)
|
||||
end
|
||||
else
|
||||
if self._scrollbar:GetValue() == maxValue then
|
||||
local scrollAmount = min(parentScroll:_GetDimension("HEIGHT") / 3, Theme.GetMouseWheelScrollAmount())
|
||||
parentScroll._scrollbar:SetValue(parentScroll._scrollbar:GetValue() + -1 * direction * scrollAmount)
|
||||
else
|
||||
local scrollAmount = min(self:_GetDimension("HEIGHT") / 3, Theme.GetMouseWheelScrollAmount())
|
||||
self._scrollbar:SetValue(self._scrollbar:GetValue() + -1 * direction * scrollAmount)
|
||||
end
|
||||
end
|
||||
else
|
||||
local scrollAmount = min(self:_GetDimension("HEIGHT") / 3, Theme.GetMouseWheelScrollAmount())
|
||||
self._scrollbar:SetValue(self._scrollbar:GetValue() + -1 * direction * scrollAmount)
|
||||
end
|
||||
end
|
||||
741
Core/UI/Elements/ScrollingTable.lua
Normal file
741
Core/UI/Elements/ScrollingTable.lua
Normal file
@@ -0,0 +1,741 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- Scrolling Table UI Element Class.
|
||||
-- A scrolling table contains a scrollable list of rows with a fixed set of columns. It is a subclass of the @{Element}
|
||||
-- class.
|
||||
-- @classmod ScrollingTable
|
||||
|
||||
local _, TSM = ...
|
||||
local ObjectPool = TSM.Include("Util.ObjectPool")
|
||||
local Table = TSM.Include("Util.Table")
|
||||
local Math = TSM.Include("Util.Math")
|
||||
local ScriptWrapper = TSM.Include("Util.ScriptWrapper")
|
||||
local Color = TSM.Include("Util.Color")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local ScrollingTable = TSM.Include("LibTSMClass").DefineClass("ScrollingTable", TSM.UI.Element, "ABSTRACT")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(ScrollingTable)
|
||||
TSM.UI.ScrollingTable = ScrollingTable
|
||||
local private = {
|
||||
rowPool = ObjectPool.New("TABLE_ROWS", TSM.UI.Util.TableRow, 1),
|
||||
}
|
||||
local HEADER_HEIGHT = 22
|
||||
local HEADER_LINE_HEIGHT = 2
|
||||
local MORE_COL_WIDTH = 8
|
||||
local FORCE_DATA_UPDATE = newproxy()
|
||||
local IGNORE_DATA_UPDATE = newproxy()
|
||||
local SCROLL_TO_DATA_TOTAL_TIME_S = 0.1
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Meta Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function ScrollingTable.__init(self)
|
||||
local frame = UIElements.CreateFrame(self, "Frame", nil, nil, TSM.IsShadowlands() and "BackdropTemplate" or nil)
|
||||
self.__super:__init(frame)
|
||||
|
||||
frame:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8" })
|
||||
|
||||
self._lineTop = frame:CreateTexture(nil, "ARTWORK")
|
||||
self._lineTop:SetPoint("TOPLEFT")
|
||||
self._lineTop:SetPoint("TOPRIGHT")
|
||||
self._lineTop:SetHeight(HEADER_LINE_HEIGHT)
|
||||
self._lineTop:SetColorTexture(Theme.GetColor("ACTIVE_BG"):GetFractionalRGBA())
|
||||
|
||||
self._lineBottom = frame:CreateTexture(nil, "ARTWORK")
|
||||
self._lineBottom:SetPoint("TOPLEFT", 0, -HEADER_HEIGHT - HEADER_LINE_HEIGHT)
|
||||
self._lineBottom:SetPoint("TOPRIGHT", 0, -HEADER_HEIGHT - HEADER_LINE_HEIGHT)
|
||||
self._lineBottom:SetHeight(HEADER_LINE_HEIGHT)
|
||||
self._lineBottom:SetColorTexture(Theme.GetColor("ACTIVE_BG"):GetFractionalRGBA())
|
||||
|
||||
self._hScrollFrame = CreateFrame("ScrollFrame", nil, frame)
|
||||
self._hScrollFrame:SetPoint("TOPLEFT")
|
||||
self._hScrollFrame:SetPoint("BOTTOMRIGHT")
|
||||
self._hScrollFrame:EnableMouseWheel(true)
|
||||
self._hScrollFrame:SetClipsChildren(true)
|
||||
ScriptWrapper.Set(self._hScrollFrame, "OnUpdate", private.HScrollFrameOnUpdate, self)
|
||||
ScriptWrapper.Set(self._hScrollFrame, "OnMouseWheel", private.FrameOnMouseWheel, self)
|
||||
|
||||
self._hContent = CreateFrame("Frame", nil, self._hScrollFrame)
|
||||
self._hContent:SetPoint("TOPLEFT")
|
||||
self._hScrollFrame:SetScrollChild(self._hContent)
|
||||
|
||||
self._vScrollFrame = CreateFrame("ScrollFrame", nil, self._hContent)
|
||||
self._vScrollFrame:SetPoint("TOPLEFT")
|
||||
self._vScrollFrame:SetPoint("BOTTOMRIGHT")
|
||||
self._vScrollFrame:EnableMouseWheel(true)
|
||||
self._vScrollFrame:SetClipsChildren(true)
|
||||
ScriptWrapper.Set(self._vScrollFrame, "OnUpdate", private.VScrollFrameOnUpdate, self)
|
||||
ScriptWrapper.Set(self._vScrollFrame, "OnMouseWheel", private.FrameOnMouseWheel, self)
|
||||
|
||||
self._content = CreateFrame("Frame", nil, self._vScrollFrame)
|
||||
self._content:SetPoint("TOPLEFT")
|
||||
self._vScrollFrame:SetScrollChild(self._content)
|
||||
|
||||
self._hScrollbar = TSM.UI.Scrollbar.Create(frame, true)
|
||||
self._vScrollbar = TSM.UI.Scrollbar.Create(frame)
|
||||
|
||||
self._rowHeight = 20
|
||||
self._backgroundColor = "PRIMARY_BG"
|
||||
self._rows = {}
|
||||
self._data = {}
|
||||
self._hScrollValue = 0
|
||||
self._vScrollValue = 0
|
||||
self._onSelectionChangedHandler = nil
|
||||
self._onRowClickHandler = nil
|
||||
self._selection = nil
|
||||
self._selectionDisabled = nil
|
||||
self._selectionValidator = nil
|
||||
self._tableInfo = self:_CreateScrollingTableInfo()
|
||||
self._header = nil
|
||||
self._dataTranslationFunc = nil
|
||||
self._contextTable = nil
|
||||
self._defaultContextTable = nil
|
||||
self._prevDataOffset = nil
|
||||
self._lastDataUpdate = nil
|
||||
self._rowHoverEnabled = true
|
||||
self._headerHidden = false
|
||||
self._targetScrollValue = nil
|
||||
self._totalScrollDistance = nil
|
||||
self._rightClickToggle = nil
|
||||
|
||||
Theme.RegisterChangeCallback(function()
|
||||
if self:IsVisible() and self._header then
|
||||
self._header:_LayoutHeaderRow()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function ScrollingTable.Acquire(self)
|
||||
self.__super:Acquire()
|
||||
self._tableInfo:_Acquire(self)
|
||||
self._hScrollFrame:SetHorizontalScroll(0)
|
||||
self._hScrollValue = 0
|
||||
self._vScrollValue = 0
|
||||
|
||||
ScriptWrapper.Set(self._vScrollbar, "OnValueChanged", private.OnVScrollbarValueChangedNoDraw, self)
|
||||
-- don't want to cause this element to be drawn for this initial scrollbar change
|
||||
self._vScrollbar:SetValue(0)
|
||||
ScriptWrapper.Set(self._vScrollbar, "OnValueChanged", private.OnVScrollbarValueChanged, self)
|
||||
|
||||
ScriptWrapper.Set(self._hScrollbar, "OnValueChanged", private.OnHScrollbarValueChangedNoDraw, self)
|
||||
-- don't want to cause this element to be drawn for this initial scrollbar change
|
||||
self._hScrollbar:SetValue(0)
|
||||
ScriptWrapper.Set(self._hScrollbar, "OnValueChanged", private.OnHScrollbarValueChanged, self)
|
||||
end
|
||||
|
||||
function ScrollingTable.Release(self)
|
||||
self._rowHeight = 20
|
||||
self._backgroundColor = "PRIMARY_BG"
|
||||
self._onSelectionChangedHandler = nil
|
||||
self._onRowClickHandler = nil
|
||||
self._selection = nil
|
||||
self._selectionDisabled = nil
|
||||
self._selectionValidator = nil
|
||||
self._dataTranslationFunc = nil
|
||||
self._contextTable = nil
|
||||
self._defaultContextTable = nil
|
||||
self._prevDataOffset = nil
|
||||
self._lastDataUpdate = nil
|
||||
if self._header then
|
||||
self._header:Release()
|
||||
private.rowPool:Recycle(self._header)
|
||||
self._header = nil
|
||||
end
|
||||
for _, row in ipairs(self._rows) do
|
||||
row:Release()
|
||||
private.rowPool:Recycle(row)
|
||||
end
|
||||
wipe(self._rows)
|
||||
self._tableInfo:_Release()
|
||||
wipe(self._data)
|
||||
self._headerHidden = false
|
||||
self._targetScrollValue = nil
|
||||
self._totalScrollDistance = nil
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
--- Sets the background of the scrolling table.
|
||||
-- @tparam ScrollingTable self The scrolling table object
|
||||
-- @tparam number rowHeight The row height
|
||||
-- @treturn ScrollingTable The scrolling table object
|
||||
function ScrollingTable.SetRowHeight(self, rowHeight)
|
||||
self._rowHeight = rowHeight
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the background of the scrolling table.
|
||||
-- @tparam ScrollingTable self The scrolling table object
|
||||
-- @tparam boolean hidden Whether or not the header should be hidden
|
||||
-- @treturn ScrollingTable The scrolling table object
|
||||
function ScrollingTable.SetHeaderHidden(self, hidden)
|
||||
self._headerHidden = hidden
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the background of the scrolling table.
|
||||
-- @tparam ScrollingTable self The scrolling table object
|
||||
-- @tparam string color The background color as a theme color key
|
||||
-- @treturn ScrollingTable The scrolling table object
|
||||
function ScrollingTable.SetBackgroundColor(self, color)
|
||||
assert(Theme.GetColor(color))
|
||||
self._backgroundColor = color
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the context table.
|
||||
-- This table can be used to preserve the table configuration across lifecycles of the scrolling table and even WoW
|
||||
-- sessions if it's within the settings DB.
|
||||
-- @tparam ScrollingTable self The scrolling table object
|
||||
-- @tparam table tbl The context table
|
||||
-- @tparam table defaultTbl The default table (required fields: `colWidth`, `colHidden`)
|
||||
-- @treturn ScrollingTable The scrolling table object
|
||||
function ScrollingTable.SetContextTable(self, tbl, defaultTbl)
|
||||
assert(type(defaultTbl.colWidth) == "table" and type(defaultTbl.colHidden) == "table")
|
||||
tbl.colWidth = tbl.colWidth or CopyTable(defaultTbl.colWidth)
|
||||
tbl.colHidden = tbl.colHidden or CopyTable(defaultTbl.colHidden)
|
||||
self._contextTable = tbl
|
||||
self._defaultContextTable = defaultTbl
|
||||
self:_UpdateColsHidden()
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the context table from a settings object.
|
||||
-- @tparam ScrollingTable self The scrolling table object
|
||||
-- @tparam Settings settings The settings object
|
||||
-- @tparam string key The setting key
|
||||
-- @treturn ScrollingTable The scrolling table object
|
||||
function ScrollingTable.SetSettingsContext(self, settings, key)
|
||||
return self:SetContextTable(settings[key], settings:GetDefaultReadOnly(key))
|
||||
end
|
||||
|
||||
--- Forces an update of the data shown within the table.
|
||||
-- @tparam ScrollingTable self The scrolling table object
|
||||
-- @tparam[opt=false] bool redraw Whether or not to redraw the scrolling table
|
||||
-- @treturn ScrollingTable The scrolling table object
|
||||
function ScrollingTable.UpdateData(self, redraw)
|
||||
self:_ForceLastDataUpdate()
|
||||
self:_UpdateData()
|
||||
if redraw then
|
||||
self:Draw()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Gets the ScrollingTableInfo object.
|
||||
-- @tparam ScrollingTable self The scrolling table object
|
||||
-- @treturn ScrollingTableInfo The scrolling table info object
|
||||
function ScrollingTable.GetScrollingTableInfo(self)
|
||||
return self._tableInfo
|
||||
end
|
||||
|
||||
--- Commits the scrolling table info.
|
||||
-- This should be called once the scrolling table info is completely set (retrieved via @{ScrollingTable.GetScrollingTableInfo}).
|
||||
-- @tparam ScrollingTable self The scrolling table object
|
||||
-- @treturn ScrollingTable The scrolling table object
|
||||
function ScrollingTable.CommitTableInfo(self)
|
||||
self:_UpdateColsHidden()
|
||||
if self._header then
|
||||
self._header:Release()
|
||||
private.rowPool:Recycle(self._header)
|
||||
self._header = nil
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Registers a script handler.
|
||||
-- @tparam ScrollingTable self The scrolling table object
|
||||
-- @tparam string script The script to register for (supported scripts: `OnSelectionChanged`, `OnRowClick`)
|
||||
-- @tparam function handler The script handler which will be called with the scrolling table object followed by any
|
||||
-- arguments to the script
|
||||
-- @treturn ScrollingTable The scrolling table object
|
||||
function ScrollingTable.SetScript(self, script, handler)
|
||||
if script == "OnSelectionChanged" then
|
||||
self._onSelectionChangedHandler = handler
|
||||
elseif script == "OnRowClick" then
|
||||
self._onRowClickHandler = handler
|
||||
else
|
||||
error("Unknown ScrollingTable script: "..tostring(script))
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the selected row.
|
||||
-- @tparam ScrollingTable self The scrolling table object
|
||||
-- @param selection The selected row or nil to clear the selection
|
||||
-- @tparam[opt=false] boolean noDraw Don't redraw the rows
|
||||
-- @treturn ScrollingTable The scrolling table object
|
||||
function ScrollingTable.SetSelection(self, selection, noDraw)
|
||||
if selection == self._selection then
|
||||
self:_JumpToData(selection)
|
||||
return self
|
||||
elseif selection and self._selectionValidator and not self:_selectionValidator(selection) then
|
||||
return self
|
||||
end
|
||||
self:_IgnoreLastDataUpdate()
|
||||
self._selection = selection
|
||||
self:_JumpToData(selection)
|
||||
if not noDraw then
|
||||
for _, row in ipairs(self._rows) do
|
||||
if not row:IsMouseOver() and row:IsVisible() and not self:_IsSelected(row:GetData()) then
|
||||
row:SetHighlightState(nil)
|
||||
elseif row:IsMouseOver() and row:IsVisible() and not self:_IsSelected(row:GetData()) then
|
||||
row:SetHighlightState("hover")
|
||||
elseif row:IsMouseOver() and row:IsVisible() and self:_IsSelected(row:GetData()) then
|
||||
row:SetHighlightState(self._selectionDisabled and "hover" or "selectedHover")
|
||||
elseif row:IsVisible() and self:_IsSelected(row:GetData()) then
|
||||
row:SetHighlightState("selected")
|
||||
end
|
||||
end
|
||||
end
|
||||
if self._onSelectionChangedHandler then
|
||||
self:_onSelectionChangedHandler()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Gets the currently selected row.
|
||||
-- @tparam ScrollingTable self The scrolling table object
|
||||
-- @return The selected row or nil if there's nothing selected
|
||||
function ScrollingTable.GetSelection(self)
|
||||
return self._selection
|
||||
end
|
||||
|
||||
--- Sets a selection validator function.
|
||||
-- @tparam ScrollingTable self The scrolling table object
|
||||
-- @tparam function validator A function which gets called with the scrolling table object and a row to validate
|
||||
-- whether or not it's selectable (returns true if it is, false otherwise)
|
||||
-- @treturn ScrollingTable The scrolling table object
|
||||
function ScrollingTable.SetSelectionValidator(self, validator)
|
||||
self._selectionValidator = validator
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets whether or not selection is disabled.
|
||||
-- @tparam ScrollingTable self The scrolling table object
|
||||
-- @tparam boolean disabled Whether or not to disable selection
|
||||
-- @treturn ScrollingTable The scrolling table object
|
||||
function ScrollingTable.SetSelectionDisabled(self, disabled)
|
||||
self._selectionDisabled = disabled
|
||||
return self
|
||||
end
|
||||
|
||||
function ScrollingTable.Draw(self)
|
||||
self.__super:Draw()
|
||||
local frame = self:_GetBaseFrame()
|
||||
local background = Theme.GetColor(self._backgroundColor)
|
||||
frame:SetBackdropColor(background:GetFractionalRGBA())
|
||||
if self:_CanResizeCols() then
|
||||
self:_UpdateColsHidden()
|
||||
end
|
||||
|
||||
if not self._header then
|
||||
self._header = self:_GetTableRow(true)
|
||||
self._header:SetBackgroundColor(Theme.GetColor("FRAME_BG"))
|
||||
self._header:SetHeight(HEADER_HEIGHT)
|
||||
end
|
||||
|
||||
-- update the scrollbar layout
|
||||
if self._headerHidden then
|
||||
self._vScrollbar:SetPoint("TOPRIGHT", -Theme.GetScrollbarMargin(), -Theme.GetScrollbarMargin())
|
||||
else
|
||||
self._vScrollbar:SetPoint("TOPRIGHT", -Theme.GetScrollbarMargin(), -Theme.GetScrollbarMargin() - HEADER_HEIGHT - HEADER_LINE_HEIGHT * 2)
|
||||
end
|
||||
|
||||
local totalWidth = 0
|
||||
if self:_CanResizeCols() then
|
||||
-- add the "more" column
|
||||
totalWidth = totalWidth + MORE_COL_WIDTH + Theme.GetColSpacing()
|
||||
for colId, colWidth in pairs(self._contextTable.colWidth) do
|
||||
if not self._contextTable.colHidden[colId] then
|
||||
totalWidth = totalWidth + colWidth + Theme.GetColSpacing()
|
||||
end
|
||||
end
|
||||
end
|
||||
totalWidth = max(totalWidth, self:_GetDimension("WIDTH"))
|
||||
self._hContent:SetHeight(self._hScrollFrame:GetHeight())
|
||||
self._hContent:SetWidth(totalWidth)
|
||||
self._content:SetWidth(self._hContent:GetWidth())
|
||||
|
||||
local rowHeight = self._rowHeight
|
||||
local totalHeight = #self._data * rowHeight
|
||||
local visibleHeight = self._vScrollFrame:GetHeight()
|
||||
local visibleWidth = self._hScrollFrame:GetWidth()
|
||||
local numVisibleRows = min(ceil(visibleHeight / rowHeight), #self._data)
|
||||
local maxScroll = self:_GetMaxScroll()
|
||||
local vScrollOffset = min(self._vScrollValue, maxScroll)
|
||||
local hScrollOffset = min(self._hScrollValue, self:_GetMaxHScroll())
|
||||
local dataOffset = floor(vScrollOffset / rowHeight)
|
||||
|
||||
self._vScrollbar.thumb:SetHeight(TSM.UI.Scrollbar.GetLength(totalHeight, visibleHeight))
|
||||
self._vScrollbar:SetMinMaxValues(0, maxScroll)
|
||||
self._vScrollbar:SetValue(vScrollOffset)
|
||||
self._hScrollbar.thumb:SetWidth(TSM.UI.Scrollbar.GetLength(self._hContent:GetWidth(), visibleWidth))
|
||||
self._hScrollbar:SetMinMaxValues(0, self:_GetMaxHScroll())
|
||||
self._hScrollbar:SetValue(hScrollOffset)
|
||||
self._content:SetHeight(numVisibleRows * rowHeight)
|
||||
|
||||
if self._headerHidden then
|
||||
self._lineTop:Hide()
|
||||
self._lineBottom:Hide()
|
||||
self._header:SetHeight(0)
|
||||
self._header:SetBackgroundColor(Color.GetTransparent())
|
||||
self._vScrollFrame:SetPoint("TOPLEFT", 0, 0)
|
||||
else
|
||||
self._lineTop:SetColorTexture(Theme.GetColor("ACTIVE_BG"):GetFractionalRGBA())
|
||||
self._lineBottom:SetColorTexture(Theme.GetColor("ACTIVE_BG"):GetFractionalRGBA())
|
||||
self._lineTop:Show()
|
||||
self._lineBottom:Show()
|
||||
self._vScrollFrame:SetPoint("TOPLEFT", 0, -HEADER_HEIGHT - HEADER_LINE_HEIGHT * 2)
|
||||
self._header:SetBackgroundColor(Theme.GetColor("FRAME_BG"))
|
||||
self._header:SetHeight(HEADER_HEIGHT)
|
||||
end
|
||||
|
||||
if Math.Round(vScrollOffset + visibleHeight) == totalHeight then
|
||||
-- we are at the bottom
|
||||
self._vScrollFrame:SetVerticalScroll(numVisibleRows * rowHeight - visibleHeight)
|
||||
else
|
||||
self._vScrollFrame:SetVerticalScroll(0)
|
||||
end
|
||||
self._hScrollFrame:SetHorizontalScroll(hScrollOffset)
|
||||
|
||||
while #self._rows < numVisibleRows do
|
||||
local row = self:_GetTableRow(false)
|
||||
row._frame:SetPoint("TOPLEFT", 0, -rowHeight * #self._rows)
|
||||
row._frame:SetPoint("TOPRIGHT", 0, -rowHeight * #self._rows)
|
||||
tinsert(self._rows, row)
|
||||
end
|
||||
|
||||
local scrollDiff = dataOffset - (self._prevDataOffset or dataOffset)
|
||||
self._prevDataOffset = dataOffset
|
||||
if scrollDiff ~= 0 then
|
||||
-- Shuffle the rows around to accomplish the scrolling so that the data only changes
|
||||
-- for the minimal number of rows, which allows for better optimization
|
||||
for _ = 1, abs(scrollDiff) do
|
||||
if scrollDiff > 0 then
|
||||
tinsert(self._rows, tremove(self._rows, 1))
|
||||
else
|
||||
tinsert(self._rows, 1, tremove(self._rows))
|
||||
end
|
||||
end
|
||||
-- fix the points of all the rows
|
||||
for i, row in ipairs(self._rows) do
|
||||
row._frame:SetPoint("TOPLEFT", 0, -rowHeight * (i - 1))
|
||||
row._frame:SetPoint("TOPRIGHT", 0, -rowHeight * (i - 1))
|
||||
end
|
||||
end
|
||||
|
||||
for i, row in ipairs(self._rows) do
|
||||
local dataIndex = i + dataOffset
|
||||
local data = self._data[dataIndex]
|
||||
if i > numVisibleRows or not data then
|
||||
row:SetVisible(false)
|
||||
row:ClearData()
|
||||
else
|
||||
row:SetVisible(true)
|
||||
self:_SetRowData(row, data)
|
||||
row:SetBackgroundColor(background)
|
||||
row:SetHeight(rowHeight)
|
||||
end
|
||||
end
|
||||
self._lastDataUpdate = nil
|
||||
|
||||
self._header:SetHeaderData()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- ScrollingTable - Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function ScrollingTable._CreateScrollingTableInfo(self)
|
||||
return TSM.UI.Util.ScrollingTableInfo()
|
||||
end
|
||||
|
||||
function ScrollingTable._GetTableRow(self, isHeader)
|
||||
local row = private.rowPool:Get()
|
||||
row:Acquire(self, isHeader)
|
||||
return row
|
||||
end
|
||||
|
||||
function ScrollingTable._SetRowData(self, row, data)
|
||||
-- updating the row data is expensive, so only do it if necessary
|
||||
local dataUpdated = row:GetData() ~= data or not self._lastDataUpdate or self._lastDataUpdate == FORCE_DATA_UPDATE or self._lastDataUpdate == data
|
||||
local isMouseOver = row:IsMouseOver()
|
||||
local isSelected = self:_IsSelected(data)
|
||||
if not isMouseOver and isSelected then
|
||||
row:SetHighlightState("selected", dataUpdated)
|
||||
elseif isMouseOver and isSelected then
|
||||
row:SetHighlightState(self._selectionDisabled and "hover" or "selectedHover", dataUpdated)
|
||||
elseif isMouseOver and not isSelected then
|
||||
row:SetHighlightState("hover", dataUpdated)
|
||||
else
|
||||
row:SetHighlightState(nil, dataUpdated)
|
||||
end
|
||||
if dataUpdated then
|
||||
row:SetData(data)
|
||||
end
|
||||
end
|
||||
|
||||
function ScrollingTable._OnScrollValueChanged(self, value, noDraw)
|
||||
self._vScrollValue = value
|
||||
if not noDraw then
|
||||
self:Draw()
|
||||
end
|
||||
end
|
||||
|
||||
function ScrollingTable._OnHScrollValueChanged(self, value, noDraw)
|
||||
self._hScrollValue = value
|
||||
if not noDraw then
|
||||
self:Draw()
|
||||
end
|
||||
end
|
||||
|
||||
function ScrollingTable._GetMaxScroll(self)
|
||||
return max(#self._data * self._rowHeight - self._vScrollFrame:GetHeight(), 0)
|
||||
end
|
||||
|
||||
function ScrollingTable._GetMaxHScroll(self)
|
||||
return max(self._hContent:GetWidth() - self._hScrollFrame:GetWidth(), 0)
|
||||
end
|
||||
|
||||
function ScrollingTable._UpdateData(self)
|
||||
error("Must be implemented by the child class")
|
||||
end
|
||||
|
||||
function ScrollingTable._ToggleSort(self, id)
|
||||
error("Must be implemented by the child class")
|
||||
end
|
||||
|
||||
function ScrollingTable._IsSelected(self, data)
|
||||
return data == self._selection
|
||||
end
|
||||
|
||||
function ScrollingTable._HandleRowClick(self, data, mouseButton)
|
||||
if self._onRowClickHandler then
|
||||
self:_onRowClickHandler(data, mouseButton)
|
||||
end
|
||||
end
|
||||
|
||||
function ScrollingTable._GetColWidth(self, id)
|
||||
return self._contextTable.colWidth[id]
|
||||
end
|
||||
|
||||
function ScrollingTable._ResetColWidth(self, id)
|
||||
local defaultWidth = self._defaultContextTable.colWidth[id]
|
||||
local currentWidth = self._contextTable.colWidth[id]
|
||||
assert(currentWidth and defaultWidth)
|
||||
self._contextTable.colWidth[id] = defaultWidth
|
||||
self._header:_LayoutHeaderRow()
|
||||
for _, row in ipairs(self._rows) do
|
||||
row:_LayoutDataRow()
|
||||
end
|
||||
self:Draw()
|
||||
end
|
||||
|
||||
function ScrollingTable._SetColWidth(self, id, width, redraw)
|
||||
assert(not self._contextTable.colWidthLocked)
|
||||
local prevWidth = self._contextTable.colWidth[id]
|
||||
assert(prevWidth)
|
||||
if width == prevWidth and not redraw then
|
||||
return
|
||||
end
|
||||
self._contextTable.colWidth[id] = width
|
||||
for _, row in ipairs(self._rows) do
|
||||
row:_LayoutDataRow()
|
||||
end
|
||||
if redraw then
|
||||
self:Draw()
|
||||
end
|
||||
end
|
||||
|
||||
function ScrollingTable._IsColWidthLocked(self)
|
||||
return self._contextTable.colWidthLocked
|
||||
end
|
||||
|
||||
function ScrollingTable._ToogleColWidthLocked(self)
|
||||
self._contextTable.colWidthLocked = not self._contextTable.colWidthLocked or nil
|
||||
self._header:_LayoutHeaderRow()
|
||||
self:Draw()
|
||||
end
|
||||
|
||||
function ScrollingTable._CanResizeCols(self)
|
||||
return self._contextTable and true or false
|
||||
end
|
||||
|
||||
function ScrollingTable._ToggleColHide(self, id)
|
||||
if not self._contextTable then
|
||||
return
|
||||
end
|
||||
self._contextTable.colHidden[id] = not self._contextTable.colHidden[id] or nil
|
||||
self:_UpdateColsHidden()
|
||||
self._header:_LayoutHeaderRow()
|
||||
for _, row in ipairs(self._rows) do
|
||||
row:_LayoutDataRow()
|
||||
end
|
||||
self:Draw()
|
||||
end
|
||||
|
||||
function ScrollingTable._ResetContext(self)
|
||||
assert(self._contextTable)
|
||||
if self._defaultContextTable.colWidth then
|
||||
wipe(self._contextTable.colWidth)
|
||||
for col, width in pairs(self._defaultContextTable.colWidth) do
|
||||
self._contextTable.colWidth[col] = width
|
||||
end
|
||||
end
|
||||
if self._defaultContextTable.colHidden then
|
||||
wipe(self._contextTable.colHidden)
|
||||
for col, hidden in pairs(self._defaultContextTable.colHidden) do
|
||||
self._contextTable.colHidden[col] = hidden
|
||||
end
|
||||
self:_UpdateColsHidden()
|
||||
end
|
||||
self._header:_LayoutHeaderRow()
|
||||
for _, row in ipairs(self._rows) do
|
||||
row:_LayoutDataRow()
|
||||
end
|
||||
self:Draw()
|
||||
end
|
||||
|
||||
function ScrollingTable._UpdateColsHidden(self)
|
||||
for _, col in self:GetScrollingTableInfo():_ColIterator() do
|
||||
local colId = col:_GetId()
|
||||
if col:_CanHide() then
|
||||
col:_SetHidden(self._contextTable and self._contextTable.colHidden[colId] and true or false)
|
||||
elseif self._contextTable then
|
||||
self._contextTable.colHidden[colId] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ScrollingTable._SetLastDataUpdate(self, value)
|
||||
self._lastDataUpdate = value
|
||||
end
|
||||
|
||||
function ScrollingTable._IgnoreLastDataUpdate(self)
|
||||
self._lastDataUpdate = IGNORE_DATA_UPDATE
|
||||
end
|
||||
|
||||
function ScrollingTable._ForceLastDataUpdate(self)
|
||||
self._lastDataUpdate = FORCE_DATA_UPDATE
|
||||
end
|
||||
|
||||
function ScrollingTable._ScrollToData(self, data)
|
||||
local rowHeight = self._rowHeight
|
||||
local visibleHeight = self._vScrollFrame:GetHeight()
|
||||
local currentOffset = self._vScrollbar:GetValue()
|
||||
local dataIndex = Table.KeyByValue(self._data, data)
|
||||
-- if we are going to scroll up/down, we want to scroll such that the top of the passed row is in the visible area
|
||||
-- by at least 1 row height
|
||||
local scrollUpOffset = max(rowHeight * (dataIndex - 1) - rowHeight, 0)
|
||||
local scrollDownOffset = min(rowHeight * dataIndex + rowHeight - visibleHeight, self:_GetMaxScroll())
|
||||
if scrollUpOffset < currentOffset and scrollDownOffset > currentOffset then
|
||||
-- it's impossible to scroll to the right place, so do nothing
|
||||
elseif scrollUpOffset < currentOffset then
|
||||
-- we need to scroll up
|
||||
self._targetScrollValue = scrollUpOffset
|
||||
self._totalScrollDistance = currentOffset - scrollUpOffset
|
||||
elseif scrollDownOffset > currentOffset then
|
||||
-- we need to scroll down
|
||||
self._targetScrollValue = scrollDownOffset
|
||||
self._totalScrollDistance = scrollDownOffset - currentOffset
|
||||
else
|
||||
-- the data is already in the visible area, so do nothing
|
||||
end
|
||||
end
|
||||
|
||||
function ScrollingTable._JumpToData(self, data)
|
||||
if not data then
|
||||
return
|
||||
end
|
||||
local index = Table.KeyByValue(self._data, data)
|
||||
assert(index)
|
||||
-- set the scroll so that the selection is visible if necessary
|
||||
local rowHeight = self._rowHeight
|
||||
local firstVisibleIndex = ceil(self._vScrollValue / rowHeight) + 1
|
||||
local lastVisibleIndex = floor((self._vScrollValue + self:_GetDimension("HEIGHT")) / rowHeight)
|
||||
if lastVisibleIndex > firstVisibleIndex and (index < firstVisibleIndex or index > lastVisibleIndex) then
|
||||
self:_OnScrollValueChanged(min((index - 1) * rowHeight, self:_GetMaxScroll()))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- ScrollingTable - Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.OnHScrollbarValueChanged(self, value)
|
||||
value = max(min(value, self:_GetMaxHScroll()), 0)
|
||||
self:_OnHScrollValueChanged(value)
|
||||
end
|
||||
|
||||
function private.OnVScrollbarValueChanged(self, value)
|
||||
value = max(min(value, self:_GetMaxScroll()), 0)
|
||||
self:_OnScrollValueChanged(value)
|
||||
end
|
||||
|
||||
function private.OnHScrollbarValueChangedNoDraw(self, value)
|
||||
value = max(min(value, self:_GetMaxHScroll()), 0)
|
||||
self:_OnHScrollValueChanged(value, true)
|
||||
end
|
||||
|
||||
function private.OnVScrollbarValueChangedNoDraw(self, value)
|
||||
value = max(min(value, self:_GetMaxScroll()), 0)
|
||||
self:_OnScrollValueChanged(value, true)
|
||||
end
|
||||
|
||||
function private.HScrollFrameOnUpdate(self)
|
||||
if (self._hScrollFrame:IsMouseOver() and self:_GetMaxHScroll() > 1) or self._hScrollbar.dragging then
|
||||
self._hScrollbar:Show()
|
||||
else
|
||||
self._hScrollbar:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
function private.VScrollFrameOnUpdate(self, elapsed)
|
||||
elapsed = min(elapsed, 0.01)
|
||||
|
||||
if self._targetScrollValue then
|
||||
local scrollValue = self._vScrollbar:GetValue()
|
||||
local direction = scrollValue < self._targetScrollValue and 1 or -1
|
||||
local newScrollValue = scrollValue + direction * self._totalScrollDistance * elapsed / SCROLL_TO_DATA_TOTAL_TIME_S
|
||||
self._vScrollbar:SetValue(newScrollValue)
|
||||
if direction * newScrollValue >= direction * self._targetScrollValue or newScrollValue <= 0 or newScrollValue >= self:_GetMaxScroll() then
|
||||
-- we are done scrolling
|
||||
self._targetScrollValue = nil
|
||||
self._totalScrollDistance = nil
|
||||
end
|
||||
end
|
||||
|
||||
local rOffset = max(self._hContent:GetWidth() - self._hScrollFrame:GetWidth() - self._hScrollbar:GetValue(), 0)
|
||||
if (self._vScrollFrame:IsMouseOver(0, 0, 0, -rOffset) and self:_GetMaxScroll() > 1) or self._vScrollbar.dragging then
|
||||
self._vScrollbar:Show()
|
||||
else
|
||||
self._vScrollbar:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
function private.FrameOnMouseWheel(self, direction)
|
||||
local scrollAmount = -direction * Theme.GetMouseWheelScrollAmount()
|
||||
if IsShiftKeyDown() and self._hScrollbar:IsVisible() then
|
||||
-- scroll horizontally
|
||||
self._hScrollbar:SetValue(self._hScrollbar:GetValue() + scrollAmount)
|
||||
else
|
||||
self._vScrollbar:SetValue(self._vScrollbar:GetValue() + scrollAmount)
|
||||
end
|
||||
end
|
||||
165
Core/UI/Elements/SearchList.lua
Normal file
165
Core/UI/Elements/SearchList.lua
Normal file
@@ -0,0 +1,165 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- Search List UI Element Class.
|
||||
-- A search list contains a list of recent or favorite searches. It is a subclass of the @{ScrollingTable} class.
|
||||
-- @classmod SearchList
|
||||
|
||||
local _, TSM = ...
|
||||
local SearchList = TSM.Include("LibTSMClass").DefineClass("SearchList", TSM.UI.ScrollingTable)
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(SearchList)
|
||||
TSM.UI.SearchList = SearchList
|
||||
local private = {}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function SearchList.__init(self)
|
||||
self.__super:__init()
|
||||
self._onRowClickHandler = nil
|
||||
self._onFavoriteChangedHandler = nil
|
||||
self._onEditClickHandler = nil
|
||||
self._onDeleteHandler = nil
|
||||
self._query = nil
|
||||
self._editBtnHidden = false
|
||||
end
|
||||
|
||||
function SearchList.Acquire(self)
|
||||
self._headerHidden = true
|
||||
self.__super:Acquire()
|
||||
self:GetScrollingTableInfo()
|
||||
:NewColumn("name")
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextFunction(private.GetNameText)
|
||||
:SetActionIconInfo(3, 18, private.GetActionIcon, true)
|
||||
:SetActionIconClickHandler(private.OnActionIconClick)
|
||||
:DisableHiding()
|
||||
:Commit()
|
||||
:Commit()
|
||||
end
|
||||
|
||||
function SearchList.Release(self)
|
||||
self._onRowClickHandler = nil
|
||||
self._onFavoriteChangedHandler = nil
|
||||
self._onEditClickHandler = nil
|
||||
self._onDeleteHandler = nil
|
||||
if self._query then
|
||||
self._query:Release()
|
||||
self._query = nil
|
||||
end
|
||||
self._editBtnHidden = false
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
--- Sets whether or not the edit button is hidden.
|
||||
-- @tparam SearchList self The search list object
|
||||
-- @tparam boolean hidden Whether or not the edit button is hidden
|
||||
-- @treturn SearchList The search list object
|
||||
function SearchList.SetEditButtonHidden(self, hidden)
|
||||
self._editBtnHidden = hidden
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the @{DatabaseQuery} source for this list.
|
||||
-- This query is used to populate the entries in the search list.
|
||||
-- @tparam SearchList self The search list object
|
||||
-- @tparam DatabaseQuery query The query object
|
||||
-- @tparam[opt=false] bool redraw Whether or not to redraw the search list
|
||||
-- @treturn SearchList The search list object
|
||||
function SearchList.SetQuery(self, query, redraw)
|
||||
if self._query then
|
||||
self._query:Release()
|
||||
end
|
||||
self._query = query
|
||||
self._query:SetUpdateCallback(private.QueryUpdateCallback, self)
|
||||
self:UpdateData(redraw)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Registers a script handler.
|
||||
-- @tparam SearchList self The search list object
|
||||
-- @tparam string script The script to register for (supported scripts: `OnRowClick`, `OnFavoriteChanged`,
|
||||
-- `OnEditClick`, `OnDelete`)
|
||||
-- @tparam function handler The script handler which will be called with the search list object followed by any
|
||||
-- arguments to the script
|
||||
-- @treturn SearchList The search list object
|
||||
function SearchList.SetScript(self, script, handler)
|
||||
if script == "OnRowClick" then
|
||||
self._onRowClickHandler = handler
|
||||
elseif script == "OnFavoriteChanged" then
|
||||
self._onFavoriteChangedHandler = handler
|
||||
elseif script == "OnEditClick" then
|
||||
self._onEditClickHandler = handler
|
||||
elseif script == "OnDelete" then
|
||||
self._onDeleteHandler = handler
|
||||
else
|
||||
error("Unknown SearchList script: "..tostring(script))
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function SearchList._UpdateData(self)
|
||||
wipe(self._data)
|
||||
for _, row in self._query:Iterator() do
|
||||
tinsert(self._data, row)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetNameText(self, data)
|
||||
return data:GetField("name")
|
||||
end
|
||||
|
||||
function private.GetActionIcon(self, data, iconIndex, isMouseOver)
|
||||
if iconIndex == 1 then
|
||||
return true, data:GetField("isFavorite") and "iconPack.18x18/Star/Filled" or "iconPack.18x18/Star/Unfilled"
|
||||
elseif iconIndex == 2 then
|
||||
if self._editBtnHidden then
|
||||
return false, nil
|
||||
end
|
||||
return true, "iconPack.18x18/Edit"
|
||||
elseif iconIndex == 3 then
|
||||
return true, "iconPack.18x18/Delete"
|
||||
else
|
||||
error("Invalid iconIndex: "..tostring(iconIndex))
|
||||
end
|
||||
end
|
||||
|
||||
function private.OnActionIconClick(self, data, iconIndex)
|
||||
if iconIndex == 1 then
|
||||
-- favorite
|
||||
self:_onFavoriteChangedHandler(data, not data:GetField("isFavorite"))
|
||||
elseif iconIndex == 2 then
|
||||
-- edit
|
||||
assert(not self._editBtnHidden)
|
||||
self:_onEditClickHandler(data)
|
||||
elseif iconIndex == 3 then
|
||||
-- delete
|
||||
self:_onDeleteHandler(data)
|
||||
else
|
||||
error("Invalid iconIndex: "..tostring(iconIndex))
|
||||
end
|
||||
end
|
||||
|
||||
function private.QueryUpdateCallback(_, _, self)
|
||||
self:UpdateData(true)
|
||||
end
|
||||
61
Core/UI/Elements/SecureMacroActionButton.lua
Normal file
61
Core/UI/Elements/SecureMacroActionButton.lua
Normal file
@@ -0,0 +1,61 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- SecureMacroActionButton UI Element Class.
|
||||
-- A secure macro action button builds on top of WoW's `SecureActionButtonTemplate` to allow executing scripts which
|
||||
-- addon buttons would otherwise be forbidden from running. It is a subclass of the @{ActionButton} class.
|
||||
-- @classmod SecureMacroActionButton
|
||||
|
||||
local _, TSM = ...
|
||||
local ScriptWrapper = TSM.Include("Util.ScriptWrapper")
|
||||
local SecureMacroActionButton = TSM.Include("LibTSMClass").DefineClass("SecureMacroActionButton", TSM.UI.ActionButton)
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(SecureMacroActionButton)
|
||||
TSM.UI.SecureMacroActionButton = SecureMacroActionButton
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function SecureMacroActionButton.__init(self, name)
|
||||
self.__super:__init(name, true)
|
||||
local frame = self:_GetBaseFrame()
|
||||
frame:SetAttribute("type1", "macro")
|
||||
frame:SetAttribute("macrotext1", "")
|
||||
end
|
||||
|
||||
function SecureMacroActionButton.Release(self)
|
||||
local frame = self:_GetBaseFrame()
|
||||
ScriptWrapper.Clear(frame, "PreClick")
|
||||
frame:SetAttribute("macrotext1", "")
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
--- Registers a script handler.
|
||||
-- @tparam SecureMacroActionButton self The secure macro action button object
|
||||
-- @tparam string script The script to register for (supported scripts: `PreClick`)
|
||||
-- @tparam function handler The script handler which will be called with the secure macro action button object followed
|
||||
-- by any arguments to the script
|
||||
-- @treturn SecureMacroActionButton The secure macro action button object
|
||||
function SecureMacroActionButton.SetScript(self, script, handler)
|
||||
if script == "PreClick" or script == "PostClick" then
|
||||
self.__super.__super:SetScript(script, handler)
|
||||
else
|
||||
error("Unknown SecureActionButton script: "..tostring(script))
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the macro text which clicking the button executes.
|
||||
-- @tparam SecureMacroActionButton self The secure macro action button object
|
||||
-- @tparam string text THe macro text
|
||||
-- @treturn SecureMacroActionButton The secure macro action button object
|
||||
function SecureMacroActionButton.SetMacroText(self, text)
|
||||
self:_GetBaseFrame():SetAttribute("macrotext1", text)
|
||||
return self
|
||||
end
|
||||
121
Core/UI/Elements/SelectionDropdown.lua
Normal file
121
Core/UI/Elements/SelectionDropdown.lua
Normal file
@@ -0,0 +1,121 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- Selection Dropdown UI Element Class.
|
||||
-- A dropdown element allows the user to select from a dialog list. It is a subclass of the @{BaseDropdown} class.
|
||||
-- @classmod SelectionDropdown
|
||||
|
||||
local _, TSM = ...
|
||||
local Table = TSM.Include("Util.Table")
|
||||
local SelectionDropdown = TSM.Include("LibTSMClass").DefineClass("SelectionDropdown", TSM.UI.BaseDropdown)
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(SelectionDropdown)
|
||||
TSM.UI.SelectionDropdown = SelectionDropdown
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Meta Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function SelectionDropdown.__init(self)
|
||||
self.__super:__init()
|
||||
self._selectedItem = nil
|
||||
self._settingTable = nil
|
||||
self._settingKey = nil
|
||||
end
|
||||
|
||||
function SelectionDropdown.Release(self)
|
||||
self._selectedItem = nil
|
||||
self._settingTable = nil
|
||||
self._settingKey = nil
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
--- Set the currently selected item.
|
||||
-- @tparam SelectionDropdown self The dropdown object
|
||||
-- @tparam ?string item The selected item or nil if nothing should be selected
|
||||
-- @tparam[opt=false] boolean silent Don't call the OnSelectionChanged callback
|
||||
-- @treturn SelectionDropdown The dropdown object
|
||||
function SelectionDropdown.SetSelectedItem(self, item, silent)
|
||||
self._selectedItem = item
|
||||
if self._settingTable then
|
||||
self._settingTable[self._settingKey] = self._itemKeyLookup[item]
|
||||
end
|
||||
if not silent and self._onSelectionChangedHandler then
|
||||
self:_onSelectionChangedHandler()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the currently selected item by key.
|
||||
-- @tparam SelectionDropdown self The dropdown object
|
||||
-- @tparam ?string itemKey The key for the selected item or nil if nothing should be selected
|
||||
-- @tparam[opt=false] boolean silent Don't call the OnSelectionChanged callback
|
||||
-- @treturn SelectionDropdown The dropdown object
|
||||
function SelectionDropdown.SetSelectedItemByKey(self, itemKey, silent)
|
||||
local item = itemKey and Table.GetDistinctKey(self._itemKeyLookup, itemKey) or nil
|
||||
self:SetSelectedItem(item, silent)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the currently selected item.
|
||||
-- @tparam SelectionDropdown self The dropdown object
|
||||
-- @treturn ?string The selected item
|
||||
function SelectionDropdown.GetSelectedItem(self)
|
||||
return self._selectedItem
|
||||
end
|
||||
|
||||
--- Get the currently selected item.
|
||||
-- @tparam SelectionDropdown self The dropdown object
|
||||
-- @treturn ?string The selected item key
|
||||
function SelectionDropdown.GetSelectedItemKey(self)
|
||||
return self._selectedItem and self._itemKeyLookup[self._selectedItem] or nil
|
||||
end
|
||||
|
||||
--- Sets the setting info.
|
||||
-- This method is used to have the value of the dropdown automatically correspond with the value of a field in a table.
|
||||
-- This is useful for dropdowns which are tied directly to settings.
|
||||
-- @tparam SelectionDropdown self The dropdown object
|
||||
-- @tparam table tbl The table which the field to set belongs to
|
||||
-- @tparam string key The key into the table to be set based on the dropdown state
|
||||
-- @treturn SelectionDropdown The dropdown object
|
||||
function SelectionDropdown.SetSettingInfo(self, tbl, key)
|
||||
self._settingTable = tbl
|
||||
self._settingKey = key
|
||||
if tbl then
|
||||
self:SetSelectedItemByKey(tbl[key])
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function SelectionDropdown._AddDialogChildren(self, frame)
|
||||
frame:AddChild(UIElements.New("DropdownList", "list")
|
||||
:SetMultiselect(false)
|
||||
:SetItems(self._items, self._selectedItem)
|
||||
)
|
||||
end
|
||||
|
||||
function SelectionDropdown._GetCurrentSelectionString(self)
|
||||
return self._selectedItem or self._hintText
|
||||
end
|
||||
|
||||
function SelectionDropdown._OnListSelectionChanged(self, _, selection)
|
||||
self:SetOpen(false)
|
||||
self:SetSelectedItem(selection)
|
||||
end
|
||||
117
Core/UI/Elements/SelectionGroupTree.lua
Normal file
117
Core/UI/Elements/SelectionGroupTree.lua
Normal file
@@ -0,0 +1,117 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- SelectionGroupTree UI Element Class.
|
||||
-- A selection group tree allows for selecting a single group within the tree. It is a subclass of the @{GroupTree} class.
|
||||
-- @classmod SelectionGroupTree
|
||||
|
||||
local _, TSM = ...
|
||||
local Table = TSM.Include("Util.Table")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local SelectionGroupTree = TSM.Include("LibTSMClass").DefineClass("SelectionGroupTree", TSM.UI.GroupTree)
|
||||
UIElements.Register(SelectionGroupTree)
|
||||
TSM.UI.SelectionGroupTree = SelectionGroupTree
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function SelectionGroupTree.__init(self)
|
||||
self.__super:__init()
|
||||
|
||||
self._selectedGroup = TSM.CONST.ROOT_GROUP_PATH
|
||||
self._selectedGroupChangedHandler = nil
|
||||
end
|
||||
|
||||
function SelectionGroupTree.Release(self)
|
||||
self._selectedGroupChangedHandler = nil
|
||||
self._selectedGroup = TSM.CONST.ROOT_GROUP_PATH
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
--- Sets the selected group path.
|
||||
-- @tparam SelectionGroupTree self The selection group tree object
|
||||
-- @tparam string groupPath The group path string to select
|
||||
-- @treturn SelectionGroupTree The application group tree object
|
||||
function SelectionGroupTree.SetSelection(self, groupPath)
|
||||
assert(groupPath)
|
||||
self._selectedGroup = groupPath
|
||||
wipe(self._contextTable.selected)
|
||||
self._contextTable.selected[groupPath] = true
|
||||
local index = Table.KeyByValue(self._data, groupPath)
|
||||
assert(index)
|
||||
-- set the scroll so that the selection is visible if necessary
|
||||
local rowHeight = self._rowHeight
|
||||
local firstVisibleIndex = ceil(self._vScrollValue / rowHeight) + 1
|
||||
local lastVisibleIndex = floor((self._vScrollValue + self:_GetDimension("HEIGHT")) / rowHeight)
|
||||
if lastVisibleIndex > firstVisibleIndex and (index < firstVisibleIndex or index > lastVisibleIndex) then
|
||||
self:_OnScrollValueChanged(min((index - 1) * rowHeight, self:_GetMaxScroll()))
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Gets the selected group path.
|
||||
-- @tparam SelectionGroupTree self The selection group tree object
|
||||
-- @treturn string The currently selected group path string
|
||||
function SelectionGroupTree.GetSelection(self)
|
||||
return self._selectedGroup
|
||||
end
|
||||
|
||||
--- Sets the context table.
|
||||
-- This table can be used to preserve selection state across lifecycles of the application group tree and even WoW
|
||||
-- sessions if it's within the settings DB.
|
||||
-- @see GroupTree.SetContextTable
|
||||
-- @tparam SelectionGroupTree self The application group tree object
|
||||
-- @tparam table tbl The context table
|
||||
-- @tparam table defaultTbl The default table (required fields: `selected`, `collapsed`)
|
||||
-- @treturn SelectionGroupTree The application group tree object
|
||||
function SelectionGroupTree.SetContextTable(self, tbl, defaultTbl)
|
||||
assert(type(defaultTbl.selected) == "table")
|
||||
tbl.selected = tbl.selected or CopyTable(defaultTbl.selected)
|
||||
self.__super:SetContextTable(tbl, defaultTbl)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Registers a script handler.
|
||||
-- @tparam SelectionGroupTree self The selection group tree object
|
||||
-- @tparam string script The script to register for (supported scripts: `OnGroupSelectionChanged`)
|
||||
-- @tparam function handler The script handler which will be called with the selection group tree object followed by any
|
||||
-- arguments to the script
|
||||
-- @treturn SelectionGroupTree The selection group tree object
|
||||
function SelectionGroupTree.SetScript(self, script, handler)
|
||||
if script == "OnGroupSelectionChanged" then
|
||||
self._selectedGroupChangedHandler = handler
|
||||
else
|
||||
error("Unknown SelectionGroupTree script: "..tostring(script))
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function SelectionGroupTree._HandleRowClick(self, data, mouseButton)
|
||||
if mouseButton == "RightButton" then
|
||||
self.__super:_HandleRowClick(data, mouseButton)
|
||||
return
|
||||
end
|
||||
self._selectedGroup = data
|
||||
wipe(self._contextTable.selected)
|
||||
self._contextTable.selected[data] = true
|
||||
self:Draw()
|
||||
if self._selectedGroupChangedHandler then
|
||||
self:_selectedGroupChangedHandler(data)
|
||||
end
|
||||
end
|
||||
|
||||
function SelectionGroupTree._IsSelected(self, data)
|
||||
return data == self._selectedGroup
|
||||
end
|
||||
101
Core/UI/Elements/SelectionList.lua
Normal file
101
Core/UI/Elements/SelectionList.lua
Normal file
@@ -0,0 +1,101 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- SelectionList UI Element Class.
|
||||
-- A selection list is a scrollable list of entries which allows selecting a single one. It is a subclass of the
|
||||
-- @{ScrollingTable} class.
|
||||
-- @classmod SelectionList
|
||||
|
||||
local _, TSM = ...
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local SelectionList = TSM.Include("LibTSMClass").DefineClass("SelectionList", TSM.UI.ScrollingTable)
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(SelectionList)
|
||||
TSM.UI.SelectionList = SelectionList
|
||||
local private = {}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function SelectionList.__init(self)
|
||||
self.__super:__init()
|
||||
self._selectedEntry = nil
|
||||
self._onEntrySelectedHandler = nil
|
||||
end
|
||||
|
||||
function SelectionList.Acquire(self)
|
||||
self._headerHidden = true
|
||||
self.__super:Acquire()
|
||||
self:SetSelectionDisabled(true)
|
||||
self:GetScrollingTableInfo()
|
||||
:NewColumn("text")
|
||||
:SetFont("BODY_BODY2")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextFunction(private.GetText)
|
||||
:DisableHiding()
|
||||
:Commit()
|
||||
:Commit()
|
||||
end
|
||||
|
||||
function SelectionList.Release(self)
|
||||
self._selectedEntry = nil
|
||||
self._onEntrySelectedHandler = nil
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
--- Sets the entries.
|
||||
-- @tparam SelectionList self The selection list object
|
||||
-- @tparam table entries A list of entries
|
||||
-- @tparam string selectedEntry The selected entry
|
||||
-- @treturn SelectionList The selection list object
|
||||
function SelectionList.SetEntries(self, entries, selectedEntry)
|
||||
wipe(self._data)
|
||||
for _, entry in ipairs(entries) do
|
||||
tinsert(self._data, entry)
|
||||
end
|
||||
self._selectedEntry = selectedEntry
|
||||
return self
|
||||
end
|
||||
|
||||
--- Registers a script handler.
|
||||
-- @tparam SelectionList self The selection list object
|
||||
-- @tparam string script The script to register for (supported scripts: `OnEntrySelected`)
|
||||
-- @tparam function handler The script handler which will be called with the selection list object followed by any
|
||||
-- arguments to the script
|
||||
-- @treturn SelectionList The selection list object
|
||||
function SelectionList.SetScript(self, script, handler)
|
||||
if script == "OnEntrySelected" then
|
||||
self._onEntrySelectedHandler = handler
|
||||
else
|
||||
error("Unknown SelectionList script: "..tostring(script))
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function SelectionList._HandleRowClick(self, data)
|
||||
if self._onEntrySelectedHandler then
|
||||
self:_onEntrySelectedHandler(data)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetText(self, data)
|
||||
return Theme.GetColor(data == self._selectedEntry and "INDICATOR" or "TEXT"):ColorText(data)
|
||||
end
|
||||
250
Core/UI/Elements/SelectionScrollingTable.lua
Normal file
250
Core/UI/Elements/SelectionScrollingTable.lua
Normal file
@@ -0,0 +1,250 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- SelectionScrollingTable UI Element Class.
|
||||
-- A selection scrolling table is a scrolling table which allows for selecting and deselecting individual rows. It is a
|
||||
-- subclass of the @{QueryScrollingTable} class.
|
||||
-- @classmod SelectionScrollingTable
|
||||
|
||||
local _, TSM = ...
|
||||
local Table = TSM.Include("Util.Table")
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local SelectionScrollingTable = TSM.Include("LibTSMClass").DefineClass("SelectionScrollingTable", TSM.UI.QueryScrollingTable)
|
||||
UIElements.Register(SelectionScrollingTable)
|
||||
TSM.UI.SelectionScrollingTable = SelectionScrollingTable
|
||||
local private = {
|
||||
querySelectionScrollingTableLookup = {},
|
||||
sortValuesTemp = {},
|
||||
tempContextTable = {},
|
||||
}
|
||||
local TEMP_CONTEXT_TABLE_DEFAULT = {
|
||||
colWidth = {
|
||||
selected = 16,
|
||||
},
|
||||
colHidden = {},
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function SelectionScrollingTable.__init(self)
|
||||
self.__super:__init()
|
||||
|
||||
self._selectedData = {}
|
||||
self._selectionEnabledFunc = nil
|
||||
self._rightClickToggle = true
|
||||
end
|
||||
|
||||
function SelectionScrollingTable.Acquire(self)
|
||||
self.__super:Acquire()
|
||||
-- temporarily set a context table so we can create the table columns (should be overridden later)
|
||||
wipe(private.tempContextTable)
|
||||
self.__super:SetContextTable(private.tempContextTable, TEMP_CONTEXT_TABLE_DEFAULT)
|
||||
self:GetScrollingTableInfo()
|
||||
:NewColumn("selected", true)
|
||||
:SetTitleIcon("iconPack.14x14/Checkmark/Default")
|
||||
:SetWidth(14)
|
||||
:SetIconSize(12)
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("CENTER")
|
||||
:SetIconInfo(nil, private.GetSelectedIcon)
|
||||
:DisableHiding()
|
||||
:Commit()
|
||||
:Commit()
|
||||
end
|
||||
|
||||
function SelectionScrollingTable.Release(self)
|
||||
private.querySelectionScrollingTableLookup[self._query] = nil
|
||||
wipe(self._selectedData)
|
||||
self._selectionEnabledFunc = nil
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
--- Sets the @{DatabaseQuery} source for this table.
|
||||
-- This query is used to populate the entries in the selection scrolling table.
|
||||
-- @tparam SelectionScrollingTable self The selection scrolling table object
|
||||
-- @tparam DatabaseQuery query The query object
|
||||
-- @tparam[opt=false] bool redraw Whether or not to redraw the selection scrolling table
|
||||
-- @treturn SelectionScrollingTable The selection scrolling table object
|
||||
function SelectionScrollingTable.SetQuery(self, query, redraw)
|
||||
if self._query then
|
||||
private.querySelectionScrollingTableLookup[self._query] = nil
|
||||
end
|
||||
private.querySelectionScrollingTableLookup[query] = self
|
||||
self.__super:SetQuery(query, redraw)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Selects all items.
|
||||
-- @tparam SelectionScrollingTable self The selection scrolling table object
|
||||
function SelectionScrollingTable.SelectAll(self)
|
||||
for _, uuid in ipairs(self._data) do
|
||||
self._selectedData[uuid] = true
|
||||
end
|
||||
self:_UpdateData()
|
||||
self:Draw()
|
||||
if self._onSelectionChangedHandler then
|
||||
self._onSelectionChangedHandler(self)
|
||||
end
|
||||
end
|
||||
|
||||
--- Clear the selection.
|
||||
-- @tparam SelectionScrollingTable self The selection scrolling table object
|
||||
function SelectionScrollingTable.ClearSelection(self)
|
||||
wipe(self._selectedData)
|
||||
self:_UpdateData()
|
||||
self:Draw()
|
||||
if self._onSelectionChangedHandler then
|
||||
self._onSelectionChangedHandler(self)
|
||||
end
|
||||
end
|
||||
|
||||
--- Sets a selection enabled function.
|
||||
-- @tparam SelectionScrollingTable self The selection scrolling table object
|
||||
-- @tparam function func A funciton which gets called with data to determine if it's selectable or not
|
||||
-- @treturn SelectionScrollingTable The selection scrolling table object
|
||||
function SelectionScrollingTable.SetIsSelectionEnabledFunc(self, func)
|
||||
self._selectionEnabledFunc = func
|
||||
return self
|
||||
end
|
||||
|
||||
--- Toggles the selection of a record.
|
||||
-- @tparam SelectionScrollingTable self The selection scrolling table object
|
||||
-- @tparam ?table data The record to toggle the selection of
|
||||
-- @treturn SelectionScrollingTable The selection scrolling table object
|
||||
function SelectionScrollingTable.SetSelection(self, data)
|
||||
if data and self._selectionValidator and not self:_selectionValidator(self._query:GetResultRowByUUID(data)) then
|
||||
assert(not self._selectedData[data])
|
||||
return self
|
||||
end
|
||||
self._selectedData[data] = not self._selectedData[data] or nil
|
||||
for _, row in ipairs(self._rows) do
|
||||
if row:GetData() == data then
|
||||
self:_SetRowData(row, data)
|
||||
break
|
||||
end
|
||||
end
|
||||
if self._sortCol == "selected" then
|
||||
self:_UpdateData()
|
||||
self:Draw()
|
||||
end
|
||||
if self._onSelectionChangedHandler then
|
||||
self._onSelectionChangedHandler(self)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Gets whether or not all of the items are currently selected.
|
||||
-- @tparam SelectionScrollingTable self The selection scrolling table object
|
||||
-- @treturn boolean Whether or not all of the selection is selected
|
||||
function SelectionScrollingTable.IsAllSelected(self)
|
||||
for _, uuid in ipairs(self._data) do
|
||||
if not self._selectedData[uuid] then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--- Gets whether or not the selection is currently cleared.
|
||||
-- @tparam SelectionScrollingTable self The selection scrolling table object
|
||||
-- @treturn boolean Whether or not the selection is cleared
|
||||
function SelectionScrollingTable.IsSelectionCleared(self)
|
||||
return not next(self._selectedData)
|
||||
end
|
||||
|
||||
--- Gets the current selection table.
|
||||
-- @tparam SelectionScrollingTable self The selection scrolling table object
|
||||
-- @treturn table A table where the key is the data and the value is whether or not it's selected (only selected entries
|
||||
-- are in the table)
|
||||
function SelectionScrollingTable.SelectionIterator(self)
|
||||
return private.SelectionIteratorHelper, self
|
||||
end
|
||||
|
||||
--- Sets the context table.
|
||||
-- This table can be used to preserve the table configuration across lifecycles of the scrolling table and even WoW
|
||||
-- sessions if it's within the settings DB.
|
||||
-- @tparam SelectionScrollingTable self The selection scrolling table object
|
||||
-- @tparam table tbl The context table
|
||||
-- @tparam table defaultTbl The default table (required fields: `colWidth`)
|
||||
-- @treturn SelectionScrollingTable The selection scrolling table object
|
||||
function SelectionScrollingTable.SetContextTable(self, tbl, defaultTbl)
|
||||
assert(defaultTbl.colWidth.selected)
|
||||
self.__super:SetContextTable(tbl, defaultTbl)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function SelectionScrollingTable._IsSelected(self, data)
|
||||
return self._selectedData[data] == 1
|
||||
end
|
||||
|
||||
function SelectionScrollingTable._UpdateData(self)
|
||||
self.__super:_UpdateData()
|
||||
-- clear any old selection context
|
||||
local hasData = TempTable.Acquire()
|
||||
for _, data in ipairs(self._data) do
|
||||
hasData[data] = true
|
||||
end
|
||||
for data in pairs(self._selectedData) do
|
||||
if not hasData[data] then
|
||||
self._selectedData[data] = nil
|
||||
end
|
||||
end
|
||||
TempTable.Release(hasData)
|
||||
if self._sortCol == "selected" then
|
||||
local selectedValue = self._sortAscending and -1 or 1
|
||||
for _, uuid in ipairs(self._data) do
|
||||
private.sortValuesTemp[uuid] = self._selectedData[uuid] and selectedValue or 0
|
||||
end
|
||||
Table.SortWithValueLookup(self._data, private.sortValuesTemp)
|
||||
wipe(private.sortValuesTemp)
|
||||
end
|
||||
end
|
||||
|
||||
function SelectionScrollingTable._ToggleSort(self, id)
|
||||
if id ~= "selected" then
|
||||
return self.__super:_ToggleSort(id)
|
||||
end
|
||||
|
||||
if id == self._sortCol then
|
||||
self._sortAscending = not self._sortAscending
|
||||
else
|
||||
self._sortCol = id
|
||||
self._sortAscending = true
|
||||
end
|
||||
|
||||
self:_UpdateData()
|
||||
self:Draw()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.SelectionIteratorHelper(self, uuid)
|
||||
uuid = next(self._selectedData, uuid)
|
||||
if not uuid then
|
||||
return
|
||||
end
|
||||
return uuid, self._query:GetResultRowByUUID(uuid)
|
||||
end
|
||||
|
||||
function private.GetSelectedIcon(row)
|
||||
local self = private.querySelectionScrollingTableLookup[row:GetQuery()]
|
||||
return self._selectedData[row:GetUUID()] and "iconPack.14x14/Checkmark/Default" or 0
|
||||
end
|
||||
105
Core/UI/Elements/SimpleTabGroup.lua
Normal file
105
Core/UI/Elements/SimpleTabGroup.lua
Normal file
@@ -0,0 +1,105 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- SimpleTabGroup UI Element Class.
|
||||
-- A simple table group uses text to denote tabs with the selected one colored differently. It is a subclass of the
|
||||
-- @{ViewContainer} class.
|
||||
-- @classmod SimpleTabGroup
|
||||
|
||||
local _, TSM = ...
|
||||
local SimpleTabGroup = TSM.Include("LibTSMClass").DefineClass("SimpleTabGroup", TSM.UI.ViewContainer)
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(SimpleTabGroup)
|
||||
TSM.UI.SimpleTabGroup = SimpleTabGroup
|
||||
local private = {}
|
||||
local BUTTON_HEIGHT = 24
|
||||
local BUTTON_PADDING_BOTTOM = 2
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function SimpleTabGroup.__init(self)
|
||||
self.__super:__init()
|
||||
self._buttons = {}
|
||||
end
|
||||
|
||||
function SimpleTabGroup.Acquire(self)
|
||||
self.__super.__super:AddChildNoLayout(UIElements.New("Frame", "buttons")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:AddAnchor("TOPLEFT")
|
||||
:AddAnchor("TOPRIGHT")
|
||||
)
|
||||
self.__super:Acquire()
|
||||
end
|
||||
|
||||
function SimpleTabGroup.Release(self)
|
||||
wipe(self._buttons)
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function SimpleTabGroup._GetContentPadding(self, side)
|
||||
if side == "TOP" then
|
||||
return BUTTON_HEIGHT + BUTTON_PADDING_BOTTOM
|
||||
end
|
||||
return self.__super:_GetContentPadding(side)
|
||||
end
|
||||
|
||||
function SimpleTabGroup.Draw(self)
|
||||
self.__super.__super.__super:Draw()
|
||||
|
||||
local selectedPath = self:GetPath()
|
||||
local buttons = self:GetElement("buttons")
|
||||
buttons:SetHeight(BUTTON_HEIGHT + BUTTON_PADDING_BOTTOM)
|
||||
buttons:ReleaseAllChildren()
|
||||
for i, buttonPath in ipairs(self._pathsList) do
|
||||
local isSelected = buttonPath == selectedPath
|
||||
buttons:AddChild(UIElements.New("Button", self._id.."_Tab"..i)
|
||||
:SetWidth("AUTO")
|
||||
:SetMargin(8, 8, 0, BUTTON_PADDING_BOTTOM)
|
||||
:SetJustifyH("LEFT")
|
||||
:SetFont("BODY_BODY1_BOLD")
|
||||
:SetTextColor(isSelected and "INDICATOR" or "TEXT_ALT")
|
||||
:SetContext(self)
|
||||
:SetText(buttonPath)
|
||||
:SetScript("OnEnter", not isSelected and private.OnButtonEnter)
|
||||
:SetScript("OnLeave", not isSelected and private.OnButtonLeave)
|
||||
:SetScript("OnClick", private.OnButtonClicked)
|
||||
)
|
||||
end
|
||||
|
||||
self.__super:Draw()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.OnButtonEnter(button)
|
||||
button:SetTextColor("TEXT")
|
||||
:Draw()
|
||||
end
|
||||
|
||||
function private.OnButtonLeave(button)
|
||||
button:SetTextColor("TEXT_ALT")
|
||||
:Draw()
|
||||
end
|
||||
|
||||
function private.OnButtonClicked(button)
|
||||
local self = button:GetContext()
|
||||
local path = button:GetText()
|
||||
self:SetPath(path, true)
|
||||
end
|
||||
266
Core/UI/Elements/Slider.lua
Normal file
266
Core/UI/Elements/Slider.lua
Normal file
@@ -0,0 +1,266 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- Slider UI Element Class.
|
||||
-- A slider allows for selecting a numerical range. It is a subclass of the @{Element} class.
|
||||
-- @classmod Slider
|
||||
|
||||
local _, TSM = ...
|
||||
local Math = TSM.Include("Util.Math")
|
||||
local NineSlice = TSM.Include("Util.NineSlice")
|
||||
local ScriptWrapper = TSM.Include("Util.ScriptWrapper")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local Slider = TSM.Include("LibTSMClass").DefineClass("Slider", TSM.UI.Element)
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(Slider)
|
||||
TSM.UI.Slider = Slider
|
||||
local private = {}
|
||||
local THUMB_WIDTH = 8
|
||||
local INPUT_WIDTH = 50
|
||||
local INPUT_AREA_SPACE = 128
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function Slider.__init(self)
|
||||
local frame = UIElements.CreateFrame(self, "Frame")
|
||||
frame:EnableMouse(true)
|
||||
ScriptWrapper.Set(frame, "OnMouseDown", private.FrameOnMouseDown, self)
|
||||
ScriptWrapper.Set(frame, "OnMouseUp", private.FrameOnMouseUp, self)
|
||||
ScriptWrapper.Set(frame, "OnUpdate", private.FrameOnUpdate, self)
|
||||
|
||||
self.__super:__init(frame)
|
||||
|
||||
-- create the textures
|
||||
frame.barTexture = frame:CreateTexture(nil, "BACKGROUND", nil, 1)
|
||||
frame.activeBarTexture = frame:CreateTexture(nil, "BACKGROUND", nil, 2)
|
||||
frame.thumbTextureLeft = frame:CreateTexture(nil, "ARTWORK")
|
||||
frame.thumbTextureRight = frame:CreateTexture(nil, "ARTWORK")
|
||||
|
||||
frame.inputLeft = CreateFrame("EditBox", nil, frame, nil)
|
||||
frame.inputLeft:SetJustifyH("CENTER")
|
||||
frame.inputLeft:SetWidth(INPUT_WIDTH)
|
||||
frame.inputLeft:SetHeight(24)
|
||||
frame.inputLeft:SetAutoFocus(false)
|
||||
frame.inputLeft:SetNumeric(true)
|
||||
ScriptWrapper.Set(frame.inputLeft, "OnEscapePressed", private.InputOnEscapePressed)
|
||||
ScriptWrapper.Set(frame.inputLeft, "OnEnterPressed", private.LeftInputOnEnterPressed, self)
|
||||
|
||||
|
||||
frame.dash = UIElements.CreateFontString(self, frame)
|
||||
frame.dash:SetJustifyH("CENTER")
|
||||
frame.dash:SetJustifyV("MIDDLE")
|
||||
frame.dash:SetWidth(12)
|
||||
|
||||
frame.inputRight = CreateFrame("EditBox", nil, frame, nil)
|
||||
frame.inputRight:SetJustifyH("CENTER")
|
||||
frame.inputRight:SetWidth(INPUT_WIDTH)
|
||||
frame.inputRight:SetHeight(24)
|
||||
frame.inputRight:SetNumeric(true)
|
||||
frame.inputRight:SetAutoFocus(false)
|
||||
ScriptWrapper.Set(frame.inputRight, "OnEscapePressed", private.InputOnEscapePressed)
|
||||
ScriptWrapper.Set(frame.inputRight, "OnEnterPressed", private.RightInputOnEnterPressed, self)
|
||||
|
||||
self._inputLeftNineSlice = NineSlice.New(frame.inputLeft)
|
||||
self._inputRightNineSlice = NineSlice.New(frame.inputRight)
|
||||
self._leftValue = nil
|
||||
self._rightValue = nil
|
||||
self._minValue = nil
|
||||
self._maxValue = nil
|
||||
self._dragging = nil
|
||||
end
|
||||
|
||||
function Slider.Release(self)
|
||||
self._leftValue = nil
|
||||
self._rightValue = nil
|
||||
self._minValue = nil
|
||||
self._maxValue = nil
|
||||
self._dragging = nil
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
--- Set the extends of the possible range.
|
||||
-- @tparam Slider self The slider object
|
||||
-- @tparam number minValue The minimum value
|
||||
-- @tparam number maxValue The maxmimum value
|
||||
-- @treturn Slider The slider object
|
||||
function Slider.SetRange(self, minValue, maxValue)
|
||||
self._minValue = minValue
|
||||
self._maxValue = maxValue
|
||||
self._leftValue = minValue
|
||||
self._rightValue = maxValue
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the current value.
|
||||
-- @tparam Slider self The slider object
|
||||
-- @tparam number leftValue The lower end of the range
|
||||
-- @tparam number rightValue The upper end of the range
|
||||
-- @treturn Slider The slider object
|
||||
function Slider.SetValue(self, leftValue, rightValue)
|
||||
assert(leftValue < rightValue and leftValue >= self._minValue and rightValue <= self._maxValue)
|
||||
self._leftValue = leftValue
|
||||
self._rightValue = rightValue
|
||||
return self
|
||||
end
|
||||
|
||||
--- Gets the current value
|
||||
-- @tparam Slider self The slider object
|
||||
-- @treturn number The lower end of the range
|
||||
-- @treturn number The upper end of the range
|
||||
function Slider.GetValue(self)
|
||||
return self._leftValue, self._rightValue
|
||||
end
|
||||
|
||||
function Slider.Draw(self)
|
||||
self.__super:Draw()
|
||||
local frame = self:_GetBaseFrame()
|
||||
|
||||
local inputColor = Theme.GetColor("ACTIVE_BG")
|
||||
self._inputLeftNineSlice:SetStyle("rounded")
|
||||
self._inputRightNineSlice:SetStyle("rounded")
|
||||
self._inputLeftNineSlice:SetVertexColor(inputColor:GetFractionalRGBA())
|
||||
self._inputRightNineSlice:SetVertexColor(inputColor:GetFractionalRGBA())
|
||||
|
||||
local sliderHeight = self:_GetDimension("HEIGHT") / 2
|
||||
local width = self:_GetDimension("WIDTH") - INPUT_AREA_SPACE
|
||||
local leftPos = Math.Scale(self._leftValue, self._minValue, self._maxValue, 0, width - THUMB_WIDTH)
|
||||
local rightPos = Math.Scale(self._rightValue, self._minValue, self._maxValue, 0, width - THUMB_WIDTH)
|
||||
local fontPath, fontHeight = Theme.GetFont("BODY_BODY1"):GetWowFont()
|
||||
local textColor = Theme.GetColor("TEXT")
|
||||
|
||||
-- wow renders the font slightly bigger than the designs would indicate, so subtract one from the font height
|
||||
frame.inputRight:SetFont(fontPath, fontHeight)
|
||||
frame.inputRight:SetTextColor(textColor:GetFractionalRGBA())
|
||||
frame.inputRight:SetPoint("RIGHT", 0)
|
||||
frame.inputRight:SetNumber(self._rightValue)
|
||||
|
||||
frame.dash:SetFont(fontPath, fontHeight)
|
||||
frame.dash:SetTextColor(textColor:GetFractionalRGBA())
|
||||
frame.dash:SetText("-")
|
||||
frame.dash:SetPoint("RIGHT", frame.inputRight, "LEFT", 0, 0)
|
||||
|
||||
-- wow renders the font slightly bigger than the designs would indicate, so subtract one from the font height
|
||||
frame.inputLeft:SetFont(fontPath, fontHeight)
|
||||
frame.inputLeft:SetTextColor(textColor:GetFractionalRGBA())
|
||||
frame.inputLeft:SetPoint("RIGHT", frame.dash, "LEFT", 0)
|
||||
frame.inputLeft:SetNumber(self._leftValue)
|
||||
|
||||
frame.barTexture:ClearAllPoints()
|
||||
frame.barTexture:SetPoint("LEFT", 0, 0)
|
||||
frame.barTexture:SetPoint("RIGHT", frame.inputLeft, "LEFT", -16, 0)
|
||||
frame.barTexture:SetHeight(sliderHeight / 3)
|
||||
frame.barTexture:SetColorTexture(Theme.GetColor("FRAME_BG"):GetFractionalRGBA())
|
||||
|
||||
TSM.UI.TexturePacks.SetTextureAndSize(frame.thumbTextureLeft, "iconPack.14x14/Circle")
|
||||
frame.thumbTextureLeft:SetPoint("LEFT", frame.barTexture, leftPos, 0)
|
||||
|
||||
TSM.UI.TexturePacks.SetTextureAndSize(frame.thumbTextureRight, "iconPack.14x14/Circle")
|
||||
frame.thumbTextureRight:SetPoint("LEFT", frame.barTexture, rightPos, 0)
|
||||
|
||||
frame.activeBarTexture:SetPoint("LEFT", frame.thumbTextureLeft, "CENTER")
|
||||
frame.activeBarTexture:SetPoint("RIGHT", frame.thumbTextureRight, "CENTER")
|
||||
frame.activeBarTexture:SetHeight(sliderHeight / 3)
|
||||
frame.activeBarTexture:SetColorTexture(Theme.GetColor("TEXT"):GetFractionalRGBA())
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function Slider._GetCursorPositionValue(self)
|
||||
local frame = self:_GetBaseFrame()
|
||||
local x = GetCursorPosition() / frame:GetEffectiveScale()
|
||||
local left = frame:GetLeft() + THUMB_WIDTH / 2
|
||||
local right = frame:GetRight() - THUMB_WIDTH - INPUT_AREA_SPACE * 2 / 2
|
||||
x = min(max(x, left), right)
|
||||
local value = Math.Scale(x, left, right, self._minValue, self._maxValue)
|
||||
return min(max(Math.Round(value), self._minValue), self._maxValue)
|
||||
end
|
||||
|
||||
function Slider._UpdateLeftValue(self, value)
|
||||
local newValue = max(min(value, self._rightValue), self._minValue)
|
||||
if newValue == self._leftValue then
|
||||
return
|
||||
end
|
||||
self._leftValue = newValue
|
||||
self:Draw()
|
||||
end
|
||||
|
||||
function Slider._UpdateRightValue(self, value)
|
||||
local newValue = min(max(value, self._leftValue), self._maxValue)
|
||||
if newValue == self._rightValue then
|
||||
return
|
||||
end
|
||||
self._rightValue = newValue
|
||||
self:Draw()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.InputOnEscapePressed(input)
|
||||
input:ClearFocus()
|
||||
end
|
||||
|
||||
function private.LeftInputOnEnterPressed(self)
|
||||
local input = self:_GetBaseFrame().inputLeft
|
||||
self:_UpdateLeftValue(input:GetNumber())
|
||||
end
|
||||
|
||||
function private.RightInputOnEnterPressed(self)
|
||||
local input = self:_GetBaseFrame().inputRight
|
||||
self:_UpdateRightValue(input:GetNumber())
|
||||
end
|
||||
|
||||
function private.FrameOnMouseDown(self)
|
||||
local frame = self:_GetBaseFrame()
|
||||
frame.inputLeft:ClearFocus()
|
||||
frame.inputRight:ClearFocus()
|
||||
local value = self:_GetCursorPositionValue()
|
||||
local leftDiff = abs(value - self._leftValue)
|
||||
local rightDiff = abs(value - self._rightValue)
|
||||
if value < self._leftValue then
|
||||
-- clicked to the left of the left thumb, so drag that
|
||||
self._dragging = "left"
|
||||
elseif value > self._rightValue then
|
||||
-- clicked to the right of the right thumb, so drag that
|
||||
self._dragging = "right"
|
||||
elseif self._leftValue == self._rightValue then
|
||||
-- just ignore this click since they clicked on both thumbs
|
||||
elseif leftDiff < rightDiff then
|
||||
-- clicked closer to the left thumb, so drag that
|
||||
self._dragging = "left"
|
||||
else
|
||||
-- clicked closer to the right thumb (or right in the middle), so drag that
|
||||
self._dragging = "right"
|
||||
end
|
||||
end
|
||||
|
||||
function private.FrameOnMouseUp(self)
|
||||
self._dragging = nil
|
||||
end
|
||||
|
||||
function private.FrameOnUpdate(self)
|
||||
if not self._dragging then
|
||||
return
|
||||
end
|
||||
if self._dragging == "left" then
|
||||
self:_UpdateLeftValue(self:_GetCursorPositionValue())
|
||||
elseif self._dragging == "right" then
|
||||
self:_UpdateRightValue(self:_GetCursorPositionValue())
|
||||
else
|
||||
error("Unexpected dragging: "..tostring(self._dragging))
|
||||
end
|
||||
end
|
||||
128
Core/UI/Elements/SniperScrollingTable.lua
Normal file
128
Core/UI/Elements/SniperScrollingTable.lua
Normal file
@@ -0,0 +1,128 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- SniperScrollingTable UI Element Class.
|
||||
-- A special shopping scrolling table used for sniper which has an extra icon column on the left. It is a subclass of
|
||||
-- the @{AuctionScrollingTable} class.
|
||||
-- @classmod SniperScrollingTable
|
||||
|
||||
local _, TSM = ...
|
||||
local SniperScrollingTable = TSM.Include("LibTSMClass").DefineClass("SniperScrollingTable", TSM.UI.AuctionScrollingTable)
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(SniperScrollingTable)
|
||||
TSM.UI.SniperScrollingTable = SniperScrollingTable
|
||||
local private = {}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function SniperScrollingTable.__init(self)
|
||||
self.__super:__init()
|
||||
|
||||
self._highestBrowseId = 0
|
||||
self._onRowRemovedHandler = nil
|
||||
end
|
||||
|
||||
function SniperScrollingTable.Acquire(self)
|
||||
self.__super:Acquire()
|
||||
self:GetScrollingTableInfo()
|
||||
:NewColumn("icon", true)
|
||||
:SetTitleIcon("iconPack.14x14/Attention")
|
||||
:SetIconSize(14)
|
||||
:SetIconHoverEnabled(true)
|
||||
:SetIconClickHandler(private.RemoveIconClickHandler)
|
||||
:SetIconFunction(private.RemoveIconFunction)
|
||||
:SetJustifyH("CENTER")
|
||||
:SetFont("BODY_BODY3")
|
||||
:Commit()
|
||||
:RemoveColumn("timeLeft")
|
||||
:Commit()
|
||||
if TSM.IsWowClassic() then
|
||||
self._sortCol = "icon"
|
||||
self._sortAscending = true
|
||||
end
|
||||
self._highestBrowseId = 0
|
||||
end
|
||||
|
||||
function SniperScrollingTable.Release(self)
|
||||
self._onRowRemovedHandler = nil
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
--- Registers a script handler.
|
||||
-- @tparam SniperScrollingTable self The sniper scrolling table object
|
||||
-- @tparam string script The script to register for (supported scripts: `OnRowRemoved`)
|
||||
-- @tparam function handler The script handler which will be called with the sniper scrolling table object followed by
|
||||
-- any arguments to the script
|
||||
-- @treturn SniperScrollingTable The sniper scrolling table object
|
||||
function SniperScrollingTable.SetScript(self, script, handler)
|
||||
if script == "OnRowRemoved" then
|
||||
self._onRowRemovedHandler = handler
|
||||
else
|
||||
self.__super:SetScript(script, handler)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function SniperScrollingTable._UpdateData(self, queryChanged)
|
||||
self.__super:_UpdateData(queryChanged)
|
||||
self._highestBrowseId = 0
|
||||
for _, row in ipairs(self._data) do
|
||||
if row:IsSubRow() then
|
||||
local _, _, browseId = row:GetListingInfo()
|
||||
self._highestBrowseId = max(self._highestBrowseId, browseId or 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function SniperScrollingTable._GetSortValue(self, row, id, isAscending)
|
||||
if id == "icon" then
|
||||
if not row:IsSubRow() then
|
||||
return 0
|
||||
end
|
||||
local _, _, browseId = row:GetListingInfo()
|
||||
return -browseId
|
||||
else
|
||||
return self.__super:_GetSortValue(row, id, isAscending)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.RemoveIconClickHandler(self, subRow)
|
||||
if not subRow:IsSubRow() then
|
||||
local baseItemString = subRow:GetBaseItemString()
|
||||
subRow = self._firstSubRowByItem[baseItemString] or subRow
|
||||
end
|
||||
if self._onRowRemovedHandler then
|
||||
self:_onRowRemovedHandler(subRow)
|
||||
end
|
||||
end
|
||||
|
||||
function private.RemoveIconFunction(self, row, isMouseOver)
|
||||
if isMouseOver then
|
||||
return "iconPack.14x14/Close/Default"
|
||||
end
|
||||
local isRecent = true
|
||||
if row:IsSubRow() then
|
||||
local _, _, browseId = row:GetListingInfo()
|
||||
isRecent = self._highestBrowseId == browseId
|
||||
end
|
||||
return isRecent and "iconPack.14x14/Attention" or "iconPack.14x14/Close/Default"
|
||||
end
|
||||
106
Core/UI/Elements/Spacer.lua
Normal file
106
Core/UI/Elements/Spacer.lua
Normal file
@@ -0,0 +1,106 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- Spacer UI Element Class.
|
||||
-- A spacer is a light-weight element which doesn't have any content but can be used to assist with layouts. It is a
|
||||
-- subclass of the @{Element} class.
|
||||
-- @classmod Spacer
|
||||
|
||||
local _, TSM = ...
|
||||
local Spacer = TSM.Include("LibTSMClass").DefineClass("Spacer", TSM.UI.Element)
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(Spacer)
|
||||
TSM.UI.Spacer = Spacer
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Fake Frame Methods
|
||||
-- ============================================================================
|
||||
|
||||
local FAKE_FRAME_MT = {
|
||||
__index = {
|
||||
SetParent = function(self, parent)
|
||||
self._parent = parent
|
||||
end,
|
||||
|
||||
GetParent = function(self)
|
||||
return self._parent
|
||||
end,
|
||||
|
||||
SetScale = function(self, scale)
|
||||
self._scale = scale
|
||||
end,
|
||||
|
||||
GetScale = function(self)
|
||||
return self._scale
|
||||
end,
|
||||
|
||||
SetWidth = function(self, width)
|
||||
self._width = width
|
||||
end,
|
||||
|
||||
GetWidth = function(self)
|
||||
return self._width
|
||||
end,
|
||||
|
||||
SetHeight = function(self, height)
|
||||
self._height = height
|
||||
end,
|
||||
|
||||
GetHeight = function(self)
|
||||
return self._height
|
||||
end,
|
||||
|
||||
Show = function(self)
|
||||
self._visible = true
|
||||
end,
|
||||
|
||||
Hide = function(self)
|
||||
self._visible = false
|
||||
end,
|
||||
|
||||
IsVisible = function(self)
|
||||
return self._visible
|
||||
end,
|
||||
|
||||
ClearAllPoints = function(self)
|
||||
-- do nothing
|
||||
end,
|
||||
|
||||
SetPoint = function(self, ...)
|
||||
-- do nothing
|
||||
end,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function Spacer.__init(self)
|
||||
self.__super:__init(self)
|
||||
local fakeFrame = {
|
||||
_parent = nil,
|
||||
_scale = 1,
|
||||
_width = 0,
|
||||
_height = 0,
|
||||
_visible = false,
|
||||
}
|
||||
self._fakeFrame = setmetatable(fakeFrame, FAKE_FRAME_MT)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function Spacer._GetBaseFrame(self)
|
||||
return self._fakeFrame
|
||||
end
|
||||
113
Core/UI/Elements/TabGroup.lua
Normal file
113
Core/UI/Elements/TabGroup.lua
Normal file
@@ -0,0 +1,113 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- TabGroup UI Element Class.
|
||||
-- A tab group uses text and a horizontal line to denote the tabs, with coloring indicating the one which is selected.
|
||||
-- It is a subclass of the @{ViewContainer} class.
|
||||
-- @classmod TabGroup
|
||||
|
||||
local _, TSM = ...
|
||||
local TabGroup = TSM.Include("LibTSMClass").DefineClass("TabGroup", TSM.UI.ViewContainer)
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(TabGroup)
|
||||
TSM.UI.TabGroup = TabGroup
|
||||
local private = {}
|
||||
local BUTTON_HEIGHT = 24
|
||||
local BUTTON_PADDING_BOTTOM = 4
|
||||
local LINE_THICKNESS = 2
|
||||
local LINE_THICKNESS_SELECTED = 2
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function TabGroup.__init(self)
|
||||
self.__super:__init()
|
||||
self._buttons = {}
|
||||
end
|
||||
|
||||
function TabGroup.Acquire(self)
|
||||
self.__super.__super:AddChildNoLayout(UIElements.New("Frame", "buttons")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:AddAnchor("TOPLEFT")
|
||||
:AddAnchor("TOPRIGHT")
|
||||
)
|
||||
self.__super:Acquire()
|
||||
end
|
||||
|
||||
function TabGroup.Release(self)
|
||||
wipe(self._buttons)
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function TabGroup._GetContentPadding(self, side)
|
||||
if side == "TOP" then
|
||||
return BUTTON_HEIGHT + BUTTON_PADDING_BOTTOM + LINE_THICKNESS
|
||||
end
|
||||
return self.__super:_GetContentPadding(side)
|
||||
end
|
||||
|
||||
function TabGroup.Draw(self)
|
||||
self.__super.__super.__super:Draw()
|
||||
|
||||
local selectedPath = self:GetPath()
|
||||
local buttons = self:GetElement("buttons")
|
||||
buttons:SetHeight(BUTTON_HEIGHT + BUTTON_PADDING_BOTTOM + LINE_THICKNESS)
|
||||
buttons:ReleaseAllChildren()
|
||||
for i, buttonPath in ipairs(self._pathsList) do
|
||||
local isSelected = buttonPath == selectedPath
|
||||
buttons:AddChild(UIElements.New("Frame", self._id.."_Tab"..i)
|
||||
:SetLayout("VERTICAL")
|
||||
:AddChild(UIElements.New("Button", "button")
|
||||
:SetMargin(0, 0, 0, BUTTON_PADDING_BOTTOM)
|
||||
:SetFont("BODY_BODY1_BOLD")
|
||||
:SetJustifyH("CENTER")
|
||||
:SetTextColor(isSelected and "INDICATOR" or "TEXT_ALT")
|
||||
:SetContext(self)
|
||||
:SetText(buttonPath)
|
||||
:SetScript("OnEnter", not isSelected and private.OnButtonEnter)
|
||||
:SetScript("OnLeave", not isSelected and private.OnButtonLeave)
|
||||
:SetScript("OnClick", private.OnButtonClicked)
|
||||
)
|
||||
:AddChild(UIElements.New("Texture", "line")
|
||||
:SetHeight(isSelected and LINE_THICKNESS_SELECTED or LINE_THICKNESS)
|
||||
:SetTexture(isSelected and "INDICATOR" or "TEXT_ALT")
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
self.__super:Draw()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.OnButtonEnter(button)
|
||||
button:SetTextColor("TEXT")
|
||||
:Draw()
|
||||
end
|
||||
|
||||
function private.OnButtonLeave(button)
|
||||
button:SetTextColor("TEXT_ALT")
|
||||
:Draw()
|
||||
end
|
||||
|
||||
function private.OnButtonClicked(button)
|
||||
local self = button:GetContext()
|
||||
local path = button:GetText()
|
||||
self:SetPath(path, self:GetPath() ~= path)
|
||||
end
|
||||
217
Core/UI/Elements/Text.lua
Normal file
217
Core/UI/Elements/Text.lua
Normal file
@@ -0,0 +1,217 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- Text UI Element Class.
|
||||
-- A text element simply holds a text string. It is a subclass of the @{Element} class.
|
||||
-- @classmod Text
|
||||
|
||||
local _, TSM = ...
|
||||
local Text = TSM.Include("LibTSMClass").DefineClass("Text", TSM.UI.Element)
|
||||
local Color = TSM.Include("Util.Color")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(Text)
|
||||
TSM.UI.Text = Text
|
||||
local STRING_RIGHT_PADDING = 4
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function Text.__init(self, frame)
|
||||
frame = frame or UIElements.CreateFrame(self, "Frame")
|
||||
self.__super:__init(frame)
|
||||
frame.text = UIElements.CreateFontString(self, frame)
|
||||
|
||||
self._textStr = ""
|
||||
self._autoWidth = false
|
||||
self._textColor = "TEXT"
|
||||
self._font = nil
|
||||
self._justifyH = "LEFT"
|
||||
self._justifyV = "MIDDLE"
|
||||
end
|
||||
|
||||
function Text.Release(self)
|
||||
self._textStr = ""
|
||||
self._autoWidth = false
|
||||
self._textColor = "TEXT"
|
||||
self._font = nil
|
||||
self._justifyH = "LEFT"
|
||||
self._justifyV = "MIDDLE"
|
||||
self:_GetBaseFrame().text:SetSpacing(0)
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
--- Sets the width of the text.
|
||||
-- @tparam Text self The text object
|
||||
-- @tparam ?number|string width The width of the text, "AUTO" to set the width based on the length
|
||||
-- of the text, or nil to have an undefined width
|
||||
-- @treturn Text The text object
|
||||
function Text.SetWidth(self, width)
|
||||
if width == "AUTO" then
|
||||
self._autoWidth = true
|
||||
else
|
||||
self._autoWidth = false
|
||||
self.__super:SetWidth(width)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the font.
|
||||
-- @tparam Text self The text object
|
||||
-- @tparam string font The font key
|
||||
-- @treturn Text The text object
|
||||
function Text.SetFont(self, font)
|
||||
assert(Theme.GetFont(font))
|
||||
self._font = font
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the color of the text.
|
||||
-- @tparam Text self The text object
|
||||
-- @tparam Color|string color The text color as a Color object or a theme color key
|
||||
-- @treturn Text The text object
|
||||
function Text.SetTextColor(self, color)
|
||||
assert((type(color) == "string" and Theme.GetColor(color)) or Color.IsInstance(color))
|
||||
self._textColor = color
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the horizontal justification of the text.
|
||||
-- @tparam Text self The text object
|
||||
-- @tparam string justifyH The horizontal justification (either "LEFT", "CENTER" or "RIGHT")
|
||||
-- @treturn Text The text object
|
||||
function Text.SetJustifyH(self, justifyH)
|
||||
assert(justifyH == "LEFT" or justifyH == "CENTER" or justifyH == "RIGHT")
|
||||
self._justifyH = justifyH
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the vertical justification of the text.
|
||||
-- @tparam Text self The text object
|
||||
-- @tparam string justifyV The vertical justification (either "TOP", "MIDDLE" or "BOTTOM")
|
||||
-- @treturn Text The text object
|
||||
function Text.SetJustifyV(self, justifyV)
|
||||
assert(justifyV == "TOP" or justifyV == "MIDDLE" or justifyV == "BOTTOM")
|
||||
self._justifyV = justifyV
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the text.
|
||||
-- @tparam Text self The text object
|
||||
-- @tparam ?string|number text The text
|
||||
-- @treturn Text The text object
|
||||
function Text.SetText(self, text)
|
||||
if type(text) == "number" then
|
||||
text = tostring(text)
|
||||
end
|
||||
assert(type(text) == "string")
|
||||
self._textStr = text
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set formatted text.
|
||||
-- @tparam Text self The text object
|
||||
-- @tparam vararg ... The format string and parameters
|
||||
-- @treturn Text The text object
|
||||
function Text.SetFormattedText(self, ...)
|
||||
self:SetText(format(...))
|
||||
return self
|
||||
end
|
||||
|
||||
--- Gets the text string.
|
||||
-- @tparam Text self The text object
|
||||
-- @treturn string The text string
|
||||
function Text.GetText(self)
|
||||
return self._textStr
|
||||
end
|
||||
|
||||
--- Get the rendered text string width.
|
||||
-- @tparam Text self The text object
|
||||
-- @treturn number The rendered text string width
|
||||
function Text.GetStringWidth(self)
|
||||
local text = self:_GetBaseFrame().text
|
||||
self:_ApplyFont()
|
||||
text:SetText(self._textStr)
|
||||
return text:GetStringWidth()
|
||||
end
|
||||
|
||||
--- Get the rendered text string height.
|
||||
-- @tparam Text self The text object
|
||||
-- @treturn number The rendered text string height
|
||||
function Text.GetStringHeight(self)
|
||||
local text = self:_GetBaseFrame().text
|
||||
self:_ApplyFont()
|
||||
text:SetText(self._textStr)
|
||||
return text:GetStringHeight()
|
||||
end
|
||||
|
||||
function Text.Draw(self)
|
||||
self.__super:Draw()
|
||||
|
||||
local text = self:_GetBaseFrame().text
|
||||
text:ClearAllPoints()
|
||||
text:SetAllPoints()
|
||||
|
||||
-- set the font
|
||||
self:_ApplyFont()
|
||||
|
||||
-- set the justification
|
||||
text:SetJustifyH(self._justifyH)
|
||||
text:SetJustifyV(self._justifyV)
|
||||
|
||||
-- set the text color
|
||||
text:SetTextColor(self:_GetTextColor():GetFractionalRGBA())
|
||||
|
||||
-- set the text
|
||||
text:SetText(self._textStr)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function Text._GetTextColor(self)
|
||||
if type(self._textColor) == "string" then
|
||||
return Theme.GetColor(self._textColor)
|
||||
else
|
||||
assert(Color.IsInstance(self._textColor))
|
||||
return self._textColor
|
||||
end
|
||||
end
|
||||
|
||||
function Text._GetMinimumDimension(self, dimension)
|
||||
if dimension == "WIDTH" and self._autoWidth then
|
||||
return 0, self._width == nil
|
||||
else
|
||||
return self.__super:_GetMinimumDimension(dimension)
|
||||
end
|
||||
end
|
||||
|
||||
function Text._GetPreferredDimension(self, dimension)
|
||||
if dimension == "WIDTH" and self._autoWidth then
|
||||
return self:GetStringWidth() + STRING_RIGHT_PADDING
|
||||
else
|
||||
return self.__super:_GetPreferredDimension(dimension)
|
||||
end
|
||||
end
|
||||
|
||||
function Text._ApplyFont(self)
|
||||
local text = self:_GetBaseFrame().text
|
||||
local font = Theme.GetFont(self._font)
|
||||
text:SetFont(font:GetWowFont())
|
||||
-- There's a Blizzard bug where spacing incorrectly gets applied to embedded textures, so just set it to 0 in that case
|
||||
-- TODO: come up with a better fix if we need multi-line text with embedded textures
|
||||
if strfind(self._textStr, "\124T") then
|
||||
text:SetSpacing(0)
|
||||
else
|
||||
text:SetSpacing(font:GetSpacing())
|
||||
end
|
||||
end
|
||||
106
Core/UI/Elements/Texture.lua
Normal file
106
Core/UI/Elements/Texture.lua
Normal file
@@ -0,0 +1,106 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- Texture UI Element Class.
|
||||
-- This is a simple, light-weight element which is used to display a texture. It is a subclass of the @{Element} class.
|
||||
-- @classmod Texture
|
||||
|
||||
local _, TSM = ...
|
||||
local Texture = TSM.Include("LibTSMClass").DefineClass("Texture", TSM.UI.Element)
|
||||
local Color = TSM.Include("Util.Color")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local ItemInfo = TSM.Include("Service.ItemInfo")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(Texture)
|
||||
TSM.UI.Texture = Texture
|
||||
local private = {}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function Texture.__init(self)
|
||||
local texture = UIParent:CreateTexture()
|
||||
-- hook SetParent/GetParent since textures can't have a nil parent
|
||||
texture._oldSetParent = texture.SetParent
|
||||
texture.SetParent = private.SetParent
|
||||
texture.GetParent = private.GetParent
|
||||
self.__super:__init(texture)
|
||||
self._texture = nil
|
||||
end
|
||||
|
||||
function Texture.Release(self)
|
||||
self._texture = nil
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
--- Sets the texture.
|
||||
-- @tparam Texture self The texture object
|
||||
-- @tparam ?string|number texture Either a texture pack string, itemString, WoW file id, or theme color key
|
||||
-- @treturn Texture The texture object
|
||||
function Texture.SetTexture(self, texture)
|
||||
self._texture = texture
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the texture and size based on a texture pack string.
|
||||
-- @tparam Texture self The texture object
|
||||
-- @tparam string texturePack A texture pack string
|
||||
-- @treturn Texture The texture object
|
||||
function Texture.SetTextureAndSize(self, texturePack)
|
||||
self:SetTexture(texturePack)
|
||||
self:SetSize(TSM.UI.TexturePacks.GetSize(texturePack))
|
||||
return self
|
||||
end
|
||||
|
||||
function Texture.Draw(self)
|
||||
self.__super:Draw()
|
||||
|
||||
local texture = self:_GetBaseFrame()
|
||||
texture:SetTexture(nil)
|
||||
texture:SetTexCoord(0, 1, 0, 1)
|
||||
texture:SetVertexColor(1, 1, 1, 1)
|
||||
|
||||
if type(self._texture) == "string" and TSM.UI.TexturePacks.IsValid(self._texture) then
|
||||
-- this is a texture pack
|
||||
TSM.UI.TexturePacks.SetTexture(texture, self._texture)
|
||||
elseif type(self._texture) == "string" and strmatch(self._texture, "^[ip]:%d+") then
|
||||
-- this is an itemString
|
||||
texture:SetTexture(ItemInfo.GetTexture(self._texture))
|
||||
elseif type(self._texture) == "string" then
|
||||
-- this is a theme color key
|
||||
texture:SetColorTexture(Theme.GetColor(self._texture):GetFractionalRGBA())
|
||||
elseif type(self._texture) == "number" then
|
||||
-- this is a wow file id
|
||||
texture:SetTexture(self._texture)
|
||||
elseif Color.IsInstance(self._texture) then
|
||||
texture:SetColorTexture(self._texture:GetFractionalRGBA())
|
||||
else
|
||||
error("Invalid texture: "..tostring(self._texture))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.SetParent(self, parent)
|
||||
self._parent = parent
|
||||
if parent then
|
||||
self:Show()
|
||||
else
|
||||
self:Hide()
|
||||
end
|
||||
self:_oldSetParent(parent or UIParent)
|
||||
end
|
||||
|
||||
function private.GetParent(self)
|
||||
return self._parent
|
||||
end
|
||||
180
Core/UI/Elements/Toggle.lua
Normal file
180
Core/UI/Elements/Toggle.lua
Normal file
@@ -0,0 +1,180 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- Toggle UI Element Class.
|
||||
-- A toggle element allows the user to select between a fixed set of options. It is a subclass of the @{Container} class.
|
||||
-- @classmod Toggle
|
||||
|
||||
local _, TSM = ...
|
||||
local Toggle = TSM.Include("LibTSMClass").DefineClass("Toggle", TSM.UI.Container)
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(Toggle)
|
||||
TSM.UI.Toggle = Toggle
|
||||
local private = {}
|
||||
local BUTTON_PADDING = 16
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function Toggle.__init(self)
|
||||
local frame = UIElements.CreateFrame(self, "Frame")
|
||||
|
||||
self.__super:__init(frame)
|
||||
|
||||
self._optionsList = {}
|
||||
self._buttons = {}
|
||||
self._onValueChangedHandler = nil
|
||||
self._selectedOption = nil
|
||||
self._booleanKey = nil
|
||||
self._font = "BODY_BODY3"
|
||||
end
|
||||
|
||||
function Toggle.Release(self)
|
||||
wipe(self._optionsList)
|
||||
wipe(self._buttons)
|
||||
self._onValueChangedHandler = nil
|
||||
self._selectedOption = nil
|
||||
self._booleanKey = nil
|
||||
self._font = "BODY_BODY3"
|
||||
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
--- Add an option.
|
||||
-- @tparam Toggle self The toggle object
|
||||
-- @tparam string option The text that goes with the option
|
||||
-- @tparam boolean setSelected Whether or not to set this as the selected option
|
||||
-- @treturn Toggle The toggle object
|
||||
function Toggle.AddOption(self, option, setSelected)
|
||||
tinsert(self._optionsList, option)
|
||||
if setSelected then
|
||||
self:SetOption(option)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the currently selected option.
|
||||
-- @tparam Toggle self The toggle object
|
||||
-- @tparam string option The selected option
|
||||
-- @tparam boolean redraw Whether or not to redraw the toggle
|
||||
-- @treturn Toggle The toggle object
|
||||
function Toggle.SetOption(self, option, redraw)
|
||||
if option ~= self._selectedOption then
|
||||
self._selectedOption = option
|
||||
if self._onValueChangedHandler then
|
||||
self:_onValueChangedHandler(option)
|
||||
end
|
||||
end
|
||||
if redraw then
|
||||
self:Draw()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Clears the currently selected option.
|
||||
-- @tparam Toggle self The toggle object
|
||||
-- @tparam boolean redraw Whether or not to redraw the toggle
|
||||
-- @treturn Toggle The toggle object
|
||||
function Toggle.ClearOption(self, redraw)
|
||||
self._selectedOption = nil
|
||||
if redraw then
|
||||
self:Draw()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets whether or not the toggle is disabled.
|
||||
-- @tparam Toggle self The toggle object
|
||||
-- @tparam boolean disabled Whether or not the toggle is disabled
|
||||
-- @treturn Toggle The toggle object
|
||||
function Toggle.SetDisabled(self, disabled)
|
||||
self._disabled = disabled
|
||||
return self
|
||||
end
|
||||
|
||||
--- Registers a script handler.
|
||||
-- @tparam Toggle self The toggle object
|
||||
-- @tparam string script The script to register for (supported scripts: `OnValueChanged`)
|
||||
-- @tparam function handler The script handler which will be called with the toggle object followed by any arguments to
|
||||
-- the script
|
||||
-- @treturn Toggle The toggle object
|
||||
function Toggle.SetScript(self, script, handler)
|
||||
if script == "OnValueChanged" then
|
||||
self._onValueChangedHandler = handler
|
||||
else
|
||||
error("Unknown Toggle script: "..tostring(script))
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function Toggle.SetFont(self, font)
|
||||
self._font = font
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the selected option.
|
||||
-- @tparam Toggle self The toggle object
|
||||
-- @treturn string The selected option
|
||||
function Toggle.GetValue(self)
|
||||
return self._selectedOption
|
||||
end
|
||||
|
||||
function Toggle.Draw(self)
|
||||
self.__super.__super:Draw()
|
||||
-- add new buttons if necessary
|
||||
while #self._buttons < #self._optionsList do
|
||||
local num = #self._buttons + 1
|
||||
local button = UIElements.New("Checkbox", self._id.."_Button"..num)
|
||||
:SetFont(self._font)
|
||||
:SetScript("OnValueChanged", private.ButtonOnClick)
|
||||
self:AddChildNoLayout(button)
|
||||
tinsert(self._buttons, button)
|
||||
end
|
||||
|
||||
local selectedPath = self._selectedOption
|
||||
local height = self:_GetDimension("HEIGHT")
|
||||
local buttonWidth = (self:_GetDimension("WIDTH") / #self._buttons) + BUTTON_PADDING
|
||||
local offsetX = 0
|
||||
for i, button in ipairs(self._buttons) do
|
||||
local buttonPath = self._optionsList[i]
|
||||
if i <= #self._optionsList then
|
||||
button:SetFont(self._font)
|
||||
button:SetWidth("AUTO")
|
||||
button:SetTheme("RADIO")
|
||||
button:SetCheckboxPosition("LEFT")
|
||||
button:SetText(buttonPath)
|
||||
button:SetSize(buttonWidth, height)
|
||||
button:SetDisabled(self._disabled)
|
||||
button:WipeAnchors()
|
||||
button:AddAnchor("TOPLEFT", offsetX, 0)
|
||||
offsetX = offsetX + buttonWidth
|
||||
else
|
||||
button:Hide()
|
||||
end
|
||||
|
||||
if buttonPath == selectedPath then
|
||||
button:SetChecked(true, true)
|
||||
else
|
||||
button:SetChecked(false, true)
|
||||
end
|
||||
end
|
||||
|
||||
self.__super:Draw()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.ButtonOnClick(button)
|
||||
local self = button:GetParentElement()
|
||||
self:SetOption(button:GetText(), true)
|
||||
end
|
||||
197
Core/UI/Elements/ToggleOnOff.lua
Normal file
197
Core/UI/Elements/ToggleOnOff.lua
Normal file
@@ -0,0 +1,197 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- ToggleOnOff UI Element Class.
|
||||
-- This is a simple on/off toggle which uses different textures for the different states. It is a subclass of the
|
||||
-- @{Container} class.
|
||||
-- @classmod ToggleOnOff
|
||||
|
||||
local _, TSM = ...
|
||||
local ToggleOnOff = TSM.Include("LibTSMClass").DefineClass("ToggleOnOff", TSM.UI.Container)
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(ToggleOnOff)
|
||||
TSM.UI.ToggleOnOff = ToggleOnOff
|
||||
local private = {}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function ToggleOnOff.__init(self)
|
||||
local frame = UIElements.CreateFrame(self, "Frame")
|
||||
|
||||
self.__super:__init(frame)
|
||||
|
||||
self._value = false
|
||||
self._disabled = false
|
||||
self._settingTable = nil
|
||||
self._settingKey = nil
|
||||
self._onValueChangedHandler = nil
|
||||
end
|
||||
|
||||
function ToggleOnOff.Acquire(self)
|
||||
local frame = self:_GetBaseFrame()
|
||||
self:AddChildNoLayout(UIElements.New("Frame", "toggle")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:AddAnchor("TOPLEFT", frame)
|
||||
:AddAnchor("BOTTOMRIGHT", frame)
|
||||
:SetContext(self)
|
||||
:AddChild(UIElements.New("Checkbox", "yes")
|
||||
:SetWidth("AUTO")
|
||||
:SetTheme("RADIO")
|
||||
:SetFont("BODY_BODY2")
|
||||
:SetText(YES)
|
||||
:SetCheckboxPosition("LEFT")
|
||||
:SetScript("OnValueChanged", private.OnYesClickHandler)
|
||||
)
|
||||
:AddChild(UIElements.New("Checkbox", "no")
|
||||
:SetWidth("AUTO")
|
||||
:SetTheme("RADIO")
|
||||
:SetFont("BODY_BODY2")
|
||||
:SetMargin(8, 0, 0, 0)
|
||||
:SetText(NO)
|
||||
:SetCheckboxPosition("LEFT")
|
||||
:SetScript("OnValueChanged", private.OnNoClickHandler)
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
)
|
||||
|
||||
self.__super:Acquire()
|
||||
end
|
||||
|
||||
function ToggleOnOff.Release(self)
|
||||
self._value = false
|
||||
self._disabled = false
|
||||
self._settingTable = nil
|
||||
self._settingKey = nil
|
||||
self._onValueChangedHandler = nil
|
||||
--self:_GetBaseFrame():Enable()
|
||||
|
||||
self.__super:Release()
|
||||
end
|
||||
|
||||
--- Sets the setting info.
|
||||
-- This method is used to have the value of the toggle automatically correspond with the value of a field in a table.
|
||||
-- This is useful for toggles which are tied directly to settings.
|
||||
-- @tparam ToggleOnOff self The toggles object
|
||||
-- @tparam table tbl The table which the field to set belongs to
|
||||
-- @tparam string key The key into the table to be set based on the toggle's state
|
||||
-- @treturn ToggleOnOff The toggles object
|
||||
function ToggleOnOff.SetSettingInfo(self, tbl, key)
|
||||
self._settingTable = tbl
|
||||
self._settingKey = key
|
||||
self._value = tbl[key]
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets whether or not the toggle is disabled.
|
||||
-- @tparam ToggleOnOff self The toggles object
|
||||
-- @tparam boolean disabled Whether or not the toggle is disabled
|
||||
-- @tparam boolean redraw Whether or not to redraw the toggle
|
||||
-- @treturn ToggleOnOff The toggles object
|
||||
function ToggleOnOff.SetDisabled(self, disabled, redraw)
|
||||
self._disabled = disabled
|
||||
if disabled then
|
||||
self:GetElement("toggle.yes"):SetDisabled(true)
|
||||
self:GetElement("toggle.no"):SetDisabled(true)
|
||||
else
|
||||
self:GetElement("toggle.yes"):SetDisabled(false)
|
||||
self:GetElement("toggle.no"):SetDisabled(false)
|
||||
end
|
||||
if redraw then
|
||||
self:Draw()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the value of the toggle.
|
||||
-- @tparam ToggleOnOff self The toggles object
|
||||
-- @tparam boolean value Whether the value is on (true) or off (false)
|
||||
-- @tparam boolean redraw Whether or not to redraw the toggle
|
||||
-- @treturn ToggleOnOff The toggles object
|
||||
function ToggleOnOff.SetValue(self, value, redraw)
|
||||
if value ~= self._value then
|
||||
self._value = value
|
||||
if self._settingTable then
|
||||
self._settingTable[self._settingKey] = value
|
||||
end
|
||||
if self._onValueChangedHandler then
|
||||
self:_onValueChangedHandler(value)
|
||||
end
|
||||
end
|
||||
if redraw then
|
||||
self:Draw()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Registers a script handler.
|
||||
-- @tparam ToggleOnOff self The toggles object
|
||||
-- @tparam string script The script to register for (supported scripts: `OnValueChanged`)
|
||||
-- @tparam function handler The script handler which will be called with the toggles object followed by any
|
||||
-- arguments to the script
|
||||
-- @treturn ToggleOnOff The toggles object
|
||||
function ToggleOnOff.SetScript(self, script, handler)
|
||||
if script == "OnValueChanged" then
|
||||
self._onValueChangedHandler = handler
|
||||
else
|
||||
error("Unknown ToggleOnOff script: "..tostring(script))
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the value of the toggle.
|
||||
-- @tparam ToggleOnOff self The toggles object
|
||||
-- @treturn boolean The value of the toggle
|
||||
function ToggleOnOff.GetValue(self)
|
||||
return self._value
|
||||
end
|
||||
|
||||
function ToggleOnOff.Draw(self)
|
||||
if self._value then
|
||||
self:GetElement("toggle.yes"):SetChecked(true, true)
|
||||
self:GetElement("toggle.no"):SetChecked(false, true)
|
||||
else
|
||||
self:GetElement("toggle.yes"):SetChecked(false, true)
|
||||
self:GetElement("toggle.no"):SetChecked(true, true)
|
||||
end
|
||||
|
||||
if self._disabled then
|
||||
self:GetElement("toggle.yes"):SetDisabled(true)
|
||||
self:GetElement("toggle.no"):SetDisabled(true)
|
||||
else
|
||||
self:GetElement("toggle.yes"):SetDisabled(false)
|
||||
self:GetElement("toggle.no"):SetDisabled(false)
|
||||
end
|
||||
|
||||
self.__super:Draw()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.OnYesClickHandler(button)
|
||||
if not button:IsChecked() then
|
||||
button:SetChecked(true, true)
|
||||
return
|
||||
end
|
||||
local self = button:GetParentElement():GetContext()
|
||||
self:SetValue(true, true)
|
||||
end
|
||||
|
||||
function private.OnNoClickHandler(button)
|
||||
if not button:IsChecked() then
|
||||
button:SetChecked(true, true)
|
||||
return
|
||||
end
|
||||
local self = button:GetParentElement():GetContext()
|
||||
self:SetValue(false, true)
|
||||
end
|
||||
238
Core/UI/Elements/ViewContainer.lua
Normal file
238
Core/UI/Elements/ViewContainer.lua
Normal file
@@ -0,0 +1,238 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- ViewContainer UI Element Class.
|
||||
-- A view container allows the content to be changed depending on the selected view (called the path). It is a subclass of the @{Container} class.
|
||||
-- @classmod ViewContainer
|
||||
|
||||
local _, TSM = ...
|
||||
local ViewContainer = TSM.Include("LibTSMClass").DefineClass("ViewContainer", TSM.UI.Container)
|
||||
local Table = TSM.Include("Util.Table")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
UIElements.Register(ViewContainer)
|
||||
TSM.UI.ViewContainer = ViewContainer
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function ViewContainer.__init(self)
|
||||
local frame = UIElements.CreateFrame(self, "Frame")
|
||||
self.__super:__init(frame)
|
||||
self._pathsList = {}
|
||||
self._contextTable = nil
|
||||
self._defaultContextTable = nil
|
||||
end
|
||||
|
||||
function ViewContainer.Acquire(self)
|
||||
self._path = nil
|
||||
self._navCallback = nil
|
||||
self.__super:Acquire()
|
||||
end
|
||||
|
||||
function ViewContainer.Release(self)
|
||||
wipe(self._pathsList)
|
||||
self.__super:Release()
|
||||
self._contextTable = nil
|
||||
self._defaultContextTable = nil
|
||||
end
|
||||
|
||||
function ViewContainer.SetLayout(self, layout)
|
||||
error("ViewContainer doesn't support this method")
|
||||
end
|
||||
|
||||
function ViewContainer.AddChild(self, child)
|
||||
error("ViewContainer doesn't support this method")
|
||||
end
|
||||
|
||||
function ViewContainer.AddChildNoLayout(self, child)
|
||||
error("ViewContainer doesn't support this method")
|
||||
end
|
||||
|
||||
--- Set the navigation callback.
|
||||
-- @tparam ViewContainer self The view container object
|
||||
-- @tparam function callback The function called when the selected path changes to get the new content
|
||||
-- @treturn ViewContainer The view container object
|
||||
function ViewContainer.SetNavCallback(self, callback)
|
||||
self._navCallback = callback
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a path (view).
|
||||
-- @tparam ViewContainer self The view container object
|
||||
-- @tparam string path The path
|
||||
-- @tparam[opt=false] boolean setSelected Set this as the selected path (view)
|
||||
-- @treturn ViewContainer The view container object
|
||||
function ViewContainer.AddPath(self, path, setSelected)
|
||||
tinsert(self._pathsList, path)
|
||||
if self._contextTable then
|
||||
assert(setSelected == nil, "Cannot set selected path when using a context table")
|
||||
local newPathIndex = Table.KeyByValue(self._pathsList, path)
|
||||
if self._contextTable.pathIndex == newPathIndex then
|
||||
self:SetPath(path)
|
||||
end
|
||||
elseif setSelected then
|
||||
self:SetPath(path)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Renames a path (view).
|
||||
-- @tparam ViewContainer self The view container object
|
||||
-- @tparam string path The new path
|
||||
-- @tparam number index The index of the path to change
|
||||
-- @treturn ViewContainer The view container object
|
||||
function ViewContainer.RenamePath(self, path, index)
|
||||
local changePath = self._pathsList[index] == self._path
|
||||
self._pathsList[index] = path
|
||||
|
||||
if changePath then
|
||||
self:SetPath(path)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the selected path (view).
|
||||
-- @tparam ViewContainer self The view container object
|
||||
-- @tparam string path The selected path
|
||||
-- @tparam boolean redraw Whether or not to redraw the view container
|
||||
-- @treturn ViewContainer The view container object
|
||||
function ViewContainer.SetPath(self, path, redraw)
|
||||
if path ~= self._path then
|
||||
local child = self:_GetChild()
|
||||
if child then
|
||||
assert(#self._layoutChildren == 1)
|
||||
self:RemoveChild(child)
|
||||
child:Release()
|
||||
end
|
||||
self.__super:AddChild(self:_navCallback(path))
|
||||
self._path = path
|
||||
-- Save the path index of the new selected path to the context table
|
||||
if self._contextTable then
|
||||
self._contextTable.pathIndex = Table.KeyByValue(self._pathsList, path)
|
||||
end
|
||||
end
|
||||
if redraw then
|
||||
self:Draw()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Reload the current view.
|
||||
-- @tparam ViewContainer self The view container object
|
||||
function ViewContainer.ReloadContent(self)
|
||||
local path = self._path
|
||||
self._path = nil
|
||||
self:SetPath(path, true)
|
||||
end
|
||||
|
||||
--- Get the current path (view).
|
||||
-- @tparam ViewContainer self The view container object
|
||||
-- @treturn string The current path
|
||||
function ViewContainer.GetPath(self)
|
||||
return self._path
|
||||
end
|
||||
|
||||
--- Get a list of the paths for the view container.
|
||||
-- @tparam ViewContainer self The view container object
|
||||
-- @treturn table The path list
|
||||
function ViewContainer.GetPathList(self)
|
||||
return self._pathsList
|
||||
end
|
||||
|
||||
function ViewContainer.Draw(self)
|
||||
self.__super.__super:Draw()
|
||||
local child = self:_GetChild()
|
||||
local childFrame = child:_GetBaseFrame()
|
||||
|
||||
-- set the child to be full-size
|
||||
childFrame:ClearAllPoints()
|
||||
local xOffset, yOffset = child:_GetMarginAnchorOffsets("BOTTOMLEFT")
|
||||
local paddingXOffset, paddingYOffset = self:_GetPaddingAnchorOffsets("BOTTOMLEFT")
|
||||
xOffset = xOffset + paddingXOffset - self:_GetContentPadding("LEFT")
|
||||
yOffset = yOffset + paddingYOffset - self:_GetContentPadding("BOTTOM")
|
||||
childFrame:SetPoint("BOTTOMLEFT", xOffset, yOffset)
|
||||
xOffset, yOffset = child:_GetMarginAnchorOffsets("TOPRIGHT")
|
||||
paddingXOffset, paddingYOffset = self:_GetPaddingAnchorOffsets("TOPRIGHT")
|
||||
xOffset = xOffset + paddingXOffset - self:_GetContentPadding("RIGHT")
|
||||
yOffset = yOffset + paddingYOffset - self:_GetContentPadding("TOP")
|
||||
childFrame:SetPoint("TOPRIGHT", xOffset, yOffset)
|
||||
child:Draw()
|
||||
|
||||
-- draw the no-layout children
|
||||
for _, noLayoutChild in ipairs(self._noLayoutChildren) do
|
||||
noLayoutChild:Draw()
|
||||
end
|
||||
end
|
||||
|
||||
--- Sets the context table.
|
||||
-- This table can be used to save which tab is active, refrenced by the path index
|
||||
-- @tparam ViewContainer self The view container object
|
||||
-- @tparam table tbl The context table
|
||||
-- @tparam table defaultTbl Default values
|
||||
-- @treturn ViewContainer The view container object
|
||||
function ViewContainer.SetContextTable(self, tbl, defaultTbl)
|
||||
assert(defaultTbl.pathIndex ~= nil)
|
||||
tbl.pathIndex = tbl.pathIndex or defaultTbl.pathIndex
|
||||
self._contextTable = tbl
|
||||
self._defaultContextTable = defaultTbl
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the context table from a settings object.
|
||||
-- @tparam ViewContainer self The view container object
|
||||
-- @tparam Settings settings The settings object
|
||||
-- @tparam string key The setting key
|
||||
-- @treturn ViewContainer The view container object
|
||||
function ViewContainer.SetSettingsContext(self, settings, key)
|
||||
return self:SetContextTable(settings[key], settings:GetDefaultReadOnly(key))
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function ViewContainer._GetMinimumDimension(self, dimension)
|
||||
if dimension == "WIDTH" then
|
||||
local width = self._width
|
||||
if width then
|
||||
return width, false
|
||||
else
|
||||
return self:_GetChild():_GetMinimumDimension(dimension)
|
||||
end
|
||||
elseif dimension == "HEIGHT" then
|
||||
local height = self._height
|
||||
if height then
|
||||
return height, false
|
||||
else
|
||||
return self:_GetChild():_GetMinimumDimension(dimension)
|
||||
end
|
||||
else
|
||||
error("Invalid dimension: "..tostring(dimension))
|
||||
end
|
||||
end
|
||||
|
||||
function ViewContainer._GetContentPadding(self, side)
|
||||
if side == "TOP" then
|
||||
return 0
|
||||
elseif side == "BOTTOM" then
|
||||
return 0
|
||||
elseif side == "LEFT" then
|
||||
return 0
|
||||
elseif side == "RIGHT" then
|
||||
return 0
|
||||
else
|
||||
error("Invalid side: "..tostring(side))
|
||||
end
|
||||
end
|
||||
|
||||
function ViewContainer._GetChild(self)
|
||||
return self._layoutChildren[1]
|
||||
end
|
||||
Reference in New Issue
Block a user