initial commit
This commit is contained in:
1758
Core/UI/AuctionUI/Auctioning.lua
Normal file
1758
Core/UI/AuctionUI/Auctioning.lua
Normal file
File diff suppressed because it is too large
Load Diff
441
Core/UI/AuctionUI/BuyUtil.lua
Normal file
441
Core/UI/AuctionUI/BuyUtil.lua
Normal file
@@ -0,0 +1,441 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local BuyUtil = TSM.UI.AuctionUI:NewPackage("BuyUtil")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local Money = TSM.Include("Util.Money")
|
||||
local Log = TSM.Include("Util.Log")
|
||||
local Math = TSM.Include("Util.Math")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local ItemInfo = TSM.Include("Service.ItemInfo")
|
||||
local CustomPrice = TSM.Include("Service.CustomPrice")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local private = {
|
||||
totalBuyout = nil,
|
||||
isBuy = nil,
|
||||
auctionScan = nil,
|
||||
subRow = nil,
|
||||
index = nil,
|
||||
noSeller = nil,
|
||||
baseFrame = nil,
|
||||
dialogFrame = nil,
|
||||
future = nil,
|
||||
prepareQuantity = nil,
|
||||
prepareSuccess = false,
|
||||
marketValueFunc = nil,
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function BuyUtil.ShowConfirmation(baseFrame, subRow, isBuy, auctionNum, numFound, maxQuantity, callback, auctionScan, index, noSeller, marketValueFunc)
|
||||
auctionNum = min(auctionNum, numFound)
|
||||
local buyout = subRow:GetBuyouts()
|
||||
if not isBuy then
|
||||
buyout = subRow:GetRequiredBid(subRow)
|
||||
end
|
||||
local quantity = subRow:GetQuantities()
|
||||
local itemString = subRow:GetItemString()
|
||||
local _, _, _, isHighBidder = subRow:GetBidInfo()
|
||||
local isCommodity = not TSM.IsWowClassic() and subRow:IsCommodity()
|
||||
local shouldConfirm = false
|
||||
if isCommodity then
|
||||
shouldConfirm = true
|
||||
elseif isBuy and isHighBidder then
|
||||
shouldConfirm = true
|
||||
elseif TSM.db.global.shoppingOptions.buyoutConfirm then
|
||||
shouldConfirm = ceil(buyout / quantity) >= (CustomPrice.GetValue(TSM.db.global.shoppingOptions.buyoutAlertSource, itemString) or 0)
|
||||
end
|
||||
if not shouldConfirm then
|
||||
return false
|
||||
end
|
||||
|
||||
baseFrame = baseFrame:GetBaseElement()
|
||||
private.isBuy = isBuy
|
||||
private.auctionScan = auctionScan
|
||||
private.subRow = subRow
|
||||
private.index = index
|
||||
private.noSeller = noSeller
|
||||
private.baseFrame = baseFrame
|
||||
private.marketValueFunc = marketValueFunc
|
||||
if private.dialogFrame then
|
||||
return true
|
||||
end
|
||||
|
||||
local defaultQuantity = isCommodity and numFound or 1
|
||||
assert(not isCommodity or isBuy)
|
||||
|
||||
local displayItemBuyout, displayTotalBuyout = nil, nil
|
||||
if isCommodity then
|
||||
displayTotalBuyout = private.CommodityResultsByQuantity(itemString, defaultQuantity)
|
||||
displayItemBuyout = Math.Ceil(displayTotalBuyout / defaultQuantity, COPPER_PER_SILVER)
|
||||
else
|
||||
displayItemBuyout = ceil(buyout / quantity)
|
||||
displayTotalBuyout = TSM.IsWowClassic() and buyout or ceil(buyout / quantity)
|
||||
end
|
||||
|
||||
private.dialogFrame = UIElements.New("Frame", "frame")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetSize(isCommodity and 600 or 326, isCommodity and 272 or 262)
|
||||
:SetPadding(12)
|
||||
:AddAnchor("CENTER")
|
||||
:SetContext(callback)
|
||||
:SetMouseEnabled(true)
|
||||
:SetBackgroundColor("FRAME_BG", true)
|
||||
:AddChild(UIElements.New("Frame", "header")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, -4, 10)
|
||||
:AddChild(UIElements.New("Spacer", "spacer")
|
||||
:SetWidth(20)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "title")
|
||||
:SetJustifyH("CENTER")
|
||||
:SetFont("BODY_BODY1_BOLD")
|
||||
:SetText(isCommodity and L["Order Confirmation"] or L["Buyout"])
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "closeBtn")
|
||||
:SetMargin(0, -4, 0, 0)
|
||||
:SetBackgroundAndSize("iconPack.24x24/Close/Default")
|
||||
:SetScript("OnClick", private.BuyoutConfirmCloseBtnOnClick)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "content")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:AddChild(UIElements.New("Frame", "left")
|
||||
:SetLayout("VERTICAL")
|
||||
:AddChild(UIElements.New("Frame", "item")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetPadding(6)
|
||||
:SetMargin(0, 0, 0, 16)
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT", true)
|
||||
:AddChild(UIElements.New("Button", "icon")
|
||||
:SetSize(36, 36)
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetBackground(ItemInfo.GetTexture(itemString))
|
||||
:SetTooltip(itemString)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "name")
|
||||
:SetHeight(36)
|
||||
:SetFont("ITEM_BODY1")
|
||||
:SetText(TSM.UI.GetColoredItemName(itemString))
|
||||
)
|
||||
)
|
||||
:AddChildIf(isCommodity, UIElements.New("Frame", "quantity")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:AddChild(UIElements.New("Text", "desc")
|
||||
:SetFont("BODY_BODY2")
|
||||
:SetText(L["Quantity"]..":")
|
||||
)
|
||||
:AddChild(UIElements.New("Input", "input")
|
||||
:SetWidth(140)
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT")
|
||||
:SetValidateFunc("NUMBER", "1:"..maxQuantity)
|
||||
:SetValue(tostring(defaultQuantity))
|
||||
:SetContext(maxQuantity)
|
||||
:SetScript("OnValueChanged", private.InputQtyOnValueChanged)
|
||||
)
|
||||
)
|
||||
:AddChildIf(not isCommodity, UIElements.New("Frame", "stacks")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 10)
|
||||
:AddChild(UIElements.New("Text", "desc")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY2")
|
||||
:SetText(isBuy and L["Purchasing Auction"]..":" or L["Bidding Auction"]..":")
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "number")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetText(auctionNum.."/"..numFound)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
:AddChild(UIElements.New("Frame", "price")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 10)
|
||||
:AddChild(UIElements.New("Text", "desc")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY2")
|
||||
:SetText(L["Unit Price"]..":")
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "money")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetContext(displayItemBuyout)
|
||||
:SetText(private.GetUnitPriceMoneyStr(displayItemBuyout))
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "total")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 10)
|
||||
:AddChild(UIElements.New("Text", "desc")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY2")
|
||||
:SetText(L["Total Price"]..":")
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "money")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetText(Money.ToString(displayTotalBuyout, nil, "OPT_83_NO_COPPER"))
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "warning")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 10)
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetJustifyH("CENTER")
|
||||
:SetTextColor(Theme.GetFeedbackColor("YELLOW"))
|
||||
:SetText("")
|
||||
)
|
||||
:AddChild(UIElements.NewNamed("ActionButton", "confirmBtn", "TSMBidBuyConfirmBtn")
|
||||
:SetHeight(24)
|
||||
:SetText(isCommodity and L["Buy Commodity"] or (isBuy and L["Buy Auction"] or L["Bid Auction"]))
|
||||
:SetContext(not isCommodity and quantity or nil)
|
||||
:SetDisabled(private.future and true or false)
|
||||
:SetScript("OnClick", private.ConfirmBtnOnClick)
|
||||
)
|
||||
)
|
||||
:AddChildIf(isCommodity, UIElements.New("Frame", "item")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetWidth(266)
|
||||
:SetMargin(12, 0, 0, 0)
|
||||
:SetPadding(4, 4, 8, 8)
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT", true)
|
||||
:AddChild(UIElements.New("CommodityList", "items")
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT")
|
||||
:SetData(subRow:GetResultRow())
|
||||
:SetMarketValueFunction(marketValueFunc)
|
||||
:SetAlertThreshold(TSM.db.global.shoppingOptions.buyoutConfirm and (CustomPrice.GetValue(TSM.db.global.shoppingOptions.buyoutAlertSource, itemString) or 0) or nil)
|
||||
:SelectQuantity(defaultQuantity)
|
||||
:SetScript("OnRowClick", private.CommodityOnRowClick)
|
||||
)
|
||||
)
|
||||
)
|
||||
:SetScript("OnHide", private.DialogOnHide)
|
||||
baseFrame:ShowDialogFrame(private.dialogFrame)
|
||||
private.prepareQuantity = nil
|
||||
private.Prepare(defaultQuantity)
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.DialogOnHide()
|
||||
if private.future then
|
||||
private.future:Cancel()
|
||||
private.future = nil
|
||||
end
|
||||
private.baseFrame = nil
|
||||
private.dialogFrame = nil
|
||||
private.isBuy = nil
|
||||
private.auctionScan = nil
|
||||
private.subRow = nil
|
||||
private.index = nil
|
||||
private.noSeller = nil
|
||||
end
|
||||
|
||||
function private.BuyoutConfirmCloseBtnOnClick(button)
|
||||
button:GetBaseElement():HideDialog()
|
||||
end
|
||||
|
||||
function private.EnableFutureOnDone()
|
||||
local result = private.future:GetValue()
|
||||
private.future = nil
|
||||
if not result or result ~= (private.subRow:IsCommodity() and private.totalBuyout or select(2, private.subRow:GetBuyouts())) then
|
||||
-- the unit price changed
|
||||
Log.PrintUser(L["Failed to buy auction."])
|
||||
private.baseFrame:HideDialog()
|
||||
return
|
||||
end
|
||||
local input = private.dialogFrame:GetElement("content.left.quantity.input")
|
||||
private.prepareSuccess = tonumber(input:GetValue()) == private.prepareQuantity
|
||||
private.UpdateConfirmButton()
|
||||
end
|
||||
|
||||
function private.GetUnitPriceMoneyStr(itemBuyout)
|
||||
local priceStr = Money.ToString(itemBuyout, nil, "OPT_83_NO_COPPER")
|
||||
local marketValueStr = nil
|
||||
local marketValue = private.marketValueFunc(private.subRow)
|
||||
local pct = marketValue and marketValue > 0 and itemBuyout > 0 and Math.Round(100 * itemBuyout / marketValue) or nil
|
||||
if pct then
|
||||
local pctColor = Theme.GetAuctionPercentColor(pct)
|
||||
if pct > 999 then
|
||||
marketValueStr = pctColor:ColorText(">999%")
|
||||
else
|
||||
marketValueStr = pctColor:ColorText(pct.."%")
|
||||
end
|
||||
else
|
||||
marketValueStr = "---"
|
||||
end
|
||||
return format("%s (%s)", priceStr, marketValueStr)
|
||||
end
|
||||
|
||||
function private.CommodityResultsByQuantity(itemString, quantity)
|
||||
local remainingQuantity = quantity
|
||||
local totalPrice, maxPrice = 0, 0
|
||||
for _, query in private.auctionScan:QueryIterator() do
|
||||
for _, subRow in query:ItemSubRowIterator(itemString) do
|
||||
if remainingQuantity > 0 then
|
||||
local _, itemBuyout = subRow:GetBuyouts()
|
||||
local _, numOwnerItems = subRow:GetOwnerInfo()
|
||||
local quantityAvailable = subRow:GetQuantities() - numOwnerItems
|
||||
local quantityToBuy = min(quantityAvailable, remainingQuantity)
|
||||
totalPrice = totalPrice + (itemBuyout * quantityToBuy)
|
||||
remainingQuantity = remainingQuantity - quantityToBuy
|
||||
maxPrice = max(maxPrice, itemBuyout)
|
||||
end
|
||||
end
|
||||
end
|
||||
return totalPrice, maxPrice
|
||||
end
|
||||
|
||||
function private.InputQtyOnValueChanged(input, noListUpdate)
|
||||
local quantity = tonumber(input:GetValue())
|
||||
input:SetValue(quantity)
|
||||
local totalBuyout = private.subRow:IsCommodity() and private.CommodityResultsByQuantity(private.subRow:GetItemString(), quantity) or input:GetElement("__parent.__parent.price.money"):GetContext() * quantity
|
||||
local totalQuantity = quantity
|
||||
local itemBuyout = totalQuantity > 0 and Math.Ceil(totalBuyout / totalQuantity, COPPER_PER_SILVER) or 0
|
||||
input:GetElement("__parent.__parent.price.money")
|
||||
:SetContext(itemBuyout)
|
||||
:SetText(private.GetUnitPriceMoneyStr(itemBuyout))
|
||||
:Draw()
|
||||
input:GetElement("__parent.__parent.total.money")
|
||||
:SetText(Money.ToString(totalBuyout, nil, "OPT_83_NO_COPPER"))
|
||||
:Draw()
|
||||
if not noListUpdate then
|
||||
input:GetElement("__parent.__parent.__parent.item.items")
|
||||
:SelectQuantity(quantity)
|
||||
end
|
||||
if quantity ~= private.prepareQuantity then
|
||||
private.prepareSuccess = false
|
||||
private.prepareQuantity = nil
|
||||
end
|
||||
private.UpdateConfirmButton()
|
||||
end
|
||||
|
||||
function private.CommodityOnRowClick(list, index)
|
||||
local input = list:GetElement("__parent.__parent.left.quantity.input")
|
||||
input:SetValue(list:GetTotalQuantity(index))
|
||||
:Draw()
|
||||
private.Prepare(tonumber(input:GetValue()))
|
||||
private.InputQtyOnValueChanged(input, true)
|
||||
end
|
||||
|
||||
function private.ConfirmBtnOnClick(button)
|
||||
local inputQuantity = nil
|
||||
if not TSM.IsWowClassic() and private.subRow:IsCommodity() then
|
||||
local input = private.dialogFrame:GetElement("content.left.quantity.input")
|
||||
inputQuantity = tonumber(input:GetValue())
|
||||
if not private.prepareSuccess and not TSM.IsWowClassic() then
|
||||
-- this is a prepare click
|
||||
private.Prepare(inputQuantity)
|
||||
return
|
||||
end
|
||||
assert(private.prepareQuantity == inputQuantity)
|
||||
end
|
||||
|
||||
local isBuy = private.isBuy
|
||||
local callbackQuantity = button:GetContext()
|
||||
if callbackQuantity == nil then
|
||||
assert(inputQuantity)
|
||||
callbackQuantity = inputQuantity
|
||||
end
|
||||
local callback = button:GetElement("__parent.__parent.__parent"):GetContext()
|
||||
button:GetBaseElement():HideDialog()
|
||||
callback(isBuy, callbackQuantity)
|
||||
end
|
||||
|
||||
function private.UpdateConfirmButton()
|
||||
local confirmBtn = private.dialogFrame:GetElement("content.left.confirmBtn")
|
||||
local text, disabled, requireManualClick = nil, false, false
|
||||
if not TSM.IsWowClassic() and private.subRow:IsCommodity() then
|
||||
local input = confirmBtn:GetElement("__parent.quantity.input")
|
||||
local inputQuantity = tonumber(input:GetValue())
|
||||
local minQuantity = 1
|
||||
local maxQuantity = confirmBtn:GetElement("__parent.quantity.input"):GetContext()
|
||||
local itemString = private.subRow:GetItemString()
|
||||
local totalCost, maxCost = private.CommodityResultsByQuantity(itemString, inputQuantity)
|
||||
local alertThreshold = TSM.db.global.shoppingOptions.buyoutConfirm and (CustomPrice.GetValue(TSM.db.global.shoppingOptions.buyoutAlertSource, itemString) or 0) or math.huge
|
||||
if maxCost >= alertThreshold then
|
||||
requireManualClick = true
|
||||
confirmBtn:GetElement("__parent.warning")
|
||||
:SetText(L["Contains auctions above your alert threshold!"])
|
||||
:Draw()
|
||||
else
|
||||
confirmBtn:GetElement("__parent.warning")
|
||||
:SetText("")
|
||||
:Draw()
|
||||
end
|
||||
|
||||
if GetMoney() < totalCost then
|
||||
text = L["Not Enough Money"]
|
||||
disabled = true
|
||||
elseif totalCost <= 0 or inputQuantity < minQuantity or inputQuantity > maxQuantity then
|
||||
text = L["Invalid Quantity"]
|
||||
disabled = true
|
||||
elseif private.prepareSuccess or TSM.IsWowClassic() then
|
||||
text = L["Buy Commodity"]
|
||||
disabled = false
|
||||
elseif private.prepareQuantity then
|
||||
text = L["Preparing..."]
|
||||
disabled = true
|
||||
else
|
||||
text = private.isBuy and L["Prepare Buy"] or L["Prepare Bid"]
|
||||
disabled = false
|
||||
end
|
||||
else
|
||||
if GetMoney() < confirmBtn:GetElement("__parent.price.money"):GetContext() then
|
||||
text = L["Not Enough Money"]
|
||||
disabled = true
|
||||
else
|
||||
text = private.isBuy and L["Buy Auction"] or L["Bid Auction"]
|
||||
disabled = false
|
||||
end
|
||||
end
|
||||
confirmBtn:SetText(text)
|
||||
:SetDisabled(disabled)
|
||||
:SetRequireManualClick(requireManualClick)
|
||||
:Draw()
|
||||
end
|
||||
|
||||
function private.Prepare(quantity)
|
||||
if quantity == private.prepareQuantity then
|
||||
return
|
||||
end
|
||||
if private.future then
|
||||
private.future:Cancel()
|
||||
private.future = nil
|
||||
end
|
||||
private.prepareQuantity = quantity
|
||||
private.prepareSuccess = false
|
||||
local totalBuyout = not TSM.IsWowClassic() and private.subRow:IsCommodity() and private.CommodityResultsByQuantity(private.subRow:GetItemString(), quantity) or (select(2, private.subRow:GetBuyouts()))
|
||||
local totalQuantity = quantity
|
||||
private.totalBuyout = totalBuyout
|
||||
local itemBuyout = totalQuantity > 0 and Math.Ceil(totalBuyout / totalQuantity, COPPER_PER_SILVER)
|
||||
local result, future = private.auctionScan:PrepareForBidOrBuyout(private.index, private.subRow, private.noSeller, quantity, itemBuyout)
|
||||
if not result then
|
||||
private.prepareQuantity = nil
|
||||
return
|
||||
elseif future then
|
||||
private.future = future
|
||||
future:SetScript("OnDone", private.EnableFutureOnDone)
|
||||
end
|
||||
private.UpdateConfirmButton()
|
||||
end
|
||||
271
Core/UI/AuctionUI/Core.lua
Normal file
271
Core/UI/AuctionUI/Core.lua
Normal file
@@ -0,0 +1,271 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local AuctionUI = TSM.UI:NewPackage("AuctionUI")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local Delay = TSM.Include("Util.Delay")
|
||||
local Event = TSM.Include("Util.Event")
|
||||
local Log = TSM.Include("Util.Log")
|
||||
local ScriptWrapper = TSM.Include("Util.ScriptWrapper")
|
||||
local Settings = TSM.Include("Service.Settings")
|
||||
local ItemLinked = TSM.Include("Service.ItemLinked")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local private = {
|
||||
settings = nil,
|
||||
topLevelPages = {},
|
||||
frame = nil,
|
||||
hasShown = false,
|
||||
isSwitching = false,
|
||||
scanningPage = nil,
|
||||
updateCallbacks = {},
|
||||
defaultFrame = nil,
|
||||
}
|
||||
local MIN_FRAME_SIZE = { width = 750, height = 450 }
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function AuctionUI.OnInitialize()
|
||||
private.settings = Settings.NewView()
|
||||
:AddKey("global", "auctionUIContext", "showDefault")
|
||||
:AddKey("global", "auctionUIContext", "frame")
|
||||
UIParent:UnregisterEvent("AUCTION_HOUSE_SHOW")
|
||||
Event.Register("AUCTION_HOUSE_SHOW", private.AuctionFrameInit)
|
||||
Event.Register("AUCTION_HOUSE_CLOSED", private.HideAuctionFrame)
|
||||
if TSM.IsWowClassic() then
|
||||
Delay.AfterTime(1, function() LoadAddOn("Blizzard_AuctionUI") end)
|
||||
else
|
||||
Delay.AfterTime(1, function() LoadAddOn("Blizzard_AuctionHouseUI") end)
|
||||
end
|
||||
ItemLinked.RegisterCallback(private.ItemLinkedCallback)
|
||||
end
|
||||
|
||||
function AuctionUI.OnDisable()
|
||||
if private.frame then
|
||||
-- hide the frame
|
||||
private.frame:Hide()
|
||||
assert(not private.frame)
|
||||
end
|
||||
end
|
||||
|
||||
function AuctionUI.RegisterTopLevelPage(name, callback, itemLinkedHandler)
|
||||
tinsert(private.topLevelPages, { name = name, callback = callback, itemLinkedHandler = itemLinkedHandler })
|
||||
end
|
||||
|
||||
function AuctionUI.StartingScan(pageName)
|
||||
if private.scanningPage and private.scanningPage ~= pageName then
|
||||
Log.PrintfUser(L["A scan is already in progress. Please stop that scan before starting another one."])
|
||||
return false
|
||||
end
|
||||
private.scanningPage = pageName
|
||||
Log.Info("Starting scan %s", pageName)
|
||||
if private.frame then
|
||||
private.frame:SetPulsingNavButton(private.scanningPage)
|
||||
end
|
||||
for _, callback in ipairs(private.updateCallbacks) do
|
||||
callback()
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function AuctionUI.EndedScan(pageName)
|
||||
if private.scanningPage == pageName then
|
||||
Log.Info("Ended scan %s", pageName)
|
||||
private.scanningPage = nil
|
||||
if private.frame then
|
||||
private.frame:SetPulsingNavButton()
|
||||
end
|
||||
for _, callback in ipairs(private.updateCallbacks) do
|
||||
callback()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function AuctionUI.SetOpenPage(name)
|
||||
private.frame:SetSelectedNavButton(name, true)
|
||||
end
|
||||
|
||||
function AuctionUI.IsPageOpen(name)
|
||||
if not private.frame then
|
||||
return false
|
||||
end
|
||||
return private.frame:GetSelectedNavButton() == name
|
||||
end
|
||||
|
||||
function AuctionUI.IsScanning()
|
||||
return private.scanningPage and true or false
|
||||
end
|
||||
|
||||
function AuctionUI.RegisterUpdateCallback(callback)
|
||||
tinsert(private.updateCallbacks, callback)
|
||||
end
|
||||
|
||||
function AuctionUI.IsVisible()
|
||||
return private.frame and true or false
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Main Frame
|
||||
-- ============================================================================
|
||||
|
||||
function private.AuctionFrameInit()
|
||||
local tabTemplateName = nil
|
||||
if TSM.IsWowClassic() then
|
||||
private.defaultFrame = AuctionFrame
|
||||
tabTemplateName = "AuctionTabTemplate"
|
||||
else
|
||||
private.defaultFrame = AuctionHouseFrame
|
||||
tabTemplateName = "AuctionHouseFrameTabTemplate"
|
||||
end
|
||||
if not private.hasShown then
|
||||
private.hasShown = true
|
||||
local tabId = private.defaultFrame.numTabs + 1
|
||||
local tab = CreateFrame("Button", "AuctionFrameTab"..tabId, private.defaultFrame, tabTemplateName)
|
||||
tab:Hide()
|
||||
tab:SetID(tabId)
|
||||
tab:SetText(Log.ColorUserAccentText("TSM4"))
|
||||
tab:SetNormalFontObject(GameFontHighlightSmall)
|
||||
if TSM.IsWowClassic() then
|
||||
tab:SetPoint("LEFT", _G["AuctionFrameTab"..tabId - 1], "RIGHT", -8, 0)
|
||||
else
|
||||
tab:SetPoint("LEFT", AuctionHouseFrame.Tabs[tabId - 1], "RIGHT", -15, 0)
|
||||
tinsert(AuctionHouseFrame.Tabs, tab)
|
||||
end
|
||||
tab:Show()
|
||||
PanelTemplates_SetNumTabs(private.defaultFrame, tabId)
|
||||
PanelTemplates_EnableTab(private.defaultFrame, tabId)
|
||||
ScriptWrapper.Set(tab, "OnClick", private.TSMTabOnClick)
|
||||
end
|
||||
if private.settings.showDefault then
|
||||
UIParent_OnEvent(UIParent, "AUCTION_HOUSE_SHOW")
|
||||
else
|
||||
PlaySound(SOUNDKIT.AUCTION_WINDOW_OPEN)
|
||||
private.ShowAuctionFrame()
|
||||
end
|
||||
end
|
||||
|
||||
function private.ShowAuctionFrame()
|
||||
if private.frame then
|
||||
return
|
||||
end
|
||||
private.frame = private.CreateMainFrame()
|
||||
private.frame:Show()
|
||||
private.frame:Draw()
|
||||
for _, callback in ipairs(private.updateCallbacks) do
|
||||
callback()
|
||||
end
|
||||
end
|
||||
|
||||
function private.HideAuctionFrame()
|
||||
if not private.frame then
|
||||
return
|
||||
end
|
||||
private.frame:Hide()
|
||||
assert(not private.frame)
|
||||
for _, callback in ipairs(private.updateCallbacks) do
|
||||
callback()
|
||||
end
|
||||
end
|
||||
|
||||
function private.CreateMainFrame()
|
||||
TSM.UI.AnalyticsRecordPathChange("auction")
|
||||
local frame = UIElements.New("LargeApplicationFrame", "base")
|
||||
:SetParent(UIParent)
|
||||
:SetSettingsContext(private.settings, "frame")
|
||||
:SetMinResize(MIN_FRAME_SIZE.width, MIN_FRAME_SIZE.height)
|
||||
:SetStrata("HIGH")
|
||||
:SetProtected(TSM.db.global.coreOptions.protectAuctionHouse)
|
||||
:AddPlayerGold()
|
||||
:AddAppStatusIcon()
|
||||
:AddSwitchButton(private.SwitchBtnOnClick)
|
||||
:SetScript("OnHide", private.BaseFrameOnHide)
|
||||
for _, info in ipairs(private.topLevelPages) do
|
||||
frame:AddNavButton(info.name, info.callback)
|
||||
end
|
||||
local whatsNewDialog = TSM.UI.WhatsNew.GetDialog()
|
||||
if whatsNewDialog then
|
||||
frame:ShowDialogFrame(whatsNewDialog)
|
||||
end
|
||||
return frame
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.BaseFrameOnHide(frame)
|
||||
assert(frame == private.frame)
|
||||
frame:Release()
|
||||
private.frame = nil
|
||||
if not private.isSwitching then
|
||||
PlaySound(SOUNDKIT.AUCTION_WINDOW_CLOSE)
|
||||
if TSM.IsWowClassic() then
|
||||
CloseAuctionHouse()
|
||||
else
|
||||
C_AuctionHouse.CloseAuctionHouse()
|
||||
end
|
||||
end
|
||||
TSM.UI.AnalyticsRecordClose("auction")
|
||||
end
|
||||
|
||||
function private.SwitchBtnOnClick(button)
|
||||
private.isSwitching = true
|
||||
private.settings.showDefault = true
|
||||
private.HideAuctionFrame()
|
||||
UIParent_OnEvent(UIParent, "AUCTION_HOUSE_SHOW")
|
||||
private.isSwitching = false
|
||||
end
|
||||
|
||||
local function NoOp()
|
||||
-- do nothing - what did you expect?
|
||||
end
|
||||
|
||||
function private.TSMTabOnClick()
|
||||
private.settings.showDefault = false
|
||||
if TSM.IsWowClassic() then
|
||||
ClearCursor()
|
||||
ClickAuctionSellItemButton(AuctionsItemButton, "LeftButton")
|
||||
end
|
||||
ClearCursor()
|
||||
-- Replace CloseAuctionHouse() with a no-op while hiding the AH frame so we don't stop interacting with the AH NPC
|
||||
if TSM.IsWowClassic() then
|
||||
local origCloseAuctionHouse = CloseAuctionHouse
|
||||
CloseAuctionHouse = NoOp
|
||||
AuctionFrame_Hide()
|
||||
CloseAuctionHouse = origCloseAuctionHouse
|
||||
else
|
||||
local origCloseAuctionHouse = C_AuctionHouse.CloseAuctionHouse
|
||||
C_AuctionHouse.CloseAuctionHouse = NoOp
|
||||
HideUIPanel(private.defaultFrame)
|
||||
C_AuctionHouse.CloseAuctionHouse = origCloseAuctionHouse
|
||||
end
|
||||
private.ShowAuctionFrame()
|
||||
end
|
||||
|
||||
function private.ItemLinkedCallback(name, itemLink)
|
||||
if not private.frame then
|
||||
return
|
||||
end
|
||||
local path = private.frame:GetSelectedNavButton()
|
||||
for _, info in ipairs(private.topLevelPages) do
|
||||
if info.name == path then
|
||||
if info.itemLinkedHandler(name, itemLink) then
|
||||
return true
|
||||
else
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
error("Invalid frame path")
|
||||
end
|
||||
715
Core/UI/AuctionUI/MyAuctions.lua
Normal file
715
Core/UI/AuctionUI/MyAuctions.lua
Normal file
@@ -0,0 +1,715 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local MyAuctions = TSM.UI.AuctionUI:NewPackage("MyAuctions")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local FSM = TSM.Include("Util.FSM")
|
||||
local Money = TSM.Include("Util.Money")
|
||||
local String = TSM.Include("Util.String")
|
||||
local Math = TSM.Include("Util.Math")
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local ItemString = TSM.Include("Util.ItemString")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local ItemInfo = TSM.Include("Service.ItemInfo")
|
||||
local Settings = TSM.Include("Service.Settings")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local private = {
|
||||
settings = nil,
|
||||
fsm = nil,
|
||||
frame = nil,
|
||||
query = nil,
|
||||
}
|
||||
local DURATION_LIST = {
|
||||
L["None"],
|
||||
AUCTION_TIME_LEFT1_DETAIL,
|
||||
AUCTION_TIME_LEFT2_DETAIL,
|
||||
AUCTION_TIME_LEFT3_DETAIL,
|
||||
AUCTION_TIME_LEFT4_DETAIL,
|
||||
}
|
||||
local SECONDS_PER_MIN = 60
|
||||
local SECONDS_PER_HOUR = 60 * SECONDS_PER_MIN
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function MyAuctions.OnInitialize()
|
||||
private.settings = Settings.NewView()
|
||||
:AddKey("global", "auctionUIContext", "myAuctionsScrollingTable")
|
||||
private.FSMCreate()
|
||||
TSM.UI.AuctionUI.RegisterTopLevelPage(L["My Auctions"], private.GetMyAuctionsFrame, private.OnItemLinked)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- MyAuctions UI
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetMyAuctionsFrame()
|
||||
TSM.UI.AnalyticsRecordPathChange("auction", "my_auctions")
|
||||
private.query = private.query or TSM.MyAuctions.CreateQuery()
|
||||
local frame = UIElements.New("Frame", "myAuctions")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT")
|
||||
:AddChild(UIElements.New("Frame", "header")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(72)
|
||||
:SetPadding(8)
|
||||
:AddChild(UIElements.New("Frame", "filters")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, 0, 8)
|
||||
:AddChild(UIElements.New("SelectionDropdown", "duration")
|
||||
:SetWidth(232)
|
||||
:SetItems(DURATION_LIST)
|
||||
:SetHintText(L["Filter by duration"])
|
||||
:SetScript("OnSelectionChanged", private.DurationFilterChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("GroupSelector", "group")
|
||||
:SetWidth(232)
|
||||
:SetMargin(8, 8, 0, 0)
|
||||
:SetHintText(L["Filter by groups"])
|
||||
:SetScript("OnSelectionChanged", private.FilterChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("Input", "keyword")
|
||||
:SetIconTexture("iconPack.18x18/Search")
|
||||
:AllowItemInsert(false)
|
||||
:SetClearButtonEnabled(true)
|
||||
:SetHintText(L["Filter by keyword"])
|
||||
:SetScript("OnValueChanged", private.FilterChanged)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "buttons")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:AddChild(UIElements.New("Checkbox", "ignoreBid")
|
||||
:SetWidth("AUTO")
|
||||
:SetCheckboxPosition("LEFT")
|
||||
:SetText(L["Hide auctions with bids"])
|
||||
:SetScript("OnValueChanged", private.ToggleFilterChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("Checkbox", "onlyBid")
|
||||
:SetMargin(16, 0, 0, 0)
|
||||
:SetWidth("AUTO")
|
||||
:SetCheckboxPosition("LEFT")
|
||||
:SetText(L["Show only auctions with bids"])
|
||||
:SetScript("OnValueChanged", private.ToggleFilterChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("Checkbox", "onlySold")
|
||||
:SetMargin(16, 0, 0, 0)
|
||||
:SetWidth("AUTO")
|
||||
:SetCheckboxPosition("LEFT")
|
||||
:SetText(L["Only show sold auctions"])
|
||||
:SetScript("OnValueChanged", private.ToggleFilterChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer"))
|
||||
:AddChild(UIElements.New("ActionButton", "clearfilter")
|
||||
:SetSize(142, 24)
|
||||
:SetText(L["Clear Filters"])
|
||||
:SetScript("OnClick", private.ClearFilterOnClick)
|
||||
)
|
||||
)
|
||||
)
|
||||
if TSM.IsWowClassic() then
|
||||
frame:AddChild(UIElements.New("MyAuctionsScrollingTable", "auctions")
|
||||
:SetSettingsContext(private.settings, "myAuctionsScrollingTable")
|
||||
:GetScrollingTableInfo()
|
||||
:NewColumn("item")
|
||||
:SetTitle(L["Item Name"])
|
||||
:SetIconSize(12)
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo(nil, private.AuctionsGetItemText)
|
||||
:SetIconInfo("itemString", ItemInfo.GetTexture)
|
||||
:SetTooltipInfo("itemString", private.AuctionsGetItemTooltip)
|
||||
:SetActionIconInfo(1, 12, private.AuctionsGetSoldIcon)
|
||||
:DisableHiding()
|
||||
:Commit()
|
||||
:NewColumn("stackSize")
|
||||
:SetTitle(L["Qty"])
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo(nil, private.AuctionsGetStackSizeText)
|
||||
:Commit()
|
||||
:NewColumn("timeLeft")
|
||||
:SetTitleIcon("iconPack.14x14/Clock")
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetJustifyH("CENTER")
|
||||
:SetTextInfo(nil, private.AuctionsGetTimeLeftText)
|
||||
:Commit()
|
||||
:NewColumn("highbidder")
|
||||
:SetTitle(L["High Bidder"])
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo(nil, private.AuctionsGetHighBidderText)
|
||||
:Commit()
|
||||
:NewColumn("group")
|
||||
:SetTitle(GROUP)
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo(nil, private.AuctionsGetGroupNameText)
|
||||
:Commit()
|
||||
:NewColumn("currentBid")
|
||||
:SetTitle(BID)
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo(nil, private.AuctionsGetCurrentBidText)
|
||||
:Commit()
|
||||
:NewColumn("buyout")
|
||||
:SetTitle(BUYOUT)
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo(nil, private.AuctionsGetCurrentBuyoutText)
|
||||
:Commit()
|
||||
:Commit()
|
||||
:SetQuery(private.query)
|
||||
:SetSelectionValidator(private.AuctionsValidateSelection)
|
||||
:SetScript("OnSelectionChanged", private.AuctionsOnSelectionChanged)
|
||||
:SetScript("OnDataUpdated", private.AuctionsOnDataUpdated)
|
||||
)
|
||||
else
|
||||
frame:AddChild(UIElements.New("MyAuctionsScrollingTable", "auctions")
|
||||
:SetSettingsContext(private.settings, "myAuctionsScrollingTable")
|
||||
:GetScrollingTableInfo()
|
||||
:NewColumn("item")
|
||||
:SetTitle(L["Item Name"])
|
||||
:SetIconSize(12)
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo(nil, private.AuctionsGetItemText)
|
||||
:SetIconInfo("itemString", ItemInfo.GetTexture)
|
||||
:SetTooltipInfo("itemString", private.AuctionsGetItemTooltip)
|
||||
:SetActionIconInfo(1, 12, private.AuctionsGetSoldIcon)
|
||||
:SetSortInfo("name")
|
||||
:DisableHiding()
|
||||
:Commit()
|
||||
:NewColumn("stackSize")
|
||||
:SetTitle(L["Qty"])
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo(nil, private.AuctionsGetStackSizeText)
|
||||
:SetSortInfo("stackSize")
|
||||
:Commit()
|
||||
:NewColumn("timeLeft")
|
||||
:SetTitleIcon("iconPack.14x14/Clock")
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetJustifyH("CENTER")
|
||||
:SetTextInfo(nil, private.AuctionsGetTimeLeftText)
|
||||
:SetSortInfo("duration")
|
||||
:Commit()
|
||||
:NewColumn("group")
|
||||
:SetTitle(GROUP)
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo(nil, private.AuctionsGetGroupNameText)
|
||||
:SetSortInfo("group")
|
||||
:Commit()
|
||||
:NewColumn("currentBid")
|
||||
:SetTitle(BID)
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo(nil, private.AuctionsGetCurrentBidText)
|
||||
:SetSortInfo("currentBid")
|
||||
:Commit()
|
||||
:NewColumn("buyout")
|
||||
:SetTitle(BUYOUT)
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo(nil, private.AuctionsGetCurrentBuyoutText)
|
||||
:SetSortInfo("buyout")
|
||||
:Commit()
|
||||
:Commit()
|
||||
:SetQuery(private.query)
|
||||
:SetSelectionValidator(private.AuctionsValidateSelection)
|
||||
:SetScript("OnSelectionChanged", private.AuctionsOnSelectionChanged)
|
||||
:SetScript("OnDataUpdated", private.AuctionsOnDataUpdated)
|
||||
)
|
||||
end
|
||||
frame:AddChild(UIElements.New("Texture", "line")
|
||||
:SetHeight(2)
|
||||
:SetTexture("ACTIVE_BG")
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "bottom")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(68)
|
||||
:SetPadding(8)
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT")
|
||||
:AddChild(UIElements.New("Frame", "row1")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 8)
|
||||
:AddChild(UIElements.New("Text", "sold")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "soldValue")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
)
|
||||
:AddChild(UIElements.New("Texture", "vline")
|
||||
:SetWidth(1)
|
||||
:SetMargin(8, 8, 0, 0)
|
||||
:SetTexture("ACTIVE_BG_ALT")
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "posted")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "postedValue")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "row2")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:AddChild(UIElements.New("ProgressBar", "progressBar")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetProgress(0)
|
||||
)
|
||||
:AddChild(UIElements.NewNamed("ActionButton", "cancelBtn", "TSMCancelAuctionBtn")
|
||||
:SetSize(110, 24)
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetText(CANCEL)
|
||||
:SetDisabled(true)
|
||||
:DisableClickCooldown(true)
|
||||
:SetScript("OnClick", private.CancelButtonOnClick)
|
||||
)
|
||||
:AddChild(UIElements.New("ActionButton", "skipBtn")
|
||||
:SetSize(110, 24)
|
||||
:SetText(L["Skip"])
|
||||
:SetDisabled(true)
|
||||
:DisableClickCooldown(true)
|
||||
:SetScript("OnClick", private.SkipButtonOnClick)
|
||||
)
|
||||
)
|
||||
)
|
||||
:SetScript("OnUpdate", private.FrameOnUpdate)
|
||||
:SetScript("OnHide", private.FrameOnHide)
|
||||
private.frame = frame
|
||||
return frame
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.OnItemLinked(name)
|
||||
private.frame:GetElement("header.filters.keyword")
|
||||
:SetValue(name)
|
||||
:Draw()
|
||||
private.FilterChanged()
|
||||
return true
|
||||
end
|
||||
|
||||
function private.FrameOnUpdate(frame)
|
||||
frame:SetScript("OnUpdate", nil)
|
||||
private.fsm:ProcessEvent("EV_FRAME_SHOWN", frame)
|
||||
end
|
||||
|
||||
function private.FrameOnHide(frame)
|
||||
assert(frame == private.frame)
|
||||
private.frame = nil
|
||||
private.fsm:ProcessEvent("EV_FRAME_HIDDEN")
|
||||
end
|
||||
|
||||
function private.ToggleFilterChanged(toggle)
|
||||
local ignoreBidToggle = toggle:GetElement("__parent.ignoreBid")
|
||||
local onlyBidToggle = toggle:GetElement("__parent.onlyBid")
|
||||
local onlySoldToggle = toggle:GetElement("__parent.onlySold")
|
||||
if ignoreBidToggle:IsChecked() and onlyBidToggle:IsChecked() then
|
||||
-- uncheck the other toggle in the pair of "bid" toggles
|
||||
if toggle == ignoreBidToggle then
|
||||
onlyBidToggle:SetChecked(false, true)
|
||||
:Draw()
|
||||
else
|
||||
ignoreBidToggle:SetChecked(false, true)
|
||||
:Draw()
|
||||
end
|
||||
end
|
||||
if onlyBidToggle:IsChecked() and onlySoldToggle:IsChecked() then
|
||||
-- uncheck the other toggle in the pair of "only" toggles
|
||||
if toggle == onlyBidToggle then
|
||||
onlySoldToggle:SetChecked(false, true)
|
||||
:Draw()
|
||||
else
|
||||
onlyBidToggle:SetChecked(false, true)
|
||||
:Draw()
|
||||
end
|
||||
end
|
||||
private.FilterChanged()
|
||||
end
|
||||
|
||||
function private.DurationFilterChanged(dropdown)
|
||||
if dropdown:GetSelectedItemKey() == 1 then
|
||||
-- none
|
||||
dropdown:SetSelectedItem(nil, true)
|
||||
end
|
||||
private.FilterChanged()
|
||||
end
|
||||
|
||||
function private.FilterChanged()
|
||||
private.fsm:ProcessEvent("EV_FILTER_UPDATED")
|
||||
end
|
||||
|
||||
function private.ClearFilterOnClick(button)
|
||||
button:GetElement("__parent.__parent.filters.duration")
|
||||
:SetOpen(false)
|
||||
:SetSelectedItem()
|
||||
button:GetElement("__parent.__parent.filters.group")
|
||||
:ClearSelectedGroups()
|
||||
button:GetElement("__parent.__parent.filters.keyword")
|
||||
:SetValue("")
|
||||
:SetFocused(false)
|
||||
button:GetElement("__parent.ignoreBid")
|
||||
:SetChecked(false, true)
|
||||
button:GetElement("__parent.onlyBid")
|
||||
:SetChecked(false, true)
|
||||
button:GetElement("__parent.onlySold")
|
||||
:SetChecked(false, true)
|
||||
button:GetParentElement():GetParentElement():Draw()
|
||||
private.fsm:ProcessEvent("EV_FILTER_UPDATED")
|
||||
end
|
||||
|
||||
function private.AuctionsValidateSelection(_, row)
|
||||
return row:GetField("saleStatus") == 0
|
||||
end
|
||||
|
||||
function private.AuctionsOnSelectionChanged()
|
||||
private.fsm:ProcessEvent("EV_SELECTION_CHANGED")
|
||||
end
|
||||
|
||||
function private.AuctionsOnDataUpdated()
|
||||
if not private.frame then
|
||||
return
|
||||
end
|
||||
|
||||
private.fsm:SetLoggingEnabled(false)
|
||||
private.fsm:ProcessEvent("EV_DATA_UPDATED")
|
||||
private.fsm:SetLoggingEnabled(true)
|
||||
end
|
||||
|
||||
function private.CancelButtonOnClick(button)
|
||||
private.fsm:ProcessEvent("EV_CANCEL_CLICKED")
|
||||
end
|
||||
|
||||
function private.SkipButtonOnClick(button)
|
||||
private.fsm:ProcessEvent("EV_SKIP_CLICKED")
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- FSM
|
||||
-- ============================================================================
|
||||
|
||||
function private.FSMCreate()
|
||||
local fsmContext = {
|
||||
frame = nil,
|
||||
durationFilter = nil,
|
||||
keywordFilter = nil,
|
||||
groupFilter = {},
|
||||
bidFilter = nil,
|
||||
soldFilter = false,
|
||||
filterChanged = false,
|
||||
}
|
||||
local function UpdateFrame(context)
|
||||
if not context.frame then
|
||||
return
|
||||
end
|
||||
|
||||
local auctions = context.frame:GetElement("auctions")
|
||||
if context.filterChanged then
|
||||
context.filterChanged = false
|
||||
private.query:ResetFilters()
|
||||
if context.durationFilter then
|
||||
if TSM.IsWowClassic() then
|
||||
private.query:Equal("duration", context.durationFilter)
|
||||
else
|
||||
if context.durationFilter == 1 then
|
||||
private.query:LessThan("duration", time() + (30 * SECONDS_PER_MIN))
|
||||
elseif context.durationFilter == 2 then
|
||||
private.query:LessThan("duration", time() + (2 * SECONDS_PER_HOUR))
|
||||
elseif context.durationFilter == 3 then
|
||||
private.query:LessThanOrEqual("duration", time() + (12 * SECONDS_PER_HOUR))
|
||||
else
|
||||
private.query:GreaterThan("duration", time() + (12 * SECONDS_PER_HOUR))
|
||||
end
|
||||
end
|
||||
end
|
||||
if context.keywordFilter then
|
||||
private.query:Matches("itemName", context.keywordFilter)
|
||||
end
|
||||
if next(context.groupFilter) then
|
||||
private.query:InTable("group", context.groupFilter)
|
||||
end
|
||||
if context.bidFilter ~= nil then
|
||||
if context.bidFilter then
|
||||
private.query:NotEqual("highBidder", "")
|
||||
else
|
||||
private.query:Equal("highBidder", "")
|
||||
end
|
||||
end
|
||||
if context.soldFilter then
|
||||
private.query:Equal("saleStatus", 1)
|
||||
end
|
||||
auctions:SetQuery(private.query, true)
|
||||
end
|
||||
local selectedRow = auctions:GetSelection()
|
||||
local hasFilter = context.durationFilter or context.keywordFilter or next(context.groupFilter) or context.bidFilter ~= nil or context.soldFilter
|
||||
context.frame:GetElement("header.buttons.clearfilter")
|
||||
:SetDisabled(not hasFilter)
|
||||
:Draw()
|
||||
|
||||
local numPending = TSM.MyAuctions.GetNumPending()
|
||||
local progressText = nil
|
||||
if numPending > 0 then
|
||||
progressText = format(L["Canceling %d Auctions..."], numPending)
|
||||
elseif selectedRow then
|
||||
progressText = L["Ready to Cancel"]
|
||||
else
|
||||
progressText = L["Select Auction to Cancel"]
|
||||
end
|
||||
local row2 = context.frame:GetElement("bottom.row2")
|
||||
row2:GetElement("cancelBtn")
|
||||
:SetDisabled(not selectedRow or (not TSM.IsWowClassic() and numPending > 0) or (not TSM.IsWowClassic() and C_AuctionHouse.GetCancelCost(selectedRow:GetField("auctionId")) > GetMoney()))
|
||||
row2:GetElement("skipBtn")
|
||||
:SetDisabled(not selectedRow)
|
||||
row2:GetElement("progressBar")
|
||||
:SetProgressIconHidden(numPending == 0)
|
||||
:SetText(progressText)
|
||||
row2:Draw()
|
||||
local numPosted, numSold, postedGold, soldGold = 0, 0, 0, 0
|
||||
for _, row in private.query:Iterator() do
|
||||
local itemString, saleStatus, buyout, currentBid, stackSize = row:GetFields("itemString", "saleStatus", "buyout", "currentBid", "stackSize")
|
||||
if saleStatus == 1 then
|
||||
numSold = numSold + 1
|
||||
-- if somebody did a buyout, then bid will be equal to buyout, otherwise it'll be the winning bid
|
||||
soldGold = soldGold + currentBid
|
||||
else
|
||||
numPosted = numPosted + 1
|
||||
if ItemInfo.IsCommodity(itemString) then
|
||||
postedGold = postedGold + (buyout * stackSize)
|
||||
else
|
||||
postedGold = postedGold + buyout
|
||||
end
|
||||
end
|
||||
end
|
||||
local row1 = context.frame:GetElement("bottom.row1")
|
||||
row1:GetElement("sold")
|
||||
:SetFormattedText((hasFilter and L["%s Sold Auctions (Filtered)"] or L["%s Sold Auctions"])..":", Theme.GetColor("INDICATOR"):ColorText(numSold))
|
||||
row1:GetElement("soldValue")
|
||||
:SetText(Money.ToString(soldGold))
|
||||
row1:GetElement("posted")
|
||||
:SetFormattedText((hasFilter and L["%s Posted Auctions (Filtered)"] or L["%s Posted Auctions"])..":", Theme.GetColor("INDICATOR_ALT"):ColorText(numPosted))
|
||||
row1:GetElement("postedValue")
|
||||
:SetText(Money.ToString(postedGold))
|
||||
row1:Draw()
|
||||
end
|
||||
private.fsm = FSM.New("MY_AUCTIONS")
|
||||
:AddState(FSM.NewState("ST_HIDDEN")
|
||||
:SetOnEnter(function(context)
|
||||
context.frame = nil
|
||||
context.durationFilter = nil
|
||||
context.keywordFilter = nil
|
||||
wipe(context.groupFilter)
|
||||
context.bidFilter = nil
|
||||
context.soldFilter = false
|
||||
end)
|
||||
:AddTransition("ST_HIDDEN")
|
||||
:AddTransition("ST_SHOWNING")
|
||||
:AddEventTransition("EV_FRAME_SHOWN", "ST_SHOWNING")
|
||||
)
|
||||
:AddState(FSM.NewState("ST_SHOWNING")
|
||||
:SetOnEnter(function(context, frame)
|
||||
context.frame = frame
|
||||
context.filterChanged = true
|
||||
return "ST_SHOWN"
|
||||
end)
|
||||
:AddTransition("ST_SHOWN")
|
||||
)
|
||||
:AddState(FSM.NewState("ST_SHOWN")
|
||||
:SetOnEnter(function(context)
|
||||
UpdateFrame(context)
|
||||
end)
|
||||
:AddTransition("ST_HIDDEN")
|
||||
:AddTransition("ST_SHOWN")
|
||||
:AddTransition("ST_CANCELING")
|
||||
:AddEventTransition("EV_CANCEL_CLICKED", "ST_CANCELING")
|
||||
:AddEventTransition("EV_SELECTION_CHANGED", "ST_SHOWN")
|
||||
:AddEventTransition("EV_DATA_UPDATED", "ST_SHOWN")
|
||||
:AddEvent("EV_SKIP_CLICKED", function(context)
|
||||
context.frame:GetElement("auctions"):SelectNextRow()
|
||||
return "ST_SHOWN"
|
||||
end)
|
||||
:AddEvent("EV_FILTER_UPDATED", function(context)
|
||||
local didChange = false
|
||||
local durationFilter = context.frame:GetElement("header.filters.duration"):GetSelectedItemKey()
|
||||
durationFilter = durationFilter and (durationFilter - 1) or nil
|
||||
if durationFilter ~= context.durationFilter then
|
||||
context.durationFilter = durationFilter
|
||||
didChange = true
|
||||
end
|
||||
local keywordFilter = context.frame:GetElement("header.filters.keyword"):GetValue()
|
||||
keywordFilter = keywordFilter ~= "" and String.Escape(keywordFilter) or nil
|
||||
if keywordFilter ~= context.keywordFilter then
|
||||
context.keywordFilter = keywordFilter
|
||||
didChange = true
|
||||
end
|
||||
local newGroupFilter = TempTable.Acquire()
|
||||
for groupPath in context.frame:GetElement("header.filters.group"):SelectedGroupIterator() do
|
||||
newGroupFilter[groupPath] = true
|
||||
end
|
||||
if Math.CalculateHash(newGroupFilter) ~= Math.CalculateHash(context.groupFilter) then
|
||||
didChange = true
|
||||
wipe(context.groupFilter)
|
||||
for groupPath in pairs(newGroupFilter) do
|
||||
context.groupFilter[groupPath] = true
|
||||
end
|
||||
end
|
||||
TempTable.Release(newGroupFilter)
|
||||
local bidFilter = nil
|
||||
if context.frame:GetElement("header.buttons.ignoreBid"):IsChecked() then
|
||||
bidFilter = false
|
||||
elseif context.frame:GetElement("header.buttons.onlyBid"):IsChecked() then
|
||||
bidFilter = true
|
||||
end
|
||||
if bidFilter ~= context.bidFilter then
|
||||
context.bidFilter = bidFilter
|
||||
didChange = true
|
||||
end
|
||||
local soldFilter = context.frame:GetElement("header.buttons.onlySold"):IsChecked()
|
||||
if soldFilter ~= context.soldFilter then
|
||||
context.soldFilter = soldFilter
|
||||
didChange = true
|
||||
end
|
||||
if didChange then
|
||||
context.filterChanged = true
|
||||
return "ST_SHOWN"
|
||||
end
|
||||
end)
|
||||
)
|
||||
:AddState(FSM.NewState("ST_CANCELING")
|
||||
:SetOnEnter(function(context)
|
||||
local buttonsFrame = context.frame:GetElement("bottom")
|
||||
buttonsFrame:GetElement("row2.cancelBtn"):SetDisabled(true)
|
||||
buttonsFrame:GetElement("row2.skipBtn"):SetDisabled(true)
|
||||
buttonsFrame:Draw()
|
||||
local auctionId = context.frame:GetElement("auctions"):GetSelection():GetField("auctionId")
|
||||
if TSM.MyAuctions.CanCancel(auctionId) then
|
||||
TSM.MyAuctions.CancelAuction(auctionId)
|
||||
end
|
||||
return "ST_SHOWN"
|
||||
end)
|
||||
:AddTransition("ST_HIDDEN")
|
||||
:AddTransition("ST_SHOWN")
|
||||
)
|
||||
:AddDefaultEventTransition("EV_FRAME_HIDDEN", "ST_HIDDEN")
|
||||
:Init("ST_HIDDEN", fsmContext)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.AuctionsGetItemText(row)
|
||||
if row:GetField("saleStatus") == 1 then
|
||||
return Theme.GetColor("INDICATOR"):ColorText(row:GetField("itemName"))
|
||||
else
|
||||
return TSM.UI.GetQualityColoredText(row:GetField("itemName"), row:GetField("itemQuality"))
|
||||
end
|
||||
end
|
||||
|
||||
function private.AuctionsGetItemTooltip(itemString)
|
||||
return itemString ~= ItemString.GetPetCage() and itemString or nil
|
||||
end
|
||||
|
||||
function private.AuctionsGetSoldIcon(self, data, iconIndex)
|
||||
assert(iconIndex == 1)
|
||||
local row = private.query:GetResultRowByUUID(data)
|
||||
if row:GetField("saleStatus") ~= 1 then
|
||||
return
|
||||
end
|
||||
return true, "iconPack.12x12/Bid", true
|
||||
end
|
||||
|
||||
function private.AuctionsGetStackSizeText(row)
|
||||
if row:GetField("saleStatus") == 1 then
|
||||
return Theme.GetColor("INDICATOR"):ColorText(row:GetField("stackSize"))
|
||||
else
|
||||
return row:GetField("stackSize")
|
||||
end
|
||||
end
|
||||
|
||||
function private.AuctionsGetTimeLeftText(row)
|
||||
local saleStatus, duration, isPending = row:GetFields("saleStatus", "duration", "isPending")
|
||||
if saleStatus == 0 and isPending then
|
||||
return "..."
|
||||
elseif saleStatus == 1 or not TSM.IsWowClassic() then
|
||||
local timeLeft = duration - time()
|
||||
local color = nil
|
||||
if saleStatus == 0 then
|
||||
if timeLeft <= 2 * SECONDS_PER_HOUR then
|
||||
color = Theme.GetFeedbackColor("RED")
|
||||
elseif timeLeft <= (TSM.IsWowClassic() and 8 or 24) * SECONDS_PER_HOUR then
|
||||
color = Theme.GetFeedbackColor("YELLOW")
|
||||
else
|
||||
color = Theme.GetFeedbackColor("GREEN")
|
||||
end
|
||||
else
|
||||
color = Theme.GetColor("INDICATOR")
|
||||
end
|
||||
local str = nil
|
||||
if timeLeft < SECONDS_PER_MIN then
|
||||
str = timeLeft.."s"
|
||||
elseif timeLeft < SECONDS_PER_HOUR then
|
||||
str = floor(timeLeft / SECONDS_PER_MIN).."m"
|
||||
else
|
||||
str = floor(timeLeft / SECONDS_PER_HOUR).."h"
|
||||
end
|
||||
return color and color:ColorText(str) or str
|
||||
else
|
||||
return TSM.UI.GetTimeLeftString(duration)
|
||||
end
|
||||
end
|
||||
|
||||
function private.AuctionsGetHighBidderText(row)
|
||||
if row:GetField("saleStatus") == 1 then
|
||||
return Theme.GetColor("INDICATOR"):ColorText(row:GetField("highBidder"))
|
||||
else
|
||||
return row:GetField("highBidder")
|
||||
end
|
||||
end
|
||||
|
||||
function private.AuctionsGetGroupNameText(row)
|
||||
local groupPath = row:GetField("group")
|
||||
local groupName = TSM.Groups.Path.GetName(groupPath)
|
||||
local level = select('#', strsplit(TSM.CONST.GROUP_SEP, groupPath))
|
||||
return Theme.GetGroupColor(level):ColorText(groupName)
|
||||
end
|
||||
|
||||
function private.AuctionsGetCurrentBidText(row)
|
||||
if row:GetField("saleStatus") == 1 then
|
||||
return Theme.GetColor("INDICATOR"):ColorText(L["Sold for:"])
|
||||
elseif not TSM.IsWowClassic() and row:GetField("highBidder") ~= "" then
|
||||
return Money.ToString(row:GetField("currentBid"), Theme.GetColor("INDICATOR"):GetTextColorPrefix())
|
||||
else
|
||||
return Money.ToString(row:GetField("currentBid"))
|
||||
end
|
||||
end
|
||||
|
||||
function private.AuctionsGetCurrentBuyoutText(row)
|
||||
return Money.ToString(row:GetField(row:GetField("saleStatus") == 1 and "currentBid" or "buyout"))
|
||||
end
|
||||
2658
Core/UI/AuctionUI/Shopping.lua
Normal file
2658
Core/UI/AuctionUI/Shopping.lua
Normal file
File diff suppressed because it is too large
Load Diff
818
Core/UI/AuctionUI/Sniper.lua
Normal file
818
Core/UI/AuctionUI/Sniper.lua
Normal file
@@ -0,0 +1,818 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Sniper = TSM.UI.AuctionUI:NewPackage("Sniper")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local Delay = TSM.Include("Util.Delay")
|
||||
local Event = TSM.Include("Util.Event")
|
||||
local FSM = TSM.Include("Util.FSM")
|
||||
local Sound = TSM.Include("Util.Sound")
|
||||
local Money = TSM.Include("Util.Money")
|
||||
local Log = TSM.Include("Util.Log")
|
||||
local ItemString = TSM.Include("Util.ItemString")
|
||||
local ItemInfo = TSM.Include("Service.ItemInfo")
|
||||
local AuctionScan = TSM.Include("Service.AuctionScan")
|
||||
local MailTracking = TSM.Include("Service.MailTracking")
|
||||
local Settings = TSM.Include("Service.Settings")
|
||||
local AuctionHouseWrapper = TSM.Include("Service.AuctionHouseWrapper")
|
||||
local PlayerInfo = TSM.Include("Service.PlayerInfo")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local private = {
|
||||
settings = nil,
|
||||
fsm = nil,
|
||||
selectionFrame = nil,
|
||||
hasLastScan = nil,
|
||||
contentPath = "selection",
|
||||
}
|
||||
local PHASED_TIME = 60
|
||||
local RETAIL_RESCAN_DELAY = 30
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Sniper.OnInitialize()
|
||||
private.settings = Settings.NewView()
|
||||
:AddKey("global", "auctionUIContext", "sniperScrollingTable")
|
||||
:AddKey("global", "sniperOptions", "sniperSound")
|
||||
TSM.UI.AuctionUI.RegisterTopLevelPage(L["Sniper"], private.GetSniperFrame, private.OnItemLinked)
|
||||
private.FSMCreate()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Sniper UI
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetSniperFrame()
|
||||
TSM.UI.AnalyticsRecordPathChange("auction", "sniper")
|
||||
if not private.hasLastScan then
|
||||
private.contentPath = "selection"
|
||||
end
|
||||
return UIElements.New("ViewContainer", "sniper")
|
||||
:SetNavCallback(private.GetSniperContentFrame)
|
||||
:AddPath("selection")
|
||||
:AddPath("scan")
|
||||
:SetPath(private.contentPath)
|
||||
end
|
||||
|
||||
function private.GetSniperContentFrame(viewContainer, path)
|
||||
private.contentPath = path
|
||||
if path == "selection" then
|
||||
return private.GetSelectionFrame()
|
||||
elseif path == "scan" then
|
||||
return private.GetScanFrame()
|
||||
else
|
||||
error("Unexpected path: "..tostring(path))
|
||||
end
|
||||
end
|
||||
|
||||
function private.GetSelectionFrame()
|
||||
TSM.UI.AnalyticsRecordPathChange("auction", "sniper", "selection")
|
||||
local frame = UIElements.New("Frame", "selection")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT")
|
||||
:AddChildIf(TSM.IsWowClassic(), UIElements.New("Text", "text")
|
||||
:SetHeight(20)
|
||||
:SetMargin(8, 8, 12, 0)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetJustifyH("CENTER")
|
||||
:SetText(L["Start either a 'Buyout' or 'Bid' sniper using the buttons above."])
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "buttons")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:SetMargin(8, 8, 12, 12)
|
||||
:AddChild(UIElements.New("ActionButton", "buyoutScanBtn")
|
||||
:SetMargin(0, TSM.IsWowClassic() and 8 or 0, 0, 0)
|
||||
:SetText(L["Run Buyout Sniper"])
|
||||
:SetScript("OnClick", private.BuyoutScanButtonOnClick)
|
||||
)
|
||||
:AddChildIf(TSM.IsWowClassic(), UIElements.New("ActionButton", "bidScanBtn")
|
||||
:SetText(L["Run Bid Sniper"])
|
||||
:SetScript("OnClick", private.BidScanButtonOnClick)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("SniperScrollingTable", "auctions")
|
||||
:SetSettingsContext(private.settings, "sniperScrollingTable")
|
||||
)
|
||||
:SetScript("OnHide", private.SelectionFrameOnHide)
|
||||
private.selectionFrame = frame
|
||||
return frame
|
||||
end
|
||||
|
||||
function private.GetScanFrame()
|
||||
TSM.UI.AnalyticsRecordPathChange("auction", "sniper", "scan")
|
||||
return UIElements.New("Frame", "scan")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT")
|
||||
:AddChild(UIElements.New("Frame", "header")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(48)
|
||||
:SetPadding(8, 8, 14, 14)
|
||||
:AddChild(UIElements.New("ActionButton", "backBtn")
|
||||
:SetSize(64, 24)
|
||||
:SetMargin(0, 16, 0, 0)
|
||||
:SetIcon("iconPack.14x14/Chevron/Right@180")
|
||||
:SetText(BACK)
|
||||
:SetScript("OnClick", private.BackButtonOnClick)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "title")
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetJustifyH("CENTER")
|
||||
)
|
||||
:AddChild(UIElements.New("ActionButton", "restartBtn")
|
||||
:SetSize(80, 24)
|
||||
:SetText(L["Restart"])
|
||||
:SetScript("OnClick", private.RestartButtonOnClick)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("SniperScrollingTable", "auctions")
|
||||
:SetSettingsContext(private.settings, "sniperScrollingTable")
|
||||
:SetScript("OnSelectionChanged", private.AuctionsOnSelectionChanged)
|
||||
:SetScript("OnRowRemoved", private.AuctionsOnRowRemoved)
|
||||
)
|
||||
:AddChild(UIElements.New("Texture", "line")
|
||||
:SetHeight(2)
|
||||
:SetTexture("ACTIVE_BG")
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "bottom")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(40)
|
||||
:SetPadding(8)
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT")
|
||||
:AddChild(UIElements.New("ActionButton", "pauseResumeBtn")
|
||||
:SetSize(24, 24)
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetIcon("iconPack.18x18/PlayPause")
|
||||
:SetScript("OnClick", private.PauseResumeBtnOnClick)
|
||||
)
|
||||
:AddChild(UIElements.New("ProgressBar", "progressBar")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetProgress(0)
|
||||
:SetText(L["Starting Scan..."])
|
||||
)
|
||||
:AddChild(UIElements.NewNamed("ActionButton", "actionBtn", "TSMSniperBtn")
|
||||
:SetSize(165, 24)
|
||||
:SetText(BID)
|
||||
:SetDisabled(true)
|
||||
:DisableClickCooldown(true)
|
||||
:SetScript("OnClick", private.ActionButtonOnClick)
|
||||
)
|
||||
)
|
||||
:SetScript("OnUpdate", private.ScanFrameOnUpdate)
|
||||
:SetScript("OnHide", private.ScanFrameOnHide)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.OnItemLinked(name, itemLink)
|
||||
if private.selectionFrame then
|
||||
return false
|
||||
end
|
||||
private.fsm:ProcessEvent("EV_STOP_CLICKED")
|
||||
TSM.UI.AuctionUI.SetOpenPage(L["Browse"])
|
||||
TSM.UI.AuctionUI.Shopping.StartItemSearch(itemLink)
|
||||
return true
|
||||
end
|
||||
|
||||
function private.SelectionFrameOnHide(frame)
|
||||
assert(frame == private.selectionFrame)
|
||||
private.selectionFrame = nil
|
||||
end
|
||||
|
||||
function private.StartScanHelper(viewContainer, searchContext)
|
||||
if not TSM.UI.AuctionUI.StartingScan(L["Sniper"]) then
|
||||
return
|
||||
end
|
||||
viewContainer:SetPath("scan", true)
|
||||
private.fsm:ProcessEvent("EV_START_SCAN", searchContext)
|
||||
end
|
||||
|
||||
function private.BuyoutScanButtonOnClick(button)
|
||||
local viewContainer = button:GetParentElement():GetParentElement():GetParentElement()
|
||||
local searchContext = TSM.Sniper.BuyoutSearch.GetSearchContext()
|
||||
private.StartScanHelper(viewContainer, searchContext)
|
||||
end
|
||||
|
||||
function private.BidScanButtonOnClick(button)
|
||||
local viewContainer = button:GetParentElement():GetParentElement():GetParentElement()
|
||||
local searchContext = TSM.Sniper.BidSearch.GetSearchContext()
|
||||
private.StartScanHelper(viewContainer, searchContext)
|
||||
end
|
||||
|
||||
function private.AuctionsOnSelectionChanged()
|
||||
private.fsm:ProcessEvent("EV_AUCTION_SELECTION_CHANGED")
|
||||
end
|
||||
|
||||
function private.AuctionsOnRowRemoved(_, row)
|
||||
private.fsm:ProcessEvent("EV_AUCTION_ROW_REMOVED", row)
|
||||
end
|
||||
|
||||
function private.BackButtonOnClick()
|
||||
private.fsm:ProcessEvent("EV_STOP_CLICKED")
|
||||
end
|
||||
|
||||
function private.PauseResumeBtnOnClick(button)
|
||||
private.fsm:ProcessEvent("EV_PAUSE_RESUME_CLICKED")
|
||||
end
|
||||
|
||||
function private.ActionButtonOnClick(button)
|
||||
private.fsm:ProcessEvent("EV_ACTION_CLICKED")
|
||||
end
|
||||
|
||||
function private.RestartButtonOnClick(button)
|
||||
if not TSM.UI.AuctionUI.StartingScan(L["Sniper"]) then
|
||||
return
|
||||
end
|
||||
local lastScanType = private.hasLastScan
|
||||
local sniperFrame = button:GetParentElement():GetParentElement():GetParentElement()
|
||||
private.fsm:ProcessEvent("EV_STOP_CLICKED")
|
||||
if lastScanType == "bid" then
|
||||
sniperFrame:GetElement("selection.buttons.bidScanBtn"):Click()
|
||||
elseif lastScanType == "buyout" then
|
||||
sniperFrame:GetElement("selection.buttons.buyoutScanBtn"):Click()
|
||||
else
|
||||
error("Invalid last scan type: "..tostring(lastScanType))
|
||||
end
|
||||
end
|
||||
|
||||
function private.ScanFrameOnUpdate(frame)
|
||||
frame:SetScript("OnUpdate", nil)
|
||||
private.fsm:ProcessEvent("EV_SCAN_FRAME_SHOWN", frame)
|
||||
end
|
||||
|
||||
function private.ScanFrameOnHide(frame)
|
||||
private.fsm:ProcessEvent("EV_SCAN_FRAME_HIDDEN")
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- FSM
|
||||
-- ============================================================================
|
||||
|
||||
function private.FSMCreate()
|
||||
local fsmContext = {
|
||||
scanFrame = nil,
|
||||
auctionScan = nil,
|
||||
query = nil,
|
||||
progress = 0,
|
||||
progressText = L["Running Sniper Scan"],
|
||||
buttonsDisabled = true,
|
||||
findHash = nil,
|
||||
findAuction = nil,
|
||||
findResult = nil,
|
||||
numFound = 0,
|
||||
maxQuantity = 0,
|
||||
numActioned = 0,
|
||||
lastBuyQuantity = 0,
|
||||
numConfirmed = 0,
|
||||
searchContext = nil,
|
||||
scanDone = false,
|
||||
}
|
||||
Event.Register("CHAT_MSG_SYSTEM", private.FSMMessageEventHandler)
|
||||
Event.Register("UI_ERROR_MESSAGE", private.FSMMessageEventHandler)
|
||||
if not TSM.IsWowClassic() then
|
||||
Event.Register("COMMODITY_PURCHASE_SUCCEEDED", private.FSMBuyoutSuccess)
|
||||
end
|
||||
Event.Register("AUCTION_HOUSE_CLOSED", function()
|
||||
private.fsm:ProcessEvent("EV_AUCTION_HOUSE_CLOSED")
|
||||
end)
|
||||
AuctionHouseWrapper.RegisterAuctionIdUpdateCallback(function(...)
|
||||
private.fsm:ProcessEvent("EV_AUCTION_ID_UPDATE", ...)
|
||||
end)
|
||||
local function UpdateScanFrame(context)
|
||||
if not context.scanFrame or not context.searchContext then
|
||||
return
|
||||
end
|
||||
local actionText = nil
|
||||
if context.searchContext:IsBuyoutScan() then
|
||||
actionText = BUYOUT
|
||||
elseif context.searchContext:IsBidScan() then
|
||||
actionText = BID
|
||||
else
|
||||
error("Invalid scan type")
|
||||
end
|
||||
local bottom = context.scanFrame:GetElement("bottom")
|
||||
bottom:GetElement("actionBtn")
|
||||
:SetText(actionText)
|
||||
:SetDisabled(context.buttonsDisabled)
|
||||
bottom:GetElement("progressBar")
|
||||
:SetProgress(context.progress)
|
||||
:SetText(context.progressText or "")
|
||||
local auctionList = context.scanFrame:GetElement("auctions")
|
||||
:SetContext(context.auctionScan)
|
||||
:SetAuctionScan(context.auctionScan)
|
||||
:SetMarketValueFunction(context.searchContext:GetMarketValueFunc())
|
||||
if context.findAuction and not auctionList:GetSelection() then
|
||||
auctionList:SetSelection(context.findAuction)
|
||||
end
|
||||
local title = context.scanFrame:GetElement("header.title")
|
||||
if context.scanPausing or auctionList:GetSelection() then
|
||||
if context.searchContext:IsBuyoutScan() then
|
||||
title:SetText(L["Buyout Sniper Paused"])
|
||||
elseif context.searchContext:IsBidScan() then
|
||||
title:SetText(L["Bid Sniper Paused"])
|
||||
else
|
||||
error("Invalid scan type")
|
||||
end
|
||||
else
|
||||
if context.searchContext:IsBuyoutScan() then
|
||||
title:SetText(L["Buyout Sniper Running"])
|
||||
elseif context.searchContext:IsBidScan() then
|
||||
title:SetText(L["Bid Sniper Running"])
|
||||
else
|
||||
error("Invalid scan type")
|
||||
end
|
||||
end
|
||||
context.scanFrame:Draw()
|
||||
end
|
||||
local function ScanOnFilterDone(self, filter, numNewResults)
|
||||
if numNewResults > 0 then
|
||||
Sound.PlaySound(private.settings.sniperSound)
|
||||
end
|
||||
end
|
||||
private.fsm = FSM.New("SNIPER")
|
||||
:AddState(FSM.NewState("ST_INIT")
|
||||
:SetOnEnter(function(context, searchContext)
|
||||
private.hasLastScan = nil
|
||||
if context.searchContext then
|
||||
context.searchContext:KillThread()
|
||||
context.searchContext = nil
|
||||
end
|
||||
context.progress = 0
|
||||
context.progressText = L["Running Sniper Scan"]
|
||||
context.buttonsDisabled = true
|
||||
context.findHash = nil
|
||||
context.findAuction = nil
|
||||
context.findResult = nil
|
||||
context.numFound = 0
|
||||
context.numActioned = 0
|
||||
context.lastBuyQuantity = 0
|
||||
context.numConfirmed = 0
|
||||
if context.auctionScan then
|
||||
context.auctionScan:Release()
|
||||
context.auctionScan = nil
|
||||
end
|
||||
if searchContext then
|
||||
context.searchContext = searchContext
|
||||
return "ST_RUNNING_SCAN"
|
||||
elseif context.scanFrame then
|
||||
context.scanFrame:GetParentElement():SetPath("selection", true)
|
||||
context.scanFrame = nil
|
||||
end
|
||||
TSM.UI.AuctionUI.EndedScan(L["Sniper"])
|
||||
end)
|
||||
:AddTransition("ST_INIT")
|
||||
:AddTransition("ST_RUNNING_SCAN")
|
||||
:AddEventTransition("EV_START_SCAN", "ST_INIT")
|
||||
)
|
||||
:AddState(FSM.NewState("ST_RUNNING_SCAN")
|
||||
:SetOnEnter(function(context)
|
||||
context.scanDone = false
|
||||
if not context.searchContext then
|
||||
private.hasLastScan = nil
|
||||
elseif context.searchContext:IsBuyoutScan() then
|
||||
private.hasLastScan = "buyout"
|
||||
elseif context.searchContext:IsBidScan() then
|
||||
private.hasLastScan = "bid"
|
||||
else
|
||||
error("Invalid scan type")
|
||||
end
|
||||
if not context.auctionScan then
|
||||
context.auctionScan = AuctionScan.GetManager()
|
||||
:SetResolveSellers(false)
|
||||
:SetScript("OnQueryDone", ScanOnFilterDone)
|
||||
end
|
||||
if context.scanFrame then
|
||||
context.scanFrame:GetElement("bottom.progressBar"):SetProgressIconHidden(false)
|
||||
end
|
||||
UpdateScanFrame(context)
|
||||
context.searchContext:StartThread(private.FSMScanCallback, context.auctionScan)
|
||||
if TSM.IsWowClassic() then
|
||||
Delay.AfterTime("sniperPhaseDetect", PHASED_TIME, private.FSMPhasedCallback)
|
||||
end
|
||||
end)
|
||||
:SetOnExit(function(context)
|
||||
Delay.Cancel("sniperPhaseDetect")
|
||||
end)
|
||||
:AddTransition("ST_RESULTS")
|
||||
:AddTransition("ST_WAITING_FOR_PAUSE")
|
||||
:AddTransition("ST_FINDING_AUCTION")
|
||||
:AddTransition("ST_INIT")
|
||||
:AddEvent("EV_PAUSE_RESUME_CLICKED", function(context)
|
||||
return "ST_WAITING_FOR_PAUSE"
|
||||
end)
|
||||
:AddEvent("EV_SCAN_COMPLETE", function(context)
|
||||
local selection = context.scanFrame and context.scanFrame:GetElement("auctions"):GetSelection()
|
||||
if selection and selection:IsSubRow() then
|
||||
return "ST_FINDING_AUCTION"
|
||||
else
|
||||
if TSM.IsWowClassic() then
|
||||
return "ST_RESULTS"
|
||||
else
|
||||
-- wait 30 seconds before rescanning to avoid spamming the server with API calls
|
||||
context.scanDone = true
|
||||
Delay.AfterTime("SNIPER_RESCAN_DELAY", RETAIL_RESCAN_DELAY, private.FSMRescanDelayed)
|
||||
end
|
||||
end
|
||||
end)
|
||||
:AddEventTransition("EV_RESCAN_DELAYED", "ST_RESULTS")
|
||||
:AddEventTransition("EV_SCAN_FAILED", "ST_INIT")
|
||||
:AddEvent("EV_PHASED", function()
|
||||
Log.PrintUser(L["You've been phased which has caused the AH to stop working due to a bug on Blizzard's end. Please close and reopen the AH and restart Sniper."])
|
||||
return "ST_INIT"
|
||||
end)
|
||||
:AddEvent("EV_AUCTION_SELECTION_CHANGED", function(context)
|
||||
assert(context.scanFrame)
|
||||
if context.scanFrame:GetElement("auctions"):GetSelection() then
|
||||
if context.scanDone then
|
||||
return "ST_RESULTS"
|
||||
else
|
||||
-- the user selected something, so cancel the current scan
|
||||
context.auctionScan:Cancel()
|
||||
end
|
||||
end
|
||||
end)
|
||||
)
|
||||
:AddState(FSM.NewState("ST_WAITING_FOR_PAUSE")
|
||||
:SetOnEnter(function(context)
|
||||
context.scanPausing = true
|
||||
context.progressText = L["Scan Paused"]
|
||||
if context.scanFrame then
|
||||
context.scanFrame:GetElement("bottom.progressBar"):SetProgressIconHidden(true)
|
||||
end
|
||||
UpdateScanFrame(context)
|
||||
end)
|
||||
:SetOnExit(function(context)
|
||||
context.scanPausing = false
|
||||
context.progressText = L["Running Sniper Scan"]
|
||||
if context.scanFrame then
|
||||
context.scanFrame:GetElement("bottom.progressBar"):SetProgressIconHidden(false)
|
||||
end
|
||||
UpdateScanFrame(context)
|
||||
end)
|
||||
:AddEvent("EV_PAUSE_RESUME_CLICKED", function(context)
|
||||
return "ST_RESULTS"
|
||||
end)
|
||||
:AddTransition("ST_RESULTS")
|
||||
:AddTransition("ST_INIT")
|
||||
)
|
||||
:AddState(FSM.NewState("ST_RESULTS")
|
||||
:SetOnEnter(function(context)
|
||||
context.searchContext:KillThread()
|
||||
context.findAuction = nil
|
||||
context.findResult = nil
|
||||
context.numFound = 0
|
||||
context.numActioned = 0
|
||||
context.lastBuyQuantity = 0
|
||||
context.numConfirmed = 0
|
||||
context.progress = 0
|
||||
context.progressText = L["Running Sniper Scan"]
|
||||
context.buttonsDisabled = true
|
||||
UpdateScanFrame(context)
|
||||
local selection = context.scanFrame and context.scanFrame:GetElement("auctions"):GetSelection()
|
||||
if selection and selection:IsSubRow() then
|
||||
return "ST_FINDING_AUCTION"
|
||||
else
|
||||
return "ST_RUNNING_SCAN"
|
||||
end
|
||||
end)
|
||||
:AddTransition("ST_RUNNING_SCAN")
|
||||
:AddTransition("ST_AUCTION_FOUND")
|
||||
:AddTransition("ST_FINDING_AUCTION")
|
||||
:AddTransition("ST_INIT")
|
||||
)
|
||||
:AddState(FSM.NewState("ST_FINDING_AUCTION")
|
||||
:SetOnEnter(function(context)
|
||||
assert(context.scanFrame)
|
||||
context.findAuction = context.scanFrame:GetElement("auctions"):GetSelection()
|
||||
assert(context.findAuction:IsSubRow())
|
||||
context.findHash = context.findAuction:GetHashes()
|
||||
context.progress = 0
|
||||
context.progressText = L["Finding Selected Auction"]
|
||||
context.buttonsDisabled = true
|
||||
if context.scanFrame then
|
||||
context.scanFrame:GetElement("bottom.progressBar"):SetProgressIconHidden(false)
|
||||
end
|
||||
UpdateScanFrame(context)
|
||||
TSM.Shopping.SearchCommon.StartFindAuction(context.auctionScan, context.findAuction, private.FSMFindAuctionCallback, true)
|
||||
end)
|
||||
:SetOnExit(function(context)
|
||||
TSM.Shopping.SearchCommon.StopFindAuction()
|
||||
end)
|
||||
:AddTransition("ST_RESULTS")
|
||||
:AddTransition("ST_FINDING_AUCTION")
|
||||
:AddTransition("ST_AUCTION_FOUND")
|
||||
:AddTransition("ST_AUCTION_NOT_FOUND")
|
||||
:AddTransition("ST_INIT")
|
||||
:AddEventTransition("EV_AUCTION_FOUND", "ST_AUCTION_FOUND")
|
||||
:AddEventTransition("EV_AUCTION_NOT_FOUND", "ST_AUCTION_NOT_FOUND")
|
||||
:AddEvent("EV_AUCTION_SELECTION_CHANGED", function(context)
|
||||
assert(context.scanFrame)
|
||||
local selection = context.scanFrame and context.scanFrame:GetElement("auctions"):GetSelection()
|
||||
if selection and selection:IsSubRow() then
|
||||
return "ST_FINDING_AUCTION"
|
||||
else
|
||||
return "ST_RESULTS"
|
||||
end
|
||||
end)
|
||||
:AddEvent("EV_AUCTION_ROW_REMOVED", function(context, row)
|
||||
if not row:IsSubRow() then
|
||||
return
|
||||
end
|
||||
local removingFindAuction = context.findAuction == row
|
||||
row:GetResultRow():RemoveSubRow(row)
|
||||
context.scanFrame:GetElement("auctions"):UpdateData(true)
|
||||
if removingFindAuction then
|
||||
return "ST_RESULTS"
|
||||
end
|
||||
end)
|
||||
:AddEvent("EV_SCAN_FRAME_HIDDEN", function(context)
|
||||
context.scanFrame = nil
|
||||
context.findAuction = nil
|
||||
return "ST_RESULTS"
|
||||
end)
|
||||
)
|
||||
:AddState(FSM.NewState("ST_AUCTION_FOUND")
|
||||
:SetOnEnter(function(context, result)
|
||||
local selection = context.scanFrame:GetElement("auctions"):GetSelection()
|
||||
-- update the selection in case the result rows changed
|
||||
if context.findHash == selection:GetHashes() then
|
||||
context.findAuction = selection
|
||||
end
|
||||
if TSM.IsWowClassic() then
|
||||
context.findResult = result
|
||||
context.numFound = #result
|
||||
else
|
||||
local maxCommodity = context.findAuction:IsCommodity() and context.findAuction:GetResultRow():GetMaxQuantities()
|
||||
local numCanBuy = maxCommodity or result
|
||||
context.findResult = numCanBuy > 0
|
||||
context.numFound = numCanBuy
|
||||
context.maxQuantity = maxCommodity or 1
|
||||
end
|
||||
assert(context.numActioned == 0 and context.numConfirmed == 0)
|
||||
return "ST_BIDDING_BUYING"
|
||||
end)
|
||||
:AddTransition("ST_BIDDING_BUYING")
|
||||
)
|
||||
:AddState(FSM.NewState("ST_AUCTION_NOT_FOUND")
|
||||
:SetOnEnter(function(context)
|
||||
local _, rawLink = context.findAuction:GetLinks()
|
||||
context.findAuction:GetResultRow():RemoveSubRow(context.findAuction)
|
||||
Log.PrintfUser(L["Failed to find auction for %s, so removing it from the results."], rawLink)
|
||||
return "ST_RESULTS"
|
||||
end)
|
||||
:AddTransition("ST_RESULTS")
|
||||
)
|
||||
:AddState(FSM.NewState("ST_BIDDING_BUYING")
|
||||
:SetOnEnter(function(context, numToRemove)
|
||||
if numToRemove then
|
||||
-- remove the one we just bought
|
||||
context.findAuction:DecrementQuantity(numToRemove)
|
||||
context.scanFrame:GetElement("auctions"):UpdateData()
|
||||
context.findAuction = context.scanFrame and context.scanFrame:GetElement("auctions"):GetSelection()
|
||||
if context.findAuction and not context.findAuction:IsSubRow() then
|
||||
context.findAuction = nil
|
||||
end
|
||||
end
|
||||
local selection = context.scanFrame and context.scanFrame:GetElement("auctions"):GetSelection()
|
||||
if selection and not selection:IsSubRow() then
|
||||
selection = nil
|
||||
end
|
||||
local isPlayer, isHighBidder = false, false
|
||||
if selection then
|
||||
assert(selection:IsSubRow())
|
||||
local ownerStr = selection and selection:GetOwnerInfo() or nil
|
||||
isPlayer = PlayerInfo.IsPlayer(ownerStr, true, true, true)
|
||||
isHighBidder = select(4, selection:GetBidInfo())
|
||||
end
|
||||
local auctionSelected = selection and context.findHash == selection:GetHashes()
|
||||
local numCanAction = not auctionSelected and 0 or (context.numFound - context.numActioned)
|
||||
local numConfirming = context.numActioned - context.numConfirmed
|
||||
local progressText = nil
|
||||
local actionFormatStr = nil
|
||||
if context.searchContext:IsBuyoutScan() then
|
||||
actionFormatStr = L["Buy %d / %d"]
|
||||
elseif context.searchContext:IsBidScan() then
|
||||
actionFormatStr = L["Bid %d / %d"]
|
||||
else
|
||||
error("Invalid scan type")
|
||||
end
|
||||
if numConfirming == 0 and numCanAction == 0 then
|
||||
-- we're done bidding/buying and confirming this batch
|
||||
return "ST_RESULTS"
|
||||
elseif numConfirming == 0 then
|
||||
-- we can still bid/buy more
|
||||
progressText = format(actionFormatStr, context.numActioned + 1, context.numFound)
|
||||
elseif numCanAction == 0 then
|
||||
-- we're just confirming
|
||||
progressText = format(L["Confirming %d / %d"], context.numConfirmed + 1, context.numFound)
|
||||
else
|
||||
-- we can bid/buy more while confirming
|
||||
progressText = format(actionFormatStr.." ("..L["Confirming %d / %d"]..")", context.numActioned + 1, context.numFound, context.numConfirmed + 1, context.numFound)
|
||||
end
|
||||
context.progress = context.numConfirmed / context.numFound
|
||||
context.progressText = L["Scan Paused"].." - "..progressText
|
||||
if numCanAction == 0 or isPlayer or (not TSM.IsWowClassic() and numConfirming > 0) then
|
||||
context.buttonsDisabled = true
|
||||
else
|
||||
if context.searchContext:IsBuyoutScan() then
|
||||
context.buttonsDisabled = not AuctionScan.CanBuyout(selection, context.auctionScan)
|
||||
elseif context.searchContext:IsBidScan() then
|
||||
context.buttonsDisabled = not AuctionScan.CanBid(selection)
|
||||
else
|
||||
error("Invalid scan type")
|
||||
end
|
||||
end
|
||||
if context.scanFrame then
|
||||
context.scanFrame:GetElement("bottom.progressBar")
|
||||
:SetProgressIconHidden(context.numConfirmed == context.numActioned)
|
||||
context.scanFrame:GetElement("bottom.actionBtn")
|
||||
:SetDisabled(isPlayer or (isHighBidder and context.searchContext:IsBidScan()))
|
||||
:Draw()
|
||||
end
|
||||
UpdateScanFrame(context)
|
||||
end)
|
||||
:AddTransition("ST_BID_BUY_CONFIRMATION")
|
||||
:AddTransition("ST_BIDDING_BUYING")
|
||||
:AddTransition("ST_PLACING_BID_BUY")
|
||||
:AddTransition("ST_CONFIRMING_BID_BUY")
|
||||
:AddTransition("ST_RESULTS")
|
||||
:AddTransition("ST_INIT")
|
||||
:AddEvent("EV_PAUSE_RESUME_CLICKED", function(context)
|
||||
context.scanFrame:GetElement("auctions"):SetSelection(nil)
|
||||
return "ST_RESULTS"
|
||||
end)
|
||||
:AddEventTransition("EV_AUCTION_SELECTION_CHANGED", "ST_RESULTS")
|
||||
:AddEventTransition("EV_ACTION_CLICKED", "ST_BID_BUY_CONFIRMATION")
|
||||
:AddEvent("EV_CONFIRMED", function(context, isBuy, quantity)
|
||||
assert(isBuy == context.searchContext:IsBuyoutScan())
|
||||
return "ST_PLACING_BID_BUY", quantity
|
||||
end)
|
||||
:AddEvent("EV_MSG", function(context, msg)
|
||||
local _, rawLink = context.findAuction:GetLinks()
|
||||
if msg == LE_GAME_ERR_AUCTION_HIGHER_BID or msg == LE_GAME_ERR_ITEM_NOT_FOUND or msg == LE_GAME_ERR_AUCTION_BID_OWN or msg == LE_GAME_ERR_NOT_ENOUGH_MONEY or msg == LE_GAME_ERR_ITEM_MAX_COUNT then
|
||||
-- failed to bid/buy an auction
|
||||
return "ST_CONFIRMING_BID_BUY", false
|
||||
elseif context.searchContext:IsBidScan() and msg == ERR_AUCTION_BID_PLACED then
|
||||
-- bid on an auction
|
||||
return "ST_CONFIRMING_BID_BUY", true
|
||||
elseif context.searchContext:IsBuyoutScan() and msg == format(ERR_AUCTION_WON_S, ItemInfo.GetName(rawLink)) then
|
||||
-- bought an auction
|
||||
return "ST_CONFIRMING_BID_BUY", true
|
||||
end
|
||||
end)
|
||||
:AddEvent("EV_BUYOUT_SUCCESS", function(context)
|
||||
return "ST_CONFIRMING_BID_BUY", true
|
||||
end)
|
||||
:AddEvent("EV_AUCTION_ID_UPDATE", function(context, oldAuctionId, newAuctionId, newResultInfo)
|
||||
if not context.findAuction or select(2, context.findAuction:GetListingInfo()) ~= oldAuctionId then
|
||||
return
|
||||
end
|
||||
context.findAuction:UpdateResultInfo(newAuctionId, newResultInfo)
|
||||
context.findHash = context.findAuction:GetHashes()
|
||||
end)
|
||||
)
|
||||
:AddState(FSM.NewState("ST_BID_BUY_CONFIRMATION")
|
||||
:SetOnEnter(function(context)
|
||||
local selection = context.scanFrame:GetElement("auctions"):GetSelection()
|
||||
local index = TSM.IsWowClassic() and context.findResult[#context.findResult] or nil
|
||||
if TSM.UI.AuctionUI.BuyUtil.ShowConfirmation(context.scanFrame, selection, context.searchContext:IsBuyoutScan(), context.numConfirmed + 1, context.numFound, context.maxQuantity, private.FSMConfirmationCallback, context.auctionScan, index, true, context.searchContext:GetMarketValueFunc()) then
|
||||
return "ST_BIDDING_BUYING"
|
||||
else
|
||||
local quantity = selection:GetQuantities()
|
||||
return "ST_PLACING_BID_BUY", quantity
|
||||
end
|
||||
end)
|
||||
:AddTransition("ST_PLACING_BID_BUY")
|
||||
:AddTransition("ST_BIDDING_BUYING")
|
||||
)
|
||||
:AddState(FSM.NewState("ST_PLACING_BID_BUY")
|
||||
:SetOnEnter(function(context, quantity)
|
||||
local index = TSM.IsWowClassic() and tremove(context.findResult, #context.findResult) or nil
|
||||
assert(not TSM.IsWowClassic() or index)
|
||||
local bidBuyout = nil
|
||||
if context.searchContext:IsBuyoutScan() then
|
||||
bidBuyout = context.findAuction:GetBuyouts()
|
||||
elseif context.searchContext:IsBidScan() then
|
||||
bidBuyout = context.findAuction:GetRequiredBid()
|
||||
else
|
||||
error("Invalid scan type")
|
||||
end
|
||||
local result = context.auctionScan:PlaceBidOrBuyout(index, bidBuyout, context.findAuction, quantity)
|
||||
if result then
|
||||
MailTracking.RecordAuctionBuyout(ItemString.GetBaseFast(context.findAuction:GetItemString()), quantity)
|
||||
context.numActioned = context.numActioned + (TSM.IsWowClassic() and 1 or quantity)
|
||||
context.lastBuyQuantity = quantity
|
||||
else
|
||||
local _, rawLink = context.findAuction:GetLinks()
|
||||
if context.searchContext:IsBuyoutScan() then
|
||||
Log.PrintfUser(L["Failed to buy auction of %s (x%s) for %s."], rawLink, quantity, Money.ToString(bidBuyout, nil, "OPT_83_NO_COPPER"))
|
||||
elseif context.searchContext:IsBidScan() then
|
||||
Log.PrintfUser(L["Failed to bid on auction of %s (x%s) for %s."], rawLink, quantity, Money.ToString(bidBuyout, nil, "OPT_83_NO_COPPER"))
|
||||
else
|
||||
error("Invalid scan type")
|
||||
end
|
||||
end
|
||||
return "ST_BIDDING_BUYING"
|
||||
end)
|
||||
:AddTransition("ST_BIDDING_BUYING")
|
||||
)
|
||||
:AddState(FSM.NewState("ST_CONFIRMING_BID_BUY")
|
||||
:SetOnEnter(function(context, success)
|
||||
if not success then
|
||||
local _, rawLink = context.findAuction:GetLinks()
|
||||
local quantity = context.findAuction:GetQuantities()
|
||||
local bidBuyout = nil
|
||||
if context.searchContext:IsBuyoutScan() then
|
||||
bidBuyout = context.findAuction:GetBuyouts()
|
||||
elseif context.searchContext:IsBidScan() then
|
||||
bidBuyout = context.findAuction:GetRequiredBid()
|
||||
else
|
||||
error("Invalid scan type")
|
||||
end
|
||||
if context.searchContext:IsBuyoutScan() then
|
||||
Log.PrintfUser(L["Failed to buy auction of %s (x%s) for %s."], rawLink, quantity, Money.ToString(bidBuyout, nil, "OPT_83_NO_COPPER"))
|
||||
elseif context.searchContext:IsBidScan() then
|
||||
Log.PrintfUser(L["Failed to bid on auction of %s (x%s) for %s."], rawLink, quantity, Money.ToString(bidBuyout, nil, "OPT_83_NO_COPPER"))
|
||||
else
|
||||
error("Invalid scan type")
|
||||
end
|
||||
end
|
||||
context.numConfirmed = context.numConfirmed + (TSM.IsWowClassic() and 1 or context.lastBuyQuantity)
|
||||
context.findAuction = context.scanFrame and context.scanFrame:GetElement("auctions"):GetSelection()
|
||||
return "ST_BIDDING_BUYING", context.lastBuyQuantity
|
||||
end)
|
||||
:AddTransition("ST_BIDDING_BUYING")
|
||||
)
|
||||
:AddDefaultEvent("EV_SCAN_FRAME_SHOWN", function(context, scanFrame)
|
||||
context.scanFrame = scanFrame
|
||||
UpdateScanFrame(context)
|
||||
end)
|
||||
:AddDefaultEvent("EV_SCAN_FRAME_HIDDEN", function(context)
|
||||
context.scanFrame = nil
|
||||
context.findAuction = nil
|
||||
end)
|
||||
:AddDefaultEventTransition("EV_AUCTION_HOUSE_CLOSED", "ST_INIT")
|
||||
:AddDefaultEventTransition("EV_STOP_CLICKED", "ST_INIT")
|
||||
:AddDefaultEvent("EV_AUCTION_ROW_REMOVED", function(context, row)
|
||||
if not row:IsSubRow() then
|
||||
return
|
||||
end
|
||||
row:GetResultRow():RemoveSubRow(row)
|
||||
context.scanFrame:GetElement("auctions"):UpdateData(true)
|
||||
end)
|
||||
:Init("ST_INIT", fsmContext)
|
||||
end
|
||||
|
||||
function private.FSMMessageEventHandler(_, msg)
|
||||
private.fsm:SetLoggingEnabled(false)
|
||||
private.fsm:ProcessEvent("EV_MSG", msg)
|
||||
private.fsm:SetLoggingEnabled(true)
|
||||
end
|
||||
|
||||
function private.FSMBuyoutSuccess()
|
||||
private.fsm:ProcessEvent("EV_BUYOUT_SUCCESS")
|
||||
end
|
||||
|
||||
function private.FSMRescanDelayed()
|
||||
private.fsm:ProcessEvent("EV_RESCAN_DELAYED")
|
||||
end
|
||||
|
||||
function private.FSMScanCallback(success)
|
||||
if success then
|
||||
private.fsm:ProcessEvent("EV_SCAN_COMPLETE")
|
||||
else
|
||||
private.fsm:ProcessEvent("EV_SCAN_FAILED")
|
||||
end
|
||||
end
|
||||
|
||||
function private.FSMPhasedCallback()
|
||||
private.fsm:ProcessEvent("EV_PHASED")
|
||||
end
|
||||
|
||||
function private.FSMFindAuctionCallback(result)
|
||||
if result then
|
||||
private.fsm:ProcessEvent("EV_AUCTION_FOUND", result)
|
||||
else
|
||||
private.fsm:ProcessEvent("EV_AUCTION_NOT_FOUND")
|
||||
end
|
||||
end
|
||||
|
||||
function private.FSMConfirmationCallback(isBuy, quantity)
|
||||
private.fsm:ProcessEvent("EV_CONFIRMED", isBuy, quantity)
|
||||
end
|
||||
556
Core/UI/BankingUI/Core.lua
Normal file
556
Core/UI/BankingUI/Core.lua
Normal file
@@ -0,0 +1,556 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local BankingUI = TSM.UI:NewPackage("BankingUI")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local FSM = TSM.Include("Util.FSM")
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local Log = TSM.Include("Util.Log")
|
||||
local Settings = TSM.Include("Service.Settings")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local private = {
|
||||
settings = nil,
|
||||
fsm = nil,
|
||||
groupSearch = "",
|
||||
}
|
||||
local MIN_FRAME_SIZE = { width = 325, height = 600 }
|
||||
local MODULE_LIST = {
|
||||
"Warehousing",
|
||||
"Auctioning",
|
||||
"Mailing",
|
||||
}
|
||||
local BUTTON_TEXT_LOOKUP = {
|
||||
Warehousing = L["Warehousing"],
|
||||
Auctioning = L["Auctioning"],
|
||||
Mailing = L["Mailing"],
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function BankingUI.OnInitialize()
|
||||
private.settings = Settings.NewView()
|
||||
:AddKey("global", "bankingUIContext", "frame")
|
||||
:AddKey("global", "bankingUIContext", "isOpen")
|
||||
:AddKey("global", "bankingUIContext", "tab")
|
||||
:AddKey("char", "bankingUIContext", "warehousingGroupTree")
|
||||
:AddKey("char", "bankingUIContext", "auctioningGroupTree")
|
||||
:AddKey("char", "bankingUIContext", "mailingGroupTree")
|
||||
private.FSMCreate()
|
||||
end
|
||||
|
||||
function BankingUI.OnDisable()
|
||||
-- hide the frame
|
||||
private.fsm:ProcessEvent("EV_BANK_CLOSED")
|
||||
end
|
||||
|
||||
function BankingUI.Toggle()
|
||||
private.fsm:ProcessEvent("EV_TOGGLE")
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Main Frame
|
||||
-- ============================================================================
|
||||
|
||||
function private.CreateMainFrame()
|
||||
TSM.UI.AnalyticsRecordPathChange("banking")
|
||||
local frame = UIElements.New("ApplicationFrame", "base")
|
||||
:SetParent(UIParent)
|
||||
:SetSettingsContext(private.settings, "frame")
|
||||
:SetMinResize(MIN_FRAME_SIZE.width, MIN_FRAME_SIZE.height)
|
||||
:SetStrata("HIGH")
|
||||
:SetTitle(L["Banking"])
|
||||
:SetScript("OnHide", private.BaseFrameOnHide)
|
||||
:SetContentFrame(UIElements.New("Frame", "content")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetBackgroundColor("PRIMARY_BG")
|
||||
:AddChild(UIElements.New("Frame", "navButtons")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:SetMargin(8)
|
||||
:SetPadding(-4, 0, 0, 0) -- account for the left margin of the first button
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "search")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:SetMargin(8, 8, 0, 12)
|
||||
:AddChild(UIElements.New("Input", "input")
|
||||
:SetIconTexture("iconPack.18x18/Search")
|
||||
:AllowItemInsert(true)
|
||||
:SetClearButtonEnabled(true)
|
||||
:SetValue(private.groupSearch)
|
||||
:SetHintText(L["Search Groups"])
|
||||
:SetScript("OnValueChanged", private.GroupSearchOnValueChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "expandAllBtn")
|
||||
:SetSize(24, 24)
|
||||
:SetMargin(8, 4, 0, 0)
|
||||
:SetBackground("iconPack.18x18/Expand All")
|
||||
:SetScript("OnClick", private.ExpandAllGroupsOnClick)
|
||||
:SetTooltip(L["Expand / Collapse All Groups"])
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "selectAllBtn")
|
||||
:SetSize(24, 24)
|
||||
:SetBackground("iconPack.18x18/Select All")
|
||||
:SetScript("OnClick", private.SelectAllGroupsOnClick)
|
||||
:SetTooltip(L["Select / Deselect All Groups"])
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("ApplicationGroupTree", "groupTree")
|
||||
:SetSettingsContext(private.settings, private.GetSettingsContextKey())
|
||||
:SetQuery(TSM.Groups.CreateQuery(), private.settings.tab)
|
||||
:SetSearchString(private.groupSearch)
|
||||
)
|
||||
:AddChild(UIElements.New("Texture", "line")
|
||||
:SetHeight(2)
|
||||
:SetTexture("ACTIVE_BG")
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "footer")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(170)
|
||||
:SetPadding(8)
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT")
|
||||
:AddChild(UIElements.New("ProgressBar", "progressBar")
|
||||
:SetHeight(24)
|
||||
:SetProgress(0)
|
||||
:SetProgressIconHidden(true)
|
||||
:SetText(L["Select Action"])
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "buttons")
|
||||
:SetLayout("VERTICAL")
|
||||
)
|
||||
)
|
||||
)
|
||||
frame:GetElement("titleFrame.closeBtn"):SetScript("OnClick", private.CloseBtnOnClick)
|
||||
|
||||
for _, module in ipairs(MODULE_LIST) do
|
||||
frame:GetElement("content.navButtons"):AddChild(UIElements.New("ActionButton", "navBtn_"..module)
|
||||
:SetHeight(20)
|
||||
:SetMargin(4, 0, 0, 0)
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetContext(module)
|
||||
:SetText(BUTTON_TEXT_LOOKUP[module])
|
||||
:SetScript("OnClick", private.NavBtnOnClick)
|
||||
)
|
||||
end
|
||||
|
||||
private.UpdateCurrentModule(frame)
|
||||
|
||||
return frame
|
||||
end
|
||||
|
||||
function private.GetSettingsContextKey()
|
||||
if private.settings.tab == "Warehousing" then
|
||||
return "warehousingGroupTree"
|
||||
elseif private.settings.tab == "Auctioning" then
|
||||
return "auctioningGroupTree"
|
||||
elseif private.settings.tab == "Mailing" then
|
||||
return "mailingGroupTree"
|
||||
else
|
||||
error("Unexpected tab: "..tostring(private.settings.tab))
|
||||
end
|
||||
end
|
||||
|
||||
function private.UpdateCurrentModule(frame)
|
||||
if not TSM.IsWowClassic() then
|
||||
ReagentBankFrame_OnShow(ReagentBankFrame)
|
||||
end
|
||||
-- update nav buttons
|
||||
local navButtonsFrame = frame:GetElement("content.navButtons")
|
||||
for _, module in ipairs(MODULE_LIST) do
|
||||
navButtonsFrame:GetElement("navBtn_"..module)
|
||||
:SetPressed(module == private.settings.tab)
|
||||
end
|
||||
navButtonsFrame:Draw()
|
||||
|
||||
-- update group tree
|
||||
frame:GetElement("content.groupTree")
|
||||
:SetSettingsContext(private.settings, private.GetSettingsContextKey())
|
||||
:SetQuery(TSM.Groups.CreateQuery(), private.settings.tab)
|
||||
:UpdateData(true)
|
||||
:Draw()
|
||||
|
||||
-- update footer buttons
|
||||
local footerButtonsFrame = frame:GetElement("content.footer.buttons")
|
||||
footerButtonsFrame:ReleaseAllChildren()
|
||||
if private.settings.tab == "Warehousing" then
|
||||
footerButtonsFrame:AddChild(UIElements.New("Frame", "row1")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:AddChild(UIElements.New("ActionButton", "moveBankBtn")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 8, 8, 0)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Move to bank"])
|
||||
:SetContext(TSM.Banking.Warehousing.MoveGroupsToBank)
|
||||
:SetScript("OnClick", private.GroupBtnOnClick)
|
||||
)
|
||||
:AddChild(UIElements.New("ActionButton", "moveBagsBtn")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, 8, 0)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Move to bags"])
|
||||
:SetContext(TSM.Banking.Warehousing.MoveGroupsToBags)
|
||||
:SetScript("OnClick", private.GroupBtnOnClick)
|
||||
)
|
||||
)
|
||||
footerButtonsFrame:AddChild(UIElements.New("ActionButton", "restockBagsBtn")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, 8, 0)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Restock bags"])
|
||||
:SetContext(TSM.Banking.Warehousing.RestockBags)
|
||||
:SetScript("OnClick", private.GroupBtnOnClick)
|
||||
)
|
||||
footerButtonsFrame:AddChild(UIElements.New("ActionButton", "depositReagentsBtn")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, 8, 0)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetDisabled(TSM.IsWowClassic())
|
||||
:SetText(L["Deposit reagents"])
|
||||
:SetScript("OnClick", private.WarehousingDepositReagentsBtnOnClick)
|
||||
)
|
||||
footerButtonsFrame:AddChild(UIElements.New("Frame", "row4")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:AddChild(UIElements.New("ActionButton", "emptyBagsBtn")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 8, 8, 0)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Empty bags"])
|
||||
:SetContext(TSM.Banking.EmptyBags)
|
||||
:SetScript("OnClick", private.SimpleBtnOnClick)
|
||||
)
|
||||
:AddChild(UIElements.New("ActionButton", "restoreBagsBtn")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, 8, 0)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Restore bags"])
|
||||
:SetContext(TSM.Banking.RestoreBags)
|
||||
:SetScript("OnClick", private.SimpleBtnOnClick)
|
||||
)
|
||||
)
|
||||
elseif private.settings.tab == "Auctioning" then
|
||||
footerButtonsFrame:AddChild(UIElements.New("ActionButton", "moveBankBtn")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, 8, 0)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Move to bank"])
|
||||
:SetContext(TSM.Banking.Auctioning.MoveGroupsToBank)
|
||||
:SetScript("OnClick", private.GroupBtnOnClick)
|
||||
)
|
||||
footerButtonsFrame:AddChild(UIElements.New("ActionButton", "postCapBagsBtn")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, 8, 0)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Post cap to bags"])
|
||||
:SetContext(TSM.Banking.Auctioning.PostCapToBags)
|
||||
:SetScript("OnClick", private.GroupBtnOnClick)
|
||||
)
|
||||
footerButtonsFrame:AddChild(UIElements.New("ActionButton", "shortfallBagsBtn")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, 8, 0)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Shortfall to bags"])
|
||||
:SetContext(TSM.Banking.Auctioning.ShortfallToBags)
|
||||
:SetScript("OnClick", private.GroupBtnOnClick)
|
||||
)
|
||||
footerButtonsFrame:AddChild(UIElements.New("ActionButton", "maxExpBankBtn")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, 8, 0)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Max expires to bank"])
|
||||
:SetContext(TSM.Banking.Auctioning.MaxExpiresToBank)
|
||||
:SetScript("OnClick", private.GroupBtnOnClick)
|
||||
)
|
||||
elseif private.settings.tab == "Mailing" then
|
||||
footerButtonsFrame:AddChild(UIElements.New("ActionButton", "moveBankBtn")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, 8, 0)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Move to bank"])
|
||||
:SetContext(TSM.Banking.Mailing.MoveGroupsToBank)
|
||||
:SetScript("OnClick", private.GroupBtnOnClick)
|
||||
)
|
||||
footerButtonsFrame:AddChild(UIElements.New("ActionButton", "nongroupBankBtn")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, 8, 0)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Nongroup to bank"])
|
||||
:SetContext(TSM.Banking.Mailing.NongroupToBank)
|
||||
:SetScript("OnClick", private.SimpleBtnOnClick)
|
||||
)
|
||||
footerButtonsFrame:AddChild(UIElements.New("ActionButton", "targetShortfallBagsBtn")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, 8, 0)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Target shortfall to bags"])
|
||||
:SetContext(TSM.Banking.Mailing.TargetShortfallToBags)
|
||||
:SetScript("OnClick", private.GroupBtnOnClick)
|
||||
)
|
||||
footerButtonsFrame:AddChild(UIElements.New("Frame", "row4")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:AddChild(UIElements.New("ActionButton", "emptyBagsBtn")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 8, 8, 0)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Empty bags"])
|
||||
:SetContext(TSM.Banking.EmptyBags)
|
||||
:SetScript("OnClick", private.SimpleBtnOnClick)
|
||||
)
|
||||
:AddChild(UIElements.New("ActionButton", "restoreBagsBtn")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, 8, 0)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Restore bags"])
|
||||
:SetContext(TSM.Banking.RestoreBags)
|
||||
:SetScript("OnClick", private.SimpleBtnOnClick)
|
||||
)
|
||||
)
|
||||
else
|
||||
error("Unexpected module: "..tostring(private.settings.tab))
|
||||
end
|
||||
footerButtonsFrame:Draw()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.BaseFrameOnHide()
|
||||
TSM.UI.AnalyticsRecordClose("banking")
|
||||
end
|
||||
|
||||
function private.CloseBtnOnClick(button)
|
||||
Log.PrintUser(L["Hiding the TSM Banking UI. Type '/tsm bankui' to reopen it."])
|
||||
button:GetParentElement():Hide()
|
||||
private.fsm:ProcessEvent("EV_FRAME_HIDDEN")
|
||||
end
|
||||
|
||||
function private.GroupSearchOnValueChanged(input)
|
||||
private.groupSearch = strlower(input:GetValue())
|
||||
input:GetElement("__parent.__parent.groupTree")
|
||||
:SetSearchString(private.groupSearch)
|
||||
:Draw()
|
||||
end
|
||||
|
||||
function private.NavBtnOnClick(button)
|
||||
private.settings.tab = button:GetContext()
|
||||
private.UpdateCurrentModule(button:GetBaseElement())
|
||||
private.fsm:ProcessEvent("EV_NAV_CHANGED")
|
||||
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.WarehousingDepositReagentsBtnOnClick()
|
||||
DepositReagentBank()
|
||||
end
|
||||
|
||||
function private.SimpleBtnOnClick(button)
|
||||
private.fsm:ProcessEvent("EV_BUTTON_CLICKED", button, button:GetContext())
|
||||
end
|
||||
|
||||
function private.GroupBtnOnClick(button)
|
||||
local groups = TempTable.Acquire()
|
||||
for _, groupPath in button:GetElement("__base.content.groupTree"):SelectedGroupsIterator() do
|
||||
groups[groupPath] = true
|
||||
end
|
||||
private.fsm:ProcessEvent("EV_BUTTON_CLICKED", button, button:GetContext(), groups)
|
||||
TempTable.Release(groups)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- FSM
|
||||
-- ============================================================================
|
||||
|
||||
function private.FSMCreate()
|
||||
TSM.Banking.RegisterFrameCallback(function(openFrame)
|
||||
private.fsm:ProcessEvent(openFrame and "EV_BANK_OPENED" or "EV_BANK_CLOSED")
|
||||
end)
|
||||
|
||||
local fsmContext = {
|
||||
frame = nil,
|
||||
progress = nil,
|
||||
activeButton = nil,
|
||||
}
|
||||
local function UpdateFrame(context)
|
||||
if context.activeButton and not context.progress then
|
||||
context.activeButton
|
||||
:SetPressed(false)
|
||||
:Draw()
|
||||
context.activeButton = nil
|
||||
end
|
||||
|
||||
-- update the nav button state
|
||||
local navButtonsFrame = context.frame:GetElement("content.navButtons")
|
||||
for _, module in ipairs(MODULE_LIST) do
|
||||
navButtonsFrame:GetElement("navBtn_"..module)
|
||||
:SetDisabled(context.progress)
|
||||
end
|
||||
navButtonsFrame:Draw()
|
||||
|
||||
-- update the progress bar
|
||||
context.frame:GetElement("content.footer.progressBar")
|
||||
:SetProgress(context.progress or 0)
|
||||
:SetProgressIconHidden(not context.progress)
|
||||
:SetText(context.progress and L["Moving"] or L["Select Action"])
|
||||
:Draw()
|
||||
|
||||
-- update the action button state
|
||||
local footerButtonsFrame = context.frame:GetElement("content.footer.buttons")
|
||||
if private.settings.tab == "Warehousing" then
|
||||
footerButtonsFrame:GetElement("row1.moveBankBtn")
|
||||
:SetDisabled(context.progress)
|
||||
footerButtonsFrame:GetElement("row1.moveBagsBtn")
|
||||
:SetDisabled(context.progress)
|
||||
footerButtonsFrame:GetElement("restockBagsBtn")
|
||||
:SetDisabled(context.progress)
|
||||
footerButtonsFrame:GetElement("depositReagentsBtn")
|
||||
:SetDisabled(context.progress or TSM.IsWowClassic())
|
||||
footerButtonsFrame:GetElement("row4.emptyBagsBtn")
|
||||
:SetDisabled(context.progress)
|
||||
footerButtonsFrame:GetElement("row4.restoreBagsBtn")
|
||||
:SetDisabled(context.progress or not TSM.Banking.CanRestoreBags())
|
||||
elseif private.settings.tab == "Auctioning" then
|
||||
footerButtonsFrame:GetElement("moveBankBtn")
|
||||
:SetDisabled(context.progress)
|
||||
footerButtonsFrame:GetElement("postCapBagsBtn")
|
||||
:SetDisabled(context.progress)
|
||||
footerButtonsFrame:GetElement("shortfallBagsBtn")
|
||||
:SetDisabled(context.progress)
|
||||
footerButtonsFrame:GetElement("maxExpBankBtn")
|
||||
:SetDisabled(context.progress)
|
||||
elseif private.settings.tab == "Mailing" then
|
||||
footerButtonsFrame:GetElement("moveBankBtn")
|
||||
:SetDisabled(context.progress)
|
||||
footerButtonsFrame:GetElement("nongroupBankBtn")
|
||||
:SetDisabled(context.progress)
|
||||
footerButtonsFrame:GetElement("targetShortfallBagsBtn")
|
||||
:SetDisabled(context.progress)
|
||||
footerButtonsFrame:GetElement("row4.emptyBagsBtn")
|
||||
:SetDisabled(context.progress)
|
||||
footerButtonsFrame:GetElement("row4.restoreBagsBtn")
|
||||
:SetDisabled(context.progress or not TSM.Banking.CanRestoreBags())
|
||||
else
|
||||
error("Unexpected module: "..tostring(private.settings.tab))
|
||||
end
|
||||
footerButtonsFrame:Draw()
|
||||
end
|
||||
private.fsm = FSM.New("BANKING_UI")
|
||||
:AddState(FSM.NewState("ST_CLOSED")
|
||||
:SetOnEnter(function(context)
|
||||
if context.frame then
|
||||
context.frame:Hide()
|
||||
context.frame:Release()
|
||||
context.frame = nil
|
||||
end
|
||||
context.activeButton = nil
|
||||
end)
|
||||
:AddTransition("ST_CLOSED")
|
||||
:AddTransition("ST_FRAME_OPEN")
|
||||
:AddTransition("ST_FRAME_HIDDEN")
|
||||
:AddEvent("EV_BANK_OPENED", function(context)
|
||||
assert(not context.frame)
|
||||
if not private.settings.isOpen then
|
||||
return "ST_FRAME_HIDDEN"
|
||||
end
|
||||
return "ST_FRAME_OPEN"
|
||||
end)
|
||||
)
|
||||
:AddState(FSM.NewState("ST_FRAME_HIDDEN")
|
||||
:SetOnEnter(function(context)
|
||||
private.settings.isOpen = false
|
||||
if context.frame then
|
||||
context.frame:Hide()
|
||||
context.frame:Release()
|
||||
context.frame = nil
|
||||
end
|
||||
context.activeButton = nil
|
||||
end)
|
||||
:AddTransition("ST_FRAME_OPEN")
|
||||
:AddTransition("ST_CLOSED")
|
||||
:AddEvent("EV_TOGGLE", function()
|
||||
private.settings.isOpen = true
|
||||
return "ST_FRAME_OPEN"
|
||||
end)
|
||||
)
|
||||
:AddState(FSM.NewState("ST_FRAME_OPEN")
|
||||
:SetOnEnter(function(context)
|
||||
if not context.frame then
|
||||
context.frame = private.CreateMainFrame()
|
||||
context.frame:Show()
|
||||
context.frame:Draw()
|
||||
end
|
||||
UpdateFrame(context)
|
||||
end)
|
||||
:AddTransition("ST_FRAME_OPEN")
|
||||
:AddTransition("ST_FRAME_HIDDEN")
|
||||
:AddTransition("ST_PROCESSING")
|
||||
:AddTransition("ST_CLOSED")
|
||||
:AddEventTransition("EV_BUTTON_CLICKED", "ST_PROCESSING")
|
||||
:AddEventTransition("EV_TOGGLE", "ST_FRAME_HIDDEN")
|
||||
:AddEventTransition("EV_FRAME_HIDDEN", "ST_FRAME_HIDDEN")
|
||||
:AddEventTransition("EV_NAV_CHANGED", "ST_FRAME_OPEN")
|
||||
)
|
||||
:AddState(FSM.NewState("ST_PROCESSING")
|
||||
:SetOnEnter(function(context, button, startFunc, ...)
|
||||
context.activeButton = button
|
||||
context.activeButton
|
||||
:SetPressed(true)
|
||||
:Draw()
|
||||
context.progress = 0
|
||||
startFunc(private.FSMThreadCallback, ...)
|
||||
UpdateFrame(context)
|
||||
end)
|
||||
:SetOnExit(function(context)
|
||||
context.progress = nil
|
||||
end)
|
||||
:AddTransition("ST_FRAME_OPEN")
|
||||
:AddTransition("ST_FRAME_HIDDEN")
|
||||
:AddTransition("ST_CLOSED")
|
||||
:AddEvent("EV_THREAD_PROGRESS", function(context, progress)
|
||||
context.progress = progress
|
||||
UpdateFrame(context)
|
||||
end)
|
||||
:AddEvent("EV_THREAD_DONE", function(context)
|
||||
if context.progress == 0 then
|
||||
Log.PrintUser(L["Nothing to move."])
|
||||
end
|
||||
return "ST_FRAME_OPEN"
|
||||
end)
|
||||
:AddEventTransition("EV_TOGGLE", "ST_FRAME_HIDDEN")
|
||||
)
|
||||
:AddDefaultEventTransition("EV_BANK_CLOSED", "ST_CLOSED")
|
||||
:Init("ST_CLOSED", fsmContext)
|
||||
end
|
||||
|
||||
function private.FSMThreadCallback(event, ...)
|
||||
if event == "PROGRESS" then
|
||||
private.fsm:ProcessEvent("EV_THREAD_PROGRESS", ...)
|
||||
elseif event == "DONE" then
|
||||
private.fsm:ProcessEvent("EV_THREAD_DONE")
|
||||
elseif event == "MOVED" then
|
||||
-- ignore this event
|
||||
else
|
||||
error("Unexpected event: "..tostring(event))
|
||||
end
|
||||
end
|
||||
340
Core/UI/CraftingUI/Core.lua
Normal file
340
Core/UI/CraftingUI/Core.lua
Normal file
@@ -0,0 +1,340 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local CraftingUI = TSM.UI:NewPackage("CraftingUI")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local FSM = TSM.Include("Util.FSM")
|
||||
local Event = TSM.Include("Util.Event")
|
||||
local ScriptWrapper = TSM.Include("Util.ScriptWrapper")
|
||||
local Settings = TSM.Include("Service.Settings")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local private = {
|
||||
settings = nil,
|
||||
topLevelPages = {},
|
||||
fsm = nil,
|
||||
craftOpen = nil,
|
||||
tradeSkillOpen = nil,
|
||||
defaultUISwitchBtn = nil,
|
||||
isVisible = false,
|
||||
}
|
||||
local MIN_FRAME_SIZE = { width = 650, height = 587 }
|
||||
local BEAST_TRAINING_DE = "Bestienausbildung"
|
||||
local BEAST_TRAINING_ES = "Entrenamiento de bestias"
|
||||
local BEAST_TRAINING_RUS = "Воспитание питомца"
|
||||
local IGNORED_PROFESSIONS = {
|
||||
[53428] = true, -- Runeforging
|
||||
[158756] = true, -- Skinning Skills
|
||||
[193290] = true, -- Herbalism Skills
|
||||
[7620] = true, -- Fishing Skills (shows up as Fishing)
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function CraftingUI.OnInitialize()
|
||||
private.settings = Settings.NewView()
|
||||
:AddKey("global", "craftingUIContext", "showDefault")
|
||||
:AddKey("global", "craftingUIContext", "frame")
|
||||
private.FSMCreate()
|
||||
TSM.Crafting.ProfessionScanner.SetDisabled(private.settings.showDefault)
|
||||
end
|
||||
|
||||
function CraftingUI.OnDisable()
|
||||
-- hide the frame
|
||||
if private.isVisible then
|
||||
TSM.Crafting.ProfessionScanner.SetDisabled(false)
|
||||
private.fsm:ProcessEvent("EV_FRAME_TOGGLE")
|
||||
end
|
||||
end
|
||||
|
||||
function CraftingUI.RegisterTopLevelPage(name, callback)
|
||||
tinsert(private.topLevelPages, { name = name, callback = callback })
|
||||
end
|
||||
|
||||
function CraftingUI.Toggle()
|
||||
private.settings.showDefault = false
|
||||
TSM.Crafting.ProfessionScanner.SetDisabled(false)
|
||||
private.fsm:ProcessEvent("EV_FRAME_TOGGLE")
|
||||
end
|
||||
|
||||
function CraftingUI.IsProfessionIgnored(name)
|
||||
if TSM.IsWowClassic() then
|
||||
if name == GetSpellInfo(5149) or name == BEAST_TRAINING_DE or name == BEAST_TRAINING_ES or name == BEAST_TRAINING_RUS then -- Beast Training
|
||||
return true
|
||||
elseif name == GetSpellInfo(7620) then -- Fishing
|
||||
return true
|
||||
elseif name == GetSpellInfo(2366) then -- Herb Gathering
|
||||
return true
|
||||
elseif name == GetSpellInfo(8613) then -- Skinning
|
||||
return true
|
||||
end
|
||||
end
|
||||
for i in pairs(IGNORED_PROFESSIONS) do
|
||||
local ignoredName = GetSpellInfo(i)
|
||||
if ignoredName == name then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function CraftingUI.IsVisible()
|
||||
return private.isVisible
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Main Frame
|
||||
-- ============================================================================
|
||||
|
||||
function private.CreateMainFrame()
|
||||
TSM.UI.AnalyticsRecordPathChange("crafting")
|
||||
local frame = UIElements.New("LargeApplicationFrame", "base")
|
||||
:SetParent(UIParent)
|
||||
:SetSettingsContext(private.settings, "frame")
|
||||
:SetMinResize(MIN_FRAME_SIZE.width, MIN_FRAME_SIZE.height)
|
||||
:SetStrata("HIGH")
|
||||
:AddPlayerGold()
|
||||
:AddAppStatusIcon()
|
||||
:AddSwitchButton(private.SwitchBtnOnClick)
|
||||
:SetScript("OnHide", private.BaseFrameOnHide)
|
||||
|
||||
for _, info in ipairs(private.topLevelPages) do
|
||||
frame:AddNavButton(info.name, info.callback)
|
||||
end
|
||||
|
||||
return frame
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.BaseFrameOnHide()
|
||||
TSM.UI.AnalyticsRecordClose("crafting")
|
||||
private.fsm:ProcessEvent("EV_FRAME_HIDE")
|
||||
end
|
||||
|
||||
function private.SwitchBtnOnClick(button)
|
||||
private.settings.showDefault = button ~= private.defaultUISwitchBtn
|
||||
TSM.Crafting.ProfessionScanner.SetDisabled(private.settings.showDefault)
|
||||
private.fsm:ProcessEvent("EV_SWITCH_BTN_CLICKED")
|
||||
end
|
||||
|
||||
function private.SwitchButtonOnEnter(button)
|
||||
button:SetTextColor("TEXT")
|
||||
:Draw()
|
||||
end
|
||||
|
||||
function private.SwitchButtonOnLeave(button)
|
||||
button:SetTextColor("TEXT_ALT")
|
||||
:Draw()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- FSM
|
||||
-- ============================================================================
|
||||
|
||||
function private.FSMCreate()
|
||||
if TSM.IsWowClassic() then
|
||||
Event.Register("CRAFT_SHOW", function()
|
||||
CloseTradeSkill()
|
||||
private.craftOpen = true
|
||||
TSM.Crafting.ProfessionState.SetCraftOpen(true)
|
||||
private.fsm:ProcessEvent("EV_TRADE_SKILL_SHOW")
|
||||
end)
|
||||
Event.Register("CRAFT_CLOSE", function()
|
||||
private.craftOpen = false
|
||||
TSM.Crafting.ProfessionState.SetCraftOpen(false)
|
||||
if not private.tradeSkillOpen then
|
||||
private.fsm:ProcessEvent("EV_TRADE_SKILL_CLOSED")
|
||||
end
|
||||
end)
|
||||
end
|
||||
Event.Register("TRADE_SKILL_SHOW", function()
|
||||
if TSM.IsWowClassic() then
|
||||
CloseCraft()
|
||||
end
|
||||
private.tradeSkillOpen = true
|
||||
private.fsm:ProcessEvent("EV_TRADE_SKILL_SHOW")
|
||||
end)
|
||||
Event.Register("TRADE_SKILL_CLOSE", function()
|
||||
private.tradeSkillOpen = false
|
||||
if not private.craftOpen then
|
||||
private.fsm:ProcessEvent("EV_TRADE_SKILL_CLOSED")
|
||||
end
|
||||
end)
|
||||
-- we'll implement UIParent's event handler directly when necessary for TRADE_SKILL_SHOW
|
||||
if TSM.IsWowClassic() then
|
||||
UIParent:UnregisterEvent("CRAFT_SHOW")
|
||||
end
|
||||
UIParent:UnregisterEvent("TRADE_SKILL_SHOW")
|
||||
|
||||
local fsmContext = {
|
||||
frame = nil,
|
||||
}
|
||||
local function UpdateDefaultCraftButton()
|
||||
if CraftFrame and CraftCreateButton and private.craftOpen then
|
||||
CraftCreateButton:SetParent(CraftFrame)
|
||||
CraftCreateButton:ClearAllPoints()
|
||||
CraftCreateButton:SetPoint("CENTER", CraftFrame, "TOPLEFT", 224, -422)
|
||||
CraftCreateButton:SetFrameLevel(2)
|
||||
CraftCreateButton:EnableDrawLayer("BACKGROUND")
|
||||
CraftCreateButton:EnableDrawLayer("ARTWORK")
|
||||
CraftCreateButton:SetHighlightTexture("Interface\\Buttons\\UI-Panel-Button-Highlight")
|
||||
CraftCreateButton:GetHighlightTexture():SetTexCoord(0, 0.625, 0, 0.6875)
|
||||
end
|
||||
end
|
||||
local function DefaultFrameOnHide()
|
||||
private.fsm:ProcessEvent("EV_FRAME_HIDE")
|
||||
end
|
||||
private.fsm = FSM.New("CRAFTING_UI")
|
||||
:AddState(FSM.NewState("ST_CLOSED")
|
||||
:AddTransition("ST_DEFAULT_OPEN")
|
||||
:AddTransition("ST_FRAME_OPEN")
|
||||
:AddEvent("EV_FRAME_TOGGLE", function(context)
|
||||
assert(not private.settings.showDefault)
|
||||
TSM.Crafting.ProfessionScanner.SetDisabled(false)
|
||||
return "ST_FRAME_OPEN"
|
||||
end)
|
||||
:AddEvent("EV_TRADE_SKILL_SHOW", function(context)
|
||||
TSM.Crafting.ProfessionScanner.SetDisabled(private.settings.showDefault)
|
||||
local name = TSM.Crafting.ProfessionUtil.GetCurrentProfessionName()
|
||||
if CraftingUI.IsProfessionIgnored(name) then
|
||||
return "ST_DEFAULT_OPEN", true
|
||||
elseif private.settings.showDefault then
|
||||
return "ST_DEFAULT_OPEN"
|
||||
else
|
||||
return "ST_FRAME_OPEN"
|
||||
end
|
||||
end)
|
||||
)
|
||||
:AddState(FSM.NewState("ST_DEFAULT_OPEN")
|
||||
:SetOnEnter(function(context, isIgnored)
|
||||
if private.craftOpen then
|
||||
UIParent_OnEvent(UIParent, "CRAFT_SHOW")
|
||||
UpdateDefaultCraftButton()
|
||||
else
|
||||
UIParent_OnEvent(UIParent, "TRADE_SKILL_SHOW")
|
||||
end
|
||||
if not private.defaultUISwitchBtn then
|
||||
private.defaultUISwitchBtn = UIElements.New("ActionButton", "switchBtn")
|
||||
:SetSize(60, TSM.IsWowClassic() and 16 or 15)
|
||||
:AddAnchor("TOPRIGHT", TSM.IsWowClassic() and -60 or -27, TSM.IsWowClassic() and -16 or -4)
|
||||
:SetRelativeLevel(3)
|
||||
:DisableClickCooldown()
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetText(L["TSM4"])
|
||||
:SetScript("OnClick", private.SwitchBtnOnClick)
|
||||
:SetScript("OnEnter", private.SwitchButtonOnEnter)
|
||||
:SetScript("OnLeave", private.SwitchButtonOnLeave)
|
||||
private.defaultUISwitchBtn:_GetBaseFrame():SetParent(TradeSkillFrame)
|
||||
end
|
||||
private.defaultUISwitchBtn:_GetBaseFrame():SetParent(private.craftOpen and CraftFrame or TradeSkillFrame)
|
||||
if isIgnored then
|
||||
TSM.Crafting.ProfessionScanner.SetDisabled(true)
|
||||
private.defaultUISwitchBtn:Hide()
|
||||
else
|
||||
private.defaultUISwitchBtn:Show()
|
||||
private.defaultUISwitchBtn:Draw()
|
||||
end
|
||||
if private.craftOpen then
|
||||
ScriptWrapper.Set(CraftFrame, "OnHide", DefaultFrameOnHide)
|
||||
else
|
||||
ScriptWrapper.Set(TradeSkillFrame, "OnHide", DefaultFrameOnHide)
|
||||
end
|
||||
if not TSM.IsWowClassic() then
|
||||
local linked, linkedName = TSM.Crafting.ProfessionUtil.IsLinkedProfession()
|
||||
if TSM.Crafting.ProfessionUtil.IsDataStable() and not TSM.Crafting.ProfessionUtil.IsGuildProfession() and (not linked or (linked and linkedName == UnitName("player"))) then
|
||||
TradeSkillFrame:OnEvent("TRADE_SKILL_DATA_SOURCE_CHANGED")
|
||||
TradeSkillFrame:OnEvent("TRADE_SKILL_LIST_UPDATE")
|
||||
end
|
||||
end
|
||||
end)
|
||||
:SetOnExit(function(context)
|
||||
if private.craftOpen then
|
||||
if CraftFrame then
|
||||
ScriptWrapper.Clear(CraftFrame, "OnHide")
|
||||
HideUIPanel(CraftFrame)
|
||||
end
|
||||
else
|
||||
if TradeSkillFrame then
|
||||
ScriptWrapper.Clear(TradeSkillFrame, "OnHide")
|
||||
HideUIPanel(TradeSkillFrame)
|
||||
end
|
||||
end
|
||||
end)
|
||||
:AddTransition("ST_CLOSED")
|
||||
:AddTransition("ST_FRAME_OPEN")
|
||||
:AddTransition("ST_DEFAULT_OPEN")
|
||||
:AddEvent("EV_FRAME_HIDE", function(context)
|
||||
TSM.Crafting.ProfessionUtil.CloseTradeSkill(false, private.craftOpen)
|
||||
return "ST_CLOSED"
|
||||
end)
|
||||
:AddEvent("EV_TRADE_SKILL_SHOW", function(context)
|
||||
if CraftingUI.IsProfessionIgnored(TSM.Crafting.ProfessionUtil.GetCurrentProfessionName()) then
|
||||
return "ST_DEFAULT_OPEN", true
|
||||
else
|
||||
if private.settings.showDefault then
|
||||
return "ST_DEFAULT_OPEN"
|
||||
else
|
||||
TSM.Crafting.ProfessionScanner.SetDisabled(private.settings.showDefault)
|
||||
return "ST_FRAME_OPEN"
|
||||
end
|
||||
end
|
||||
end)
|
||||
:AddEventTransition("EV_TRADE_SKILL_CLOSED", "ST_CLOSED")
|
||||
:AddEventTransition("EV_SWITCH_BTN_CLICKED", "ST_FRAME_OPEN")
|
||||
)
|
||||
:AddState(FSM.NewState("ST_FRAME_OPEN")
|
||||
:SetOnEnter(function(context)
|
||||
assert(not context.frame)
|
||||
context.frame = private.CreateMainFrame()
|
||||
context.frame:Show()
|
||||
if TSM.Crafting.ProfessionUtil.GetCurrentProfessionName() then
|
||||
context.frame:GetElement("titleFrame.switchBtn"):Show()
|
||||
else
|
||||
context.frame:GetElement("titleFrame.switchBtn"):Hide()
|
||||
end
|
||||
context.frame:Draw()
|
||||
private.isVisible = true
|
||||
end)
|
||||
:SetOnExit(function(context)
|
||||
context.frame:Hide()
|
||||
context.frame:Release()
|
||||
context.frame = nil
|
||||
private.isVisible = false
|
||||
if TSM.IsWowClassic() then
|
||||
UpdateDefaultCraftButton()
|
||||
end
|
||||
end)
|
||||
:AddTransition("ST_CLOSED")
|
||||
:AddTransition("ST_DEFAULT_OPEN")
|
||||
:AddEvent("EV_FRAME_HIDE", function(context)
|
||||
TSM.Crafting.ProfessionUtil.CloseTradeSkill(true)
|
||||
return "ST_CLOSED"
|
||||
end)
|
||||
:AddEvent("EV_TRADE_SKILL_SHOW", function(context)
|
||||
if CraftingUI.IsProfessionIgnored(TSM.Crafting.ProfessionUtil.GetCurrentProfessionName()) then
|
||||
return "ST_DEFAULT_OPEN", true
|
||||
end
|
||||
context.frame:GetElement("titleFrame.switchBtn"):Show()
|
||||
context.frame:GetElement("titleFrame"):Draw()
|
||||
end)
|
||||
:AddEventTransition("EV_TRADE_SKILL_CLOSED", "ST_CLOSED")
|
||||
:AddEventTransition("EV_SWITCH_BTN_CLICKED", "ST_DEFAULT_OPEN")
|
||||
:AddEventTransition("EV_FRAME_TOGGLE", "ST_CLOSED")
|
||||
)
|
||||
:Init("ST_CLOSED", fsmContext)
|
||||
end
|
||||
1294
Core/UI/CraftingUI/Crafting.lua
Normal file
1294
Core/UI/CraftingUI/Crafting.lua
Normal file
File diff suppressed because it is too large
Load Diff
673
Core/UI/CraftingUI/CraftingReports.lua
Normal file
673
Core/UI/CraftingUI/CraftingReports.lua
Normal file
@@ -0,0 +1,673 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local CraftingReports = TSM.UI.CraftingUI:NewPackage("CraftingReports")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local Math = TSM.Include("Util.Math")
|
||||
local Log = TSM.Include("Util.Log")
|
||||
local Money = TSM.Include("Util.Money")
|
||||
local String = TSM.Include("Util.String")
|
||||
local ItemString = TSM.Include("Util.ItemString")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local ItemInfo = TSM.Include("Service.ItemInfo")
|
||||
local Settings = TSM.Include("Service.Settings")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local private = {
|
||||
settings = nil,
|
||||
craftsQuery = nil,
|
||||
matsQuery = nil,
|
||||
filterText = "",
|
||||
craftProfessions = {},
|
||||
matProfessions = {},
|
||||
}
|
||||
local MAT_PRICE_SOURCES = {ALL, L["Default Price"], L["Custom Price"]}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function CraftingReports.OnInitialize()
|
||||
private.settings = Settings.NewView()
|
||||
:AddKey("global", "craftingUIContext", "craftsScrollingTable")
|
||||
:AddKey("global", "craftingUIContext", "matsScrollingTable")
|
||||
:AddKey("factionrealm", "internalData", "mats")
|
||||
:AddKey("global", "craftingOptions", "defaultMatCostMethod")
|
||||
TSM.UI.CraftingUI.RegisterTopLevelPage(L["Reports"], private.GetCraftingReportsFrame)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- CraftingReports UI
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetCraftingReportsFrame()
|
||||
TSM.UI.AnalyticsRecordPathChange("crafting", "crafting_reports")
|
||||
if not private.craftsQuery then
|
||||
private.craftsQuery = TSM.Crafting.CreateCraftsQuery()
|
||||
private.craftsQuery:VirtualField("firstOperation", "string", private.FirstOperationVirtualField, "itemString")
|
||||
end
|
||||
private.craftsQuery:ResetFilters()
|
||||
private.craftsQuery:ResetOrderBy()
|
||||
private.craftsQuery:OrderBy("itemName", true)
|
||||
private.matsQuery = private.matsQuery or TSM.Crafting.CreateMatItemQuery()
|
||||
private.matsQuery:ResetFilters()
|
||||
private.matsQuery:ResetOrderBy()
|
||||
private.matsQuery:OrderBy("name", true)
|
||||
return UIElements.New("Frame", "craftingReportsContent")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetPadding(0, 0, 6, 0)
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT")
|
||||
:AddChild(UIElements.New("TabGroup", "buttons")
|
||||
:SetNavCallback(private.GetTabElements)
|
||||
:AddPath(L["Crafts"], true)
|
||||
:AddPath(L["Materials"])
|
||||
)
|
||||
end
|
||||
|
||||
function private.GetTabElements(self, path)
|
||||
if path == L["Crafts"] then
|
||||
TSM.UI.AnalyticsRecordPathChange("crafting", "crafting_reports", "crafts")
|
||||
private.filterText = ""
|
||||
wipe(private.craftProfessions)
|
||||
tinsert(private.craftProfessions, L["All Professions"])
|
||||
for _, player, profession in TSM.Crafting.PlayerProfessions.Iterator() do
|
||||
tinsert(private.craftProfessions, format("%s - %s", profession, player))
|
||||
end
|
||||
private.craftsQuery:ResetFilters()
|
||||
|
||||
return UIElements.New("Frame", "crafts")
|
||||
:SetLayout("VERTICAL")
|
||||
:AddChild(UIElements.New("Frame", "filters")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(72)
|
||||
:SetPadding(10, 10, 8, 16)
|
||||
:AddChild(UIElements.New("Frame", "search")
|
||||
:SetLayout("VERTICAL")
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 4)
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetText(L["Filter by Keyword"])
|
||||
)
|
||||
:AddChild(UIElements.New("Input", "input")
|
||||
:SetHeight(24)
|
||||
:AllowItemInsert()
|
||||
:SetIconTexture("iconPack.18x18/Search")
|
||||
:SetClearButtonEnabled(true)
|
||||
:SetHintText(L["Enter Keyword"])
|
||||
:SetScript("OnValueChanged", private.CraftsInputOnValueChanged)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "profession")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetMargin(16, 16, 0, 0)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 4)
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetText(L["Filter by Profession"])
|
||||
)
|
||||
:AddChild(UIElements.New("SelectionDropdown", "dropdown")
|
||||
:SetHeight(24)
|
||||
:SetItems(private.craftProfessions)
|
||||
:SetSelectedItem(private.craftProfessions[1], true)
|
||||
:SetScript("OnSelectionChanged", private.CraftsDropdownOnSelectionChanged)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "craftable")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetSize(176, 24)
|
||||
:SetMargin(0, 0, 24, 0)
|
||||
:AddChild(UIElements.New("Checkbox", "checkbox")
|
||||
:SetWidth(24)
|
||||
:SetFont("BODY_BODY2")
|
||||
:SetScript("OnValueChanged", private.CheckboxOnValueChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY2")
|
||||
:SetText(L["Only show craftable"])
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("QueryScrollingTable", "crafts")
|
||||
:SetSettingsContext(private.settings, "craftsScrollingTable")
|
||||
:GetScrollingTableInfo()
|
||||
:SetMenuInfo(private.CraftsMatsMenuIterator, private.CraftsMenuClickHandler)
|
||||
:NewColumn("queued")
|
||||
:SetTitleIcon("iconPack.18x18/Queue")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("CENTER")
|
||||
:SetTextInfo("num")
|
||||
:SetSortInfo("num")
|
||||
:Commit()
|
||||
:NewColumn("craftName")
|
||||
:SetTitle(L["Name"])
|
||||
:SetIconSize(12)
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo(nil, private.CraftsGetCraftNameText)
|
||||
:SetIconInfo("itemString", ItemInfo.GetTexture)
|
||||
:SetTooltipInfo("itemString")
|
||||
:SetSortInfo("itemName")
|
||||
:DisableHiding()
|
||||
:Commit()
|
||||
:NewColumn("operation")
|
||||
:SetTitle(L["Operation"])
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo("firstOperation")
|
||||
:SetSortInfo("firstOperation")
|
||||
:Commit()
|
||||
:NewColumn("bags")
|
||||
:SetTitle(L["Bag"])
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("bagQuantity", private.CraftsGetBagsText)
|
||||
:SetSortInfo("bagQuantity")
|
||||
:Commit()
|
||||
:NewColumn("ah")
|
||||
:SetTitle(L["AH"])
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("auctionQuantity", private.CraftsGetAHText)
|
||||
:SetSortInfo("auctionQuantity")
|
||||
:Commit()
|
||||
:NewColumn("craftingCost")
|
||||
:SetTitle(L["Crafting Cost"])
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("craftingCost", private.CraftsGetCostItemValueText)
|
||||
:SetSortInfo("craftingCost")
|
||||
:Commit()
|
||||
:NewColumn("itemValue")
|
||||
:SetTitle(L["Item Value"])
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("itemValue", private.CraftsGetCostItemValueText)
|
||||
:SetSortInfo("itemValue")
|
||||
:Commit()
|
||||
:NewColumn("profit")
|
||||
:SetTitle(L["Profit"])
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("profit", private.CraftsGetProfitText)
|
||||
:SetSortInfo("profit")
|
||||
:Commit()
|
||||
:NewColumn("profitPct")
|
||||
:SetTitle("%")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("profitPct", private.GetProfitPctText)
|
||||
:SetSortInfo("profitPct")
|
||||
:Commit()
|
||||
:NewColumn("saleRate")
|
||||
:SetTitleIcon("iconPack.18x18/SaleRate")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("CENTER")
|
||||
:SetTextInfo("saleRate", private.CraftsGetSaleRateText)
|
||||
:SetSortInfo("saleRate")
|
||||
:Commit()
|
||||
:Commit()
|
||||
:SetQuery(private.craftsQuery)
|
||||
:SetSelectionDisabled(true)
|
||||
:SetScript("OnRowClick", private.CraftsOnRowClick)
|
||||
)
|
||||
elseif path == L["Materials"] then
|
||||
TSM.UI.AnalyticsRecordPathChange("crafting", "crafting_reports", "materials")
|
||||
wipe(private.matProfessions)
|
||||
tinsert(private.matProfessions, L["All Professions"])
|
||||
for _, _, profession in TSM.Crafting.PlayerProfessions.Iterator() do
|
||||
if not private.matProfessions[profession] then
|
||||
tinsert(private.matProfessions, profession)
|
||||
private.matProfessions[profession] = true
|
||||
end
|
||||
end
|
||||
private.matsQuery:ResetFilters()
|
||||
|
||||
return UIElements.New("Frame", "materials")
|
||||
:SetLayout("VERTICAL")
|
||||
:AddChild(UIElements.New("Frame", "filters")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(72)
|
||||
:SetPadding(10, 10, 8, 16)
|
||||
:AddChild(UIElements.New("Frame", "search")
|
||||
:SetLayout("VERTICAL")
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 4)
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetText(L["Filter by Keyword"])
|
||||
)
|
||||
:AddChild(UIElements.New("Input", "input")
|
||||
:SetHeight(24)
|
||||
:AllowItemInsert()
|
||||
:SetIconTexture("iconPack.18x18/Search")
|
||||
:SetClearButtonEnabled(true)
|
||||
:SetHintText(L["Enter Keyword"])
|
||||
:SetScript("OnValueChanged", private.MatsInputOnValueChanged)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "profession")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetMargin(16, 0, 0, 0)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 4)
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetText(L["Filter by Profession"])
|
||||
)
|
||||
:AddChild(UIElements.New("SelectionDropdown", "dropdown")
|
||||
:SetHeight(24)
|
||||
:SetItems(private.matProfessions)
|
||||
:SetSelectedItem(private.matProfessions[1], true)
|
||||
:SetScript("OnSelectionChanged", private.MatsDropdownOnSelectionChanged)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "priceSource")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetMargin(16, 0, 0, 0)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 4)
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetText(L["Filter by Price Source"])
|
||||
)
|
||||
:AddChild(UIElements.New("SelectionDropdown", "dropdown")
|
||||
:SetHeight(24)
|
||||
:SetItems(MAT_PRICE_SOURCES)
|
||||
:SetSelectedItem(MAT_PRICE_SOURCES[1], true)
|
||||
:SetScript("OnSelectionChanged", private.MatsDropdownOnSelectionChanged)
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("QueryScrollingTable", "mats")
|
||||
:SetSettingsContext(private.settings, "matsScrollingTable")
|
||||
:GetScrollingTableInfo()
|
||||
:SetMenuInfo(private.CraftsMatsMenuIterator, private.MatsMenuClickHandler)
|
||||
:NewColumn("name")
|
||||
:SetTitle(L["Name"])
|
||||
:SetIconSize(12)
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo("itemString", private.MatsGetNameText)
|
||||
:SetIconInfo("itemString", ItemInfo.GetTexture)
|
||||
:SetTooltipInfo("itemString")
|
||||
:SetSortInfo("name")
|
||||
:DisableHiding()
|
||||
:Commit()
|
||||
:NewColumn("price")
|
||||
:SetTitle(L["Mat Price"])
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("matCost", private.MatsGetPriceText)
|
||||
:SetSortInfo("matCost")
|
||||
:Commit()
|
||||
:NewColumn("professions")
|
||||
:SetTitle(L["Professions Used In"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo("professions")
|
||||
:SetSortInfo("professions")
|
||||
:Commit()
|
||||
:NewColumn("num")
|
||||
:SetTitle(L["Number Owned"])
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("totalQuantity", private.MatsGetNumText)
|
||||
:SetSortInfo("totalQuantity")
|
||||
:Commit()
|
||||
:Commit()
|
||||
:SetQuery(private.matsQuery)
|
||||
:SetSelectionDisabled(true)
|
||||
:SetScript("OnRowClick", private.MatsOnRowClick)
|
||||
)
|
||||
else
|
||||
error("Unknown path: "..tostring(path))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- ScrollingTable Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.CraftsGetCraftNameText(row)
|
||||
return TSM.UI.GetColoredItemName(row:GetField("itemString")) or row:GetField("name")
|
||||
end
|
||||
|
||||
function private.CraftsGetBagsText(bagQuantity)
|
||||
return bagQuantity or "0"
|
||||
end
|
||||
|
||||
function private.CraftsGetAHText(bagQuantity)
|
||||
return bagQuantity or "0"
|
||||
end
|
||||
|
||||
function private.CraftsGetCostItemValueText(costItemValue)
|
||||
if Math.IsNan(costItemValue) then
|
||||
return ""
|
||||
end
|
||||
return Money.ToString(costItemValue)
|
||||
end
|
||||
|
||||
function private.CraftsGetProfitText(profit)
|
||||
if Math.IsNan(profit) then
|
||||
return ""
|
||||
end
|
||||
return Money.ToString(profit, (profit >= 0 and Theme.GetFeedbackColor("GREEN") or Theme.GetFeedbackColor("RED")):GetTextColorPrefix())
|
||||
end
|
||||
|
||||
function private.GetProfitPctText(profitPct)
|
||||
if Math.IsNan(profitPct) then
|
||||
return ""
|
||||
end
|
||||
local color = Theme.GetFeedbackColor(profitPct >= 0 and "GREEN" or "RED")
|
||||
return color:ColorText(profitPct.."%") or ""
|
||||
end
|
||||
|
||||
function private.CraftsGetSaleRateText(saleRate)
|
||||
if Math.IsNan(saleRate) then
|
||||
return ""
|
||||
end
|
||||
return format("%0.2f", saleRate)
|
||||
end
|
||||
|
||||
function private.MatsGetNameText(itemString)
|
||||
return TSM.UI.GetColoredItemName(itemString) or TSM.UI.GetColoredItemName(ItemString.GetUnknown())
|
||||
end
|
||||
|
||||
function private.MatsGetPriceText(matCost)
|
||||
if Math.IsNan(matCost) then
|
||||
return ""
|
||||
end
|
||||
return Money.ToString(matCost)
|
||||
end
|
||||
|
||||
function private.MatsGetNumText(totalQuantity)
|
||||
return totalQuantity or "0"
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.CraftsInputOnValueChanged(input)
|
||||
local text = input:GetValue()
|
||||
if text == private.filterText then
|
||||
return
|
||||
end
|
||||
private.filterText = text
|
||||
private.UpdateCraftsQueryWithFilters(input:GetParentElement():GetParentElement())
|
||||
end
|
||||
|
||||
function private.CraftsDropdownOnSelectionChanged(dropdown)
|
||||
private.UpdateCraftsQueryWithFilters(dropdown:GetParentElement():GetParentElement())
|
||||
end
|
||||
|
||||
function private.CheckboxOnValueChanged(checkbox)
|
||||
private.UpdateCraftsQueryWithFilters(checkbox:GetParentElement():GetParentElement())
|
||||
end
|
||||
|
||||
function private.CraftsOnRowClick(scrollingTable, record, mouseButton)
|
||||
if mouseButton == "LeftButton" then
|
||||
TSM.Crafting.Queue.Add(record:GetField("spellId"), 1)
|
||||
elseif mouseButton == "RightButton" then
|
||||
TSM.Crafting.Queue.Remove(record:GetField("spellId"), 1)
|
||||
end
|
||||
scrollingTable:Draw()
|
||||
end
|
||||
|
||||
function private.MatsInputOnValueChanged(input)
|
||||
private.UpdateMatsQueryWithFilters(input:GetParentElement():GetParentElement())
|
||||
end
|
||||
|
||||
function private.MatsDropdownOnSelectionChanged(dropdown)
|
||||
private.UpdateMatsQueryWithFilters(dropdown:GetParentElement():GetParentElement())
|
||||
end
|
||||
|
||||
function private.MatsOnRowClick(scrollingTable, row)
|
||||
local itemString = row:GetField("itemString")
|
||||
local priceStr = private.settings.mats[itemString].customValue or private.settings.defaultMatCostMethod
|
||||
scrollingTable:GetBaseElement():ShowDialogFrame(UIElements.New("Frame", "frame")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetSize(478, 312)
|
||||
:SetPadding(12)
|
||||
:AddAnchor("CENTER")
|
||||
:SetBackgroundColor("FRAME_BG", true)
|
||||
:SetContext(itemString)
|
||||
:AddChild(UIElements.New("Frame", "header")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, -4, 10)
|
||||
:AddChild(UIElements.New("Spacer", "spacer")
|
||||
:SetWidth(20)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "title")
|
||||
:SetJustifyH("CENTER")
|
||||
:SetFont("BODY_BODY1_BOLD")
|
||||
:SetText(L["Edit Material Price"])
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "closeBtn")
|
||||
:SetMargin(0, -4, 0, 0)
|
||||
:SetBackgroundAndSize("iconPack.24x24/Close/Default")
|
||||
:SetScript("OnClick", private.DialogCloseBtnOnClick)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "item")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetPadding(6)
|
||||
:SetMargin(0, 0, 0, 10)
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT", true)
|
||||
:AddChild(UIElements.New("Button", "icon")
|
||||
:SetSize(36, 36)
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetBackground(ItemInfo.GetTexture(itemString))
|
||||
:SetTooltip(itemString)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "name")
|
||||
:SetHeight(36)
|
||||
:SetFont("ITEM_BODY1")
|
||||
:SetText(TSM.UI.GetColoredItemName(itemString))
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "desc")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 6)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Material Price"])
|
||||
)
|
||||
:AddChild(UIElements.New("MultiLineInput", "input")
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT")
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetValidateFunc("CUSTOM_PRICE")
|
||||
:SetValue(Money.ToString(priceStr) or priceStr)
|
||||
:SetScript("OnValueChanged", private.MatPriceInputOnValueChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "buttons")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:AddChild(UIElements.New("Button", "resetBtn")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetTextColor(private.settings.mats[itemString].customValue and "TEXT" or "TEXT_ALT")
|
||||
:SetDisabled(not private.settings.mats[itemString].customValue)
|
||||
:SetText(L["Reset to Default"])
|
||||
:SetScript("OnClick", private.ResetButtonOnClick)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "spacer"))
|
||||
:AddChild(UIElements.New("ActionButton", "closeBtn")
|
||||
:SetWidth(342)
|
||||
:SetText(L["Save"])
|
||||
:SetScript("OnClick", private.DialogCloseBtnOnClick)
|
||||
)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
function private.DialogCloseBtnOnClick(button)
|
||||
button:GetBaseElement():HideDialog()
|
||||
end
|
||||
|
||||
function private.MatPriceInputOnValueChanged(input)
|
||||
local value = input:GetValue()
|
||||
local itemString = input:GetParentElement():GetContext()
|
||||
TSM.Crafting.SetMatCustomValue(itemString, value)
|
||||
input:GetElement("__parent.buttons.resetBtn")
|
||||
:SetTextColor("TEXT")
|
||||
:SetDisabled(false)
|
||||
:Draw()
|
||||
end
|
||||
|
||||
function private.ResetButtonOnClick(button)
|
||||
local itemString = button:GetParentElement():GetParentElement():GetContext()
|
||||
TSM.Crafting.SetMatCustomValue(itemString, nil)
|
||||
assert(not private.settings.mats[itemString].customValue)
|
||||
button:SetTextColor("TEXT_ALT")
|
||||
button:SetDisabled(true)
|
||||
button:Draw()
|
||||
button:GetElement("__parent.__parent.input")
|
||||
:SetValue(Money.ToString(private.settings.defaultMatCostMethod) or private.settings.defaultMatCostMethod)
|
||||
:Draw()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.FirstOperationVirtualField(itemString)
|
||||
return TSM.Operations.GetFirstOperationByItem("Crafting", itemString) or ""
|
||||
end
|
||||
|
||||
function private.UpdateCraftsQueryWithFilters(frame)
|
||||
private.craftsQuery:ResetFilters()
|
||||
-- apply search filter
|
||||
local filter = strtrim(frame:GetElement("search.input"):GetValue())
|
||||
if filter ~= "" then
|
||||
private.craftsQuery:Matches("itemName", String.Escape(filter))
|
||||
end
|
||||
-- apply dropdown filter
|
||||
local professionPlayer = frame:GetElement("profession.dropdown"):GetSelectedItem()
|
||||
if professionPlayer ~= private.craftProfessions[1] then
|
||||
local profession, player = strmatch(professionPlayer, "^(.+) %- ([^ ]+)$")
|
||||
private.craftsQuery
|
||||
:Equal("profession", profession)
|
||||
:Or()
|
||||
:Equal("players", player)
|
||||
:Matches("players", "^"..player..",")
|
||||
:Matches("players", ","..player..",")
|
||||
:Matches("players", ","..player.."$")
|
||||
:End()
|
||||
end
|
||||
-- apply craftable filter
|
||||
local craftableOnly = frame:GetElement("craftable.checkbox"):IsChecked()
|
||||
if craftableOnly then
|
||||
private.craftsQuery:Custom(private.IsCraftableQueryFilter)
|
||||
end
|
||||
frame:GetElement("__parent.crafts"):SetQuery(private.craftsQuery, true)
|
||||
end
|
||||
|
||||
function private.IsCraftableQueryFilter(record)
|
||||
return TSM.Crafting.ProfessionUtil.GetNumCraftableFromDB(record:GetField("spellId")) > 0
|
||||
end
|
||||
|
||||
function private.UpdateMatsQueryWithFilters(frame)
|
||||
private.matsQuery:ResetFilters()
|
||||
-- apply search filter
|
||||
local filter = strtrim(frame:GetElement("search.input"):GetValue())
|
||||
if filter ~= "" then
|
||||
private.matsQuery:Custom(private.MatItemNameQueryFilter, strlower(String.Escape(filter)))
|
||||
end
|
||||
-- apply dropdown filters
|
||||
local profession = frame:GetElement("profession.dropdown"):GetSelectedItem()
|
||||
if profession ~= private.matProfessions[1] then
|
||||
private.matsQuery
|
||||
:Or()
|
||||
:Equal("professions", profession)
|
||||
:Matches("professions", "^"..profession..",")
|
||||
:Matches("professions", ","..profession..",")
|
||||
:Matches("professions", ","..profession.."$")
|
||||
:End()
|
||||
end
|
||||
local priceSource = frame:GetElement("priceSource.dropdown"):GetSelectedItem()
|
||||
if priceSource == MAT_PRICE_SOURCES[2] then
|
||||
private.matsQuery:Equal("customValue", "")
|
||||
elseif priceSource == MAT_PRICE_SOURCES[3] then
|
||||
private.matsQuery:NotEqual("customValue", "")
|
||||
end
|
||||
frame:GetElement("__parent.mats"):SetQuery(private.matsQuery, true)
|
||||
end
|
||||
|
||||
function private.MatItemNameQueryFilter(row, filter)
|
||||
local name = ItemInfo.GetName(row:GetField("itemString"))
|
||||
if not name then return end
|
||||
return strmatch(strlower(name), filter)
|
||||
end
|
||||
|
||||
function private.CraftsMatsMenuIterator(scrollingTable, prevIndex)
|
||||
if prevIndex == "CREATE_GROUPS" then
|
||||
-- we're done
|
||||
return
|
||||
else
|
||||
return "CREATE_GROUPS", L["Create Groups from Table"]
|
||||
end
|
||||
end
|
||||
|
||||
function private.CraftsMenuClickHandler(scrollingTable, index1, index2)
|
||||
if index1 == "CREATE_GROUPS" then
|
||||
assert(not index2)
|
||||
scrollingTable:GetBaseElement():HideDialog()
|
||||
local numCreated, numAdded = 0, 0
|
||||
for _, row in private.craftsQuery:Iterator() do
|
||||
local itemString = row:GetField("itemString")
|
||||
local groupPath = TSM.Groups.Path.Join(L["Crafted Items"], row:GetField("profession"))
|
||||
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
|
||||
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.MatsMenuClickHandler(scrollingTable, index1, index2)
|
||||
if index1 == "CREATE_GROUPS" then
|
||||
assert(not index2)
|
||||
scrollingTable:GetBaseElement():HideDialog()
|
||||
local numCreated, numAdded = 0, 0
|
||||
for _, row in private.matsQuery:Iterator() do
|
||||
local itemString = row:GetField("itemString")
|
||||
local groupPath = L["Materials"]
|
||||
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
|
||||
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
|
||||
354
Core/UI/CraftingUI/Gathering.lua
Normal file
354
Core/UI/CraftingUI/Gathering.lua
Normal file
@@ -0,0 +1,354 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Gathering = TSM.UI.CraftingUI:NewPackage("Gathering")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local Table = TSM.Include("Util.Table")
|
||||
local ItemInfo = TSM.Include("Service.ItemInfo")
|
||||
local Settings = TSM.Include("Service.Settings")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local private = {
|
||||
settings = nil,
|
||||
frame = nil,
|
||||
query = nil,
|
||||
}
|
||||
local SOURCE_LIST = {
|
||||
"vendor",
|
||||
"guildBank",
|
||||
"alt",
|
||||
"altGuildBank",
|
||||
"craftProfit",
|
||||
"craftNoProfit",
|
||||
"auction",
|
||||
"auctionDE",
|
||||
"auctionCrafting"
|
||||
}
|
||||
local SOURCE_TEXT_LIST = {
|
||||
L["Vendor"],
|
||||
L["Guild Bank"],
|
||||
L["Alts"],
|
||||
L["Alt Guild Bank"],
|
||||
L["Craft (Profitable)"],
|
||||
L["Craft (Unprofitable)"],
|
||||
L["AH"],
|
||||
L["AH (Disenchanting)"],
|
||||
L["AH (Crafting)"],
|
||||
}
|
||||
if TSM.IsWowClassic() then
|
||||
Table.RemoveByValue(SOURCE_LIST, "guildBank")
|
||||
Table.RemoveByValue(SOURCE_LIST, "altGuildBank")
|
||||
Table.RemoveByValue(SOURCE_TEXT_LIST, L["Guild Bank"])
|
||||
Table.RemoveByValue(SOURCE_TEXT_LIST, L["Alt Guild Bank"])
|
||||
end
|
||||
assert(#SOURCE_LIST == #SOURCE_TEXT_LIST)
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Gathering.OnInitialize()
|
||||
private.settings = Settings.NewView()
|
||||
:AddKey("global", "craftingUIContext", "gatheringDividedContainer")
|
||||
:AddKey("global", "craftingUIContext", "gatheringScrollingTable")
|
||||
:AddKey("profile", "gatheringOptions", "sources")
|
||||
TSM.UI.CraftingUI.RegisterTopLevelPage(L["Gathering"], private.GetGatheringFrame)
|
||||
TSM.Crafting.Gathering.SetContextChangedCallback(private.ContextChangedCallback)
|
||||
TSM.UI.TaskListUI.RegisterUpdateCallback(private.UpdateButtonState)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Gathering UI
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetGatheringFrame()
|
||||
TSM.UI.AnalyticsRecordPathChange("crafting", "gathering")
|
||||
assert(not private.query)
|
||||
private.query = TSM.Crafting.Gathering.CreateQuery()
|
||||
:SetUpdateCallback(private.UpdateButtonState)
|
||||
local frame = UIElements.New("DividedContainer", "gathering")
|
||||
:SetMinWidth(284, 200)
|
||||
:SetBackgroundColor("PRIMARY_BG")
|
||||
:SetSettingsContext(private.settings, "gatheringDividedContainer")
|
||||
:SetLeftChild(UIElements.New("ScrollFrame", "setup")
|
||||
:SetPadding(12)
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT")
|
||||
:AddChild(UIElements.New("Text", "title")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 8)
|
||||
:SetFont("BODY_BODY1_BOLD")
|
||||
:SetText(L["Gathering Setup"])
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "crafterDropdownLabel")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 2)
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetTextColor("INDICATOR")
|
||||
:SetText(L["Crafter"])
|
||||
)
|
||||
:AddChild(UIElements.New("SelectionDropdown", "crafterDropdown")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, 0, 8)
|
||||
:SetHintText(L["Select crafter"])
|
||||
:SetScript("OnSelectionChanged", private.CrafterDropdownOnSelectionChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "professionDropdownLabel")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 2)
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetTextColor("INDICATOR")
|
||||
:SetText(L["Profession"])
|
||||
)
|
||||
:AddChild(UIElements.New("MultiselectionDropdown", "professionDropdown")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, 0, 24)
|
||||
:SetHintText(L["Select professions"])
|
||||
:SetSelectionText(L["No Professions"], L["%d Professions"], L["All Professions"])
|
||||
:SetScript("OnSelectionChanged", private.ProfessionDropdownOnSelectionChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "sourcesCategoryText")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 2)
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetTextColor("INDICATOR")
|
||||
:SetText(L["Sources"])
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "sourcesDesc")
|
||||
:SetHeight(28)
|
||||
:SetMargin(0, 0, 0, 4)
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetTextColor("TEXT_ALT")
|
||||
:SetText(L["Define what priority Gathering gives certain sources."])
|
||||
)
|
||||
:AddChildrenWithFunction(private.CreateSourceRows)
|
||||
)
|
||||
:SetRightChild(UIElements.New("Frame", "mats")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT")
|
||||
:AddChild(UIElements.New("Text", "title")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 8, 8)
|
||||
:SetFont("BODY_BODY1_BOLD")
|
||||
:SetJustifyH("CENTER")
|
||||
:SetText(L["Materials to Gather"])
|
||||
)
|
||||
:AddChild(UIElements.New("QueryScrollingTable", "table")
|
||||
:SetSettingsContext(private.settings, "gatheringScrollingTable")
|
||||
:GetScrollingTableInfo()
|
||||
:NewColumn("name")
|
||||
:SetTitle(NAME)
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetIconSize(12)
|
||||
:SetTextInfo("itemString", TSM.UI.GetColoredItemName)
|
||||
:SetIconInfo("itemString", ItemInfo.GetTexture)
|
||||
:SetTooltipInfo("itemString")
|
||||
:SetSortInfo("name")
|
||||
:DisableHiding()
|
||||
:Commit()
|
||||
:NewColumn("sources")
|
||||
:SetTitle(L["Sources"])
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo("sourcesStr", private.MatsGetSourcesStrText)
|
||||
:SetSortInfo("sourcesStr")
|
||||
:Commit()
|
||||
:NewColumn("have")
|
||||
:SetTitle(L["Have"])
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("numHave")
|
||||
:SetSortInfo("numHave")
|
||||
:Commit()
|
||||
:NewColumn("need")
|
||||
:SetTitle(NEED)
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("numNeed")
|
||||
:SetSortInfo("numNeed")
|
||||
:Commit()
|
||||
:Commit()
|
||||
:SetQuery(TSM.Crafting.Gathering.CreateQuery()
|
||||
:InnerJoin(ItemInfo.GetDBForJoin(), "itemString")
|
||||
:OrderBy("name", true)
|
||||
)
|
||||
:SetSelectionDisabled(true)
|
||||
:SetAutoReleaseQuery(true)
|
||||
)
|
||||
:AddChild(UIElements.New("Texture", "headerTopLine")
|
||||
:SetHeight(2)
|
||||
:SetTexture("ACTIVE_BG")
|
||||
)
|
||||
:AddChild(UIElements.New("ActionButton", "openTaskListBtn")
|
||||
:SetHeight(26)
|
||||
:SetMargin(8)
|
||||
:SetScript("OnClick", TSM.UI.TaskListUI.Toggle)
|
||||
)
|
||||
)
|
||||
:SetScript("OnUpdate", private.FrameOnUpdate)
|
||||
:SetScript("OnHide", private.FrameOnHide)
|
||||
private.frame = frame
|
||||
return frame
|
||||
end
|
||||
|
||||
function private.MatsGetSourcesStrText(str)
|
||||
str = gsub(str, "/[^,]+", "")
|
||||
for i = 1, #SOURCE_LIST do
|
||||
str = gsub(str, SOURCE_LIST[i], SOURCE_TEXT_LIST[i])
|
||||
end
|
||||
return str
|
||||
end
|
||||
|
||||
function private.CreateSourceRows(frame)
|
||||
for i = 1, #SOURCE_LIST do
|
||||
frame:AddChild(UIElements.New("Frame", "sourceFrame"..i)
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, 8, 0)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetTextColor((i > #private.settings.sources + 1) and "TEXT_ALT+DISABLED" or "TEXT_ALT")
|
||||
:SetFormattedText(L["SOURCE %d"], i)
|
||||
)
|
||||
:AddChild(UIElements.New("SelectionDropdown", "dropdown")
|
||||
:SetWidth(188)
|
||||
:SetContext(i)
|
||||
:SetHintText(L["Select a Source"])
|
||||
:SetScript("OnSelectionChanged", private.SourceDropdownOnSelectionChanged)
|
||||
)
|
||||
)
|
||||
end
|
||||
private.UpdateSourceRows(frame)
|
||||
end
|
||||
|
||||
function private.UpdateSourceRows(setupFrame)
|
||||
if TSM.IsWowClassic() then
|
||||
Table.RemoveByValue(private.settings.sources, "guildBank")
|
||||
Table.RemoveByValue(private.settings.sources, "altGuildBank")
|
||||
end
|
||||
local texts = TempTable.Acquire()
|
||||
local sources = TempTable.Acquire()
|
||||
for i = 1, #SOURCE_LIST do
|
||||
wipe(texts)
|
||||
wipe(sources)
|
||||
for j = 1, #SOURCE_LIST do
|
||||
local index = Table.KeyByValue(private.settings.sources, SOURCE_LIST[j])
|
||||
if not index or index >= i then
|
||||
tinsert(texts, SOURCE_TEXT_LIST[j])
|
||||
tinsert(sources, SOURCE_LIST[j])
|
||||
end
|
||||
end
|
||||
if i <= #private.settings.sources then
|
||||
tinsert(texts, "<"..strupper(REMOVE)..">")
|
||||
tinsert(sources, "")
|
||||
end
|
||||
setupFrame:GetElement("sourceFrame"..i..".label")
|
||||
:SetTextColor((i > #private.settings.sources + 1) and "TEXT_ALT+DISABLED" or "TEXT_ALT")
|
||||
setupFrame:GetElement("sourceFrame"..i..".dropdown")
|
||||
:SetItems(texts, sources)
|
||||
:SetDisabled(i > #private.settings.sources + 1)
|
||||
:SetHintText(L["Select a Source"])
|
||||
:SetSelectedItemByKey(private.settings.sources[i], true)
|
||||
end
|
||||
TempTable.Release(texts)
|
||||
TempTable.Release(sources)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.FrameOnUpdate(frame)
|
||||
private.UpdateButtonState()
|
||||
frame:SetScript("OnUpdate", nil)
|
||||
private.ContextChangedCallback()
|
||||
end
|
||||
|
||||
function private.FrameOnHide(frame)
|
||||
assert(frame == private.frame)
|
||||
private.frame = nil
|
||||
private.query:Release()
|
||||
private.query = nil
|
||||
end
|
||||
|
||||
function private.CrafterDropdownOnSelectionChanged(dropdown)
|
||||
TSM.Crafting.Gathering.SetCrafter(dropdown:GetSelectedItem() or "")
|
||||
dropdown:GetElement("__parent.professionDropdown")
|
||||
:SetItems(TSM.Crafting.Gathering.GetProfessionList())
|
||||
:SetSelectedItems(TSM.Crafting.Gathering.GetProfessions())
|
||||
:Draw()
|
||||
end
|
||||
|
||||
function private.ProfessionDropdownOnSelectionChanged(dropdown)
|
||||
local professions = TempTable.Acquire()
|
||||
dropdown:GetSelectedItems(professions)
|
||||
TSM.Crafting.Gathering.SetProfessions(professions)
|
||||
TempTable.Release(professions)
|
||||
end
|
||||
|
||||
function private.SourceDropdownOnSelectionChanged(dropdown)
|
||||
local index = dropdown:GetContext()
|
||||
local source = dropdown:GetSelectedItemKey()
|
||||
if source == "" then
|
||||
tremove(private.settings.sources, index)
|
||||
else
|
||||
private.settings.sources[index] = source
|
||||
for i = #private.settings.sources, index + 1, -1 do
|
||||
if private.settings.sources[i] == source then
|
||||
tremove(private.settings.sources, i)
|
||||
end
|
||||
end
|
||||
end
|
||||
local setupFrame = dropdown:GetParentElement():GetParentElement()
|
||||
private.UpdateSourceRows(setupFrame)
|
||||
setupFrame:Draw()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.ContextChangedCallback()
|
||||
if not private.frame then
|
||||
return
|
||||
end
|
||||
|
||||
private.frame:GetElement("setup.crafterDropdown")
|
||||
:SetItems(TSM.Crafting.Gathering.GetCrafterList())
|
||||
:SetSelectedItem(TSM.Crafting.Gathering.GetCrafter())
|
||||
:Draw()
|
||||
private.frame:GetElement("setup.professionDropdown")
|
||||
:SetItems(TSM.Crafting.Gathering.GetProfessionList())
|
||||
:SetSelectedItems(TSM.Crafting.Gathering.GetProfessions())
|
||||
:Draw()
|
||||
end
|
||||
|
||||
function private.UpdateButtonState()
|
||||
if not private.frame then
|
||||
return
|
||||
end
|
||||
local button = private.frame:GetElement("mats.openTaskListBtn")
|
||||
if private.query:Count() == 0 then
|
||||
button:SetText(L["No Materials to Gather"])
|
||||
button:SetDisabled(true)
|
||||
elseif TSM.UI.TaskListUI.IsVisible() then
|
||||
button:SetText(L["Tasks Added to Task List"])
|
||||
button:SetDisabled(true)
|
||||
else
|
||||
button:SetText(L["Open Task List"])
|
||||
button:SetDisabled(false)
|
||||
end
|
||||
button:Draw()
|
||||
end
|
||||
289
Core/UI/Debug/DBViewer.lua
Normal file
289
Core/UI/Debug/DBViewer.lua
Normal file
@@ -0,0 +1,289 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local DBViewer = TSM.UI:NewPackage("DBViewer")
|
||||
local Database = TSM.Include("Util.Database")
|
||||
local Log = TSM.Include("Util.Log")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local private = {
|
||||
frame = nil,
|
||||
frameContext = {},
|
||||
dividedContainerContext = {},
|
||||
selectedDBName = nil,
|
||||
structureScrollingTableContext = {},
|
||||
browseScrollingTableContext = {},
|
||||
defaultBrowseScrollingTableContext = { colWidth = {}, colHidden = {} },
|
||||
}
|
||||
local DEFAULT_FRAME_CONTEXT = {
|
||||
width = 900,
|
||||
height = 600,
|
||||
centerX = 100,
|
||||
centerY = 0,
|
||||
scale = 1,
|
||||
}
|
||||
local MIN_FRAME_SIZE = {
|
||||
width = 900,
|
||||
height = 600
|
||||
}
|
||||
local DEFAULT_DIVIDED_CONTAINER_CONTEXT = {
|
||||
leftWidth = 200,
|
||||
}
|
||||
local DEFAULT_STRUCTURE_SCROLLING_TABLE_CONTEXT = {
|
||||
colWidth = {
|
||||
order = 24,
|
||||
field = 450,
|
||||
type = 60,
|
||||
attributes = 96,
|
||||
},
|
||||
colHidden = {},
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function DBViewer.OnDisable()
|
||||
-- hide the frame
|
||||
if private.frame then
|
||||
DBViewer.Toggle()
|
||||
end
|
||||
end
|
||||
|
||||
function DBViewer.Toggle()
|
||||
if not private.frame then
|
||||
private.frame = private.CreateMainFrame()
|
||||
private.frame:Draw()
|
||||
private.frame:Show()
|
||||
else
|
||||
private.frame:Hide()
|
||||
assert(not private.frame)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- UI Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.CreateMainFrame()
|
||||
private.selectedDBName = nil
|
||||
return UIElements.New("ApplicationFrame", "base")
|
||||
:SetParent(UIParent)
|
||||
:SetMinResize(MIN_FRAME_SIZE.width, MIN_FRAME_SIZE.height)
|
||||
:SetContextTable(private.frameContext, DEFAULT_FRAME_CONTEXT)
|
||||
:SetStrata("HIGH")
|
||||
:SetTitle("TSM DB Viewer")
|
||||
:SetScript("OnHide", private.FrameOnHide)
|
||||
:SetContentFrame(UIElements.New("DividedContainer", "container")
|
||||
:SetContextTable(private.dividedContainerContext, DEFAULT_DIVIDED_CONTAINER_CONTEXT)
|
||||
:SetBackgroundColor("PRIMARY_BG")
|
||||
:SetMinWidth(100, 100)
|
||||
:SetLeftChild(UIElements.New("ScrollFrame", "left")
|
||||
:AddChildrenWithFunction(private.AddTableRows)
|
||||
)
|
||||
:SetRightChild(UIElements.New("Frame", "right")
|
||||
:SetLayout("VERTICAL")
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
function private.AddTableRows(frame)
|
||||
for _, name in Database.InfoNameIterator() do
|
||||
frame:AddChild(UIElements.New("Button", "nav_"..name)
|
||||
:SetHeight(20)
|
||||
:SetPadding(8, 0, 0, 0)
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetHighlightEnabled(true)
|
||||
:SetText(name)
|
||||
:SetBackground("PRIMARY_BG")
|
||||
:SetScript("OnClick", private.NavButtonOnClick)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
function private.CreateTableContent()
|
||||
local contentFrame = private.frame:GetElement("container.right")
|
||||
contentFrame:ReleaseAllChildren()
|
||||
contentFrame:AddChild(UIElements.New("TabGroup", "tabs")
|
||||
:SetMargin(0, 0, 4, 0)
|
||||
:SetNavCallback(private.ContentNavCallback)
|
||||
:AddPath("Structure", true)
|
||||
:AddPath("Browse")
|
||||
)
|
||||
contentFrame:Draw()
|
||||
end
|
||||
|
||||
function private.ContentNavCallback(_, path)
|
||||
if path == "Structure" then
|
||||
return private.CreateStructureFrame()
|
||||
elseif path == "Browse" then
|
||||
return private.CreateBrowseFrame()
|
||||
else
|
||||
error("Invalid path: "..tostring(path))
|
||||
end
|
||||
end
|
||||
|
||||
function private.CreateStructureFrame()
|
||||
local query = Database.CreateInfoFieldQuery(private.selectedDBName)
|
||||
:OrderBy("order", true)
|
||||
return UIElements.New("Frame", "structure")
|
||||
:SetLayout("VERTICAL")
|
||||
:AddChild(UIElements.New("Frame", "info")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:SetMargin(4)
|
||||
:AddChild(UIElements.New("Text", "numRows")
|
||||
:SetFont("BODY_BODY2")
|
||||
:SetText("Total Rows: "..Database.GetNumRows(private.selectedDBName))
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "numRows")
|
||||
:SetFont("BODY_BODY2")
|
||||
:SetText("Active Queries: "..Database.GetNumActiveQueries(private.selectedDBName))
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("QueryScrollingTable", "table")
|
||||
:SetContextTable(private.structureScrollingTableContext, DEFAULT_STRUCTURE_SCROLLING_TABLE_CONTEXT)
|
||||
:GetScrollingTableInfo()
|
||||
:NewColumn("order")
|
||||
:SetTitle("#")
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo("order")
|
||||
:SetSortInfo("order")
|
||||
:Commit()
|
||||
:NewColumn("field")
|
||||
:SetTitle("Field")
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo("field")
|
||||
:SetSortInfo("field")
|
||||
:Commit()
|
||||
:NewColumn("type")
|
||||
:SetTitle("Type")
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo("type")
|
||||
:SetSortInfo("type")
|
||||
:Commit()
|
||||
:NewColumn("attributes")
|
||||
:SetTitle("Attributes")
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo("attributes")
|
||||
:SetSortInfo("attributes")
|
||||
:Commit()
|
||||
:Commit()
|
||||
:SetQuery(query)
|
||||
:SetAutoReleaseQuery(true)
|
||||
:SetSelectionDisabled(true)
|
||||
)
|
||||
end
|
||||
|
||||
function private.CreateBrowseFrame()
|
||||
local query = Database.CreateDBQuery(private.selectedDBName)
|
||||
local fieldQuery = Database.CreateInfoFieldQuery(private.selectedDBName)
|
||||
:Select("field")
|
||||
:OrderBy("order", true)
|
||||
wipe(private.defaultBrowseScrollingTableContext.colWidth)
|
||||
for _, field in fieldQuery:Iterator() do
|
||||
private.defaultBrowseScrollingTableContext.colWidth[field] = 100
|
||||
end
|
||||
wipe(private.browseScrollingTableContext)
|
||||
|
||||
local frame = UIElements.New("Frame", "browse")
|
||||
:SetLayout("VERTICAL")
|
||||
:AddChild(UIElements.New("Input", "queryInput")
|
||||
:SetHeight(26)
|
||||
:SetMargin(8)
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT")
|
||||
:SetValue("query")
|
||||
:SetScript("OnEnterPressed", private.QueryInputOnEnterPressed)
|
||||
)
|
||||
:AddChild(UIElements.New("QueryScrollingTable", "table")
|
||||
:SetContextTable(private.browseScrollingTableContext, private.defaultBrowseScrollingTableContext)
|
||||
:SetContext(query)
|
||||
:SetQuery(query)
|
||||
:SetAutoReleaseQuery(true)
|
||||
:SetSelectionDisabled(true)
|
||||
)
|
||||
|
||||
local stInfo = frame:GetElement("table"):GetScrollingTableInfo()
|
||||
for _, field in fieldQuery:Iterator() do
|
||||
stInfo:NewColumn(field)
|
||||
:SetTitle(field)
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo(field, tostring)
|
||||
:SetTooltipInfo(field, private.TooltipFunc)
|
||||
:Commit()
|
||||
end
|
||||
fieldQuery:Release()
|
||||
stInfo:Commit()
|
||||
|
||||
return frame
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.FrameOnHide(frame)
|
||||
assert(frame == private.frame)
|
||||
private.frame:Release()
|
||||
private.frame = nil
|
||||
end
|
||||
|
||||
function private.NavButtonOnClick(button)
|
||||
local navFrame = button:GetParentElement()
|
||||
private.selectedDBName = button:GetText()
|
||||
for _, name in Database.InfoNameIterator() do
|
||||
navFrame:GetElement("nav_"..name)
|
||||
:SetTextColor(name == private.selectedDBName and "INDICATOR" or "TEXT")
|
||||
:Draw()
|
||||
end
|
||||
private.CreateTableContent()
|
||||
end
|
||||
|
||||
function private.QueryInputOnEnterPressed(input)
|
||||
local func, errStr = loadstring(input:GetValue())
|
||||
if not func then
|
||||
Log.PrintfUser("Failed to compile code: "..errStr)
|
||||
return
|
||||
end
|
||||
local tableElement = input:GetElement("__parent.table")
|
||||
local query = tableElement:GetContext()
|
||||
query:Reset()
|
||||
setfenv(func, { query = query })
|
||||
local ok, funcErrStr = pcall(func)
|
||||
if not ok then
|
||||
Log.PrintfUser("Failed to execute code: "..funcErrStr)
|
||||
return
|
||||
end
|
||||
tableElement:UpdateData(true)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.TooltipFunc(value)
|
||||
value = tostring(value)
|
||||
if strmatch(value, "item:") or strmatch(value, "battlepet:") or strmatch(value, "[ip]:") then
|
||||
-- this is an item string or item link
|
||||
return value
|
||||
else
|
||||
return "Value: "..value
|
||||
end
|
||||
end
|
||||
455
Core/UI/DestroyingUI/Core.lua
Normal file
455
Core/UI/DestroyingUI/Core.lua
Normal file
@@ -0,0 +1,455 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local DestroyingUI = TSM.UI:NewPackage("DestroyingUI")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local DisenchantInfo = TSM.Include("Data.DisenchantInfo")
|
||||
local FSM = TSM.Include("Util.FSM")
|
||||
local ItemString = TSM.Include("Util.ItemString")
|
||||
local Log = TSM.Include("Util.Log")
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local Event = TSM.Include("Util.Event")
|
||||
local Delay = TSM.Include("Util.Delay")
|
||||
local ItemInfo = TSM.Include("Service.ItemInfo")
|
||||
local Conversions = TSM.Include("Service.Conversions")
|
||||
local Settings = TSM.Include("Service.Settings")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local private = {
|
||||
enterWorldTime = nil,
|
||||
settings = nil,
|
||||
fsm = nil,
|
||||
query = nil,
|
||||
}
|
||||
local MIN_FRAME_SIZE = { width = 280, height = 280 }
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function DestroyingUI.OnInitialize()
|
||||
Event.Register("PLAYER_ENTERING_WORLD", function()
|
||||
private.enterWorldTime = GetTime()
|
||||
end)
|
||||
private.settings = Settings.NewView()
|
||||
:AddKey("global", "destroyingUIContext", "frame")
|
||||
:AddKey("global", "destroyingUIContext", "itemsScrollingTable")
|
||||
:AddKey("global", "destroyingOptions", "autoShow")
|
||||
:AddKey("global", "destroyingOptions", "autoStack")
|
||||
private.FSMCreate()
|
||||
end
|
||||
|
||||
function DestroyingUI.OnDisable()
|
||||
-- hide the frame
|
||||
private.fsm:ProcessEvent("EV_FRAME_HIDE")
|
||||
end
|
||||
|
||||
function DestroyingUI.Toggle()
|
||||
private.fsm:ProcessEvent("EV_FRAME_TOGGLE")
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Main Frame
|
||||
-- ============================================================================
|
||||
|
||||
function private.CreateMainFrame()
|
||||
TSM.UI.AnalyticsRecordPathChange("destroying")
|
||||
local frame = UIElements.New("ApplicationFrame", "base")
|
||||
:SetParent(UIParent)
|
||||
:SetSettingsContext(private.settings, "frame")
|
||||
:SetMinResize(MIN_FRAME_SIZE.width, MIN_FRAME_SIZE.height)
|
||||
:SetStrata("HIGH")
|
||||
:SetTitle(L["Destroying"])
|
||||
:SetScript("OnHide", private.FrameOnHide)
|
||||
:SetContentFrame(UIElements.New("Frame", "content")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT")
|
||||
:AddChild(UIElements.New("Frame", "item")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(82)
|
||||
:SetMargin(8)
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT", true)
|
||||
:SetBorderColor("FRAME_BG")
|
||||
:AddChild(UIElements.New("Frame", "header")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetPadding(8, 8, 8, 4)
|
||||
:SetHeight(32)
|
||||
:SetBackgroundColor("FRAME_BG", true)
|
||||
:AddChild(UIElements.New("Button", "icon")
|
||||
:SetSize(20, 20)
|
||||
:SetMargin(0, 5, 0, 0)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "name")
|
||||
:SetHeight(20)
|
||||
:SetFont("ITEM_BODY2")
|
||||
)
|
||||
)
|
||||
-- draw a line along the bottom to hide the rounded corners at the bottom of the header frame
|
||||
:AddChildNoLayout(UIElements.New("Texture", "line")
|
||||
:AddAnchor("BOTTOMLEFT", "header")
|
||||
:AddAnchor("BOTTOMRIGHT", "header")
|
||||
:SetHeight(4)
|
||||
:SetTexture("FRAME_BG")
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "container")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetPadding(0, 0, 4, 4)
|
||||
:AddChild(UIElements.New("ScrollFrame", "scroll")
|
||||
:SetPadding(8, 8, 0, 0)
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("QueryScrollingTable", "items")
|
||||
:SetSettingsContext(private.settings, "itemsScrollingTable")
|
||||
:GetScrollingTableInfo()
|
||||
:NewColumn("item")
|
||||
:SetTitle(L["Item"])
|
||||
:SetIconSize(12)
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo("itemString", TSM.UI.GetColoredItemName)
|
||||
:SetIconInfo("itemString", ItemInfo.GetTexture)
|
||||
:SetTooltipInfo("itemString")
|
||||
:SetSortInfo("name")
|
||||
:SetTooltipLinkingDisabled(true)
|
||||
:SetActionIconInfo(1, 12, private.GetHideIcon)
|
||||
:SetActionIconClickHandler(private.OnHideIconClick)
|
||||
:DisableHiding()
|
||||
:Commit()
|
||||
:NewColumn("num")
|
||||
:SetTitle("Qty")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("CENTER")
|
||||
:SetTextInfo("quantity")
|
||||
:SetSortInfo("quantity")
|
||||
:Commit()
|
||||
:Commit()
|
||||
:SetQuery(private.query)
|
||||
:SetScript("OnSelectionChanged", private.ItemsOnSelectionChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("Texture", "lineBottom")
|
||||
:SetHeight(2)
|
||||
:SetTexture("ACTIVE_BG")
|
||||
)
|
||||
:AddChild(UIElements.New("ActionButton", "combineBtn")
|
||||
:SetHeight(26)
|
||||
:SetMargin(12, 12, 12, 0)
|
||||
:SetText(L["Combine Partial Stacks"])
|
||||
:SetScript("OnClick", private.CombineButtonOnClick)
|
||||
)
|
||||
:AddChild(UIElements.NewNamed("SecureMacroActionButton", "destroyBtn", "TSMDestroyBtn")
|
||||
:SetHeight(26)
|
||||
:SetMargin(12, 12, 8, 12)
|
||||
:SetText(L["Destroy Next"])
|
||||
:SetScript("PreClick", private.DestroyButtonPreClick)
|
||||
)
|
||||
)
|
||||
frame:GetElement("titleFrame.closeBtn"):SetScript("OnClick", private.CloseButtonOnClick)
|
||||
:Draw()
|
||||
return frame
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.FrameOnHide()
|
||||
TSM.UI.AnalyticsRecordClose("destroying")
|
||||
private.fsm:ProcessEvent("EV_FRAME_TOGGLE")
|
||||
end
|
||||
|
||||
function private.GetHideIcon(self, data, iconIndex, isMouseOver)
|
||||
assert(iconIndex == 1)
|
||||
-- TODO: needs a new texture for the icon
|
||||
return true, isMouseOver and TSM.UI.TexturePacks.GetColoredKey("iconPack.12x12/Hide", "TEXT_ALT") or "iconPack.12x12/Hide", true, L["Click to hide this item for the current session. Hold shift to hide this item permanently."]
|
||||
end
|
||||
|
||||
function private.OnHideIconClick(self, data, iconIndex, mouseButton)
|
||||
assert(iconIndex == 1)
|
||||
if mouseButton ~= "LeftButton" then
|
||||
return
|
||||
end
|
||||
local row = self._query:GetResultRowByUUID(data)
|
||||
local itemString = row:GetField("itemString")
|
||||
if IsShiftKeyDown() then
|
||||
Log.PrintfUser(L["Destroying will ignore %s permanently. You can remove it from the ignored list in the settings."], ItemInfo.GetName(itemString))
|
||||
TSM.Destroying.IgnoreItemPermanent(itemString)
|
||||
else
|
||||
Log.PrintfUser(L["Destroying will ignore %s until you log out."], ItemInfo.GetName(itemString))
|
||||
TSM.Destroying.IgnoreItemSession(itemString)
|
||||
end
|
||||
if self._query:Count() == 0 then
|
||||
private.fsm:ProcessEvent("EV_FRAME_TOGGLE")
|
||||
end
|
||||
end
|
||||
|
||||
function private.GetDestroyInfo(itemString)
|
||||
local quality = ItemInfo.GetQuality(itemString)
|
||||
local ilvl = ItemInfo.GetItemLevel(ItemString.GetBase(itemString))
|
||||
local classId = ItemInfo.GetClassId(itemString)
|
||||
local info = TempTable.Acquire()
|
||||
local targetItems = TempTable.Acquire()
|
||||
for targetItemString in DisenchantInfo.TargetItemIterator() do
|
||||
local amountOfMats, matRate, minAmount, maxAmount = DisenchantInfo.GetTargetItemSourceInfo(targetItemString, classId, quality, ilvl)
|
||||
if amountOfMats then
|
||||
local name = ItemInfo.GetName(targetItemString)
|
||||
local color = ItemInfo.GetQualityColor(targetItemString)
|
||||
if name and color then
|
||||
matRate = matRate and matRate * 100
|
||||
matRate = matRate and matRate.."% " or ""
|
||||
local range = (minAmount and maxAmount) and Theme.GetFeedbackColor("YELLOW"):ColorText(minAmount ~= maxAmount and (" ["..minAmount.."-"..maxAmount.."]") or (" ["..minAmount.."]")) or ""
|
||||
tinsert(info, color..matRate..name.." x"..amountOfMats.."|r"..range)
|
||||
tinsert(targetItems, targetItemString)
|
||||
end
|
||||
end
|
||||
end
|
||||
for targetItemString, amountOfMats, matRate, minAmount, maxAmount in Conversions.TargetItemsByMethodIterator(itemString, Conversions.METHOD.PROSPECT) do
|
||||
local name = ItemInfo.GetName(targetItemString)
|
||||
local color = ItemInfo.GetQualityColor(targetItemString)
|
||||
if name and color then
|
||||
matRate = matRate and matRate * 100
|
||||
matRate = matRate and matRate.."% " or ""
|
||||
local range = (minAmount and maxAmount) and Theme.GetFeedbackColor("YELLOW"):ColorText(minAmount ~= maxAmount and (" ["..minAmount.."-"..maxAmount.."]") or (" ["..minAmount.."]")) or ""
|
||||
tinsert(info, color..matRate..name.." x"..amountOfMats.."|r"..range)
|
||||
tinsert(targetItems, targetItemString)
|
||||
end
|
||||
end
|
||||
for targetItemString, rate in Conversions.TargetItemsByMethodIterator(itemString, Conversions.METHOD.MILL) do
|
||||
local name = ItemInfo.GetName(targetItemString)
|
||||
local color = ItemInfo.GetQualityColor(targetItemString)
|
||||
if name and color then
|
||||
tinsert(info, color..ItemInfo.GetName(targetItemString).." x"..rate.."|r")
|
||||
tinsert(targetItems, targetItemString)
|
||||
end
|
||||
end
|
||||
return info, targetItems
|
||||
end
|
||||
|
||||
function private.ItemsOnSelectionChanged(self)
|
||||
if not self:GetSelection() then
|
||||
return
|
||||
end
|
||||
|
||||
local itemString = self:GetSelection():GetField("itemString")
|
||||
local itemFrame = self:GetElement("__parent.item")
|
||||
itemFrame:GetElement("header.icon")
|
||||
:SetBackground(ItemInfo.GetTexture(itemString))
|
||||
:SetTooltip(itemString)
|
||||
itemFrame:GetElement("header.name")
|
||||
:SetText(TSM.UI.GetColoredItemName(itemString) or "")
|
||||
|
||||
local info, targetItems = private.GetDestroyInfo(itemString)
|
||||
local scrollFrame = itemFrame:GetElement("container.scroll")
|
||||
scrollFrame:ReleaseAllChildren()
|
||||
for i, text in ipairs(info) do
|
||||
scrollFrame:AddChild(UIElements.New("Button", "row"..i)
|
||||
:SetHeight(14)
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetText(text)
|
||||
:SetTooltip(targetItems[i])
|
||||
)
|
||||
end
|
||||
TempTable.Release(info)
|
||||
TempTable.Release(targetItems)
|
||||
itemFrame:Draw()
|
||||
end
|
||||
|
||||
function private.CloseButtonOnClick(button)
|
||||
private.fsm:ProcessEvent("EV_FRAME_TOGGLE")
|
||||
end
|
||||
|
||||
function private.CombineButtonOnClick(button)
|
||||
button:SetPressed(true)
|
||||
button:Draw()
|
||||
private.fsm:ProcessEvent("EV_COMBINE_BUTTON_CLICKED")
|
||||
end
|
||||
|
||||
function private.DestroyButtonPreClick(button)
|
||||
button:SetPressed(true)
|
||||
button:Draw()
|
||||
private.fsm:ProcessEvent("EV_DESTROY_BUTTON_PRE_CLICK")
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- FSM
|
||||
-- ============================================================================
|
||||
|
||||
function private.FSMCreate()
|
||||
TSM.Destroying.SetBagUpdateCallback(private.FSMBagUpdate)
|
||||
local fsmContext = {
|
||||
frame = nil,
|
||||
combineFuture = nil,
|
||||
destroyFuture = nil,
|
||||
didShowOnce = false,
|
||||
didAutoCombine = false,
|
||||
}
|
||||
local function UpdateDestroyingFrame(context, noDraw)
|
||||
if not context.frame then
|
||||
return
|
||||
end
|
||||
|
||||
local combineBtn = context.frame:GetElement("content.combineBtn")
|
||||
combineBtn:SetText(context.combineFuture and L["Combining..."] or L["Combine Partial Stacks"])
|
||||
combineBtn:SetDisabled(context.combineFuture or context.destroyFuture or not TSM.Destroying.CanCombine())
|
||||
local destroyBtn = context.frame:GetElement("content.destroyBtn")
|
||||
destroyBtn:SetText(context.destroyFuture and L["Destroying..."] or L["Destroy Next"])
|
||||
destroyBtn:SetDisabled(context.combineFuture or context.destroyFuture or private.query:Count() == 0)
|
||||
if not noDraw then
|
||||
context.frame:Draw()
|
||||
end
|
||||
end
|
||||
private.fsm = FSM.New("DESTROYING")
|
||||
:AddState(FSM.NewState("ST_FRAME_CLOSED")
|
||||
:SetOnEnter(function(context)
|
||||
if context.frame then
|
||||
context.frame:Hide()
|
||||
context.frame:Release()
|
||||
context.frame = nil
|
||||
end
|
||||
if context.combineFuture then
|
||||
context.combineFuture:Cancel()
|
||||
context.combineFuture = nil
|
||||
end
|
||||
if context.destroyFuture then
|
||||
context.destroyFuture:Cancel()
|
||||
context.destroyFuture = nil
|
||||
end
|
||||
context.didAutoCombine = false
|
||||
end)
|
||||
:AddTransition("ST_FRAME_OPENING")
|
||||
:AddEventTransition("EV_FRAME_TOGGLE", "ST_FRAME_OPENING")
|
||||
:AddEvent("EV_BAG_UPDATE", function(context)
|
||||
if not context.didShowOnce and private.settings.autoShow then
|
||||
return "ST_FRAME_OPENING"
|
||||
end
|
||||
end)
|
||||
)
|
||||
:AddState(FSM.NewState("ST_FRAME_OPENING")
|
||||
:SetOnEnter(function(context)
|
||||
private.query = private.query or TSM.Destroying.CreateBagQuery()
|
||||
private.query:ResetOrderBy()
|
||||
private.query:OrderBy("name", true)
|
||||
if (not private.settings.autoStack or not TSM.Destroying.CanCombine()) and private.query:Count() == 0 then
|
||||
-- nothing to destroy or combine, so bail
|
||||
return "ST_FRAME_CLOSED"
|
||||
end
|
||||
context.didShowOnce = true
|
||||
context.frame = private.CreateMainFrame()
|
||||
context.frame:Show()
|
||||
private.ItemsOnSelectionChanged(context.frame:GetElement("content.items"))
|
||||
return "ST_FRAME_OPEN"
|
||||
end)
|
||||
:AddTransition("ST_FRAME_OPEN")
|
||||
:AddTransition("ST_FRAME_CLOSED")
|
||||
)
|
||||
:AddState(FSM.NewState("ST_FRAME_OPEN")
|
||||
:SetOnEnter(function(context)
|
||||
UpdateDestroyingFrame(context)
|
||||
if not context.frame:GetElement("content.items"):GetSelection() then
|
||||
-- select the first row
|
||||
local result = private.query:GetFirstResult()
|
||||
context.frame:GetElement("content.items"):SetSelection(result and result:GetUUID() or nil)
|
||||
end
|
||||
if private.settings.autoStack and not context.didAutoCombine and TSM.Destroying.CanCombine() then
|
||||
context.didAutoCombine = true
|
||||
context.frame:GetElement("content.combineBtn")
|
||||
:SetPressed(true)
|
||||
:Draw()
|
||||
return "ST_COMBINING_STACKS"
|
||||
elseif not TSM.Destroying.CanCombine() and private.query:Count() == 0 then
|
||||
-- nothing left to destroy or combine
|
||||
return "ST_FRAME_CLOSED"
|
||||
end
|
||||
context.didAutoCombine = true
|
||||
end)
|
||||
:AddTransition("ST_FRAME_OPEN")
|
||||
:AddTransition("ST_COMBINING_STACKS")
|
||||
:AddTransition("ST_DESTROYING")
|
||||
:AddTransition("ST_FRAME_CLOSED")
|
||||
:AddEventTransition("EV_COMBINE_BUTTON_CLICKED", "ST_COMBINING_STACKS")
|
||||
:AddEventTransition("EV_DESTROY_BUTTON_PRE_CLICK", "ST_DESTROYING")
|
||||
:AddEventTransition("EV_BAG_UPDATE", "ST_FRAME_OPEN")
|
||||
:AddEventTransition("EV_FRAME_HIDE", "ST_FRAME_CLOSED")
|
||||
)
|
||||
:AddState(FSM.NewState("ST_COMBINING_STACKS")
|
||||
:SetOnEnter(function(context)
|
||||
assert(not context.combineFuture)
|
||||
context.combineFuture = TSM.Destroying.StartCombine()
|
||||
context.combineFuture:SetScript("OnDone", private.FSMCombineFutureOnDone)
|
||||
UpdateDestroyingFrame(context, true)
|
||||
end)
|
||||
:AddTransition("ST_COMBINING_DONE")
|
||||
:AddTransition("ST_FRAME_CLOSED")
|
||||
:AddEventTransition("EV_COMBINE_DONE", "ST_COMBINING_DONE")
|
||||
:AddEventTransition("EV_FRAME_HIDE", "ST_FRAME_CLOSED")
|
||||
)
|
||||
:AddState(FSM.NewState("ST_COMBINING_DONE")
|
||||
:SetOnEnter(function(context)
|
||||
-- don't care what the result was
|
||||
context.combineFuture:GetValue()
|
||||
context.combineFuture = nil
|
||||
context.frame:GetElement("content.combineBtn")
|
||||
:SetPressed(false)
|
||||
:Draw()
|
||||
return "ST_FRAME_OPEN"
|
||||
end)
|
||||
:AddTransition("ST_FRAME_OPEN")
|
||||
)
|
||||
:AddState(FSM.NewState("ST_DESTROYING")
|
||||
:SetOnEnter(function(context)
|
||||
assert(not context.destroyFuture)
|
||||
context.destroyFuture = TSM.Destroying.StartDestroy(context.frame:GetElement("content.destroyBtn"), context.frame:GetElement("content.items"):GetSelection())
|
||||
context.destroyFuture:SetScript("OnDone", private.FSMDestroyFutureOnDone)
|
||||
UpdateDestroyingFrame(context, true)
|
||||
end)
|
||||
:AddTransition("ST_DESTROYING_DONE")
|
||||
:AddTransition("ST_FRAME_CLOSED")
|
||||
:AddEventTransition("EV_DESTROY_DONE", "ST_DESTROYING_DONE")
|
||||
:AddEventTransition("EV_FRAME_HIDE", "ST_FRAME_CLOSED")
|
||||
)
|
||||
:AddState(FSM.NewState("ST_DESTROYING_DONE")
|
||||
:SetOnEnter(function(context)
|
||||
-- don't care what the result was
|
||||
context.destroyFuture:GetValue()
|
||||
context.destroyFuture = nil
|
||||
context.isDestroying = false
|
||||
context.frame:GetElement("content.destroyBtn")
|
||||
:SetPressed(false)
|
||||
:Draw()
|
||||
return "ST_FRAME_OPEN"
|
||||
end)
|
||||
:AddTransition("ST_FRAME_OPEN")
|
||||
)
|
||||
:AddDefaultEventTransition("EV_FRAME_TOGGLE", "ST_FRAME_CLOSED")
|
||||
:Init("ST_FRAME_CLOSED", fsmContext)
|
||||
end
|
||||
|
||||
function private.FSMBagUpdate()
|
||||
if not private.enterWorldTime or private.enterWorldTime == GetTime() then
|
||||
-- delay for another frame as wow closes special frames in its PLAYER_ENTERING_WORLD handler
|
||||
Delay.AfterTime("DESTROYING_BAG_UPDATE_DELAY", 0, private.FSMBagUpdate)
|
||||
return
|
||||
end
|
||||
private.fsm:ProcessEvent("EV_BAG_UPDATE")
|
||||
end
|
||||
|
||||
function private.FSMCombineFutureOnDone()
|
||||
private.fsm:ProcessEvent("EV_COMBINE_DONE")
|
||||
end
|
||||
|
||||
function private.FSMDestroyFutureOnDone()
|
||||
private.fsm:ProcessEvent("EV_DESTROY_DONE")
|
||||
end
|
||||
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
|
||||
339
Core/UI/FrameStack.lua
Normal file
339
Core/UI/FrameStack.lua
Normal file
@@ -0,0 +1,339 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- FrameStack Functions
|
||||
-- @module FrameStack
|
||||
|
||||
local _, TSM = ...
|
||||
local FrameStack = TSM.UI:NewPackage("FrameStack")
|
||||
local Math = TSM.Include("Util.Math")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local Table = TSM.Include("Util.Table")
|
||||
local Vararg = TSM.Include("Util.Vararg")
|
||||
local ScriptWrapper = TSM.Include("Util.ScriptWrapper")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local private = {}
|
||||
local STRATA_ORDER = {"TOOLTIP", "FULLSCREEN_DIALOG", "FULLSCREEN", "DIALOG", "HIGH", "MEDIUM", "LOW", "BACKGROUND", "WORLD"}
|
||||
local framesByStrata = {
|
||||
WORLD = {},
|
||||
BACKGROUND = {},
|
||||
LOW = {},
|
||||
MEDIUM = {},
|
||||
HIGH = {},
|
||||
DIALOG = {},
|
||||
FULLSCREEN = {},
|
||||
FULLSCREEN_DIALOG = {},
|
||||
TOOLTIP = {},
|
||||
}
|
||||
local ELEMENT_ATTR_KEYS = {
|
||||
"_hScrollbar",
|
||||
"_vScrollbar",
|
||||
"_hScrollFrame",
|
||||
"_hContent",
|
||||
"_vScrollFrame",
|
||||
"_content",
|
||||
"_header",
|
||||
"_frame",
|
||||
"_rows",
|
||||
}
|
||||
local COLOR_KEYS = {
|
||||
FRAME_BG = true,
|
||||
PRIMARY_BG = true,
|
||||
PRIMARY_BG_ALT = true,
|
||||
ACTIVE_BG = true,
|
||||
ACTIVE_BG_ALT = true,
|
||||
INDICATOR = true,
|
||||
INDICATOR_ALT = true,
|
||||
INDICATOR_DISABLED = true,
|
||||
TEXT = true,
|
||||
TEXT_ALT = true,
|
||||
TEXT_DISABLED = true,
|
||||
}
|
||||
local FEEDBACK_COLOR_KEYS = {
|
||||
RED = true,
|
||||
YELLOW = true,
|
||||
GREEN = true,
|
||||
BLUE = true,
|
||||
ORANGE = true,
|
||||
}
|
||||
local FONT_KEYS = {
|
||||
HEADING_H5 = true,
|
||||
BODY_BODY1 = true,
|
||||
BODY_BODY1_BOLD = true,
|
||||
BODY_BODY2 = true,
|
||||
BODY_BODY2_MEDIUM = true,
|
||||
BODY_BODY2_BOLD = true,
|
||||
BODY_BODY3 = true,
|
||||
BODY_BODY3_MEDIUM = true,
|
||||
ITEM_BODY1 = true,
|
||||
ITEM_BODY2 = true,
|
||||
ITEM_BODY3 = true,
|
||||
TABLE_TABLE1 = true,
|
||||
}
|
||||
local ELEMENT_STYLE_KEYS = {
|
||||
"_background",
|
||||
"_texture",
|
||||
"_backgroundColor",
|
||||
"_borderColor",
|
||||
"_textStr",
|
||||
"_textColor",
|
||||
"_font",
|
||||
"_roundedCorners",
|
||||
"_borderSize",
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function FrameStack.Toggle()
|
||||
if not TSMFrameStackTooltip then
|
||||
CreateFrame("GameTooltip", "TSMFrameStackTooltip", UIParent, "GameTooltipTemplate")
|
||||
TSMFrameStackTooltip.highlightFrame = CreateFrame("Frame", nil, nil, TSM.IsShadowlands() and "BackdropTemplate" or nil)
|
||||
TSMFrameStackTooltip.highlightFrame:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8" })
|
||||
TSMFrameStackTooltip.highlightFrame:SetBackdropColor(1, 0, 0, 0.3)
|
||||
TSMFrameStackTooltip:Hide()
|
||||
ScriptWrapper.Set(TSMFrameStackTooltip, "OnUpdate", private.OnUpdate)
|
||||
end
|
||||
if TSMFrameStackTooltip:IsVisible() then
|
||||
TSMFrameStackTooltip:Hide()
|
||||
TSMFrameStackTooltip.highlightFrame:Hide()
|
||||
else
|
||||
TSMFrameStackTooltip.lastUpdate = 0
|
||||
TSMFrameStackTooltip.altDown = nil
|
||||
TSMFrameStackTooltip.index = 1
|
||||
TSMFrameStackTooltip.numFrames = 0
|
||||
TSMFrameStackTooltip:SetOwner(UIParent, "ANCHOR_NONE")
|
||||
TSMFrameStackTooltip:SetPoint("TOPLEFT", 0, 0)
|
||||
TSMFrameStackTooltip:AddLine("Loading...")
|
||||
TSMFrameStackTooltip:Show()
|
||||
TSMFrameStackTooltip.highlightFrame:Show()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.OnUpdate(self)
|
||||
if self.lastUpdate + 0.05 >= GetTime() then
|
||||
return
|
||||
end
|
||||
self.lastUpdate = GetTime()
|
||||
|
||||
local numFrames = 0
|
||||
for _, strata in ipairs(STRATA_ORDER) do
|
||||
for _, strataFrame in ipairs(framesByStrata[strata]) do
|
||||
local name = private.GetFrameName(strataFrame)
|
||||
if not strmatch(name, "innerBorderFrame") then
|
||||
numFrames = numFrames + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
if numFrames ~= TSMFrameStackTooltip.numFrames then
|
||||
TSMFrameStackTooltip.index = 1
|
||||
TSMFrameStackTooltip.numFrames = numFrames
|
||||
end
|
||||
|
||||
local leftAltDown = IsKeyDown("LALT")
|
||||
local rightAltDown = IsKeyDown("RALT")
|
||||
if not self.altDown and leftAltDown and not rightAltDown then
|
||||
self.altDown = "LEFT"
|
||||
if TSMFrameStackTooltip.index == TSMFrameStackTooltip.numFrames then
|
||||
TSMFrameStackTooltip.index = 1
|
||||
else
|
||||
TSMFrameStackTooltip.index = TSMFrameStackTooltip.index + 1
|
||||
end
|
||||
elseif not self.altDown and not leftAltDown and rightAltDown then
|
||||
self.altDown = "RIGHT"
|
||||
if TSMFrameStackTooltip.index == 1 then
|
||||
TSMFrameStackTooltip.index = TSMFrameStackTooltip.numFrames
|
||||
else
|
||||
TSMFrameStackTooltip.index = TSMFrameStackTooltip.index - 1
|
||||
end
|
||||
elseif self.altDown == "LEFT" and not leftAltDown then
|
||||
self.altDown = nil
|
||||
elseif self.altDown == "RIGHT" and not rightAltDown then
|
||||
self.altDown = nil
|
||||
end
|
||||
|
||||
for _, strata in ipairs(STRATA_ORDER) do
|
||||
wipe(framesByStrata[strata])
|
||||
end
|
||||
|
||||
local frame = EnumerateFrames()
|
||||
while frame do
|
||||
if frame ~= self.highlightFrame and not frame:IsForbidden() and frame:IsVisible() and MouseIsOver(frame) then
|
||||
tinsert(framesByStrata[frame:GetFrameStrata()], frame)
|
||||
for _, region in Vararg.Iterator(frame:GetRegions()) do
|
||||
if region:IsObjectType("Texture") and not region:IsForbidden() and region:IsVisible() and MouseIsOver(region) and UIElements.GetByFrame(region) then
|
||||
tinsert(framesByStrata[frame:GetFrameStrata()], region)
|
||||
end
|
||||
end
|
||||
end
|
||||
frame = EnumerateFrames(frame)
|
||||
end
|
||||
|
||||
self:ClearLines()
|
||||
self:AddDoubleLine("TSM Frame Stack", format("%0.2f, %0.2f", GetCursorPosition()))
|
||||
local currentIndex = 1
|
||||
local topFrame = nil
|
||||
for _, strata in ipairs(STRATA_ORDER) do
|
||||
if #framesByStrata[strata] > 0 then
|
||||
sort(framesByStrata[strata], private.FrameLevelSortFunction)
|
||||
self:AddLine(strata, 0.6, 0.6, 1)
|
||||
for _, strataFrame in ipairs(framesByStrata[strata]) do
|
||||
local isTexture = strataFrame:IsObjectType("Texture")
|
||||
local level = (isTexture and strataFrame:GetParent() or strataFrame):GetFrameLevel()
|
||||
local width = strataFrame:GetWidth()
|
||||
local height = strataFrame:GetHeight()
|
||||
local mouseEnabled = not isTexture and strataFrame:IsMouseEnabled()
|
||||
local name = private.GetFrameName(strataFrame)
|
||||
local isIndexedFrame = false
|
||||
if not strmatch(name, "innerBorderFrame") then
|
||||
if not topFrame and currentIndex == self.index then
|
||||
topFrame = strataFrame
|
||||
isIndexedFrame = true
|
||||
end
|
||||
currentIndex = currentIndex + 1
|
||||
end
|
||||
local text = format(" <%d%s> %s (%d, %d)", level, isTexture and "+" or "", name, Math.Round(width), Math.Round(height))
|
||||
if isIndexedFrame then
|
||||
self:AddLine(text, 0.9, 0.9, 0.5)
|
||||
local element = UIElements.GetByFrame(strataFrame)
|
||||
if element then
|
||||
for _, k in ipairs(ELEMENT_STYLE_KEYS) do
|
||||
local v = element[k]
|
||||
if v ~= nil then
|
||||
local vStr = private.GetStyleValueStr(v)
|
||||
if vStr then
|
||||
self:AddLine(format(" %s = %s", tostring(k), vStr), 0.7, 0.7, 0.7)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif mouseEnabled then
|
||||
self:AddLine(text, 0.6, 1, 1)
|
||||
else
|
||||
self:AddLine(text, 0.9, 0.9, 0.9)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
self.highlightFrame:ClearAllPoints()
|
||||
self.highlightFrame:SetAllPoints(topFrame)
|
||||
self.highlightFrame:SetFrameStrata("TOOLTIP")
|
||||
self:Show()
|
||||
end
|
||||
|
||||
function private.FrameLevelSortFunction(a, b)
|
||||
local aLevel = a:IsObjectType("Texture") and (a:GetParent():GetFrameLevel() + 0.1) or a:GetFrameLevel()
|
||||
local bLevel = b:IsObjectType("Texture") and (b:GetParent():GetFrameLevel() + 0.1) or b:GetFrameLevel()
|
||||
return aLevel > bLevel
|
||||
end
|
||||
|
||||
function private.TableValueSearch(tbl, searchValue, currentKey, visited)
|
||||
visited = visited or {}
|
||||
for key, value in pairs(tbl) do
|
||||
if value == searchValue then
|
||||
return (currentKey and (currentKey..".") or "")..key
|
||||
elseif type(value) == "table" and (not value.__isa or value:__isa(TSM.UI.Element)) and not visited[value] then
|
||||
visited[value] = true
|
||||
local result = private.TableValueSearch(value, searchValue, (currentKey and (currentKey..".") or "")..key, visited)
|
||||
if result then
|
||||
return result
|
||||
end
|
||||
end
|
||||
end
|
||||
for _, key in ipairs(ELEMENT_ATTR_KEYS) do
|
||||
local value = tbl[key]
|
||||
if value == searchValue then
|
||||
return (currentKey and (currentKey..".") or "")..key
|
||||
elseif type(value) == "table" and (not value.__isa or value:__isa(TSM.UI.Element)) and not visited[value] then
|
||||
visited[value] = true
|
||||
local result = private.TableValueSearch(value, searchValue, (currentKey and (currentKey..".") or "")..key, visited)
|
||||
if result then
|
||||
return result
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function private.GetFrameNodeInfo(frame)
|
||||
local globalName = not frame:IsObjectType("Texture") and frame:GetName()
|
||||
if globalName and not strmatch(globalName, "^TSM_UI_ELEMENT:") then
|
||||
return globalName, frame:GetParent()
|
||||
end
|
||||
|
||||
local parent = frame:GetParent()
|
||||
local element = UIElements.GetByFrame(frame)
|
||||
if element then
|
||||
return element._id, parent
|
||||
end
|
||||
|
||||
if parent then
|
||||
-- check if this exists as an attribute of the parent table
|
||||
local parentKey = Table.KeyByValue(parent, frame)
|
||||
if parentKey then
|
||||
return tostring(parentKey), parent
|
||||
end
|
||||
|
||||
-- find the nearest element to which this frame belongs
|
||||
local parentElement = nil
|
||||
local testFrame = parent
|
||||
while testFrame and not parentElement do
|
||||
parentElement = UIElements.GetByFrame(testFrame)
|
||||
testFrame = testFrame:GetParent()
|
||||
end
|
||||
if parentElement then
|
||||
-- check if this exists as an attribute of this element
|
||||
local tableKey = private.TableValueSearch(parentElement, frame)
|
||||
if tableKey then
|
||||
return tableKey, parentElement._frame
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return nil, parent
|
||||
end
|
||||
|
||||
function private.GetFrameName(frame)
|
||||
local name, parent = private.GetFrameNodeInfo(frame)
|
||||
local parentName = parent and (private.GetFrameName(parent)..".") or ""
|
||||
name = name or gsub(tostring(frame), ": ?0*", ":")
|
||||
return parentName..name
|
||||
end
|
||||
|
||||
function private.GetStyleValueStr(value)
|
||||
for key in pairs(COLOR_KEYS) do
|
||||
if value == Theme.GetColor(key) then
|
||||
return "ThemeColor<"..key..">"
|
||||
end
|
||||
end
|
||||
for key in pairs(FEEDBACK_COLOR_KEYS) do
|
||||
if value == Theme.GetFeedbackColor(key) then
|
||||
return "ThemeColor<"..key..">"
|
||||
end
|
||||
end
|
||||
if value == Theme.GetBlizzardColor() then
|
||||
return "ThemeColor<BLIZZARD>"
|
||||
end
|
||||
for key in pairs(FONT_KEYS) do
|
||||
if value == Theme.GetFont(key) then
|
||||
return "ThemeFont<"..key..">"
|
||||
end
|
||||
end
|
||||
if type(value) == "string" then
|
||||
return "\""..value.."\""
|
||||
elseif value ~= false then
|
||||
return tostring(value)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
243
Core/UI/MailingUI/Core.lua
Normal file
243
Core/UI/MailingUI/Core.lua
Normal file
@@ -0,0 +1,243 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local MailingUI = TSM.UI:NewPackage("MailingUI")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local Delay = TSM.Include("Util.Delay")
|
||||
local FSM = TSM.Include("Util.FSM")
|
||||
local Event = TSM.Include("Util.Event")
|
||||
local ScriptWrapper = TSM.Include("Util.ScriptWrapper")
|
||||
local Settings = TSM.Include("Service.Settings")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local private = {
|
||||
settings = nil,
|
||||
topLevelPages = {},
|
||||
frame = nil,
|
||||
fsm = nil,
|
||||
defaultUISwitchBtn = nil,
|
||||
isVisible = false,
|
||||
}
|
||||
local MIN_FRAME_SIZE = { width = 575, height = 400 }
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function MailingUI.OnInitialize()
|
||||
private.settings = Settings.NewView()
|
||||
:AddKey("global", "mailingUIContext", "showDefault")
|
||||
:AddKey("global", "mailingUIContext", "frame")
|
||||
private.FSMCreate()
|
||||
end
|
||||
|
||||
function MailingUI.OnDisable()
|
||||
-- hide the frame
|
||||
private.fsm:ProcessEvent("EV_FRAME_HIDE")
|
||||
end
|
||||
|
||||
function MailingUI.RegisterTopLevelPage(name, callback)
|
||||
tinsert(private.topLevelPages, { name = name, callback = callback })
|
||||
end
|
||||
|
||||
function MailingUI.IsVisible()
|
||||
return private.isVisible
|
||||
end
|
||||
|
||||
function MailingUI.SetSelectedTab(buttonText, redraw)
|
||||
private.frame:SetSelectedNavButton(buttonText, redraw)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Main Frame
|
||||
-- ============================================================================
|
||||
|
||||
function private.CreateMainFrame()
|
||||
TSM.UI.AnalyticsRecordPathChange("mailing")
|
||||
-- Always show the Inbox first
|
||||
private.settings.frame.page = 1
|
||||
local frame = UIElements.New("LargeApplicationFrame", "base")
|
||||
:SetParent(UIParent)
|
||||
:SetSettingsContext(private.settings, "frame")
|
||||
:SetMinResize(MIN_FRAME_SIZE.width, MIN_FRAME_SIZE.height)
|
||||
:SetStrata("HIGH")
|
||||
:AddSwitchButton(private.SwitchBtnOnClick)
|
||||
:SetScript("OnHide", private.BaseFrameOnHide)
|
||||
|
||||
for _, info in ipairs(private.topLevelPages) do
|
||||
frame:AddNavButton(info.name, info.callback)
|
||||
end
|
||||
|
||||
private.frame = frame
|
||||
|
||||
return frame
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.BaseFrameOnHide()
|
||||
TSM.UI.AnalyticsRecordClose("mailing")
|
||||
private.fsm:ProcessEvent("EV_FRAME_HIDE")
|
||||
end
|
||||
|
||||
function private.SwitchBtnOnClick(button)
|
||||
private.settings.showDefault = button ~= private.defaultUISwitchBtn
|
||||
private.fsm:ProcessEvent("EV_SWITCH_BTN_CLICKED")
|
||||
end
|
||||
|
||||
function private.SwitchButtonOnEnter(button)
|
||||
button:SetTextColor("TEXT")
|
||||
:Draw()
|
||||
end
|
||||
|
||||
function private.SwitchButtonOnLeave(button)
|
||||
button:SetTextColor("TEXT_ALT")
|
||||
:Draw()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- FSM
|
||||
-- ============================================================================
|
||||
|
||||
function private.FSMCreate()
|
||||
local function MailShowDelayed()
|
||||
private.fsm:ProcessEvent("EV_MAIL_SHOW")
|
||||
end
|
||||
Event.Register("MAIL_SHOW", function()
|
||||
Delay.AfterFrame("MAIL_SHOW_DELAYED", 0, MailShowDelayed)
|
||||
end)
|
||||
Event.Register("MAIL_CLOSED", function()
|
||||
private.fsm:ProcessEvent("EV_MAIL_CLOSED")
|
||||
end)
|
||||
|
||||
MailFrame:UnregisterEvent("MAIL_SHOW")
|
||||
CancelEmote()
|
||||
|
||||
local fsmContext = {
|
||||
frame = nil,
|
||||
}
|
||||
|
||||
ScriptWrapper.Set(MailFrame, "OnHide", function()
|
||||
private.fsm:ProcessEvent("EV_FRAME_HIDE")
|
||||
end)
|
||||
|
||||
private.fsm = FSM.New("MAILING_UI")
|
||||
:AddState(FSM.NewState("ST_CLOSED")
|
||||
:AddTransition("ST_DEFAULT_OPEN")
|
||||
:AddTransition("ST_FRAME_OPEN")
|
||||
:AddEvent("EV_FRAME_TOGGLE", function(context)
|
||||
assert(not private.settings.showDefault)
|
||||
return "ST_FRAME_OPEN"
|
||||
end)
|
||||
:AddEvent("EV_MAIL_SHOW", function(context)
|
||||
if private.settings.showDefault then
|
||||
return "ST_DEFAULT_OPEN"
|
||||
else
|
||||
return "ST_FRAME_OPEN"
|
||||
end
|
||||
end)
|
||||
)
|
||||
:AddState(FSM.NewState("ST_DEFAULT_OPEN")
|
||||
:SetOnEnter(function(context, isIgnored)
|
||||
MailFrame_OnEvent(MailFrame, "MAIL_SHOW")
|
||||
|
||||
if not private.defaultUISwitchBtn then
|
||||
private.defaultUISwitchBtn = UIElements.New("ActionButton", "switchBtn")
|
||||
:SetSize(60, TSM.IsWowClassic() and 16 or 15)
|
||||
:SetFont("BODY_BODY3")
|
||||
:AddAnchor("TOPRIGHT", TSM.IsWowClassic() and -26 or -27, TSM.IsWowClassic() and -3 or -4)
|
||||
:DisableClickCooldown()
|
||||
:SetText(L["TSM4"])
|
||||
:SetScript("OnClick", private.SwitchBtnOnClick)
|
||||
:SetScript("OnEnter", private.SwitchButtonOnEnter)
|
||||
:SetScript("OnLeave", private.SwitchButtonOnLeave)
|
||||
private.defaultUISwitchBtn:_GetBaseFrame():SetParent(MailFrame)
|
||||
end
|
||||
|
||||
if isIgnored then
|
||||
private.defaultUISwitchBtn:Hide()
|
||||
else
|
||||
private.defaultUISwitchBtn:Show()
|
||||
private.defaultUISwitchBtn:Draw()
|
||||
end
|
||||
end)
|
||||
:AddTransition("ST_CLOSED")
|
||||
:AddTransition("ST_FRAME_OPEN")
|
||||
:AddEvent("EV_FRAME_HIDE", function(context)
|
||||
OpenMailFrame:Hide()
|
||||
CloseMail()
|
||||
|
||||
return "ST_CLOSED"
|
||||
end)
|
||||
:AddEventTransition("EV_MAIL_CLOSED", "ST_CLOSED")
|
||||
:AddEvent("EV_SWITCH_BTN_CLICKED", function()
|
||||
OpenMailFrame:Hide()
|
||||
return "ST_FRAME_OPEN"
|
||||
end)
|
||||
)
|
||||
:AddState(FSM.NewState("ST_FRAME_OPEN")
|
||||
:SetOnEnter(function(context)
|
||||
OpenAllBags()
|
||||
CheckInbox()
|
||||
DoEmote("READ", nil, true)
|
||||
HideUIPanel(MailFrame)
|
||||
|
||||
assert(not context.frame)
|
||||
context.frame = private.CreateMainFrame()
|
||||
context.frame:Show()
|
||||
context.frame:Draw()
|
||||
private.isVisible = true
|
||||
end)
|
||||
:SetOnExit(function(context)
|
||||
if context.frame then
|
||||
context.frame:Hide()
|
||||
context.frame:Release()
|
||||
context.frame = nil
|
||||
end
|
||||
private.isVisible = false
|
||||
end)
|
||||
:AddTransition("ST_CLOSED")
|
||||
:AddTransition("ST_DEFAULT_OPEN")
|
||||
:AddEvent("EV_FRAME_HIDE", function(context)
|
||||
CancelEmote()
|
||||
CloseAllBags()
|
||||
CloseMail()
|
||||
|
||||
return "ST_CLOSED"
|
||||
end)
|
||||
:AddEvent("EV_MAIL_SHOW", function(context)
|
||||
OpenAllBags()
|
||||
CheckInbox()
|
||||
|
||||
if not context.frame then
|
||||
DoEmote("READ", nil, true)
|
||||
context.frame = private.CreateMainFrame()
|
||||
context.frame:Draw()
|
||||
private.isVisible = true
|
||||
end
|
||||
end)
|
||||
:AddEvent("EV_MAIL_CLOSED", function(context)
|
||||
CancelEmote()
|
||||
CloseAllBags()
|
||||
|
||||
return "ST_CLOSED"
|
||||
end)
|
||||
:AddEvent("EV_SWITCH_BTN_CLICKED", function()
|
||||
return "ST_DEFAULT_OPEN"
|
||||
end)
|
||||
)
|
||||
:Init("ST_CLOSED", fsmContext)
|
||||
end
|
||||
266
Core/UI/MailingUI/Groups.lua
Normal file
266
Core/UI/MailingUI/Groups.lua
Normal file
@@ -0,0 +1,266 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Groups = TSM.UI.MailingUI:NewPackage("Groups")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local FSM = TSM.Include("Util.FSM")
|
||||
local Log = TSM.Include("Util.Log")
|
||||
local Settings = TSM.Include("Service.Settings")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local private = {
|
||||
settings = nil,
|
||||
filterText = "",
|
||||
fsm = nil
|
||||
}
|
||||
local SECONDS_PER_MINUTE = 60
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Groups.OnInitialize()
|
||||
private.settings = Settings.NewView()
|
||||
:AddKey("char", "mailingUIContext", "groupTree")
|
||||
:AddKey("global", "mailingOptions", "resendDelay")
|
||||
private.FSMCreate()
|
||||
TSM.UI.MailingUI.RegisterTopLevelPage(L["Groups"], private.GetGroupsFrame)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Groups UI
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetGroupsFrame()
|
||||
TSM.UI.AnalyticsRecordPathChange("mailing", "groups")
|
||||
return UIElements.New("Frame", "groups")
|
||||
:SetLayout("VERTICAL")
|
||||
:AddChild(UIElements.New("Frame", "container")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetPadding(8)
|
||||
:SetBackgroundColor("PRIMARY_BG")
|
||||
:AddChild(UIElements.New("Frame", "search")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:AddChild(UIElements.New("Input", "input")
|
||||
:SetIconTexture("iconPack.18x18/Search")
|
||||
:SetClearButtonEnabled(true)
|
||||
:AllowItemInsert(true)
|
||||
:SetHintText(L["Search Groups"])
|
||||
:SetValue(private.filterText)
|
||||
:SetScript("OnValueChanged", private.GroupSearchOnValueChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "expandAllBtn")
|
||||
:SetSize(24, 24)
|
||||
:SetMargin(8, 4, 0, 0)
|
||||
:SetBackground("iconPack.18x18/Expand All")
|
||||
:SetScript("OnClick", private.ExpandAllGroupsOnClick)
|
||||
:SetTooltip(L["Expand / Collapse All Groups"])
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "selectAllBtn")
|
||||
:SetSize(24, 24)
|
||||
:SetBackground("iconPack.18x18/Select All")
|
||||
:SetScript("OnClick", private.SelectAllGroupsOnClick)
|
||||
:SetTooltip(L["Select / Deselect All Groups"])
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Texture", "line")
|
||||
:SetHeight(2)
|
||||
:SetTexture("ACTIVE_BG")
|
||||
)
|
||||
:AddChild(UIElements.New("ApplicationGroupTree", "groupTree")
|
||||
:SetMargin(0, 0, 0, 1)
|
||||
:SetSettingsContext(private.settings, "groupTree")
|
||||
:SetQuery(TSM.Groups.CreateQuery(), "Mailing")
|
||||
:SetSearchString(private.filterText)
|
||||
:SetScript("OnGroupSelectionChanged", private.GroupTreeOnGroupSelectionChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "footer")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(26)
|
||||
:SetBackgroundColor("PRIMARY_BG")
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
:AddChild(UIElements.New("Text", "groupsText")
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetText(format(L["%d Groups Selected"], 0))
|
||||
)
|
||||
:AddChild(UIElements.New("Texture", "vline")
|
||||
:SetWidth(1)
|
||||
:SetMargin(8, 8, 2, 2)
|
||||
:SetTexture("ACTIVE_BG_ALT")
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "itemsText")
|
||||
:SetWidth("AUTO")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetText(L["Total Items"]..": ".."0")
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Texture", "line")
|
||||
:SetHeight(2)
|
||||
:SetTexture("ACTIVE_BG")
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "bottom")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(40)
|
||||
:SetPadding(8)
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT")
|
||||
:AddChild(UIElements.New("ActionButton", "mailGroupBtn")
|
||||
:SetHeight(24)
|
||||
:SetText(L["Mail Selected Groups"])
|
||||
:SetScript("OnClick", private.MailBtnOnClick)
|
||||
:SetModifierText(L["Mail Selected Groups (Dry Run)"], "CTRL")
|
||||
:SetModifierText(L["Mail Selected Groups (Auto Resend)"], "SHIFT")
|
||||
:SetModifierText(L["Mail Selected Groups (Dry Run + Auto Resend)"], "SHIFT", "CTRL")
|
||||
:SetTooltip(format(L["Hold SHIFT to automatically resend items after '%s', and CTRL to perform a dry-run where no items are actually mailed, but chat messages will still display the result."], SecondsToTime(private.settings.resendDelay * SECONDS_PER_MINUTE)))
|
||||
)
|
||||
)
|
||||
:SetScript("OnUpdate", private.FrameOnUpdate)
|
||||
:SetScript("OnHide", private.FrameOnHide)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.FrameOnUpdate(frame)
|
||||
frame:SetScript("OnUpdate", nil)
|
||||
private.GroupTreeOnGroupSelectionChanged(frame:GetElement("groupTree"))
|
||||
private.fsm:ProcessEvent("EV_FRAME_SHOW", frame)
|
||||
end
|
||||
|
||||
function private.FrameOnHide(frame)
|
||||
private.fsm:ProcessEvent("EV_FRAME_HIDE")
|
||||
end
|
||||
|
||||
function private.GroupSearchOnValueChanged(input)
|
||||
local text = strlower(input:GetValue())
|
||||
if text == private.filterText then
|
||||
return
|
||||
end
|
||||
private.filterText = text
|
||||
|
||||
input:GetElement("__parent.__parent.__parent.groupTree")
|
||||
:SetSearchString(private.filterText)
|
||||
:Draw()
|
||||
end
|
||||
|
||||
function private.ExpandAllGroupsOnClick(button)
|
||||
button:GetElement("__parent.__parent.__parent.groupTree")
|
||||
:ToggleExpandAll()
|
||||
end
|
||||
|
||||
function private.SelectAllGroupsOnClick(button)
|
||||
button:GetElement("__parent.__parent.__parent.groupTree")
|
||||
:ToggleSelectAll()
|
||||
end
|
||||
|
||||
function private.GroupTreeOnGroupSelectionChanged(groupTree)
|
||||
groupTree:GetElement("__parent.bottom.mailGroupBtn")
|
||||
:SetDisabled(groupTree:IsSelectionCleared())
|
||||
:Draw()
|
||||
|
||||
local numGroups, numItems = 0, 0
|
||||
for _, groupPath in groupTree:SelectedGroupsIterator() do
|
||||
numGroups = numGroups + 1
|
||||
if groupPath == TSM.CONST.ROOT_GROUP_PATH then
|
||||
-- TODO
|
||||
else
|
||||
for _ in TSM.Groups.ItemIterator(groupPath) do
|
||||
numItems = numItems + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
groupTree:GetElement("__parent.footer.groupsText")
|
||||
:SetText(format(L["%d Groups Selected"], numGroups))
|
||||
groupTree:GetElement("__parent.footer.itemsText")
|
||||
:SetText(L["Total Items"]..": "..numItems)
|
||||
|
||||
groupTree:GetElement("__parent.footer")
|
||||
:Draw()
|
||||
end
|
||||
|
||||
function private.MailBtnOnClick(button)
|
||||
private.fsm:ProcessEvent("EV_BUTTON_CLICKED", IsShiftKeyDown(), IsControlKeyDown())
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- FSM
|
||||
-- ============================================================================
|
||||
|
||||
function private.FSMCreate()
|
||||
local fsmContext = {
|
||||
frame = nil,
|
||||
sending = false
|
||||
}
|
||||
|
||||
local function UpdateButton(context)
|
||||
context.frame:GetElement("bottom.mailGroupBtn")
|
||||
:SetText(context.sending and L["Sending..."] or L["Mail Selected Groups"])
|
||||
:SetPressed(context.sending)
|
||||
:Draw()
|
||||
end
|
||||
|
||||
private.fsm = FSM.New("MAILING_GROUPS")
|
||||
:AddState(FSM.NewState("ST_HIDDEN")
|
||||
:SetOnEnter(function(context)
|
||||
TSM.Mailing.Send.KillThread()
|
||||
TSM.Mailing.Groups.KillThread()
|
||||
context.frame = nil
|
||||
end)
|
||||
:AddTransition("ST_SHOWN")
|
||||
:AddTransition("ST_HIDDEN")
|
||||
:AddEventTransition("EV_FRAME_SHOW", "ST_SHOWN")
|
||||
)
|
||||
:AddState(FSM.NewState("ST_SHOWN")
|
||||
:SetOnEnter(function(context, frame)
|
||||
if not context.frame then
|
||||
context.frame = frame
|
||||
end
|
||||
UpdateButton(context)
|
||||
end)
|
||||
:AddTransition("ST_HIDDEN")
|
||||
:AddTransition("ST_SENDING_START")
|
||||
:AddEventTransition("EV_BUTTON_CLICKED", "ST_SENDING_START")
|
||||
)
|
||||
:AddState(FSM.NewState("ST_SENDING_START")
|
||||
:SetOnEnter(function(context, sendRepeat, isDryRun)
|
||||
context.sending = true
|
||||
local groups = {}
|
||||
for _, groupPath in context.frame:GetElement("groupTree"):SelectedGroupsIterator() do
|
||||
tinsert(groups, groupPath)
|
||||
end
|
||||
if isDryRun then
|
||||
Log.PrintUser(L["Performing a dry-run of your Mailing operations for the selected groups."])
|
||||
end
|
||||
TSM.Mailing.Groups.StartSending(private.FSMGroupsCallback, groups, sendRepeat, isDryRun)
|
||||
UpdateButton(context)
|
||||
end)
|
||||
:SetOnExit(function(context)
|
||||
context.sending = false
|
||||
end)
|
||||
:AddTransition("ST_SHOWN")
|
||||
:AddTransition("ST_HIDDEN")
|
||||
:AddEventTransition("EV_SENDING_DONE", "ST_SHOWN")
|
||||
)
|
||||
:AddDefaultEventTransition("EV_FRAME_HIDE", "ST_HIDDEN")
|
||||
:Init("ST_HIDDEN", fsmContext)
|
||||
end
|
||||
|
||||
function private.FSMGroupsCallback()
|
||||
private.fsm:ProcessEvent("EV_SENDING_DONE")
|
||||
end
|
||||
1124
Core/UI/MailingUI/Inbox.lua
Normal file
1124
Core/UI/MailingUI/Inbox.lua
Normal file
File diff suppressed because it is too large
Load Diff
362
Core/UI/MailingUI/Other.lua
Normal file
362
Core/UI/MailingUI/Other.lua
Normal file
@@ -0,0 +1,362 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Groups = TSM.UI.MailingUI:NewPackage("Other")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local FSM = TSM.Include("Util.FSM")
|
||||
local Event = TSM.Include("Util.Event")
|
||||
local Money = TSM.Include("Util.Money")
|
||||
local String = TSM.Include("Util.String")
|
||||
local ItemInfo = TSM.Include("Service.ItemInfo")
|
||||
local BagTracking = TSM.Include("Service.BagTracking")
|
||||
local PlayerInfo = TSM.Include("Service.PlayerInfo")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local private = {
|
||||
frame = nil,
|
||||
fsm = nil,
|
||||
}
|
||||
|
||||
local PLAYER_NAME = UnitName("player")
|
||||
local PLAYER_NAME_REALM = string.gsub(PLAYER_NAME.."-"..GetRealmName(), "%s+", "")
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Groups.OnInitialize()
|
||||
private.FSMCreate()
|
||||
TSM.UI.MailingUI.RegisterTopLevelPage(OTHER, private.GetOtherFrame)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Other UI
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetOtherFrame()
|
||||
TSM.UI.AnalyticsRecordPathChange("mailing", "other")
|
||||
local frame = UIElements.New("Frame", "other")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetPadding(10)
|
||||
:SetBackgroundColor("PRIMARY_BG")
|
||||
:AddChild(UIElements.New("Text", "enchant")
|
||||
:SetHeight(24)
|
||||
:SetFont("BODY_BODY1_BOLD")
|
||||
:SetText(L["Mail Disenchantables"])
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "enchantDesc")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 4, 0)
|
||||
:SetFont("BODY_BODY2")
|
||||
:SetText(L["Quickly mail all excess disenchantable items to a character"])
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "enchantRecipient")
|
||||
:SetHeight(15)
|
||||
:SetMargin(0, 0, 12, 0)
|
||||
:SetFont("BODY_BODY2")
|
||||
:SetText(L["Recipient"])
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "enchantHeader")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, 8, 0)
|
||||
:AddChild(UIElements.New("Input", "recipient")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetAutoComplete(PlayerInfo.GetConnectedAlts())
|
||||
:SetClearButtonEnabled(true)
|
||||
:SetValue(TSM.db.factionrealm.internalData.mailDisenchantablesChar)
|
||||
:SetScript("OnValueChanged", private.EchantRecipientOnValueChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("ActionButton", "send")
|
||||
:SetSize(186, 24)
|
||||
:SetDisabled(true)
|
||||
:SetText(L["Send Disenchantables"])
|
||||
:SetScript("OnClick", private.EnchantSendBtnOnClick)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "gold")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, 24, 0)
|
||||
:SetFont("BODY_BODY1_BOLD")
|
||||
:SetText(L["Send Excess Gold to Banker"])
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "goldDesc")
|
||||
:SetHeight(18)
|
||||
:SetMargin(0, 0, 4, 0)
|
||||
:SetFont("BODY_BODY2")
|
||||
:SetText(L["Quickly mail all excess gold (limited to a certain amount) to a character"])
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "goldTextHeader")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(13)
|
||||
:SetMargin(0, 0, 12, 0)
|
||||
:AddChild(UIElements.New("Text", "recipient")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetFont("BODY_BODY2")
|
||||
:SetText(L["Recipient"])
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "limit")
|
||||
:SetWidth(128)
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetFont("BODY_BODY2")
|
||||
:SetText(L["Limit"])
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer")
|
||||
:SetWidth(106)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "goldHeader")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, 4, 0)
|
||||
:AddChild(UIElements.New("Input", "recipient")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetAutoComplete(PlayerInfo.GetConnectedAlts())
|
||||
:SetClearButtonEnabled(true)
|
||||
:SetValue(TSM.db.factionrealm.internalData.mailExcessGoldChar)
|
||||
:SetScript("OnValueChanged", private.GoldRecipientOnValueChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("Input", "limit")
|
||||
:SetSize(128, 24)
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetValidateFunc(private.LimitValidateFunc)
|
||||
:SetValue(Money.ToString(TSM.db.factionrealm.internalData.mailExcessGoldLimit, nil, "OPT_TRIM"))
|
||||
:SetScript("OnValueChanged", private.LimitOnValueChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("ActionButton", "send")
|
||||
:SetSize(106, 24)
|
||||
:SetDisabled(true)
|
||||
:SetText(L["Send Gold"])
|
||||
:SetScript("OnClick", private.GoldSendBtnOnClick)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer")
|
||||
)
|
||||
:SetScript("OnUpdate", private.FrameOnUpdate)
|
||||
:SetScript("OnHide", private.FrameOnHide)
|
||||
|
||||
private.frame = frame
|
||||
|
||||
return frame
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.FrameOnUpdate(frame)
|
||||
frame:SetScript("OnUpdate", nil)
|
||||
|
||||
private.UpdateEnchantButton()
|
||||
private.UpdateGoldButton()
|
||||
|
||||
private.fsm:ProcessEvent("EV_FRAME_SHOW", frame)
|
||||
end
|
||||
|
||||
function private.FrameOnHide(frame)
|
||||
private.fsm:ProcessEvent("EV_FRAME_HIDE")
|
||||
end
|
||||
|
||||
function private.EchantRecipientOnValueChanged(input)
|
||||
local value = input:GetValue()
|
||||
if value == TSM.db.factionrealm.internalData.mailDisenchantablesChar then
|
||||
return
|
||||
end
|
||||
TSM.db.factionrealm.internalData.mailDisenchantablesChar = value
|
||||
private.UpdateEnchantButton()
|
||||
end
|
||||
|
||||
function private.EnchantSendBtnOnClick(button)
|
||||
local items = {}
|
||||
local query = BagTracking.CreateQueryBags()
|
||||
:OrderBy("slotId", true)
|
||||
:Select("itemString", "quantity")
|
||||
:Equal("isBoP", false)
|
||||
:Equal("isBoA", false)
|
||||
for _, itemString, quantity in query:Iterator() do
|
||||
if ItemInfo.IsDisenchantable(itemString) and not ItemInfo.IsSoulbound(itemString) then
|
||||
local quality = ItemInfo.GetQuality(itemString)
|
||||
if quality <= TSM.db.global.mailingOptions.deMaxQuality then
|
||||
items[itemString] = (items[itemString] or 0) + quantity
|
||||
end
|
||||
end
|
||||
end
|
||||
query:Release()
|
||||
private.fsm:ProcessEvent("EV_BUTTON_CLICKED", TSM.db.factionrealm.internalData.mailDisenchantablesChar, 0, items)
|
||||
end
|
||||
|
||||
function private.GoldRecipientOnValueChanged(input)
|
||||
local value = input:GetValue()
|
||||
if value == TSM.db.factionrealm.internalData.mailExcessGoldChar then
|
||||
return
|
||||
end
|
||||
TSM.db.factionrealm.internalData.mailExcessGoldChar = value
|
||||
private.UpdateGoldButton()
|
||||
end
|
||||
|
||||
function private.GoldSendBtnOnClick(button)
|
||||
local money = private.GetSendMoney()
|
||||
private.fsm:ProcessEvent("EV_BUTTON_CLICKED", TSM.db.factionrealm.internalData.mailExcessGoldChar, money)
|
||||
end
|
||||
|
||||
function private.ConvertLimitValue(value)
|
||||
value = gsub(value, String.Escape(LARGE_NUMBER_SEPERATOR), "")
|
||||
value = tonumber(value) or Money.FromString(value)
|
||||
if not value then
|
||||
return nil
|
||||
end
|
||||
return value > 0 and value <= MAXIMUM_BID_PRICE and value or nil
|
||||
end
|
||||
|
||||
function private.LimitValidateFunc(_, value)
|
||||
return private.ConvertLimitValue(value) and true or false
|
||||
end
|
||||
|
||||
function private.LimitOnValueChanged(input)
|
||||
local value = private.ConvertLimitValue(input:GetValue())
|
||||
assert(value)
|
||||
if value == TSM.db.factionrealm.internalData.mailExcessGoldLimit then
|
||||
return
|
||||
end
|
||||
TSM.db.factionrealm.internalData.mailExcessGoldLimit = value
|
||||
private.UpdateGoldButton()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetSendMoney()
|
||||
local money = GetMoney() - 30 - TSM.db.factionrealm.internalData.mailExcessGoldLimit
|
||||
if money < 0 then
|
||||
money = 0
|
||||
end
|
||||
|
||||
return money
|
||||
end
|
||||
|
||||
function private.UpdateEnchantButton()
|
||||
local recipient = TSM.db.factionrealm.internalData.mailDisenchantablesChar
|
||||
local enchantButton = private.frame:GetElement("enchantHeader.send")
|
||||
|
||||
if recipient == "" or recipient == PLAYER_NAME or recipient == PLAYER_NAME_REALM then
|
||||
enchantButton:SetDisabled(true)
|
||||
:Draw()
|
||||
return
|
||||
else
|
||||
enchantButton:SetDisabled(false)
|
||||
:Draw()
|
||||
end
|
||||
end
|
||||
|
||||
function private.UpdateGoldButton()
|
||||
local recipient = TSM.db.factionrealm.internalData.mailExcessGoldChar
|
||||
local goldButton = private.frame:GetElement("goldHeader.send")
|
||||
|
||||
if recipient == "" or recipient == PLAYER_NAME or recipient == PLAYER_NAME_REALM then
|
||||
goldButton:SetDisabled(true)
|
||||
:Draw()
|
||||
return
|
||||
end
|
||||
|
||||
if private.GetSendMoney() > 0 then
|
||||
goldButton:SetDisabled(false)
|
||||
else
|
||||
goldButton:SetDisabled(true)
|
||||
end
|
||||
|
||||
goldButton:Draw()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- FSM
|
||||
-- ============================================================================
|
||||
|
||||
function private.FSMCreate()
|
||||
Event.Register("PLAYER_MONEY", function()
|
||||
private.fsm:ProcessEvent("EV_PLAYER_MONEY_UPDATE")
|
||||
end)
|
||||
|
||||
local fsmContext = {
|
||||
frame = nil,
|
||||
sending = false
|
||||
}
|
||||
|
||||
local function UpdateEnchant(context)
|
||||
context.frame:GetElement("enchantHeader.send"):SetText(context.sending and L["Sending..."] or L["Send Disenchantables"])
|
||||
:SetPressed(context.sending)
|
||||
:Draw()
|
||||
end
|
||||
|
||||
local function UpdateGold(context)
|
||||
private.UpdateGoldButton()
|
||||
|
||||
context.frame:GetElement("goldHeader.send"):SetText(context.sending and L["Sending..."] or L["Send Gold"])
|
||||
:SetPressed(context.sending)
|
||||
:Draw()
|
||||
end
|
||||
|
||||
private.fsm = FSM.New("MAILING_GROUPS")
|
||||
:AddState(FSM.NewState("ST_HIDDEN")
|
||||
:SetOnEnter(function(context)
|
||||
context.frame = nil
|
||||
end)
|
||||
:AddTransition("ST_SHOWN")
|
||||
:AddTransition("ST_HIDDEN")
|
||||
:AddEventTransition("EV_FRAME_SHOW", "ST_SHOWN")
|
||||
)
|
||||
:AddState(FSM.NewState("ST_SHOWN")
|
||||
:SetOnEnter(function(context, frame)
|
||||
if not context.frame then
|
||||
context.frame = frame
|
||||
end
|
||||
end)
|
||||
:AddTransition("ST_HIDDEN")
|
||||
:AddTransition("ST_SENDING_START")
|
||||
:AddEvent("EV_PLAYER_MONEY_UPDATE", function(context)
|
||||
UpdateGold(context)
|
||||
end)
|
||||
:AddEventTransition("EV_BUTTON_CLICKED", "ST_SENDING_START")
|
||||
)
|
||||
:AddState(FSM.NewState("ST_SENDING_START")
|
||||
:SetOnEnter(function(context, recipient, money, items)
|
||||
context.sending = true
|
||||
if money > 0 then
|
||||
TSM.Mailing.Send.StartSending(private.FSMOthersCallback, recipient, "TSM Mailing: Excess Gold", "", money)
|
||||
UpdateGold(context)
|
||||
elseif items then
|
||||
TSM.Mailing.Send.StartSending(private.FSMOthersCallback, recipient, "TSM Mailing: Disenchantables", "", money, items)
|
||||
UpdateEnchant(context)
|
||||
end
|
||||
end)
|
||||
:SetOnExit(function(context)
|
||||
context.sending = false
|
||||
UpdateEnchant(context)
|
||||
UpdateGold(context)
|
||||
end)
|
||||
:AddTransition("ST_SHOWN")
|
||||
:AddTransition("ST_HIDDEN")
|
||||
:AddEventTransition("EV_SENDING_DONE", "ST_SHOWN")
|
||||
)
|
||||
:AddDefaultEventTransition("EV_FRAME_HIDE", "ST_HIDDEN")
|
||||
:Init("ST_HIDDEN", fsmContext)
|
||||
end
|
||||
|
||||
function private.FSMOthersCallback()
|
||||
private.fsm:ProcessEvent("EV_SENDING_DONE")
|
||||
end
|
||||
839
Core/UI/MailingUI/Send.lua
Normal file
839
Core/UI/MailingUI/Send.lua
Normal file
@@ -0,0 +1,839 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Send = TSM.UI.MailingUI:NewPackage("Send")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local Delay = TSM.Include("Util.Delay")
|
||||
local Money = TSM.Include("Util.Money")
|
||||
local FSM = TSM.Include("Util.FSM")
|
||||
local Database = TSM.Include("Util.Database")
|
||||
local String = TSM.Include("Util.String")
|
||||
local Event = TSM.Include("Util.Event")
|
||||
local ItemString = TSM.Include("Util.ItemString")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local ItemInfo = TSM.Include("Service.ItemInfo")
|
||||
local InventoryInfo = TSM.Include("Service.InventoryInfo")
|
||||
local BagTracking = TSM.Include("Service.BagTracking")
|
||||
local PlayerInfo = TSM.Include("Service.PlayerInfo")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local private = {
|
||||
fsm = nil,
|
||||
frame = nil,
|
||||
db = nil,
|
||||
query = nil,
|
||||
recipient = "",
|
||||
subject = "",
|
||||
body = "",
|
||||
money = 0,
|
||||
isMoney = true,
|
||||
isCOD = false,
|
||||
}
|
||||
local PLAYER_NAME = UnitName("player")
|
||||
local PLAYER_NAME_REALM = gsub(PLAYER_NAME.."-"..GetRealmName(), "%s+", "")
|
||||
local MAX_COD_AMOUNT = 10000 * COPPER_PER_GOLD
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Send.OnInitialize()
|
||||
private.FSMCreate()
|
||||
TSM.UI.MailingUI.RegisterTopLevelPage(L["Send"], private.GetSendFrame)
|
||||
|
||||
private.db = Database.NewSchema("MAILTRACKING_SEND_INFO")
|
||||
:AddStringField("itemString")
|
||||
:AddNumberField("quantity")
|
||||
:Commit()
|
||||
private.query = private.db:NewQuery()
|
||||
end
|
||||
|
||||
function Send.SetSendRecipient(recipient)
|
||||
private.recipient = recipient
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Send UI
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetSendFrame()
|
||||
TSM.UI.AnalyticsRecordPathChange("mailing", "send")
|
||||
local frame = UIElements.New("Frame", "send")
|
||||
:SetLayout("VERTICAL")
|
||||
:AddChild(UIElements.New("Frame", "container")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT")
|
||||
:AddChild(UIElements.New("Text", "recipient")
|
||||
:SetMargin(8, 8, 8, 8)
|
||||
:SetHeight(24)
|
||||
:SetFont("BODY_BODY1_BOLD")
|
||||
:SetText(L["Recipient"])
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "name")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:SetMargin(8, 8, 0, 8)
|
||||
:AddChild(UIElements.New("Input", "input")
|
||||
:SetHintText(L["Enter recipient name"])
|
||||
:SetAutoComplete(PlayerInfo.GetConnectedAlts())
|
||||
:SetClearButtonEnabled(true)
|
||||
:SetValue(private.recipient)
|
||||
:SetScript("OnValueChanged", private.RecipientOnValueChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("ActionButton", "contacts")
|
||||
:SetWidth(152)
|
||||
:SetMargin(8, 0, 0, 0)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Contacts"])
|
||||
:SetScript("OnClick", private.ContactsBtnOnClick)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "subject")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:SetMargin(8, 8, 0, 16)
|
||||
:AddChild(UIElements.New("Button", "icon")
|
||||
:SetMargin(4, 4, 0, 0)
|
||||
:SetBackgroundAndSize("iconPack.12x12/Add/Circle")
|
||||
:SetScript("OnClick", private.SubjectBtnOnClick)
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "text")
|
||||
:SetWidth("AUTO")
|
||||
:SetText(L["Add subject & description (optional)"])
|
||||
:SetFont("BODY_BODY2")
|
||||
:SetScript("OnClick", private.SubjectBtnOnClick)
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "button")
|
||||
:SetWidth("AUTO")
|
||||
:SetMargin(8, 0, 0, 0)
|
||||
:SetFont("BODY_BODY2")
|
||||
:SetTextColor("INDICATOR")
|
||||
:SetText(L["Edit"])
|
||||
:SetScript("OnClick", private.SubjectBtnOnClick)
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "header")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:SetMargin(8, 8, 0, 4)
|
||||
:AddChild(UIElements.New("Text", "items")
|
||||
:SetFont("BODY_BODY1_BOLD")
|
||||
:SetText(L["Select Items to Attach"])
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "dragBox")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetBackgroundColor("PRIMARY_BG")
|
||||
:RegisterForDrag("LeftButton")
|
||||
:SetScript("OnReceiveDrag", private.DragBoxOnItemRecieve)
|
||||
:SetScript("OnMouseUp", private.DragBoxOnItemRecieve)
|
||||
:AddChild(UIElements.New("QueryScrollingTable", "items")
|
||||
:GetScrollingTableInfo()
|
||||
:NewColumn("item")
|
||||
:SetTitle(L["Items"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetIconSize(12)
|
||||
:SetTextInfo("itemString", TSM.UI.GetColoredItemName)
|
||||
:SetIconInfo("itemString", ItemInfo.GetTexture)
|
||||
:SetTooltipInfo("itemString")
|
||||
:SetTooltipLinkingDisabled(true)
|
||||
:DisableHiding()
|
||||
:Commit()
|
||||
:NewColumn("quantity")
|
||||
:SetTitle(L["Amount"])
|
||||
:SetWidth(60)
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo("quantity")
|
||||
:DisableHiding()
|
||||
:Commit()
|
||||
:Commit()
|
||||
:SetQuery(private.query)
|
||||
:SetScript("OnRowClick", private.QueryOnRowClick)
|
||||
:SetScript("OnDataUpdated", private.SendOnDataUpdated)
|
||||
)
|
||||
:AddChild(UIElements.New("Texture", "line")
|
||||
:SetHeight(2)
|
||||
:SetTexture("ACTIVE_BG")
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "footer")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(26)
|
||||
:SetPadding(8, 8, 3, 3)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
:AddChild(UIElements.New("Text", "items")
|
||||
:SetWidth(144)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetJustifyH("RIGHT")
|
||||
:Hide()
|
||||
)
|
||||
:AddChild(UIElements.New("Texture", "vline")
|
||||
:SetWidth(1)
|
||||
:SetMargin(8, 8, 3, 3)
|
||||
:SetTexture("ACTIVE_BG_ALT")
|
||||
:Hide()
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "postage")
|
||||
:SetWidth(150)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetText(L["Total Postage"]..": "..Money.ToString(30))
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Texture", "line")
|
||||
:SetHeight(2)
|
||||
:SetTexture("ACTIVE_BG")
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "check")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:SetMargin(8, 0, 8, 6)
|
||||
:AddChild(UIElements.New("Checkbox", "sendCheck")
|
||||
:SetWidth("AUTO")
|
||||
:SetMargin(0, 0, 1, 0)
|
||||
:SetFont("BODY_BODY2")
|
||||
:SetCheckboxPosition("LEFT")
|
||||
:SetChecked(private.isMoney)
|
||||
:SetText(L["Send Money"])
|
||||
:SetScript("OnValueChanged", private.SendOnValueChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "checkbox")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:SetMargin(8, 8, 0, 8)
|
||||
:AddChild(UIElements.New("Checkbox", "cod")
|
||||
:SetSize("AUTO", 20)
|
||||
:SetFont("BODY_BODY2")
|
||||
:SetCheckboxPosition("LEFT")
|
||||
:SetChecked(private.isCOD)
|
||||
:SetText(L["Make Cash On Delivery?"])
|
||||
:SetDisabled(true)
|
||||
:SetScript("OnValueChanged", private.CODOnValueChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "amountText")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetText(L["Amount"]..":")
|
||||
)
|
||||
:AddChild(UIElements.New("Input", "moneyInput")
|
||||
:SetWidth(160)
|
||||
:SetBackgroundColor("ACTIVE_BG")
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetValidateFunc(private.MoneyValidateFunc)
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetValue(Money.ToString(private.money))
|
||||
:SetScript("OnValueChanged", private.MoneyOnValueChanged)
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Texture", "line")
|
||||
:SetHeight(2)
|
||||
:SetTexture("ACTIVE_BG")
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "footer")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(40)
|
||||
:SetPadding(8)
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT")
|
||||
:AddChild(UIElements.New("ActionButton", "sendMail")
|
||||
:SetHeight(24)
|
||||
:SetText(L["Send Mail"])
|
||||
:SetScript("OnClick", private.SendMail)
|
||||
:SetDisabled(private.recipient == "")
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "clear")
|
||||
:SetWidth("AUTO")
|
||||
:SetHeight(24)
|
||||
:SetMargin(16, 10, 0, 0)
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetText(L["Clear All"])
|
||||
:SetScript("OnClick", private.ClearOnClick)
|
||||
)
|
||||
)
|
||||
:SetScript("OnUpdate", private.SendFrameOnUpdate)
|
||||
:SetScript("OnHide", private.SendFrameOnHide)
|
||||
|
||||
private.frame = frame
|
||||
|
||||
return frame
|
||||
end
|
||||
|
||||
function private.SubjectBtnOnClick(button)
|
||||
button:GetBaseElement():ShowDialogFrame(UIElements.New("Frame", "frame")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetSize(478, 314)
|
||||
:SetPadding(12)
|
||||
:AddAnchor("CENTER")
|
||||
:SetBackgroundColor("FRAME_BG", true)
|
||||
:AddChild(UIElements.New("Frame", "header")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, -4, 14)
|
||||
:AddChild(UIElements.New("Spacer", "spacer")
|
||||
:SetWidth(20)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "title")
|
||||
:SetFont("BODY_BODY1_BOLD")
|
||||
:SetJustifyH("CENTER")
|
||||
:SetText(L["Add Subject / Description"])
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "closeBtn")
|
||||
:SetMargin(0, -4, 0, 0)
|
||||
:SetBackgroundAndSize("iconPack.24x24/Close/Default")
|
||||
:SetScript("OnClick", private.CloseDialog)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "subjectText")
|
||||
:SetMargin(0, 0, 0, 4)
|
||||
:SetHeight(20)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Subject"])
|
||||
)
|
||||
:AddChild(UIElements.New("Input", "subjectInput")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, 0, 8)
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT")
|
||||
:SetMaxLetters(64)
|
||||
:SetClearButtonEnabled(true)
|
||||
:SetValue(private.subject)
|
||||
:SetTabPaths("__parent.descriptionInput", "__parent.descriptionInput")
|
||||
:SetScript("OnValueChanged", private.SubjectOnValueChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "descriptionText")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 4)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(DESCRIPTION)
|
||||
)
|
||||
:AddChild(UIElements.New("MultiLineInput", "descriptionInput")
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT")
|
||||
:SetIgnoreEnter()
|
||||
:SetMaxLetters(500)
|
||||
:SetValue(private.body)
|
||||
:SetTabPaths("__parent.subjectInput", "__parent.subjectInput")
|
||||
:SetScript("OnValueChanged", private.DesciptionOnValueChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "footer")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetMargin(0, 0, 4, 12)
|
||||
:AddChild(UIElements.New("Text", "title")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 4, 0, 0)
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetText(format(L["(%d/500 Characters)"], #private.body))
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "clearAll")
|
||||
:SetSize("AUTO", 20)
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetText(L["Clear All"])
|
||||
:SetDisabled(private.subject == "" and private.body == "")
|
||||
:SetScript("OnClick", private.SubjectClearAllBtnOnClick)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("ActionButton", "addMailBtn")
|
||||
:SetHeight(24)
|
||||
:SetText(L["Add to Mail"])
|
||||
:SetScript("OnClick", private.CloseDialog)
|
||||
:SetDisabled(private.subject == "" and private.body == "")
|
||||
)
|
||||
:SetScript("OnHide", private.DialogOnHide)
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.SendFrameOnUpdate(frame)
|
||||
frame:SetScript("OnUpdate", nil)
|
||||
private.fsm:ProcessEvent("EV_FRAME_SHOW", frame)
|
||||
end
|
||||
|
||||
function private.SendFrameOnHide(frame)
|
||||
assert(frame == private.frame)
|
||||
private.frame = nil
|
||||
|
||||
private.fsm:ProcessEvent("EV_FRAME_HIDE")
|
||||
end
|
||||
|
||||
function private.ClearOnClick(button)
|
||||
private.fsm:ProcessEvent("EV_MAIL_CLEAR", true)
|
||||
end
|
||||
|
||||
function private.CloseDialog(button)
|
||||
button:GetBaseElement():HideDialog()
|
||||
|
||||
private.fsm:ProcessEvent("EV_DIALOG_HIDDEN")
|
||||
end
|
||||
|
||||
function private.DialogOnHide(button)
|
||||
private.fsm:ProcessEvent("EV_DIALOG_HIDDEN")
|
||||
end
|
||||
|
||||
function private.ContactsBtnOnClick(button)
|
||||
TSM.UI.Util.Contacts.ShowDialog(button, button:GetElement("__parent.input"), private.RecipientOnValueChanged)
|
||||
end
|
||||
|
||||
function private.DragBoxOnItemRecieve(frame, button)
|
||||
if not CursorHasItem() then
|
||||
ClearCursor()
|
||||
return
|
||||
end
|
||||
|
||||
if private.query:Count() >= 12 then
|
||||
ClearCursor()
|
||||
UIErrorsFrame:AddMessage(ERR_MAIL_INVALID_ATTACHMENT_SLOT, 1.0, 0.1, 0.1, 1.0)
|
||||
return
|
||||
end
|
||||
|
||||
local _, _, subType = GetCursorInfo()
|
||||
local itemString = ItemString.Get(subType)
|
||||
local stackSize = nil
|
||||
local query = BagTracking.CreateQueryBags()
|
||||
:OrderBy("slotId", true)
|
||||
:Select("bag", "slot", "quantity")
|
||||
:Equal("isBoP", false)
|
||||
:Equal("itemString", itemString)
|
||||
for _, bag, slot, quantity in query:Iterator() do
|
||||
if InventoryInfo.IsBagSlotLocked(bag, slot) then
|
||||
stackSize = quantity
|
||||
end
|
||||
end
|
||||
query:Release()
|
||||
ClearCursor()
|
||||
if not stackSize then
|
||||
return
|
||||
end
|
||||
|
||||
private.DatabaseNewRow(itemString, stackSize)
|
||||
end
|
||||
|
||||
function private.QueryOnRowClick(scrollingTable, row, button)
|
||||
if button == "RightButton" then
|
||||
private.db:DeleteRow(row)
|
||||
end
|
||||
end
|
||||
|
||||
function private.SendOnDataUpdated()
|
||||
private.fsm:ProcessEvent("EV_MAIL_DATA_UPDATED")
|
||||
end
|
||||
|
||||
function private.SubjectClearAllBtnOnClick(button)
|
||||
private.subject = ""
|
||||
private.body = ""
|
||||
|
||||
button:GetElement("__parent.__parent.subjectInput")
|
||||
:SetFocused(false)
|
||||
:SetValue(private.subject)
|
||||
:Draw()
|
||||
button:GetElement("__parent.__parent.descriptionInput")
|
||||
:SetFocused(false)
|
||||
:SetValue(private.body)
|
||||
:Draw()
|
||||
button:GetElement("__parent.title")
|
||||
:SetText(format(L["(%d/500 Characters)"], 0))
|
||||
:Draw()
|
||||
button:SetDisabled(true)
|
||||
:Draw()
|
||||
button:GetElement("__parent.__parent.addMailBtn")
|
||||
:SetDisabled(true)
|
||||
:Draw()
|
||||
end
|
||||
|
||||
function private.RecipientOnValueChanged(input)
|
||||
local value = input:GetValue()
|
||||
if value == private.recipient then
|
||||
return
|
||||
end
|
||||
private.recipient = value
|
||||
private.UpdateSendFrame()
|
||||
end
|
||||
|
||||
function private.SubjectOnValueChanged(input)
|
||||
local value = input:GetValue()
|
||||
if value == private.subject then
|
||||
return
|
||||
end
|
||||
private.subject = value
|
||||
input:GetElement("__parent.footer.clearAll")
|
||||
:SetDisabled(private.subject == "" and private.body == "")
|
||||
:Draw()
|
||||
input:GetElement("__parent.addMailBtn")
|
||||
:SetDisabled(private.subject == "" and private.body == "")
|
||||
:Draw()
|
||||
end
|
||||
|
||||
function private.DesciptionOnValueChanged(input)
|
||||
local text = input:GetValue()
|
||||
if text == private.body then
|
||||
return
|
||||
end
|
||||
private.body = text
|
||||
input:GetElement("__parent.footer.title")
|
||||
:SetText(format(L["(%d/500 Characters)"], #private.body))
|
||||
:Draw()
|
||||
input:GetElement("__parent.footer.clearAll")
|
||||
:SetDisabled(private.subject == "" and private.body == "")
|
||||
:Draw()
|
||||
input:GetElement("__parent.addMailBtn")
|
||||
:SetDisabled(private.subject == "" and private.body == "")
|
||||
:Draw()
|
||||
end
|
||||
|
||||
function private.SendOnValueChanged(checkbox)
|
||||
if checkbox:IsChecked() then
|
||||
checkbox:GetElement("__parent.__parent.checkbox.cod"):SetChecked(false)
|
||||
:Draw()
|
||||
|
||||
private.isMoney = true
|
||||
private.isCOD = false
|
||||
else
|
||||
private.isMoney = false
|
||||
end
|
||||
end
|
||||
|
||||
function private.CODOnValueChanged(checkbox)
|
||||
if checkbox:IsChecked() then
|
||||
checkbox:GetElement("__parent.__parent.check.sendCheck"):SetChecked(false)
|
||||
:Draw()
|
||||
|
||||
local input = checkbox:GetElement("__parent.moneyInput")
|
||||
local value = private.ConvertMoneyValue(input:GetValue())
|
||||
private.money = private.isCOD and min(value, MAX_COD_AMOUNT) or value
|
||||
input:SetValue(Money.ToString(private.money))
|
||||
:Draw()
|
||||
|
||||
private.isMoney = false
|
||||
private.isCOD = true
|
||||
else
|
||||
private.isCOD = false
|
||||
end
|
||||
end
|
||||
|
||||
function private.ConvertMoneyValue(value)
|
||||
value = gsub(value, String.Escape(LARGE_NUMBER_SEPERATOR), "")
|
||||
value = tonumber(value) or Money.FromString(value)
|
||||
if not value then
|
||||
return nil
|
||||
end
|
||||
local maxVal = private.isCOD and MAX_COD_AMOUNT or MAXIMUM_BID_PRICE
|
||||
return value >= 0 and value <= maxVal and value or nil
|
||||
end
|
||||
|
||||
function private.MoneyValidateFunc(_, value)
|
||||
return private.ConvertMoneyValue(value) and true or false
|
||||
end
|
||||
|
||||
function private.MoneyOnValueChanged(input)
|
||||
local value = private.ConvertMoneyValue(input:GetValue())
|
||||
assert(value)
|
||||
if value == private.money then
|
||||
return
|
||||
end
|
||||
private.money = value
|
||||
input:SetValue(Money.ToString(value))
|
||||
:Draw()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.SendMail(button)
|
||||
local money = 0
|
||||
if private.money > 0 and private.isMoney then
|
||||
money = private.money
|
||||
elseif private.money > 0 and private.isCOD then
|
||||
money = private.money * -1
|
||||
end
|
||||
|
||||
button:GetElement("__parent.__parent.container.name.input"):SetFocused(false)
|
||||
private.UpdateRecentlyMailed(private.recipient)
|
||||
|
||||
if private.query:Count() > 0 then
|
||||
local items = {}
|
||||
for _, row in private.query:Iterator() do
|
||||
local itemString = row:GetField("itemString")
|
||||
local quantity = row:GetField("quantity")
|
||||
if items[itemString] then
|
||||
items[itemString] = items[itemString] + quantity
|
||||
else
|
||||
items[itemString] = quantity
|
||||
end
|
||||
end
|
||||
|
||||
private.fsm:ProcessEvent("EV_BUTTON_CLICKED", IsShiftKeyDown(), private.recipient, private.subject, private.body, money, items)
|
||||
else
|
||||
private.fsm:ProcessEvent("EV_BUTTON_CLICKED", IsShiftKeyDown(), private.recipient, private.subject, private.body, money)
|
||||
end
|
||||
end
|
||||
|
||||
function private.UpdateRecentlyMailed(recipient)
|
||||
if recipient == UnitName("player") or recipient == PLAYER_NAME_REALM then
|
||||
return
|
||||
end
|
||||
|
||||
local size = 0
|
||||
local oldestName = nil
|
||||
local oldestTime = nil
|
||||
for k, v in pairs(TSM.db.global.mailingOptions.recentlyMailedList) do
|
||||
size = size + 1
|
||||
if not oldestName or not oldestTime or oldestTime > v then
|
||||
oldestName = k
|
||||
oldestTime = v
|
||||
end
|
||||
end
|
||||
|
||||
if size >= 20 then
|
||||
TSM.db.global.mailingOptions.recentlyMailedList[oldestName] = nil
|
||||
end
|
||||
|
||||
TSM.db.global.mailingOptions.recentlyMailedList[recipient] = time()
|
||||
end
|
||||
|
||||
function private.UpdateSendFrame()
|
||||
if not private.frame then
|
||||
return
|
||||
end
|
||||
|
||||
local sendMail = private.frame:GetElement("footer.sendMail")
|
||||
if private.recipient ~= "" then
|
||||
sendMail:SetDisabled(false)
|
||||
else
|
||||
sendMail:SetDisabled(true)
|
||||
end
|
||||
sendMail:Draw()
|
||||
end
|
||||
|
||||
function private.DatabaseNewRow(itemString, stackSize)
|
||||
private.db:NewRow()
|
||||
:SetField("itemString", itemString)
|
||||
:SetField("quantity", stackSize)
|
||||
:Create()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- FSM
|
||||
-- ============================================================================
|
||||
|
||||
function private.FSMCreate()
|
||||
Event.Register("MAIL_CLOSED", function()
|
||||
private.fsm:ProcessEvent("EV_MAIL_CLEAR")
|
||||
end)
|
||||
Event.Register("MAIL_SEND_INFO_UPDATE", function()
|
||||
private.fsm:ProcessEvent("EV_MAIL_INFO_UPDATE")
|
||||
end)
|
||||
Event.Register("MAIL_FAILED", function()
|
||||
private.fsm:ProcessEvent("EV_SENDING_DONE")
|
||||
end)
|
||||
|
||||
local fsmContext = {
|
||||
frame = nil,
|
||||
sending = false,
|
||||
keepInfo = false
|
||||
}
|
||||
|
||||
local function UpdateFrame(context)
|
||||
if not context.frame then
|
||||
return
|
||||
end
|
||||
|
||||
local subject = context.frame:GetElement("container.subject")
|
||||
if private.subject == "" and private.body == "" then
|
||||
subject:GetElement("icon")
|
||||
:SetBackgroundAndSize("iconPack.12x12/Add/Circle")
|
||||
:Draw()
|
||||
subject:GetElement("text")
|
||||
:SetText(L["Add subject & description (optional)"])
|
||||
:Draw()
|
||||
subject:GetElement("button")
|
||||
:Hide()
|
||||
else
|
||||
subject:GetElement("icon")
|
||||
:SetBackgroundAndSize("iconPack.12x12/Checkmark/Default")
|
||||
:Draw()
|
||||
subject:GetElement("text")
|
||||
:SetText(L["Subject & Description added"])
|
||||
:Draw()
|
||||
subject:GetElement("button")
|
||||
:Show()
|
||||
end
|
||||
subject:Draw()
|
||||
|
||||
local items = context.frame:GetElement("container.dragBox.footer.items")
|
||||
local line = context.frame:GetElement("container.dragBox.footer.vline")
|
||||
local postage = context.frame:GetElement("container.dragBox.footer.postage")
|
||||
local send = context.frame:GetElement("container.check.sendCheck")
|
||||
local cod = context.frame:GetElement("container.checkbox.cod")
|
||||
|
||||
local size = private.query:Count()
|
||||
if size > 0 then
|
||||
postage:SetText(L["Total Postage"]..": "..Money.ToString(30 * size))
|
||||
:Draw()
|
||||
items:SetText(format(L["%s Items Selected"], Theme.GetFeedbackColor("GREEN"):ColorText(size.."/"..ATTACHMENTS_MAX_SEND)))
|
||||
:Show()
|
||||
:Draw()
|
||||
line:Show()
|
||||
cod:SetDisabled(false)
|
||||
:Draw()
|
||||
else
|
||||
postage:SetText(L["Total Postage"]..": "..Money.ToString(30))
|
||||
:Draw()
|
||||
items:Hide()
|
||||
line:Hide()
|
||||
cod:SetDisabled(true)
|
||||
:Draw()
|
||||
send:SetChecked(true)
|
||||
:SetDisabled(false)
|
||||
:Draw()
|
||||
end
|
||||
end
|
||||
|
||||
local function UpdateButton(context)
|
||||
context.frame:GetElement("footer.sendMail")
|
||||
:SetText(context.sending and L["Sending..."] or L["Send Mail"])
|
||||
:SetPressed(context.sending)
|
||||
:Draw()
|
||||
end
|
||||
|
||||
local function UpdateSendMailInfo(context)
|
||||
if private.query:Count() >= 12 then
|
||||
UIErrorsFrame:AddMessage(ERR_MAIL_INVALID_ATTACHMENT_SLOT, 1.0, 0.1, 0.1, 1.0)
|
||||
else
|
||||
for i = 1, ATTACHMENTS_MAX_SEND do
|
||||
local itemName, _, _, stackCount = GetSendMailItem(i)
|
||||
if itemName and stackCount then
|
||||
local itemLink = GetSendMailItemLink(i)
|
||||
local itemString = ItemString.Get(itemLink)
|
||||
|
||||
private.DatabaseNewRow(itemString, stackCount)
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ClearSendMail()
|
||||
end
|
||||
|
||||
local function ClearMail(context, keepInfo, redraw)
|
||||
if not keepInfo then
|
||||
private.recipient = ""
|
||||
end
|
||||
private.subject = ""
|
||||
private.body = ""
|
||||
private.money = 0
|
||||
private.isMoney = true
|
||||
private.isCOD = false
|
||||
|
||||
private.db:Truncate()
|
||||
|
||||
if redraw and context.frame then
|
||||
context.frame:GetElement("container.name.input")
|
||||
:SetValue(private.recipient)
|
||||
:Draw()
|
||||
context.frame:GetElement("container.checkbox.moneyInput")
|
||||
:SetValue(Money.ToString(private.money))
|
||||
:Draw()
|
||||
if not keepInfo then
|
||||
context.frame:GetElement("footer.sendMail")
|
||||
:SetDisabled(true)
|
||||
:Draw()
|
||||
end
|
||||
end
|
||||
|
||||
UpdateFrame(context)
|
||||
end
|
||||
|
||||
local function SendMailShowing()
|
||||
SetSendMailShowing(true)
|
||||
end
|
||||
|
||||
private.fsm = FSM.New("MAILING_SEND")
|
||||
:AddState(FSM.NewState("ST_HIDDEN")
|
||||
:SetOnEnter(function(context)
|
||||
TSM.Mailing.Send.KillThread()
|
||||
SetSendMailShowing(false)
|
||||
context.frame = nil
|
||||
end)
|
||||
:AddTransition("ST_SHOWN")
|
||||
:AddTransition("ST_HIDDEN")
|
||||
:AddEventTransition("EV_FRAME_SHOW", "ST_SHOWN")
|
||||
)
|
||||
:AddState(FSM.NewState("ST_SHOWN")
|
||||
:SetOnEnter(function(context, frame)
|
||||
if not context.frame then
|
||||
context.frame = frame
|
||||
UpdateFrame(context)
|
||||
Delay.AfterFrame("setMailShowing", 2, SendMailShowing)
|
||||
end
|
||||
UpdateButton(context)
|
||||
end)
|
||||
:AddTransition("ST_HIDDEN")
|
||||
:AddTransition("ST_SENDING_START")
|
||||
:AddEvent("EV_DIALOG_HIDDEN", function(context)
|
||||
UpdateFrame(context)
|
||||
end)
|
||||
:AddEvent("EV_MAIL_INFO_UPDATE", function(context)
|
||||
UpdateSendMailInfo(context)
|
||||
UpdateFrame(context)
|
||||
end)
|
||||
:AddEvent("EV_MAIL_DATA_UPDATED", function(context)
|
||||
UpdateFrame(context)
|
||||
end)
|
||||
:AddEvent("EV_MAIL_CLEAR", function(context, redraw)
|
||||
ClearMail(context, IsShiftKeyDown(), redraw)
|
||||
end)
|
||||
:AddEventTransition("EV_BUTTON_CLICKED", "ST_SENDING_START")
|
||||
)
|
||||
:AddState(FSM.NewState("ST_SENDING_START")
|
||||
:SetOnEnter(function(context, keepInfo, recipient, subject, body, money, items)
|
||||
context.sending = true
|
||||
context.keepInfo = keepInfo
|
||||
private.db:SetQueryUpdatesPaused(true)
|
||||
TSM.Mailing.Send.StartSending(private.FSMSendCallback, recipient, subject, body, money, items)
|
||||
UpdateButton(context)
|
||||
end)
|
||||
:SetOnExit(function(context)
|
||||
context.sending = false
|
||||
private.db:SetQueryUpdatesPaused(false)
|
||||
ClearMail(context, context.keepInfo, true)
|
||||
UpdateFrame(context)
|
||||
end)
|
||||
:AddTransition("ST_SHOWN")
|
||||
:AddTransition("ST_HIDDEN")
|
||||
:AddEventTransition("EV_SENDING_DONE", "ST_SHOWN")
|
||||
)
|
||||
:AddDefaultEvent("EV_FRAME_HIDE", function(context)
|
||||
context.frame = nil
|
||||
return "ST_HIDDEN"
|
||||
end)
|
||||
:Init("ST_HIDDEN", fsmContext)
|
||||
end
|
||||
|
||||
function private.FSMSendCallback()
|
||||
private.fsm:ProcessEvent("EV_SENDING_DONE")
|
||||
end
|
||||
91
Core/UI/MainUI/Core.lua
Normal file
91
Core/UI/MainUI/Core.lua
Normal file
@@ -0,0 +1,91 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local MainUI = TSM:NewPackage("MainUI")
|
||||
local Settings = TSM.Include("Service.Settings")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local private = {
|
||||
settings = nil,
|
||||
topLevelPages = {},
|
||||
frame = nil,
|
||||
}
|
||||
local MIN_FRAME_SIZE = { width = 720, height = 588 }
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function MainUI.OnInitialize()
|
||||
private.settings = Settings.NewView()
|
||||
:AddKey("global", "mainUIContext", "frame")
|
||||
end
|
||||
|
||||
function MainUI.OnDisable()
|
||||
-- hide the frame
|
||||
if private.frame then
|
||||
MainUI.Toggle()
|
||||
end
|
||||
end
|
||||
|
||||
function MainUI.RegisterTopLevelPage(name, callback)
|
||||
tinsert(private.topLevelPages, { name = name, callback = callback })
|
||||
end
|
||||
|
||||
function MainUI.Toggle()
|
||||
if private.frame then
|
||||
-- it's already shown, so hide it
|
||||
private.frame:Hide()
|
||||
assert(not private.frame)
|
||||
else
|
||||
private.frame = private.CreateMainFrame()
|
||||
private.frame:Draw()
|
||||
private.frame:Show()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Main Frame
|
||||
-- ============================================================================
|
||||
|
||||
function private.CreateMainFrame()
|
||||
TSM.UI.AnalyticsRecordPathChange("main")
|
||||
-- Always show the Dashboard first
|
||||
private.settings.frame.page = 1
|
||||
local frame = UIElements.New("LargeApplicationFrame", "base")
|
||||
:SetParent(UIParent)
|
||||
:SetSettingsContext(private.settings, "frame")
|
||||
:SetMinResize(MIN_FRAME_SIZE.width, MIN_FRAME_SIZE.height)
|
||||
:SetStrata("HIGH")
|
||||
:AddPlayerGold()
|
||||
:AddAppStatusIcon()
|
||||
:SetScript("OnHide", private.BaseFrameOnHide)
|
||||
for _, info in ipairs(private.topLevelPages) do
|
||||
frame:AddNavButton(info.name, info.callback)
|
||||
end
|
||||
local whatsNewDialog = TSM.UI.WhatsNew.GetDialog()
|
||||
if whatsNewDialog then
|
||||
frame:ShowDialogFrame(whatsNewDialog)
|
||||
end
|
||||
return frame
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.BaseFrameOnHide(frame)
|
||||
assert(frame == private.frame)
|
||||
frame:Release()
|
||||
private.frame = nil
|
||||
TSM.UI.AnalyticsRecordClose("main")
|
||||
end
|
||||
936
Core/UI/MainUI/Dashboard.lua
Normal file
936
Core/UI/MainUI/Dashboard.lua
Normal file
@@ -0,0 +1,936 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Dashboard = TSM.MainUI:NewPackage("Dashboard")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local Money = TSM.Include("Util.Money")
|
||||
local Math = TSM.Include("Util.Math")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local Analytics = TSM.Include("Util.Analytics")
|
||||
local Settings = TSM.Include("Service.Settings")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local private = {
|
||||
settings = nil,
|
||||
characterGuilds = {},
|
||||
tempTimeTable = {},
|
||||
selectedTimeRange = nil,
|
||||
}
|
||||
local SECONDS_PER_DAY = 60 * 60 * 24
|
||||
local MIN_GRAPH_STEP_SIZE = TSM.IsWowClassic() and COPPER_PER_GOLD or (COPPER_PER_GOLD * 1000)
|
||||
local TIME_RANGE_LOOKUP = {
|
||||
["1d"] = SECONDS_PER_DAY,
|
||||
["1w"] = SECONDS_PER_DAY * 7,
|
||||
["1m"] = SECONDS_PER_DAY * 30,
|
||||
["3m"] = SECONDS_PER_DAY * 91,
|
||||
["6m"] = SECONDS_PER_DAY * 183,
|
||||
["1y"] = SECONDS_PER_DAY * 365,
|
||||
["2y"] = SECONDS_PER_DAY * 730,
|
||||
["all"] = -1,
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Dashboard.OnInitialize()
|
||||
private.settings = Settings.NewView()
|
||||
:AddKey("global", "mainUIContext", "dashboardDividedContainer")
|
||||
:AddKey("global", "mainUIContext", "dashboardUnselectedCharacters")
|
||||
:AddKey("global", "mainUIContext", "dashboardTimeRange")
|
||||
private.selectedTimeRange = private.settings.dashboardTimeRange
|
||||
TSM.MainUI.RegisterTopLevelPage(L["Dashboard"], private.GetDashboardFrame)
|
||||
end
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Dashboard UI
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetDashboardFrame()
|
||||
TSM.UI.AnalyticsRecordPathChange("main", "dashboard")
|
||||
|
||||
private.selectedTimeRange = private.settings.dashboardTimeRange
|
||||
wipe(private.characterGuilds)
|
||||
local prevUnselectedCharacters = TempTable.Acquire()
|
||||
for characterGuild in pairs(private.settings.dashboardUnselectedCharacters) do
|
||||
prevUnselectedCharacters[characterGuild] = true
|
||||
end
|
||||
wipe(private.settings.dashboardUnselectedCharacters)
|
||||
for characterGuild in TSM.Accounting.GoldTracker.CharacterGuildIterator() do
|
||||
tinsert(private.characterGuilds, characterGuild)
|
||||
private.settings.dashboardUnselectedCharacters[characterGuild] = prevUnselectedCharacters[characterGuild] or nil
|
||||
end
|
||||
TempTable.Release(prevUnselectedCharacters)
|
||||
|
||||
local frame = UIElements.New("DividedContainer", "dashboard")
|
||||
:SetSettingsContext(private.settings, "dashboardDividedContainer")
|
||||
:SetMinWidth(200, 407)
|
||||
:SetBackgroundColor("PRIMARY_BG")
|
||||
:SetLeftChild(UIElements.New("Frame", "news")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetBackgroundColor("PRIMARY_BG")
|
||||
:AddChild(UIElements.New("Text", "text")
|
||||
:SetSize("AUTO", 24)
|
||||
:SetMargin(8)
|
||||
:SetFont("BODY_BODY1_BOLD")
|
||||
:SetText(L["News & Information"])
|
||||
)
|
||||
:AddChild(UIElements.New("ScrollFrame", "content")
|
||||
:SetPadding(8, 8, 0, 0)
|
||||
)
|
||||
)
|
||||
:SetRightChild(UIElements.New("Frame", "content")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetPadding(8)
|
||||
:SetBackgroundColor("PRIMARY_BG")
|
||||
:SetScript("OnUpdate", private.ContentOnUpdate)
|
||||
:AddChild(UIElements.New("Frame", "goldHeader")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:AddChild(UIElements.New("Text", "text")
|
||||
:SetWidth("AUTO")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetFont("BODY_BODY1_BOLD")
|
||||
:SetText(L["Player Gold"])
|
||||
)
|
||||
:AddChild(UIElements.New("MultiselectionDropdown", "playerDropdown")
|
||||
:SetSize(157, 22)
|
||||
:SetItems(private.characterGuilds, private.characterGuilds)
|
||||
:SetUnselectedItemKeys(private.settings.dashboardUnselectedCharacters)
|
||||
:SetSelectionText(L["No Players"], L["%d Players"], L["All Players"])
|
||||
:SetScript("OnSelectionChanged", private.DropdownOnSelectionChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer"))
|
||||
:AddChild(UIElements.New("Text", "hoverTime")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetText("")
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "timeBtns")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:AddChild(UIElements.New("Button", "1d")
|
||||
:SetMargin(8, 0, 0, 0)
|
||||
:SetSize(20, 20)
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetTextColor("ACTIVE_BG_ALT")
|
||||
:SetContext(TIME_RANGE_LOOKUP["1d"])
|
||||
:SetText(L["1D"])
|
||||
:SetScript("OnClick", private.TimeBtnOnClick)
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "1w")
|
||||
:SetMargin(8, 0, 0, 0)
|
||||
:SetSize(20, 20)
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetTextColor("ACTIVE_BG_ALT")
|
||||
:SetContext(TIME_RANGE_LOOKUP["1w"])
|
||||
:SetText(L["1W"])
|
||||
:SetScript("OnClick", private.TimeBtnOnClick)
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "1m")
|
||||
:SetMargin(8, 0, 0, 0)
|
||||
:SetSize(20, 20)
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetTextColor("ACTIVE_BG_ALT")
|
||||
:SetContext(TIME_RANGE_LOOKUP["1m"])
|
||||
:SetText(L["1M"])
|
||||
:SetScript("OnClick", private.TimeBtnOnClick)
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "3m")
|
||||
:SetMargin(8, 0, 0, 0)
|
||||
:SetSize(20, 20)
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetTextColor("ACTIVE_BG_ALT")
|
||||
:SetContext(TIME_RANGE_LOOKUP["3m"])
|
||||
:SetText(L["3M"])
|
||||
:SetScript("OnClick", private.TimeBtnOnClick)
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "6m")
|
||||
:SetMargin(8, 0, 0, 0)
|
||||
:SetSize(20, 20)
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetTextColor("ACTIVE_BG_ALT")
|
||||
:SetContext(TIME_RANGE_LOOKUP["6m"])
|
||||
:SetText(L["6M"])
|
||||
:SetScript("OnClick", private.TimeBtnOnClick)
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "1y")
|
||||
:SetMargin(8, 0, 0, 0)
|
||||
:SetSize(20, 20)
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetTextColor("ACTIVE_BG_ALT")
|
||||
:SetContext(TIME_RANGE_LOOKUP["1y"])
|
||||
:SetText(L["1Y"])
|
||||
:SetScript("OnClick", private.TimeBtnOnClick)
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "2y")
|
||||
:SetMargin(8, 0, 0, 0)
|
||||
:SetSize(20, 20)
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetTextColor("ACTIVE_BG_ALT")
|
||||
:SetContext(TIME_RANGE_LOOKUP["2y"])
|
||||
:SetText(L["2Y"])
|
||||
:SetScript("OnClick", private.TimeBtnOnClick)
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "all")
|
||||
:SetMargin(8, 0, 0, 0)
|
||||
:SetSize(20, 20)
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetTextColor("TEXT")
|
||||
:SetContext(TIME_RANGE_LOOKUP["all"])
|
||||
:SetText(ALL)
|
||||
:SetScript("OnClick", private.TimeBtnOnClick)
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "resetZoom")
|
||||
:SetMargin(8, 0, 0, 0)
|
||||
:SetSize(100, 20)
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetTextColor("TEXT")
|
||||
:SetContext(TIME_RANGE_LOOKUP["all"])
|
||||
:SetText(L["Reset Zoom"])
|
||||
:SetScript("OnClick", private.TimeBtnOnClick)
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Graph", "goldGraph")
|
||||
:SetMargin(0, 0, 8, 8)
|
||||
:SetAxisStepFunctions(private.GraphXStepFunc, private.GraphYStepFunc)
|
||||
:SetXRange(TSM.Accounting.GoldTracker.GetGraphTimeRange(private.settings.dashboardUnselectedCharacters))
|
||||
:SetYValueFunction(private.GetGraphYValue)
|
||||
:SetFormatFunctions(private.GraphFormatX, private.GraphFormatY)
|
||||
:SetScript("OnZoomChanged", private.GraphOnZoomChanged)
|
||||
:SetScript("OnHoverUpdate", private.GraphOnHoverUpdate)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "summary")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(48)
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT", true)
|
||||
:AddChild(UIElements.New("Frame", "range")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetPadding(8, 8, 2, 2)
|
||||
:AddChild(UIElements.New("Frame", "high")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 4)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetTextColor("ACTIVE_BG_ALT")
|
||||
:SetText(L["HIGH"])
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
:AddChild(UIElements.New("Text", "value")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "low")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetTextColor("ACTIVE_BG_ALT")
|
||||
:SetText(L["LOW"])
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
:AddChild(UIElements.New("Text", "value")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Texture", "line1")
|
||||
:SetWidth(1)
|
||||
:SetTexture("ACTIVE_BG")
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "daily")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetPadding(8, 8, 2, 2)
|
||||
:AddChild(UIElements.New("Frame", "sales")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 4)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetTextColor("ACTIVE_BG_ALT")
|
||||
:SetText(L["DAILY SALES"])
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
:AddChild(UIElements.New("Text", "value")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "purchases")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetTextColor("ACTIVE_BG_ALT")
|
||||
:SetText(L["DAILY PURCHASES"])
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
:AddChild(UIElements.New("Text", "value")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Texture", "line2")
|
||||
:SetWidth(1)
|
||||
:SetTexture("ACTIVE_BG")
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "top")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetPadding(8, 8, 2, 2)
|
||||
:AddChild(UIElements.New("Frame", "sale")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 4)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetTextColor("ACTIVE_BG_ALT")
|
||||
:SetText(L["TOP SALE"])
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
:AddChild(UIElements.New("Text", "value")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "expense")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetTextColor("ACTIVE_BG_ALT")
|
||||
:SetText(L["TOP PURCHASE"])
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
:AddChild(UIElements.New("Text", "value")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "details")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetMargin(0, 0, 8, 0)
|
||||
:SetPadding(8)
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT", true)
|
||||
:AddChild(UIElements.New("Text", "salesLabel")
|
||||
:SetHeight(20)
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetTextColor("ACTIVE_BG_ALT")
|
||||
:SetText(L["SALES"])
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "salesTotal")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:AddChild(UIElements.New("Text", "text")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetText(L["Total Gold Earned"]..":")
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
:AddChild(UIElements.New("Text", "amount")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "salesAvg")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 4, 0)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetText(L["Average Earned per Day"]..":")
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
:AddChild(UIElements.New("Text", "amount")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "salesTop")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 4, 0)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetText(L["Top Item"]..":")
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
:AddChild(UIElements.New("Button", "item")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("RIGHT")
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Texture", "line1")
|
||||
:SetHeight(1)
|
||||
:SetMargin(-8, -8, 4, 4)
|
||||
:SetTexture("ACTIVE_BG")
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "expensesLabel")
|
||||
:SetHeight(20)
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetTextColor("ACTIVE_BG_ALT")
|
||||
:SetText(L["EXPENSES"])
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "expensesTotal")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:AddChild(UIElements.New("Text", "text")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetText(L["Total Gold Spent"]..":")
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
:AddChild(UIElements.New("Text", "amount")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "expensesAvg")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 4, 0)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetText(L["Average Spent per Day"]..":")
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
:AddChild(UIElements.New("Text", "amount")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "expensesTop")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 4, 0)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetText(L["Top Item"]..":")
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
:AddChild(UIElements.New("Button", "item")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("RIGHT")
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Texture", "line2")
|
||||
:SetHeight(1)
|
||||
:SetMargin(-8, -8, 4, 4)
|
||||
:SetTexture("ACTIVE_BG")
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "profitLabel")
|
||||
:SetHeight(20)
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetTextColor("ACTIVE_BG_ALT")
|
||||
:SetText(L["PROFIT"])
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "profitTotal")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:AddChild(UIElements.New("Text", "text")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetText(L["Total Profit"]..":")
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
:AddChild(UIElements.New("Text", "amount")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "profitAvg")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 4, 0)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetText(L["Average Profit per Day"]..":")
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
:AddChild(UIElements.New("Text", "amount")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "profitTop")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 4, 0)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetText(L["Top Item"]..":")
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
:AddChild(UIElements.New("Button", "item")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("RIGHT")
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
frame:GetElement("content.goldHeader.timeBtns.resetZoom"):Hide()
|
||||
frame:GetElement("content.goldHeader.hoverTime"):Hide()
|
||||
|
||||
local newsContent = frame:GetElement("news.content")
|
||||
local newsEntries = TSM.GetAppNews()
|
||||
if newsEntries then
|
||||
for i, info in ipairs(newsEntries) do
|
||||
newsContent:AddChild(UIElements.New("Frame", "news"..i)
|
||||
:SetLayout("VERTICAL")
|
||||
:SetPadding(0, 0, i == 1 and 6 or 12, 0)
|
||||
:AddChild(UIElements.New("Text", "date")
|
||||
:SetHeight(20)
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetText(date("%b %d, %Y", info.timestamp))
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "title")
|
||||
:SetHeight(20)
|
||||
:SetFont("BODY_BODY2_BOLD")
|
||||
:SetText(info.title)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "content")
|
||||
:SetHeight(80)
|
||||
:SetPadding(0, 0, 4, 0)
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetText(info.content)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "readMore")
|
||||
:SetHeight(20)
|
||||
:SetPadding(0, 0, 4, 0)
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetTextColor("INDICATOR")
|
||||
:SetText(L["Read More"])
|
||||
)
|
||||
)
|
||||
:AddChildNoLayout(UIElements.New("Button", "btn")
|
||||
:AddAnchor("TOPLEFT", "news"..i)
|
||||
:AddAnchor("BOTTOMRIGHT", "news"..i)
|
||||
:SetContext(info)
|
||||
:SetScript("OnClick", private.ButtonOnClick)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
return frame
|
||||
end
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.ButtonOnClick(button)
|
||||
local info = button:GetContext()
|
||||
Analytics.Action("NEWS_READ_MORE", info.title)
|
||||
button:GetBaseElement():ShowDialogFrame(UIElements.New("Frame", "frame")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetSize(600, 450)
|
||||
:AddAnchor("CENTER")
|
||||
:SetBackgroundColor("FRAME_BG")
|
||||
:SetBorderColor("ACTIVE_BG")
|
||||
:AddChild(UIElements.New("Text", "title")
|
||||
:SetHeight(44)
|
||||
:SetMargin(16, 16, 16, 8)
|
||||
:SetFont("BODY_BODY1_BOLD")
|
||||
:SetJustifyH("CENTER")
|
||||
:SetText(info.title)
|
||||
)
|
||||
:AddChild(UIElements.New("Input", "linkInput")
|
||||
:SetHeight(26)
|
||||
:SetMargin(16, 16, 0, 16)
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT")
|
||||
:SetValidateFunc(private.LinkValidateFunc)
|
||||
:SetContext(info.link)
|
||||
:SetValue(info.link)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "content")
|
||||
:SetMargin(16, 16, 0, 16)
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetJustifyV("TOP")
|
||||
:SetText(info.content)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "buttons")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(26)
|
||||
:SetMargin(16, 16, 0, 16)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
:AddChild(UIElements.New("ActionButton", "confirmBtn")
|
||||
:SetWidth(126)
|
||||
:SetText(CLOSE)
|
||||
:SetScript("OnClick", private.DialogCloseBtnOnClick)
|
||||
)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
function private.LinkValidateFunc(input, value)
|
||||
return value == input:GetContext()
|
||||
end
|
||||
|
||||
function private.DialogCloseBtnOnClick(button)
|
||||
button:GetBaseElement():HideDialog()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.GraphFormatX(timestamp, suggestedStep)
|
||||
if suggestedStep > SECONDS_PER_DAY * 14 then
|
||||
return date("%b '%y", timestamp)
|
||||
elseif suggestedStep > SECONDS_PER_DAY * 2 then
|
||||
return date("%b %d", timestamp)
|
||||
elseif suggestedStep > SECONDS_PER_DAY / 6 then
|
||||
return date("%a", timestamp)
|
||||
else
|
||||
if GetCVar("timeMgrUseMilitaryTime") == "1" then
|
||||
return date("%H:%M", timestamp)
|
||||
else
|
||||
return strtrim(date("%I %p", timestamp), "0")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function private.GraphFormatY(value, suggestedStep, isTooltip)
|
||||
if isTooltip then
|
||||
return Money.ToString(value, nil, "OPT_TRIM")
|
||||
end
|
||||
if TSM.IsWowClassic() and value < COPPER_PER_GOLD * 1000 then
|
||||
-- "###g"
|
||||
return floor(value / COPPER_PER_GOLD)..Money.GetGoldText()
|
||||
elseif TSM.IsWowClassic() and value < COPPER_PER_GOLD * 1000 * 10 then
|
||||
-- "#.##Kg"
|
||||
return format("%.2f", value / (COPPER_PER_GOLD * 1000)).."k"..Money.GetGoldText()
|
||||
elseif value < COPPER_PER_GOLD * 1000 * 1000 then
|
||||
-- "###Kg"
|
||||
return floor(value / (COPPER_PER_GOLD * 1000)).."k"..Money.GetGoldText()
|
||||
elseif value < COPPER_PER_GOLD * 1000 * 1000 * 10 then
|
||||
-- "#.##Mg"
|
||||
return format("%.2f", value / (COPPER_PER_GOLD * 1000 * 1000)).."M"..Money.GetGoldText()
|
||||
else
|
||||
-- "###Mg"
|
||||
return floor(value / (COPPER_PER_GOLD * 1000 * 1000)).."M"..Money.GetGoldText()
|
||||
end
|
||||
end
|
||||
|
||||
function private.GetGraphYValue(xValue)
|
||||
return TSM.Accounting.GoldTracker.GetGoldAtTime(xValue, private.settings.dashboardUnselectedCharacters)
|
||||
end
|
||||
|
||||
function private.ContentOnUpdate(contentFrame)
|
||||
contentFrame:SetScript("OnUpdate", nil)
|
||||
private.UpdateTimeButtons(contentFrame:GetElement("goldHeader.timeBtns"))
|
||||
private.UpdateGraph(contentFrame)
|
||||
end
|
||||
|
||||
function private.DropdownOnSelectionChanged(dropdown)
|
||||
for _, key in ipairs(private.characterGuilds) do
|
||||
private.settings.dashboardUnselectedCharacters[key] = not dropdown:ItemIsSelectedByKey(key) or nil
|
||||
end
|
||||
private.UpdateGraph(dropdown:GetParentElement():GetParentElement())
|
||||
end
|
||||
|
||||
function private.TimeBtnOnClick(button)
|
||||
local timeRange = button:GetContext()
|
||||
assert(timeRange)
|
||||
private.selectedTimeRange = timeRange
|
||||
private.settings.dashboardTimeRange = timeRange
|
||||
private.UpdateTimeButtons(button:GetParentElement())
|
||||
private.UpdateGraph(button:GetParentElement():GetParentElement():GetParentElement())
|
||||
end
|
||||
|
||||
function private.UpdateGraph(contentFrame)
|
||||
-- update the graph
|
||||
local minTime, maxTime = TSM.Accounting.GoldTracker.GetGraphTimeRange(private.settings.dashboardUnselectedCharacters)
|
||||
local goldGraph = contentFrame:GetElement("goldGraph")
|
||||
local zoomStart, zoomEnd = goldGraph:GetZoom()
|
||||
if private.selectedTimeRange == TIME_RANGE_LOOKUP["all"] then
|
||||
zoomStart = minTime
|
||||
zoomEnd = maxTime
|
||||
elseif private.selectedTimeRange then
|
||||
zoomStart = max(time() - private.selectedTimeRange, minTime)
|
||||
zoomEnd = time()
|
||||
end
|
||||
goldGraph:SetXRange(minTime, maxTime)
|
||||
:SetZoom(zoomStart, zoomEnd)
|
||||
:Draw()
|
||||
private.PopulateDetails(contentFrame)
|
||||
end
|
||||
|
||||
function private.GraphOnZoomChanged(graph)
|
||||
private.selectedTimeRange = nil
|
||||
private.settings.dashboardTimeRange = -1
|
||||
private.UpdateTimeButtons(graph:GetElement("__parent.goldHeader.timeBtns"))
|
||||
private.PopulateDetails(graph:GetElement("__parent"))
|
||||
end
|
||||
|
||||
function private.GraphOnHoverUpdate(graph, hoverTime)
|
||||
local goldHeader = graph:GetElement("__parent.goldHeader")
|
||||
if hoverTime then
|
||||
local timeStr = nil
|
||||
if GetCVar("timeMgrUseMilitaryTime") == "1" then
|
||||
timeStr = date("%H:%M %b %d, %Y", hoverTime)
|
||||
else
|
||||
timeStr = gsub(date("%I:%M %p %b %d, %Y", hoverTime), "^0", "")
|
||||
end
|
||||
goldHeader:GetElement("timeBtns"):Hide()
|
||||
goldHeader:GetElement("hoverTime")
|
||||
:SetText(timeStr)
|
||||
:Show()
|
||||
else
|
||||
goldHeader:GetElement("timeBtns"):Show()
|
||||
goldHeader:GetElement("hoverTime"):Hide()
|
||||
private.UpdateTimeButtons(goldHeader:GetElement("timeBtns"))
|
||||
end
|
||||
goldHeader:Draw()
|
||||
end
|
||||
|
||||
function private.UpdateTimeButtons(frame)
|
||||
frame:ShowAllChildren()
|
||||
local resetButton = frame:GetElement("resetZoom")
|
||||
if private.selectedTimeRange then
|
||||
for _, button in frame:LayoutChildrenIterator() do
|
||||
if button ~= resetButton then
|
||||
button:SetTextColor(private.selectedTimeRange == button:GetContext() and "TEXT" or "ACTIVE_BG_ALT")
|
||||
end
|
||||
end
|
||||
resetButton:Hide()
|
||||
else
|
||||
for _, button in frame:LayoutChildrenIterator() do
|
||||
button:Hide()
|
||||
end
|
||||
resetButton:Show()
|
||||
end
|
||||
frame:GetParentElement():Draw()
|
||||
end
|
||||
|
||||
function private.GraphXStepFunc(prevValue, suggestedStep)
|
||||
local year, day, month, hour, min, sec = strsplit(",", date("%Y,%d,%m,%H,%M,%S", prevValue))
|
||||
private.tempTimeTable.year = tonumber(year)
|
||||
private.tempTimeTable.day = tonumber(day)
|
||||
private.tempTimeTable.month = tonumber(month)
|
||||
private.tempTimeTable.hour = tonumber(hour)
|
||||
private.tempTimeTable.min = tonumber(min)
|
||||
private.tempTimeTable.sec = tonumber(sec)
|
||||
if suggestedStep > SECONDS_PER_DAY * 14 then
|
||||
private.tempTimeTable.month = private.tempTimeTable.month + 1
|
||||
private.tempTimeTable.day = 1
|
||||
private.tempTimeTable.hour = 0
|
||||
private.tempTimeTable.min = 0
|
||||
private.tempTimeTable.sec = 0
|
||||
elseif suggestedStep > SECONDS_PER_DAY / 6 then
|
||||
private.tempTimeTable.day = private.tempTimeTable.day + 1
|
||||
if private.tempTimeTable.hour == 23 then
|
||||
-- add an extra hour to avoid DST issues
|
||||
private.tempTimeTable.hour = 1
|
||||
else
|
||||
private.tempTimeTable.hour = 0
|
||||
end
|
||||
private.tempTimeTable.min = 0
|
||||
private.tempTimeTable.sec = 0
|
||||
else
|
||||
private.tempTimeTable.hour = private.tempTimeTable.hour + 1
|
||||
private.tempTimeTable.min = 0
|
||||
private.tempTimeTable.sec = 0
|
||||
end
|
||||
local newValue = time(private.tempTimeTable)
|
||||
assert(newValue > prevValue)
|
||||
return newValue
|
||||
end
|
||||
|
||||
function private.GraphYStepFunc(mode, ...)
|
||||
if mode == "RANGE" then
|
||||
local yMin, yMax, maxNumSteps = ...
|
||||
-- find the smallest 10^X step size which still looks good
|
||||
local minStep = max((yMax - yMin) / maxNumSteps / 10, yMax / 200)
|
||||
local stepSize = MIN_GRAPH_STEP_SIZE
|
||||
while stepSize < minStep do
|
||||
stepSize = stepSize * 10
|
||||
end
|
||||
yMin = Math.Floor(yMin, stepSize)
|
||||
yMax = Math.Ceil(yMax + stepSize / 3, stepSize)
|
||||
if yMin == yMax then
|
||||
yMax = yMax + stepSize
|
||||
end
|
||||
return yMin, yMax
|
||||
elseif mode == "NEXT" then
|
||||
local prevValue, yMax = ...
|
||||
local stepSize = MIN_GRAPH_STEP_SIZE
|
||||
while stepSize < yMax / 1000 do
|
||||
stepSize = stepSize * 10
|
||||
end
|
||||
return Math.Floor(prevValue, stepSize) + stepSize
|
||||
else
|
||||
error("Invalid mode")
|
||||
end
|
||||
end
|
||||
|
||||
function private.PopulateDetails(contentFrame)
|
||||
local goldGraph = contentFrame:GetElement("goldGraph")
|
||||
local unselectedCharacters = next(private.settings.dashboardUnselectedCharacters) and private.settings.dashboardUnselectedCharacters or nil
|
||||
local timeFilterStart, timeFilterEnd, numDays = nil, nil, nil
|
||||
if private.selectedTimeRange and private.selectedTimeRange ~= -1 then
|
||||
timeFilterStart = time() - private.selectedTimeRange
|
||||
timeFilterEnd = time()
|
||||
numDays = ceil(private.selectedTimeRange / SECONDS_PER_DAY)
|
||||
elseif not private.selectedTimeRange then
|
||||
timeFilterStart, timeFilterEnd = goldGraph:GetZoom()
|
||||
numDays = ceil((timeFilterEnd - timeFilterStart) / SECONDS_PER_DAY)
|
||||
else
|
||||
local timeStart, timeEnd = goldGraph:GetXRange()
|
||||
numDays = ceil((timeEnd - timeStart) / SECONDS_PER_DAY)
|
||||
end
|
||||
numDays = max(numDays, 1)
|
||||
|
||||
local saleTotal, salePerDay, saleTopItem, saleTopValue, saleTotalQuantity = 0, nil, nil, 0, 0
|
||||
local buyTotal, buyPerDay, buyTopItem, buyTopValue, buyTotalQuantity = 0, nil, nil, 0, 0
|
||||
local profitTopItem = nil
|
||||
local query = TSM.Accounting.GetSummaryQuery(timeFilterStart, timeFilterEnd, unselectedCharacters)
|
||||
local saleNumDays, buyNumDays = 1, 1
|
||||
local saleItemTotals = TempTable.Acquire()
|
||||
local buyItemTotals = TempTable.Acquire()
|
||||
local saleItemNum = TempTable.Acquire()
|
||||
local buyItemNum = TempTable.Acquire()
|
||||
for _, recordType, itemString, price, quantity, timestamp in query:Iterator() do
|
||||
if recordType == "sale" then
|
||||
local daysAgo = floor((time() - timestamp) / (24 * 60 * 60))
|
||||
saleNumDays = max(saleNumDays, daysAgo)
|
||||
saleItemTotals[itemString] = (saleItemTotals[itemString] or 0) + price * quantity
|
||||
saleTopValue = max(saleTopValue, price)
|
||||
saleTotalQuantity = saleTotalQuantity + quantity
|
||||
saleItemNum[itemString] = (saleItemNum[itemString] or 0) + quantity
|
||||
elseif recordType == "buy" then
|
||||
local daysAgo = floor((time() - timestamp) / (24 * 60 * 60))
|
||||
buyNumDays = max(buyNumDays, daysAgo)
|
||||
buyItemTotals[itemString] = (buyItemTotals[itemString] or 0) + price * quantity
|
||||
buyTopValue = max(buyTopValue, price)
|
||||
buyTotalQuantity = buyTotalQuantity + quantity
|
||||
buyItemNum[itemString] = (buyItemNum[itemString] or 0) + quantity
|
||||
else
|
||||
error("Invalid recordType: "..tostring(recordType))
|
||||
end
|
||||
end
|
||||
query:Release()
|
||||
|
||||
local topSaleItemTotal = 0
|
||||
for itemString, itemTotal in pairs(saleItemTotals) do
|
||||
saleTotal = saleTotal + itemTotal
|
||||
if itemTotal > topSaleItemTotal then
|
||||
saleTopItem = itemString
|
||||
topSaleItemTotal = itemTotal
|
||||
end
|
||||
end
|
||||
salePerDay = Math.Round(saleTotal / saleNumDays)
|
||||
|
||||
local topBuyItemTotal = 0
|
||||
for itemString, itemTotal in pairs(buyItemTotals) do
|
||||
buyTotal = buyTotal + itemTotal
|
||||
if itemTotal > topBuyItemTotal then
|
||||
buyTopItem = itemString
|
||||
topBuyItemTotal = itemTotal
|
||||
end
|
||||
end
|
||||
buyPerDay = Math.Round(buyTotal / buyNumDays)
|
||||
|
||||
local topItemProfit = 0
|
||||
for itemString in pairs(saleItemNum) do
|
||||
if buyItemNum[itemString] then
|
||||
local profit = (saleItemTotals[itemString] / saleItemNum[itemString] - buyItemTotals[itemString] / buyItemNum[itemString]) * min(saleItemNum[itemString], buyItemNum[itemString])
|
||||
if profit > topItemProfit then
|
||||
profitTopItem = itemString
|
||||
topItemProfit = profit
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
TempTable.Release(saleItemTotals)
|
||||
TempTable.Release(buyItemTotals)
|
||||
TempTable.Release(saleItemNum)
|
||||
TempTable.Release(buyItemNum)
|
||||
|
||||
local profitTotal = saleTotal - buyTotal
|
||||
local profitPerDay = salePerDay - buyPerDay
|
||||
|
||||
local rangeLow, rangeHigh = goldGraph:GetYRange()
|
||||
contentFrame:GetElement("summary.range.low.value")
|
||||
:SetText(Money.ToString(rangeLow, nil, "OPT_TRIM") or "-")
|
||||
contentFrame:GetElement("summary.range.high.value")
|
||||
:SetText(Money.ToString(rangeHigh, nil, "OPT_TRIM") or "-")
|
||||
|
||||
contentFrame:GetElement("summary.daily.sales.value")
|
||||
:SetText(saleTotalQuantity and Math.Round(saleTotalQuantity / numDays) or "-")
|
||||
contentFrame:GetElement("summary.daily.purchases.value")
|
||||
:SetText(buyTotalQuantity and Math.Round(buyTotalQuantity / numDays) or "-")
|
||||
|
||||
contentFrame:GetElement("summary.top.sale.value")
|
||||
:SetText(Money.ToString(Math.Round(saleTopValue, TSM.IsWowClassic() and 1 or COPPER_PER_GOLD), nil, "OPT_TRIM") or "-")
|
||||
contentFrame:GetElement("summary.top.expense.value")
|
||||
:SetText(Money.ToString(Math.Round(buyTopValue, TSM.IsWowClassic() and 1 or COPPER_PER_GOLD), nil, "OPT_TRIM") or "-")
|
||||
|
||||
contentFrame:GetElement("details.salesTotal.amount")
|
||||
:SetText(Money.ToString(saleTotal))
|
||||
contentFrame:GetElement("details.salesAvg.amount")
|
||||
:SetText(Money.ToString(salePerDay))
|
||||
contentFrame:GetElement("details.salesTop.item")
|
||||
:SetText(TSM.UI.GetColoredItemName(saleTopItem) or "-")
|
||||
:SetTooltip(saleTopItem)
|
||||
|
||||
contentFrame:GetElement("details.expensesTotal.amount")
|
||||
:SetText(Money.ToString(buyTotal))
|
||||
contentFrame:GetElement("details.expensesAvg.amount")
|
||||
:SetText(Money.ToString(buyPerDay))
|
||||
contentFrame:GetElement("details.expensesTop.item")
|
||||
:SetText(TSM.UI.GetColoredItemName(buyTopItem) or "-")
|
||||
:SetTooltip(buyTopItem)
|
||||
|
||||
contentFrame:GetElement("details.profitTotal.amount")
|
||||
:SetText(Money.ToString(profitTotal, profitTotal < 0 and Theme.GetFeedbackColor("RED"):GetTextColorPrefix() or nil))
|
||||
contentFrame:GetElement("details.profitAvg.amount")
|
||||
:SetText(Money.ToString(profitPerDay, profitPerDay < 0 and Theme.GetFeedbackColor("RED"):GetTextColorPrefix() or nil))
|
||||
contentFrame:GetElement("details.profitTop.item")
|
||||
:SetText(TSM.UI.GetColoredItemName(profitTopItem) or "-")
|
||||
:SetTooltip(profitTopItem)
|
||||
|
||||
contentFrame:Draw()
|
||||
end
|
||||
1870
Core/UI/MainUI/Groups.lua
Normal file
1870
Core/UI/MainUI/Groups.lua
Normal file
File diff suppressed because it is too large
Load Diff
266
Core/UI/MainUI/Ledger/Common/Auctions.lua
Normal file
266
Core/UI/MainUI/Ledger/Common/Auctions.lua
Normal file
@@ -0,0 +1,266 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Auctions = TSM.MainUI.Ledger.Common:NewPackage("Auctions")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local Table = TSM.Include("Util.Table")
|
||||
local String = TSM.Include("Util.String")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local ItemInfo = TSM.Include("Service.ItemInfo")
|
||||
local Settings = TSM.Include("Service.Settings")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local SECONDS_PER_DAY = 24 * 60 * 60
|
||||
local private = {
|
||||
settings = nil,
|
||||
query = nil,
|
||||
characters = {},
|
||||
characterFilter = {},
|
||||
searchFilter = "",
|
||||
groupFilter = {},
|
||||
rarityList = {},
|
||||
rarityFilter = {},
|
||||
timeFrameFilter = 30 * SECONDS_PER_DAY,
|
||||
type = nil
|
||||
}
|
||||
do
|
||||
for i = 1, 4 do
|
||||
tinsert(private.rarityList, _G[format("ITEM_QUALITY%d_DESC", i)])
|
||||
private.rarityFilter[i] = true
|
||||
end
|
||||
end
|
||||
local TIME_LIST = { L["All Time"], L["Last 3 Days"], L["Last 7 Days"], L["Last 14 Days"], L["Last 30 Days"], L["Last 60 Days"] }
|
||||
local TIME_KEYS = { 0, 3 * SECONDS_PER_DAY, 7 * SECONDS_PER_DAY, 14 * SECONDS_PER_DAY, 30 * SECONDS_PER_DAY, 60 * SECONDS_PER_DAY }
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Auctions.OnInitialize()
|
||||
private.settings = Settings.NewView()
|
||||
:AddKey("global", "mainUIContext", "ledgerAuctionsScrollingTable")
|
||||
TSM.MainUI.Ledger.FailedAuctions.RegisterPage(L["Expired"], private.DrawExpiredPage)
|
||||
TSM.MainUI.Ledger.FailedAuctions.RegisterPage(L["Cancelled"], private.DrawCancelledPage)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Auctions UIs
|
||||
-- ============================================================================
|
||||
|
||||
function private.DrawExpiredPage()
|
||||
TSM.UI.AnalyticsRecordPathChange("main", "ledger", "failed_auctions", "expired")
|
||||
private.type = "expire"
|
||||
return private.DrawAuctionsPage()
|
||||
end
|
||||
|
||||
function private.DrawCancelledPage()
|
||||
TSM.UI.AnalyticsRecordPathChange("main", "ledger", "failed_auctions", "cancelled")
|
||||
private.type = "cancel"
|
||||
return private.DrawAuctionsPage()
|
||||
end
|
||||
|
||||
function private.DrawAuctionsPage()
|
||||
private.query = private.query or TSM.Accounting.Auctions.CreateQuery()
|
||||
|
||||
private.query:Reset()
|
||||
:Equal("type", "cancel")
|
||||
:Distinct("player")
|
||||
:Select("player")
|
||||
wipe(private.characters)
|
||||
for _, character in private.query:Iterator() do
|
||||
tinsert(private.characters, character)
|
||||
private.characterFilter[character] = true
|
||||
end
|
||||
|
||||
private.query:Reset()
|
||||
:InnerJoin(ItemInfo.GetDBForJoin(), "itemString")
|
||||
:LeftJoin(TSM.Groups.GetItemDBForJoin(), "itemString")
|
||||
:OrderBy("time", false)
|
||||
private.UpdateQuery()
|
||||
|
||||
return UIElements.New("Frame", "content")
|
||||
:SetLayout("VERTICAL")
|
||||
:AddChild(UIElements.New("Frame", "row1")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:SetMargin(8)
|
||||
:AddChild(UIElements.New("Input", "filter")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetIconTexture("iconPack.18x18/Search")
|
||||
:SetClearButtonEnabled(true)
|
||||
:AllowItemInsert()
|
||||
:SetHintText(L["Filter by keyword"])
|
||||
:SetValue(private.searchFilter)
|
||||
:SetScript("OnValueChanged", private.SearchFilterChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("GroupSelector", "group")
|
||||
:SetWidth(240)
|
||||
:SetHintText(L["Filter by groups"])
|
||||
:SetScript("OnSelectionChanged", private.GroupFilterChanged)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "row2")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:SetMargin(8, 8, 0, 8)
|
||||
:AddChild(UIElements.New("MultiselectionDropdown", "rarity")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetItems(private.rarityList)
|
||||
:SetSettingInfo(private, "rarityFilter")
|
||||
:SetSelectionText(L["No Rarities"], L["%d Rarities"], L["All Rarites"])
|
||||
:SetScript("OnSelectionChanged", private.DropdownCommonOnSelectionChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("MultiselectionDropdown", "character")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetItems(private.characters, private.characters)
|
||||
:SetSettingInfo(private, "characterFilter")
|
||||
:SetSelectionText(L["No Characters"], L["%d Characters"], L["All Characters"])
|
||||
:SetScript("OnSelectionChanged", private.DropdownCommonOnSelectionChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("SelectionDropdown", "time")
|
||||
:SetItems(TIME_LIST, TIME_KEYS)
|
||||
:SetSelectedItemByKey(private.timeFrameFilter)
|
||||
:SetSettingInfo(private, "timeFrameFilter")
|
||||
:SetScript("OnSelectionChanged", private.DropdownCommonOnSelectionChanged)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("QueryScrollingTable", "scrollingTable")
|
||||
:SetSettingsContext(private.settings, "ledgerAuctionsScrollingTable")
|
||||
:GetScrollingTableInfo()
|
||||
:NewColumn("item")
|
||||
:SetTitle(L["Item"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo("itemString", TSM.UI.GetColoredItemName)
|
||||
:SetTooltipInfo("itemString")
|
||||
:SetSortInfo("name")
|
||||
:DisableHiding()
|
||||
:Commit()
|
||||
:NewColumn("player")
|
||||
:SetTitle(PLAYER)
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo("player")
|
||||
:SetSortInfo("player")
|
||||
:Commit()
|
||||
:NewColumn("stackSize")
|
||||
:SetTitle(L["Stack"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("stackSize")
|
||||
:SetSortInfo("stackSize")
|
||||
:Commit()
|
||||
:NewColumn("quantity")
|
||||
:SetTitle(L["Auctions"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo(nil, private.FormatAuctions)
|
||||
:SetSortInfo("quantity")
|
||||
:Commit()
|
||||
:NewColumn("time")
|
||||
:SetTitle(L["Time Frame"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("time", private.TableGetTimeframeText)
|
||||
:SetSortInfo("time")
|
||||
:Commit()
|
||||
:Commit()
|
||||
:SetQuery(private.query)
|
||||
:SetScript("OnRowClick", private.TableSelectionChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("Texture", "line")
|
||||
:SetHeight(2)
|
||||
:SetTexture("ACTIVE_BG")
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "footer")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(40)
|
||||
:SetPadding(8)
|
||||
:SetBackgroundColor("PRIMARY_BG")
|
||||
:AddChild(UIElements.New("Text", "num")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(format(private.type == "expire" and L["%s Items Expired"] or L["%s Items Cancelled"], Theme.GetColor("INDICATOR"):ColorText(FormatLargeNumber(private.query:Sum("quantity") or 0))))
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Scrolling Table Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.TableGetTimeframeText(record)
|
||||
return SecondsToTime(time() - record)
|
||||
end
|
||||
|
||||
function private.FormatAuctions(row)
|
||||
return row:GetField("quantity") / row:GetField("stackSize")
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.DropdownCommonOnSelectionChanged(dropdown)
|
||||
private.UpdateQuery()
|
||||
dropdown:GetElement("__parent.__parent.scrollingTable")
|
||||
:UpdateData(true)
|
||||
local footer = dropdown:GetElement("__parent.__parent.footer")
|
||||
footer:GetElement("num"):SetText(format(private.type == "expire" and L["%s Items Expired"] or L["%s Items Cancelled"], Theme.GetColor("INDICATOR"):ColorText(FormatLargeNumber(private.query:Sum("quantity") or 0))))
|
||||
footer:Draw()
|
||||
end
|
||||
|
||||
function private.SearchFilterChanged(input)
|
||||
private.searchFilter = input:GetValue()
|
||||
private.DropdownCommonOnSelectionChanged(input)
|
||||
end
|
||||
|
||||
function private.GroupFilterChanged(groupSelector)
|
||||
wipe(private.groupFilter)
|
||||
for groupPath in groupSelector:SelectedGroupIterator() do
|
||||
private.groupFilter[groupPath] = true
|
||||
end
|
||||
private.DropdownCommonOnSelectionChanged(groupSelector)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.UpdateQuery()
|
||||
private.query:ResetFilters()
|
||||
:Equal("type", private.type)
|
||||
if private.searchFilter ~= "" then
|
||||
private.query:Matches("name", String.Escape(private.searchFilter))
|
||||
end
|
||||
if Table.Count(private.rarityFilter) ~= #private.rarityList then
|
||||
private.query:InTable("quality", private.rarityFilter)
|
||||
end
|
||||
if Table.Count(private.characterFilter) ~= #private.characters then
|
||||
private.query:InTable("player", private.characterFilter)
|
||||
end
|
||||
if private.timeFrameFilter ~= 0 then
|
||||
private.query:GreaterThanOrEqual("time", time() - private.timeFrameFilter)
|
||||
end
|
||||
if next(private.groupFilter) then
|
||||
private.query:InTable("groupPath", private.groupFilter)
|
||||
end
|
||||
end
|
||||
|
||||
function private.TableSelectionChanged(scrollingTable, row)
|
||||
TSM.MainUI.Ledger.ShowItemDetail(scrollingTable:GetParentElement():GetParentElement(), row:GetField("itemString"), "sale")
|
||||
end
|
||||
8
Core/UI/MainUI/Ledger/Common/Core.lua
Normal file
8
Core/UI/MainUI/Ledger/Common/Core.lua
Normal file
@@ -0,0 +1,8 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
TSM.MainUI.Ledger:NewPackage("Common")
|
||||
208
Core/UI/MainUI/Ledger/Common/Other.lua
Normal file
208
Core/UI/MainUI/Ledger/Common/Other.lua
Normal file
@@ -0,0 +1,208 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Other = TSM.MainUI.Ledger.Common:NewPackage("Other")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local Table = TSM.Include("Util.Table")
|
||||
local Money = TSM.Include("Util.Money")
|
||||
local Settings = TSM.Include("Service.Settings")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local SECONDS_PER_DAY = 24 * 60 * 60
|
||||
local private = {
|
||||
settings = nil,
|
||||
query = nil,
|
||||
characters = {},
|
||||
characterFilter = {},
|
||||
typeFilter = {},
|
||||
recordType = nil,
|
||||
timeFrameFilter = 30 * SECONDS_PER_DAY,
|
||||
}
|
||||
local TIME_LIST = { L["All Time"], L["Last 3 Days"], L["Last 7 Days"], L["Last 14 Days"], L["Last 30 Days"], L["Last 60 Days"] }
|
||||
local TIME_KEYS = { 0, 3 * SECONDS_PER_DAY, 7 * SECONDS_PER_DAY, 14 * SECONDS_PER_DAY, 30 * SECONDS_PER_DAY, 60 * SECONDS_PER_DAY }
|
||||
local TYPE_LIST = {
|
||||
expense = { L["Money Transfer"], L["Postage"], L["Repair Bill"] },
|
||||
income = { L["Money Transfer"], L["Garrison"] },
|
||||
}
|
||||
local TYPE_KEYS = {
|
||||
expense = { "Money Transfer", "Postage", "Repair Bill" },
|
||||
income = { "Money Transfer", "Garrison" },
|
||||
}
|
||||
local TYPE_STR_LOOKUP = {}
|
||||
do
|
||||
-- populate lookup table
|
||||
assert(#TYPE_LIST.expense == #TYPE_KEYS.expense)
|
||||
for i = 1, #TYPE_LIST.expense do
|
||||
TYPE_STR_LOOKUP[TYPE_KEYS.expense[i]] = TYPE_LIST.expense[i]
|
||||
end
|
||||
assert(#TYPE_LIST.income == #TYPE_KEYS.income)
|
||||
for i = 1, #TYPE_LIST.income do
|
||||
TYPE_STR_LOOKUP[TYPE_KEYS.income[i]] = TYPE_LIST.income[i]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Other.OnInitialize()
|
||||
private.settings = Settings.NewView()
|
||||
:AddKey("global", "mainUIContext", "ledgerOtherScrollingTable")
|
||||
TSM.MainUI.Ledger.Expenses.RegisterPage(OTHER, private.DrawOtherExpensesPage)
|
||||
TSM.MainUI.Ledger.Revenue.RegisterPage(OTHER, private.DrawOtherRevenuePage)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Other UIs
|
||||
-- ============================================================================
|
||||
|
||||
function private.DrawOtherExpensesPage()
|
||||
TSM.UI.AnalyticsRecordPathChange("main", "ledger", "expenses", "other")
|
||||
return private.DrawOtherPage("expense")
|
||||
end
|
||||
|
||||
function private.DrawOtherRevenuePage()
|
||||
TSM.UI.AnalyticsRecordPathChange("main", "ledger", "revenue", "other")
|
||||
return private.DrawOtherPage("income")
|
||||
end
|
||||
|
||||
function private.DrawOtherPage(recordType)
|
||||
wipe(private.characters)
|
||||
for _, character in TSM.Accounting.Money.CharacterIterator(recordType) do
|
||||
tinsert(private.characters, character)
|
||||
private.characterFilter[character] = true
|
||||
end
|
||||
wipe(private.typeFilter)
|
||||
for _, key in ipairs(TYPE_KEYS[recordType]) do
|
||||
private.typeFilter[key] = true
|
||||
end
|
||||
|
||||
if not private.query then
|
||||
private.query = TSM.Accounting.Money.CreateQuery()
|
||||
:OrderBy("time", false)
|
||||
end
|
||||
private.recordType = recordType
|
||||
private.UpdateQuery()
|
||||
|
||||
return UIElements.New("Frame", "content")
|
||||
:SetLayout("VERTICAL")
|
||||
:AddChild(UIElements.New("Frame", "row2")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:SetMargin(8)
|
||||
:AddChild(UIElements.New("MultiselectionDropdown", "type")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetItems(TYPE_LIST[recordType], TYPE_KEYS[recordType])
|
||||
:SetSettingInfo(private, "typeFilter")
|
||||
:SetSelectionText(L["No Types"], L["%d Types"], L["All Types"])
|
||||
:SetScript("OnSelectionChanged", private.DropdownChangedCommon)
|
||||
)
|
||||
:AddChild(UIElements.New("MultiselectionDropdown", "character")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetItems(private.characters, private.characters)
|
||||
:SetSettingInfo(private, "characterFilter")
|
||||
:SetSelectionText(L["No Characters"], L["%d Characters"], L["All Characters"])
|
||||
:SetScript("OnSelectionChanged", private.DropdownChangedCommon)
|
||||
)
|
||||
:AddChild(UIElements.New("SelectionDropdown", "time")
|
||||
:SetItems(TIME_LIST, TIME_KEYS)
|
||||
:SetSelectedItemByKey(private.timeFrameFilter)
|
||||
:SetSettingInfo(private, "timeFrameFilter")
|
||||
:SetScript("OnSelectionChanged", private.DropdownChangedCommon)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("QueryScrollingTable", "table")
|
||||
:SetSettingsContext(private.settings, "ledgerOtherScrollingTable")
|
||||
:GetScrollingTableInfo()
|
||||
:NewColumn("type")
|
||||
:SetTitle(L["Type"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo("type", private.TableGetTypeText)
|
||||
:SetSortInfo("type")
|
||||
:Commit()
|
||||
:NewColumn("character")
|
||||
:SetTitle(L["Character"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo("player")
|
||||
:SetSortInfo("player")
|
||||
:Commit()
|
||||
:NewColumn("otherCharacter")
|
||||
:SetTitle(L["Other Character"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo("otherPlayer")
|
||||
:SetSortInfo("otherPlayer")
|
||||
:Commit()
|
||||
:NewColumn("amount")
|
||||
:SetTitle(L["Amount"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("amount", Money.ToString)
|
||||
:SetSortInfo("amount")
|
||||
:Commit()
|
||||
:NewColumn("time")
|
||||
:SetTitle(L["Time Frame"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo("time", private.TableGetTimeText)
|
||||
:SetSortInfo("time")
|
||||
:Commit()
|
||||
:Commit()
|
||||
:SetQuery(private.query)
|
||||
:SetSelectionDisabled(true)
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Scrolling Table Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.TableGetTypeText(typeValue)
|
||||
return TYPE_STR_LOOKUP[typeValue]
|
||||
end
|
||||
|
||||
function private.TableGetTimeText(timevalue)
|
||||
return SecondsToTime(time() - timevalue)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.DropdownChangedCommon(dropdown)
|
||||
private.UpdateQuery()
|
||||
dropdown:GetElement("__parent.__parent.table"):UpdateData(true)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.UpdateQuery()
|
||||
private.query:ResetFilters()
|
||||
:Equal("recordType", private.recordType)
|
||||
if Table.Count(private.typeFilter) ~= #TYPE_KEYS[private.recordType] then
|
||||
private.query:InTable("type", private.typeFilter)
|
||||
end
|
||||
if Table.Count(private.characterFilter) ~= #private.characters then
|
||||
private.query:InTable("player", private.characterFilter)
|
||||
end
|
||||
if private.timeFrameFilter ~= 0 then
|
||||
private.query:GreaterThan("time", time() - private.timeFrameFilter)
|
||||
end
|
||||
end
|
||||
329
Core/UI/MainUI/Ledger/Common/Transactions.lua
Normal file
329
Core/UI/MainUI/Ledger/Common/Transactions.lua
Normal file
@@ -0,0 +1,329 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Transactions = TSM.MainUI.Ledger.Common:NewPackage("Transactions")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local Money = TSM.Include("Util.Money")
|
||||
local String = TSM.Include("Util.String")
|
||||
local Table = TSM.Include("Util.Table")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local ItemInfo = TSM.Include("Service.ItemInfo")
|
||||
local Settings = TSM.Include("Service.Settings")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local SECONDS_PER_DAY = 24 * 60 * 60
|
||||
local private = {
|
||||
settings = nil,
|
||||
query = nil,
|
||||
characters = {},
|
||||
characterFilter = {},
|
||||
typeFilter = {},
|
||||
searchFilter = "",
|
||||
groupFilter = {},
|
||||
rarityList = {},
|
||||
rarityFilter = {},
|
||||
timeFrameFilter = 30 * SECONDS_PER_DAY,
|
||||
type = nil
|
||||
}
|
||||
local TYPE_LIST = { L["Auction"], COD, TRADE, L["Vendor"] }
|
||||
local TYPE_KEYS = { "Auction", "COD", "Trade", "Vendor" }
|
||||
do
|
||||
for _, key in ipairs(TYPE_KEYS) do
|
||||
private.typeFilter[key] = true
|
||||
end
|
||||
for i = 1, 4 do
|
||||
tinsert(private.rarityList, _G[format("ITEM_QUALITY%d_DESC", i)])
|
||||
private.rarityFilter[i] = true
|
||||
end
|
||||
end
|
||||
local TIME_LIST = { L["All Time"], L["Last 3 Days"], L["Last 7 Days"], L["Last 14 Days"], L["Last 30 Days"], L["Last 60 Days"] }
|
||||
local TIME_KEYS = { 0, 3 * SECONDS_PER_DAY, 7 * SECONDS_PER_DAY, 14 * SECONDS_PER_DAY, 30 * SECONDS_PER_DAY, 60 * SECONDS_PER_DAY }
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Transactions.OnInitialize()
|
||||
private.settings = Settings.NewView()
|
||||
:AddKey("global", "mainUIContext", "ledgerTransactionsScrollingTable")
|
||||
TSM.MainUI.Ledger.Expenses.RegisterPage(L["Purchases"], private.DrawPurchasesPage)
|
||||
TSM.MainUI.Ledger.Revenue.RegisterPage(L["Sales"], private.DrawSalesPage)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Transactions UIs
|
||||
-- ============================================================================
|
||||
|
||||
function private.DrawPurchasesPage()
|
||||
TSM.UI.AnalyticsRecordPathChange("main", "ledger", "expenses", "purchases")
|
||||
private.type = "buy"
|
||||
return private.DrawTransactionPage()
|
||||
end
|
||||
|
||||
function private.DrawSalesPage()
|
||||
TSM.UI.AnalyticsRecordPathChange("main", "ledger", "revenue", "sales")
|
||||
private.type = "sale"
|
||||
return private.DrawTransactionPage()
|
||||
end
|
||||
|
||||
function private.DrawTransactionPage()
|
||||
private.query = private.query or TSM.Accounting.Transactions.CreateQuery()
|
||||
|
||||
private.query:Reset()
|
||||
:Equal("type", private.type)
|
||||
:Distinct("player")
|
||||
:Select("player")
|
||||
wipe(private.characters)
|
||||
for _, character in private.query:Iterator() do
|
||||
tinsert(private.characters, character)
|
||||
private.characterFilter[character] = true
|
||||
end
|
||||
|
||||
private.query:Reset()
|
||||
:InnerJoin(ItemInfo.GetDBForJoin(), "itemString")
|
||||
:LeftJoin(TSM.Groups.GetItemDBForJoin(), "itemString")
|
||||
:VirtualField("total", "number", private.GetTotal)
|
||||
:VirtualField("auctions", "number", private.GetAuctions)
|
||||
:OrderBy("time", false)
|
||||
private.UpdateQuery()
|
||||
local numItems = private.query:Sum("quantity") or 0
|
||||
local total = private.query:Sum("total") or 0
|
||||
|
||||
return UIElements.New("Frame", "content")
|
||||
:SetLayout("VERTICAL")
|
||||
:AddChild(UIElements.New("Frame", "row1")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:SetMargin(8)
|
||||
:AddChild(UIElements.New("Input", "filter")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetIconTexture("iconPack.18x18/Search")
|
||||
:SetClearButtonEnabled(true)
|
||||
:AllowItemInsert()
|
||||
:SetHintText(L["Filter by keyword"])
|
||||
:SetValue(private.searchFilter)
|
||||
:SetScript("OnValueChanged", private.SearchFilterChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("GroupSelector", "group")
|
||||
:SetWidth(240)
|
||||
:SetHintText(L["Filter by groups"])
|
||||
:SetScript("OnSelectionChanged", private.GroupFilterChanged)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "row2")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:SetMargin(8, 8, 0, 8)
|
||||
:AddChild(UIElements.New("MultiselectionDropdown", "type")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetItems(TYPE_LIST, TYPE_KEYS)
|
||||
:SetSettingInfo(private, "typeFilter")
|
||||
:SetSelectionText(L["No Types"], L["%d Types"], L["All Types"])
|
||||
:SetScript("OnSelectionChanged", private.DropdownCommonOnSelectionChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("MultiselectionDropdown", "rarity")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetItems(private.rarityList)
|
||||
:SetSettingInfo(private, "rarityFilter")
|
||||
:SetSelectionText(L["No Rarities"], L["%d Rarities"], L["All Rarities"])
|
||||
:SetScript("OnSelectionChanged", private.DropdownCommonOnSelectionChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("MultiselectionDropdown", "character")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetItems(private.characters, private.characters)
|
||||
:SetSettingInfo(private, "characterFilter")
|
||||
:SetSelectionText(L["No Characters"], L["%d Characters"], L["All Characters"])
|
||||
:SetScript("OnSelectionChanged", private.DropdownCommonOnSelectionChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("SelectionDropdown", "time")
|
||||
:SetItems(TIME_LIST, TIME_KEYS)
|
||||
:SetSelectedItemByKey(private.timeFrameFilter)
|
||||
:SetSettingInfo(private, "timeFrameFilter")
|
||||
:SetScript("OnSelectionChanged", private.DropdownCommonOnSelectionChanged)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("QueryScrollingTable", "scrollingTable")
|
||||
:SetSettingsContext(private.settings, "ledgerTransactionsScrollingTable")
|
||||
:GetScrollingTableInfo()
|
||||
:NewColumn("item")
|
||||
:SetTitle(L["Item"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo("itemString", TSM.UI.GetColoredItemName)
|
||||
:SetTooltipInfo("itemString")
|
||||
:SetSortInfo("name")
|
||||
:DisableHiding()
|
||||
:Commit()
|
||||
:NewColumn("player")
|
||||
:SetTitle(PLAYER)
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo("otherPlayer")
|
||||
:SetSortInfo("otherPlayer")
|
||||
:Commit()
|
||||
:NewColumn("type")
|
||||
:SetTitle(L["Type"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo("source")
|
||||
:SetSortInfo("source")
|
||||
:Commit()
|
||||
:NewColumn("stack")
|
||||
:SetTitle(L["Stack"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("stackSize")
|
||||
:SetSortInfo("stackSize")
|
||||
:Commit()
|
||||
:NewColumn("auctions")
|
||||
:SetTitle(L["Auctions"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("auctions")
|
||||
:SetSortInfo("auctions")
|
||||
:Commit()
|
||||
:NewColumn("perItem")
|
||||
:SetTitle(L["Per Item"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("price", private.TableGetPriceText)
|
||||
:SetSortInfo("price")
|
||||
:Commit()
|
||||
:NewColumn("total")
|
||||
:SetTitle(L["Total"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("total", private.TableGetPriceText)
|
||||
:SetSortInfo("total")
|
||||
:Commit()
|
||||
:NewColumn("time")
|
||||
:SetTitle(L["Time Frame"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("time", private.TableGetTimeframeText)
|
||||
:SetSortInfo("time")
|
||||
:Commit()
|
||||
:Commit()
|
||||
:SetQuery(private.query)
|
||||
:SetScript("OnRowClick", private.TableSelectionChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("Texture", "line")
|
||||
:SetHeight(2)
|
||||
:SetTexture("ACTIVE_BG")
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "footer")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(40)
|
||||
:SetPadding(8)
|
||||
:SetBackgroundColor("PRIMARY_BG")
|
||||
:AddChild(UIElements.New("Text", "num")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(format(private.type == "sale" and L["%s Items Sold"] or L["%s Items Bought"], Theme.GetColor("INDICATOR"):ColorText(FormatLargeNumber(numItems))))
|
||||
)
|
||||
:AddChild(UIElements.New("Texture", "line")
|
||||
:SetMargin(4, 8, 0, 0)
|
||||
:SetWidth(2)
|
||||
:SetTexture("ACTIVE_BG")
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "profit")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(format(L["%s Total"], Money.ToString(total)))
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Scrolling Table Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.TableGetPriceText(price)
|
||||
return Money.ToString(price)
|
||||
end
|
||||
|
||||
function private.TableGetTimeframeText(record)
|
||||
return SecondsToTime(time() - record)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.DropdownCommonOnSelectionChanged(dropdown)
|
||||
private.UpdateQuery()
|
||||
local numItems = private.query:Sum("quantity") or 0
|
||||
local total = private.query:Sum("total") or 0
|
||||
dropdown:GetElement("__parent.__parent.scrollingTable")
|
||||
:UpdateData(true)
|
||||
local footer = dropdown:GetElement("__parent.__parent.footer")
|
||||
footer:GetElement("num"):SetText(format(private.type == "sale" and L["%s Items Sold"] or L["%s Items Bought"], Theme.GetColor("INDICATOR"):ColorText(FormatLargeNumber(numItems))))
|
||||
footer:GetElement("profit"):SetText(format(L["%s Total"], Money.ToString(total)))
|
||||
footer:Draw()
|
||||
end
|
||||
|
||||
function private.SearchFilterChanged(input)
|
||||
private.searchFilter = input:GetValue()
|
||||
private.DropdownCommonOnSelectionChanged(input)
|
||||
end
|
||||
|
||||
function private.GroupFilterChanged(groupSelector)
|
||||
wipe(private.groupFilter)
|
||||
for groupPath in groupSelector:SelectedGroupIterator() do
|
||||
private.groupFilter[groupPath] = true
|
||||
end
|
||||
private.DropdownCommonOnSelectionChanged(groupSelector)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetTotal(row)
|
||||
return row:GetField("price") * row:GetField("quantity")
|
||||
end
|
||||
|
||||
function private.GetAuctions(row)
|
||||
return row:GetField("quantity") / row:GetField("stackSize")
|
||||
end
|
||||
|
||||
function private.UpdateQuery()
|
||||
private.query:ResetFilters()
|
||||
:Equal("type", private.type)
|
||||
if private.searchFilter ~= "" then
|
||||
private.query:Matches("name", String.Escape(private.searchFilter))
|
||||
end
|
||||
if Table.Count(private.typeFilter) ~= #TYPE_KEYS then
|
||||
private.query:InTable("source", private.typeFilter)
|
||||
end
|
||||
if Table.Count(private.rarityFilter) ~= #private.rarityList then
|
||||
private.query:InTable("quality", private.rarityFilter)
|
||||
end
|
||||
if Table.Count(private.characterFilter) ~= #private.characters then
|
||||
private.query:InTable("player", private.characterFilter)
|
||||
end
|
||||
if private.timeFrameFilter ~= 0 then
|
||||
private.query:GreaterThan("time", time() - private.timeFrameFilter)
|
||||
end
|
||||
if next(private.groupFilter) then
|
||||
private.query:InTable("groupPath", private.groupFilter)
|
||||
end
|
||||
end
|
||||
|
||||
function private.TableSelectionChanged(scrollingTable, row)
|
||||
TSM.MainUI.Ledger.ShowItemDetail(scrollingTable:GetParentElement():GetParentElement(), row:GetField("itemString"), private.type)
|
||||
end
|
||||
524
Core/UI/MainUI/Ledger/Core.lua
Normal file
524
Core/UI/MainUI/Ledger/Core.lua
Normal file
@@ -0,0 +1,524 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Ledger = TSM.MainUI:NewPackage("Ledger")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local Table = TSM.Include("Util.Table")
|
||||
local Money = TSM.Include("Util.Money")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local Log = TSM.Include("Util.Log")
|
||||
local ItemInfo = TSM.Include("Service.ItemInfo")
|
||||
local Settings = TSM.Include("Service.Settings")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local SECONDS_PER_DAY = 24 * 60 * 60
|
||||
local private = {
|
||||
settings = nil,
|
||||
pages = {},
|
||||
childPages = {},
|
||||
callback = {},
|
||||
contextPath = nil,
|
||||
contextItemString = nil,
|
||||
itemDetailType = "sale",
|
||||
}
|
||||
local NUM_TOP_PLAYERS = 3
|
||||
local PAGE_PATH_SEP = "`"
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Ledger.OnInitialize()
|
||||
private.settings = Settings.NewView()
|
||||
:AddKey("global", "mainUIContext", "ledgerDetailScrollingTable")
|
||||
TSM.MainUI.RegisterTopLevelPage(L["Ledger"], private.GetLedgerFrame)
|
||||
end
|
||||
|
||||
function Ledger.RegisterPage(name, callback)
|
||||
tinsert(private.pages, name)
|
||||
private.callback[name] = callback
|
||||
end
|
||||
|
||||
function Ledger.RegisterChildPage(parentName, childName, callback)
|
||||
local path = parentName..PAGE_PATH_SEP..childName
|
||||
private.childPages[parentName] = private.childPages[parentName] or {}
|
||||
tinsert(private.childPages[parentName], childName)
|
||||
private.callback[path] = callback
|
||||
end
|
||||
|
||||
function Ledger.ShowItemDetail(frame, itemString, detailType)
|
||||
assert(detailType == "sale" or detailType == "buy")
|
||||
private.contextItemString = itemString
|
||||
private.itemDetailType = detailType
|
||||
frame:SetPath("itemDetail", true)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Ledger UI
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetLedgerFrame()
|
||||
TSM.UI.AnalyticsRecordPathChange("main", "ledger")
|
||||
local defaultPage = private.pages[1]
|
||||
local frame = UIElements.New("Frame", "ledger")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT")
|
||||
:AddChild(UIElements.New("Frame", "navigation")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetWidth(160)
|
||||
:SetPadding(12, 12, 1, 9)
|
||||
)
|
||||
:AddChild(UIElements.New("Texture", "divider")
|
||||
:SetWidth(2)
|
||||
:SetTexture("ACTIVE_BG")
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "contentFrame")
|
||||
:SetLayout("VERTICAL")
|
||||
:AddChild(UIElements.New("ViewContainer", "content")
|
||||
:SetNavCallback(private.ContentNavCallback)
|
||||
:AddPath("itemDetail")
|
||||
)
|
||||
)
|
||||
:SetScript("OnHide", private.NavButtonOnHide)
|
||||
|
||||
local content = frame:GetElement("contentFrame.content")
|
||||
local navFrame = frame:GetElement("navigation")
|
||||
for _, pageName in ipairs(private.pages) do
|
||||
navFrame:AddChild(UIElements.New("Button", pageName)
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 8, 0)
|
||||
:SetFont("BODY_BODY2_BOLD")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetContext(pageName)
|
||||
:SetText(pageName)
|
||||
:SetScript("OnClick", private.NavButtonOnClick)
|
||||
)
|
||||
content:AddPath(pageName, pageName == defaultPage)
|
||||
if private.childPages[pageName] then
|
||||
for _, childPageName in ipairs(private.childPages[pageName]) do
|
||||
local path = pageName..PAGE_PATH_SEP..childPageName
|
||||
navFrame:AddChild(UIElements.New("Button", path)
|
||||
:SetHeight(20)
|
||||
:SetMargin(9, 0, 8, 0)
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetContext(path)
|
||||
:SetText(childPageName)
|
||||
:SetScript("OnClick", private.NavButtonOnClick)
|
||||
)
|
||||
content:AddPath(path, path == defaultPage)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- make all the navigation align to the top
|
||||
navFrame:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
|
||||
private.UpdateNavFrame(navFrame, defaultPage)
|
||||
private.contextPath = L["Inventory"]
|
||||
return frame
|
||||
end
|
||||
|
||||
function private.ContentNavCallback(self, path)
|
||||
if path == "itemDetail" then
|
||||
return private.GetItemDetail()
|
||||
else
|
||||
return private.callback[path]()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.NavButtonOnClick(button)
|
||||
local path = button:GetContext()
|
||||
if private.contextPath == path then
|
||||
return
|
||||
end
|
||||
if private.childPages[path] then
|
||||
-- select the first child
|
||||
path = path..PAGE_PATH_SEP..private.childPages[path][1]
|
||||
end
|
||||
|
||||
local ledgerFrame = button:GetParentElement():GetParentElement()
|
||||
local contentFrame = ledgerFrame:GetElement("contentFrame")
|
||||
local navFrame = ledgerFrame:GetElement("navigation")
|
||||
private.UpdateNavFrame(navFrame, path)
|
||||
navFrame:Draw()
|
||||
contentFrame:GetElement("content"):SetPath(path, private.contextPath ~= path)
|
||||
private.contextPath = path
|
||||
end
|
||||
|
||||
function private.NavButtonOnHide(button)
|
||||
private.contextPath = nil
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.UpdateNavFrame(navFrame, selectedPath)
|
||||
local selectedPage = strsplit(PAGE_PATH_SEP, selectedPath)
|
||||
for _, pageName in ipairs(private.pages) do
|
||||
navFrame:GetElement(pageName):SetTextColor(pageName == selectedPage and "TEXT" or "ACTIVE_BG_ALT")
|
||||
if private.childPages[pageName] then
|
||||
for _, childPageName in ipairs(private.childPages[pageName]) do
|
||||
local path = pageName..PAGE_PATH_SEP..childPageName
|
||||
if pageName == selectedPage then
|
||||
navFrame:GetElement(path)
|
||||
:SetTextColor(path == selectedPath and "INDICATOR" or "TEXT")
|
||||
:Show()
|
||||
else
|
||||
navFrame:GetElement(path):Hide()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function private.GetItemDetail()
|
||||
local query = TSM.Accounting.Transactions.CreateQuery()
|
||||
:Equal("itemString", private.contextItemString)
|
||||
:OrderBy("time", false)
|
||||
|
||||
local topPlayersQuantity = TempTable.Acquire()
|
||||
local topPlayers = TempTable.Acquire()
|
||||
for _, row in query:Iterator() do
|
||||
local recordType, otherPlayer, quantity = row:GetFields("type", "otherPlayer", "quantity")
|
||||
if recordType == private.itemDetailType then
|
||||
if not topPlayersQuantity[otherPlayer] then
|
||||
topPlayersQuantity[otherPlayer] = 0
|
||||
tinsert(topPlayers, otherPlayer)
|
||||
end
|
||||
topPlayersQuantity[otherPlayer] = topPlayersQuantity[otherPlayer] + quantity
|
||||
end
|
||||
end
|
||||
|
||||
Table.SortWithValueLookup(topPlayers, topPlayersQuantity, true)
|
||||
local numTopPlayers = min(#topPlayers, NUM_TOP_PLAYERS)
|
||||
local topPlayersText = ""
|
||||
if numTopPlayers > 0 then
|
||||
for i = 1, numTopPlayers do
|
||||
local player = topPlayers[i]
|
||||
local quantity = topPlayersQuantity[player]
|
||||
topPlayers[i] = player..Theme.GetColor("INDICATOR_ALT"):ColorText(" (" .. quantity .. ")")
|
||||
end
|
||||
topPlayersText = table.concat(topPlayers, ", ", 1, numTopPlayers)
|
||||
else
|
||||
topPlayersText = L["None"]
|
||||
end
|
||||
TempTable.Release(topPlayers)
|
||||
TempTable.Release(topPlayersQuantity)
|
||||
|
||||
return UIElements.New("Frame", "content")
|
||||
:SetLayout("VERTICAL")
|
||||
:AddChild(UIElements.New("Frame", "top")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetPadding(8)
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT")
|
||||
:AddChild(UIElements.New("Frame", "header")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:AddChild(UIElements.New("ActionButton", "button")
|
||||
:SetWidth(64)
|
||||
:SetIcon("iconPack.14x14/Chevron/Right@180")
|
||||
:SetText(BACK)
|
||||
:SetScript("OnClick", private.ItemDetailBackButtonOnClick)
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "icon")
|
||||
:SetSize(24, 24)
|
||||
:SetMargin(14, 8, 0, 0)
|
||||
:SetBackground(ItemInfo.GetTexture(private.contextItemString))
|
||||
:SetTooltip(private.contextItemString)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "itemName")
|
||||
:SetFont("ITEM_BODY1")
|
||||
:SetText(TSM.UI.GetColoredItemName(private.contextItemString))
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "content")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetMargin(0, 0, 8, 0)
|
||||
:SetPadding(12, 12, 8, 10)
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT", true)
|
||||
:SetBorderColor("ACTIVE_BG")
|
||||
:AddChild(UIElements.New("Frame", "heading")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:AddChild(UIElements.New("Button", "saleBtn")
|
||||
:SetWidth("AUTO")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetFont("BODY_BODY2_BOLD")
|
||||
:SetTextColor(private.itemDetailType == "sale" and "INDICATOR" or "ACTIVE_BG_ALT")
|
||||
:SetContext("sale")
|
||||
:SetText(L["Sale Data"])
|
||||
:SetScript("OnClick", private.ItemDetailTabOnClick)
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "buyBtn")
|
||||
:SetWidth("AUTO")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetFont("BODY_BODY2_BOLD")
|
||||
:SetTextColor(private.itemDetailType == "buy" and "INDICATOR" or "ACTIVE_BG_ALT")
|
||||
:SetContext("buy")
|
||||
:SetText(L["Purchase Data"])
|
||||
:SetScript("OnClick", private.ItemDetailTabOnClick)
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
:AddChild(UIElements.New("Text", "total")
|
||||
:SetWidth(120)
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetText(L["Total"])
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "last7")
|
||||
:SetWidth(120)
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetText(L["Last 7 Days"])
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "last30")
|
||||
:SetWidth(120)
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetText(L["Last 30 Days"])
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "quantity")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 8, 0)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetTextColor("TEXT_ALT")
|
||||
:SetText(private.itemDetailType == "sale" and L["Quantity Sold:"] or L["Quantity Purchased:"])
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "total")
|
||||
:SetWidth(120)
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetText(TSM.Accounting.Transactions.GetQuantity(private.contextItemString, nil, private.itemDetailType))
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "last7")
|
||||
:SetWidth(120)
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetText(TSM.Accounting.Transactions.GetQuantity(private.contextItemString, SECONDS_PER_DAY * 7, private.itemDetailType))
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "last30")
|
||||
:SetWidth(120)
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetText(TSM.Accounting.Transactions.GetQuantity(private.contextItemString, SECONDS_PER_DAY * 30, private.itemDetailType))
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "avgPrice")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 8, 0)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetTextColor("TEXT_ALT")
|
||||
:SetText(L["Average Prices:"])
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "total")
|
||||
:SetWidth(120)
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetText(Money.ToString(TSM.Accounting.Transactions.GetAveragePrice(private.contextItemString, nil, private.itemDetailType)))
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "last7")
|
||||
:SetWidth(120)
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetText(Money.ToString(TSM.Accounting.Transactions.GetAveragePrice(private.contextItemString, SECONDS_PER_DAY * 7, private.itemDetailType)))
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "last30")
|
||||
:SetWidth(120)
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetText(Money.ToString(TSM.Accounting.Transactions.GetAveragePrice(private.contextItemString, SECONDS_PER_DAY * 30, private.itemDetailType)))
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "totalPrice")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 8, 0)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetTextColor("TEXT_ALT")
|
||||
:SetText(L["Total Prices:"])
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "total")
|
||||
:SetWidth(120)
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetText(Money.ToString(TSM.Accounting.Transactions.GetTotalPrice(private.contextItemString, nil, private.itemDetailType)))
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "last7")
|
||||
:SetWidth(120)
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetText(Money.ToString(TSM.Accounting.Transactions.GetTotalPrice(private.contextItemString, SECONDS_PER_DAY * 7, private.itemDetailType)))
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "last30")
|
||||
:SetWidth(120)
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetText(Money.ToString(TSM.Accounting.Transactions.GetTotalPrice(private.contextItemString, SECONDS_PER_DAY * 30, private.itemDetailType)))
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "top")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 8, 0)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth("AUTO")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetTextColor("TEXT_ALT")
|
||||
:SetText(private.itemDetailType == "sale" and L["Top Buyers"]..":" or L["Top Sellers"]..":")
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "value")
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetText(topPlayersText)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("QueryScrollingTable", "scrollingTable")
|
||||
:SetSettingsContext(private.settings, "ledgerDetailScrollingTable")
|
||||
:GetScrollingTableInfo()
|
||||
:NewColumn("activityType")
|
||||
:SetTitle(L["Activity Type"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo("type", private.TableGetActivityTypeText)
|
||||
:Commit()
|
||||
:NewColumn("source")
|
||||
:SetTitle(L["Source"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo("source")
|
||||
:Commit()
|
||||
:NewColumn("buyerSeller")
|
||||
:SetTitle(L["Buyer/Seller"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo("otherPlayer")
|
||||
:Commit()
|
||||
:NewColumn("qty")
|
||||
:SetTitle(L["Qty"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("quantity")
|
||||
:Commit()
|
||||
:NewColumn("perItem")
|
||||
:SetTitle(L["Per Item"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo(nil, private.TableGetPerItemText)
|
||||
:Commit()
|
||||
:NewColumn("totalPrice")
|
||||
:SetTitle(L["Total Price"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo(nil, private.TableGetTotalPriceText)
|
||||
:Commit()
|
||||
:NewColumn("time")
|
||||
:SetTitle(L["Time"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("time", private.TableGetTimeframeText)
|
||||
:Commit()
|
||||
:Commit()
|
||||
:SetQuery(query)
|
||||
:SetAutoReleaseQuery(true)
|
||||
:SetSelectionDisabled(true)
|
||||
:SetScript("OnRowClick", private.ItemDetailScrollingTableOnRowClick)
|
||||
)
|
||||
end
|
||||
|
||||
function private.ItemDetailBackButtonOnClick(button)
|
||||
button:GetParentElement():GetParentElement():GetParentElement():GetParentElement():SetPath(private.contextPath, true)
|
||||
end
|
||||
|
||||
function private.ItemDetailTabOnClick(button)
|
||||
private.itemDetailType = button:GetContext()
|
||||
button:GetParentElement():GetParentElement():GetParentElement():GetParentElement():GetParentElement():ReloadContent()
|
||||
end
|
||||
|
||||
function private.ItemDetailScrollingTableOnRowClick(scrollingTable, row, button)
|
||||
if button ~= "RightButton" then
|
||||
return
|
||||
elseif not TSM.Accounting.Transactions.CanDeleteByUUID(row:GetUUID()) then
|
||||
Log.PrintUser(L["This record belongs to another account and can only be deleted on that account."])
|
||||
return
|
||||
end
|
||||
local subtitle = nil
|
||||
local recordType, itemString, quantity, otherPlayer, price = row:GetFields("type", "itemString", "quantity", "otherPlayer", "price")
|
||||
local name = TSM.UI.GetColoredItemName(itemString) or "?"
|
||||
local amount = Money.ToString(price * quantity)
|
||||
if recordType == "sale" then
|
||||
subtitle = format(L["Sold %d of %s to %s for %s"], quantity, name, otherPlayer, amount)
|
||||
elseif recordType == "buy" then
|
||||
subtitle = format(L["Bought %d of %s from %s for %s"], quantity, name, otherPlayer, amount)
|
||||
else
|
||||
error("Unexpected Type: "..tostring(recordType))
|
||||
end
|
||||
scrollingTable:GetBaseElement():ShowConfirmationDialog(L["Delete Record?"], subtitle, private.DeleteRecordConfirmed, row:GetUUID())
|
||||
end
|
||||
|
||||
function private.DeleteRecordConfirmed(uuid)
|
||||
TSM.Accounting.Transactions.RemoveRowByUUID(uuid)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Scrolling Table Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.TableGetActivityTypeText(recordType)
|
||||
if recordType == "sale" then
|
||||
return L["Sale"]
|
||||
elseif recordType == "buy" then
|
||||
return L["Buy"]
|
||||
else
|
||||
error("Unexpected Type: "..tostring(recordType))
|
||||
end
|
||||
end
|
||||
|
||||
function private.TableGetTimeframeText(timestamp)
|
||||
return SecondsToTime(time() - timestamp)
|
||||
end
|
||||
|
||||
function private.TableGetTotalPriceText(row)
|
||||
return Money.ToString(row:GetField("price") * row:GetField("quantity"))
|
||||
end
|
||||
|
||||
function private.TableGetPerItemText(row)
|
||||
return Money.ToString(row:GetField("price"))
|
||||
end
|
||||
23
Core/UI/MainUI/Ledger/Expenses/Core.lua
Normal file
23
Core/UI/MainUI/Ledger/Expenses/Core.lua
Normal file
@@ -0,0 +1,23 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Expenses = TSM.MainUI.Ledger:NewPackage("Expenses")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Expenses.OnInitialize()
|
||||
TSM.MainUI.Ledger.RegisterPage(L["Expenses"])
|
||||
end
|
||||
|
||||
function Expenses.RegisterPage(name, callback)
|
||||
TSM.MainUI.Ledger.RegisterChildPage(L["Expenses"], name, callback)
|
||||
end
|
||||
23
Core/UI/MainUI/Ledger/FailedAuctions/Core.lua
Normal file
23
Core/UI/MainUI/Ledger/FailedAuctions/Core.lua
Normal file
@@ -0,0 +1,23 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local FailedAuctions = TSM.MainUI.Ledger:NewPackage("FailedAuctions")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function FailedAuctions.OnInitialize()
|
||||
TSM.MainUI.Ledger.RegisterPage(L["Failed Auctions"])
|
||||
end
|
||||
|
||||
function FailedAuctions.RegisterPage(name, callback)
|
||||
TSM.MainUI.Ledger.RegisterChildPage(L["Failed Auctions"], name, callback)
|
||||
end
|
||||
329
Core/UI/MainUI/Ledger/Inventory.lua
Normal file
329
Core/UI/MainUI/Ledger/Inventory.lua
Normal file
@@ -0,0 +1,329 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Inventory = TSM.MainUI.Ledger:NewPackage("Inventory")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local Money = TSM.Include("Util.Money")
|
||||
local String = TSM.Include("Util.String")
|
||||
local Math = TSM.Include("Util.Math")
|
||||
local Database = TSM.Include("Util.Database")
|
||||
local ItemInfo = TSM.Include("Service.ItemInfo")
|
||||
local CustomPrice = TSM.Include("Service.CustomPrice")
|
||||
local BagTracking = TSM.Include("Service.BagTracking")
|
||||
local GuildTracking = TSM.Include("Service.GuildTracking")
|
||||
local AuctionTracking = TSM.Include("Service.AuctionTracking")
|
||||
local MailTracking = TSM.Include("Service.MailTracking")
|
||||
local AltTracking = TSM.Include("Service.AltTracking")
|
||||
local InventoryService = TSM.Include("Service.Inventory")
|
||||
local Settings = TSM.Include("Service.Settings")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local private = {
|
||||
settings = nil,
|
||||
db = nil,
|
||||
query = nil,
|
||||
searchFilter = "",
|
||||
groupFilter = {},
|
||||
valuePriceSource = "dbmarket", -- luacheck: ignore 1005 - hidden modify via SetSettingInfo()
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Inventory.OnInitialize()
|
||||
private.settings = Settings.NewView()
|
||||
:AddKey("global", "mainUIContext", "ledgerInventoryScrollingTable")
|
||||
TSM.MainUI.Ledger.RegisterPage(L["Inventory"], private.DrawInventoryPage)
|
||||
end
|
||||
|
||||
function Inventory.OnEnable()
|
||||
private.db = Database.NewSchema("LEDGER_INVENTORY")
|
||||
:AddUniqueStringField("itemString")
|
||||
:Commit()
|
||||
private.query = private.db:NewQuery()
|
||||
:VirtualField("bagQuantity", "number", BagTracking.GetBagsQuantityByBaseItemString, "itemString")
|
||||
:VirtualField("guildQuantity", "number", private.GuildQuantityVirtualField, "itemString")
|
||||
:VirtualField("auctionQuantity", "number", AuctionTracking.GetQuantityByBaseItemString, "itemString")
|
||||
:VirtualField("mailQuantity", "number", MailTracking.GetQuantityByBaseItemString, "itemString")
|
||||
:VirtualField("altQuantity", "number", AltTracking.GetQuantityByBaseItemString, "itemString")
|
||||
:VirtualField("totalQuantity", "number", private.TotalQuantityVirtualField)
|
||||
:VirtualField("totalValue", "number", private.TotalValueVirtualField)
|
||||
:VirtualField("totalBankQuantity", "number", private.GetTotalBankQuantity)
|
||||
:InnerJoin(ItemInfo.GetDBForJoin(), "itemString")
|
||||
:LeftJoin(TSM.Groups.GetItemDBForJoin(), "itemString")
|
||||
:OrderBy("name", true)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Inventory UI
|
||||
-- ============================================================================
|
||||
|
||||
function private.DrawInventoryPage()
|
||||
TSM.UI.AnalyticsRecordPathChange("main", "ledger", "inventory")
|
||||
local items = TempTable.Acquire()
|
||||
for _, itemString in BagTracking.BaseItemIterator() do
|
||||
items[itemString] = true
|
||||
end
|
||||
for _, itemString in GuildTracking.BaseItemIterator() do
|
||||
items[itemString] = true
|
||||
end
|
||||
for _, itemString in AuctionTracking.BaseItemIterator() do
|
||||
items[itemString] = true
|
||||
end
|
||||
for _, itemString in MailTracking.BaseItemIterator() do
|
||||
items[itemString] = true
|
||||
end
|
||||
for _, itemString in AltTracking.BaseItemIterator() do
|
||||
items[itemString] = true
|
||||
end
|
||||
private.db:TruncateAndBulkInsertStart()
|
||||
for itemString in pairs(items) do
|
||||
private.db:BulkInsertNewRow(itemString)
|
||||
end
|
||||
private.db:BulkInsertEnd()
|
||||
TempTable.Release(items)
|
||||
private.UpdateQuery()
|
||||
|
||||
return UIElements.New("Frame", "content")
|
||||
:SetLayout("VERTICAL")
|
||||
:AddChild(UIElements.New("Frame", "row1")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:SetMargin(8)
|
||||
:AddChild(UIElements.New("Input", "filter")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetIconTexture("iconPack.18x18/Search")
|
||||
:SetClearButtonEnabled(true)
|
||||
:AllowItemInsert()
|
||||
:SetHintText(L["Filter by keyword"])
|
||||
:SetValue(private.searchFilter)
|
||||
:SetScript("OnValueChanged", private.SearchFilterChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("GroupSelector", "group")
|
||||
:SetWidth(240)
|
||||
:SetHintText(L["Filter by groups"])
|
||||
:SetScript("OnSelectionChanged", private.GroupFilterChanged)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "row2")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:SetMargin(8, 8, 0, 8)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetText(L["Value Price Source"])
|
||||
)
|
||||
:AddChild(UIElements.New("Input", "input")
|
||||
:SetMargin(4, 8, 0, 0)
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT")
|
||||
:SetBorderColor("ACTIVE_BG")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetValidateFunc("CUSTOM_PRICE")
|
||||
:SetSettingInfo(private, "valuePriceSource")
|
||||
:SetScript("OnValueChanged", private.FilterChangedCommon)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "value")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetWidth(240)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetMargin(0, 4, 0, 0)
|
||||
:SetText(L["Total Value"]..":")
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "value")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetText(Money.ToString(private.GetTotalValue()))
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "accountingScrollingTableFrame")
|
||||
:SetLayout("VERTICAL")
|
||||
:AddChild(UIElements.New("QueryScrollingTable", "scrollingTable")
|
||||
:SetSettingsContext(private.settings, "ledgerInventoryScrollingTable")
|
||||
:GetScrollingTableInfo()
|
||||
:NewColumn("item")
|
||||
:SetTitle(L["Item"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo("itemString", TSM.UI.GetColoredItemName)
|
||||
:SetTooltipInfo("itemString")
|
||||
:SetSortInfo("name")
|
||||
:DisableHiding()
|
||||
:Commit()
|
||||
:NewColumn("totalItems")
|
||||
:SetTitle(L["Total"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("totalQuantity")
|
||||
:SetSortInfo("totalQuantity")
|
||||
:Commit()
|
||||
:NewColumn("bags")
|
||||
:SetTitle(L["Bags"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("bagQuantity")
|
||||
:SetSortInfo("bagQuantity")
|
||||
:Commit()
|
||||
:NewColumn("banks")
|
||||
:SetTitle(L["Banks"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("totalBankQuantity")
|
||||
:SetSortInfo("totalBankQuantity")
|
||||
:Commit()
|
||||
:NewColumn("mail")
|
||||
:SetTitle(L["Mail"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("mailQuantity")
|
||||
:SetSortInfo("mailQuantity")
|
||||
:Commit()
|
||||
:NewColumn("alts")
|
||||
:SetTitle(L["Alts"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("altQuantity")
|
||||
:SetSortInfo("altQuantity")
|
||||
:Commit()
|
||||
:NewColumn("guildVault")
|
||||
:SetTitle(L["GVault"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("guildQuantity")
|
||||
:SetSortInfo("guildQuantity")
|
||||
:Commit()
|
||||
:NewColumn("auctionHouse")
|
||||
:SetTitle(L["AH"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("auctionQuantity")
|
||||
:SetSortInfo("auctionQuantity")
|
||||
:Commit()
|
||||
:NewColumn("totalValue")
|
||||
:SetTitle(L["Value"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("totalValue", private.TableGetTotalValueText)
|
||||
:SetSortInfo("totalValue")
|
||||
:Commit()
|
||||
:Commit()
|
||||
:SetSelectionDisabled(true)
|
||||
:SetQuery(private.query)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.FilterChangedCommon(element)
|
||||
private.UpdateQuery()
|
||||
element:GetElement("__parent.__parent.accountingScrollingTableFrame.scrollingTable")
|
||||
:SetQuery(private.query, true)
|
||||
element:GetElement("__parent.__parent.row2.value.value")
|
||||
:SetText(Money.ToString(private.GetTotalValue()))
|
||||
:Draw()
|
||||
end
|
||||
|
||||
function private.SearchFilterChanged(input)
|
||||
private.searchFilter = input:GetValue()
|
||||
private.FilterChangedCommon(input)
|
||||
end
|
||||
|
||||
function private.GroupFilterChanged(groupSelector)
|
||||
wipe(private.groupFilter)
|
||||
for groupPath in groupSelector:SelectedGroupIterator() do
|
||||
private.groupFilter[groupPath] = true
|
||||
end
|
||||
private.FilterChangedCommon(groupSelector)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Scrolling Table Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.TableGetTotalValueText(totalValue)
|
||||
return Math.IsNan(totalValue) and "" or Money.ToString(totalValue)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.GuildQuantityVirtualField(itemString)
|
||||
local totalNum = 0
|
||||
for guildName in pairs(TSM.db.factionrealm.internalData.guildVaults) do
|
||||
local guildQuantity = InventoryService.GetGuildQuantity(itemString, guildName)
|
||||
totalNum = totalNum + guildQuantity
|
||||
end
|
||||
return totalNum
|
||||
end
|
||||
|
||||
function private.TotalQuantityVirtualField(row)
|
||||
local bagQuantity, totalBankQuantity, guildQuantity, auctionQuantity, mailQuantity, altQuantity = row:GetFields("bagQuantity", "totalBankQuantity", "guildQuantity", "auctionQuantity", "mailQuantity", "altQuantity")
|
||||
return bagQuantity + totalBankQuantity + guildQuantity + auctionQuantity + mailQuantity + altQuantity
|
||||
end
|
||||
|
||||
function private.TotalValueVirtualField(row)
|
||||
local itemString, totalQuantity = row:GetFields("itemString", "totalQuantity")
|
||||
local price = CustomPrice.GetValue(private.valuePriceSource, itemString)
|
||||
if not price then
|
||||
return Math.GetNan()
|
||||
end
|
||||
return price * totalQuantity
|
||||
end
|
||||
|
||||
function private.GetTotalBankQuantity(row)
|
||||
local itemString = row:GetField("itemString")
|
||||
local bankQuantity = BagTracking.GetBankQuantityByBaseItemString(itemString)
|
||||
local reagentBankQuantity = BagTracking.GetReagentBankQuantityByBaseItemString(itemString)
|
||||
return bankQuantity + reagentBankQuantity
|
||||
end
|
||||
|
||||
function private.GetTotalValue()
|
||||
-- can't lookup the value of items while the query is iteratoring, so grab the list of items first
|
||||
local itemQuantities = TempTable.Acquire()
|
||||
for _, row in private.query:Iterator() do
|
||||
local itemString, total = row:GetFields("itemString", "totalQuantity")
|
||||
itemQuantities[itemString] = total
|
||||
end
|
||||
local totalValue = 0
|
||||
for itemString, total in pairs(itemQuantities) do
|
||||
local price = CustomPrice.GetValue(private.valuePriceSource, itemString)
|
||||
if price then
|
||||
totalValue = totalValue + price * total
|
||||
end
|
||||
end
|
||||
TempTable.Release(itemQuantities)
|
||||
return totalValue
|
||||
end
|
||||
|
||||
function private.UpdateQuery()
|
||||
private.query:ResetFilters()
|
||||
if private.searchFilter ~= "" then
|
||||
private.query:Matches("name", String.Escape(private.searchFilter))
|
||||
end
|
||||
if next(private.groupFilter) then
|
||||
private.query:InTable("groupPath", private.groupFilter)
|
||||
end
|
||||
end
|
||||
23
Core/UI/MainUI/Ledger/Revenue/Core.lua
Normal file
23
Core/UI/MainUI/Ledger/Revenue/Core.lua
Normal file
@@ -0,0 +1,23 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Revenue = TSM.MainUI.Ledger:NewPackage("Revenue")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Revenue.OnInitialize()
|
||||
TSM.MainUI.Ledger.RegisterPage(L["Revenue"])
|
||||
end
|
||||
|
||||
function Revenue.RegisterPage(name, callback)
|
||||
TSM.MainUI.Ledger.RegisterChildPage(L["Revenue"], name, callback)
|
||||
end
|
||||
299
Core/UI/MainUI/Ledger/Revenue/Resale.lua
Normal file
299
Core/UI/MainUI/Ledger/Revenue/Resale.lua
Normal file
@@ -0,0 +1,299 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Resale = TSM.MainUI.Ledger.Revenue:NewPackage("Resale")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local Table = TSM.Include("Util.Table")
|
||||
local Money = TSM.Include("Util.Money")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local ItemInfo = TSM.Include("Service.ItemInfo")
|
||||
local Settings = TSM.Include("Service.Settings")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local SECONDS_PER_DAY = 24 * 60 * 60
|
||||
local private = {
|
||||
settings = nil,
|
||||
summaryQuery = nil,
|
||||
characters = {},
|
||||
characterFilter = {},
|
||||
typeFilter = {},
|
||||
rarityList = {},
|
||||
rarityFilter = {},
|
||||
groupFilter = {},
|
||||
searchFilter = "",
|
||||
timeFrameFilter = 30 * SECONDS_PER_DAY
|
||||
}
|
||||
local TYPE_LIST = { L["Auction"], COD, TRADE, L["Vendor"] }
|
||||
local TYPE_KEYS = { "Auction", "COD", "Trade", "Vendor" }
|
||||
do
|
||||
for _, key in ipairs(TYPE_KEYS) do
|
||||
private.typeFilter[key] = true
|
||||
end
|
||||
for i = 1, 4 do
|
||||
tinsert(private.rarityList, _G[format("ITEM_QUALITY%d_DESC", i)])
|
||||
private.rarityFilter[i] = true
|
||||
end
|
||||
end
|
||||
local TIME_LIST = { L["All Time"], L["Last 3 Days"], L["Last 7 Days"], L["Last 14 Days"], L["Last 30 Days"], L["Last 60 Days"] }
|
||||
local TIME_KEYS = { 0, 3 * SECONDS_PER_DAY, 7 * SECONDS_PER_DAY, 14 * SECONDS_PER_DAY, 30 * SECONDS_PER_DAY, 60 * SECONDS_PER_DAY }
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Resale.OnInitialize()
|
||||
private.settings = Settings.NewView()
|
||||
:AddKey("global", "mainUIContext", "ledgerResaleScrollingTable")
|
||||
TSM.MainUI.Ledger.Revenue.RegisterPage(L["Resale"], private.DrawResalePage)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Resale UI
|
||||
-- ============================================================================
|
||||
|
||||
function private.DrawResalePage()
|
||||
TSM.UI.AnalyticsRecordPathChange("main", "ledger", "revenue", "resale")
|
||||
wipe(private.characters)
|
||||
TSM.Accounting.Transactions.GetCharacters(private.characters)
|
||||
for _, character in ipairs(private.characters) do
|
||||
private.characterFilter[character] = true
|
||||
end
|
||||
|
||||
private.summaryQuery = private.summaryQuery or TSM.Accounting.Transactions.CreateSummaryQuery()
|
||||
:InnerJoin(ItemInfo.GetDBForJoin(), "itemString")
|
||||
:OrderBy("name", true)
|
||||
private.UpdateQuery()
|
||||
local totalProfit = 0
|
||||
local numItems = 0
|
||||
for _, row in private.summaryQuery:Iterator() do
|
||||
totalProfit = totalProfit + row:GetField("totalProfit")
|
||||
numItems = numItems + min(row:GetFields("sold", "bought"))
|
||||
end
|
||||
|
||||
return UIElements.New("Frame", "content")
|
||||
:SetLayout("VERTICAL")
|
||||
:AddChild(UIElements.New("Frame", "row1")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:SetMargin(8)
|
||||
:AddChild(UIElements.New("Input", "filter")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetIconTexture("iconPack.18x18/Search")
|
||||
:SetClearButtonEnabled(true)
|
||||
:AllowItemInsert()
|
||||
:SetHintText(L["Filter by keyword"])
|
||||
:SetValue(private.searchFilter)
|
||||
:SetScript("OnValueChanged", private.SearchFilterChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("GroupSelector", "group")
|
||||
:SetWidth(240)
|
||||
:SetHintText(L["Filter by groups"])
|
||||
:SetScript("OnSelectionChanged", private.GroupFilterChanged)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "row2")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:SetMargin(8, 8, 0, 8)
|
||||
:AddChild(UIElements.New("MultiselectionDropdown", "type")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetItems(TYPE_LIST, TYPE_KEYS)
|
||||
:SetSettingInfo(private, "typeFilter")
|
||||
:SetSelectionText(L["No Types"], L["%d Types"], L["All Types"])
|
||||
:SetScript("OnSelectionChanged", private.FilterChangedCommon)
|
||||
)
|
||||
:AddChild(UIElements.New("MultiselectionDropdown", "rarity")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetItems(private.rarityList)
|
||||
:SetSettingInfo(private, "rarityFilter")
|
||||
:SetSelectionText(L["No Rarities"], L["%d Rarities"], L["All Rarities"])
|
||||
:SetScript("OnSelectionChanged", private.FilterChangedCommon)
|
||||
)
|
||||
:AddChild(UIElements.New("MultiselectionDropdown", "character")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetItems(private.characters, private.characters)
|
||||
:SetSettingInfo(private, "characterFilter")
|
||||
:SetSelectionText(L["No Characters"], L["%d Characters"], L["All Characters"])
|
||||
:SetScript("OnSelectionChanged", private.FilterChangedCommon)
|
||||
)
|
||||
:AddChild(UIElements.New("SelectionDropdown", "time")
|
||||
:SetItems(TIME_LIST, TIME_KEYS)
|
||||
:SetSelectedItemByKey(private.timeFrameFilter)
|
||||
:SetSettingInfo(private, "timeFrameFilter")
|
||||
:SetScript("OnSelectionChanged", private.FilterChangedCommon)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("QueryScrollingTable", "scrollingTable")
|
||||
:SetSettingsContext(private.settings, "ledgerResaleScrollingTable")
|
||||
:GetScrollingTableInfo()
|
||||
:NewColumn("item")
|
||||
:SetTitle(L["Item"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo("itemString", TSM.UI.GetColoredItemName)
|
||||
:SetSortInfo("name")
|
||||
:SetTooltipInfo("itemString")
|
||||
:DisableHiding()
|
||||
:Commit()
|
||||
:NewColumn("bought")
|
||||
:SetTitle(L["Bought"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("bought")
|
||||
:SetSortInfo("bought")
|
||||
:Commit()
|
||||
:NewColumn("avgBuyPrice")
|
||||
:SetTitle(L["Avg Buy Price"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("avgBuyPrice", private.GetMoneyText)
|
||||
:SetSortInfo("avgBuyPrice")
|
||||
:Commit()
|
||||
:NewColumn("sold")
|
||||
:SetTitle(L["Sold"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("sold")
|
||||
:SetSortInfo("sold")
|
||||
:Commit()
|
||||
:NewColumn("avgSellPrice")
|
||||
:SetTitle(L["Avg Sell Price"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("avgSellPrice", private.GetMoneyText)
|
||||
:SetSortInfo("avgSellPrice")
|
||||
:Commit()
|
||||
:NewColumn("avgProfit")
|
||||
:SetTitle(L["Avg Profit"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("avgProfit", private.GetColoredMoneyText)
|
||||
:SetSortInfo("avgProfit")
|
||||
:Commit()
|
||||
:NewColumn("totalProfit")
|
||||
:SetTitle(L["Total Profit"])
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("totalProfit", private.GetColoredMoneyText)
|
||||
:SetSortInfo("totalProfit")
|
||||
:Commit()
|
||||
:NewColumn("profitPct")
|
||||
:SetTitle("%")
|
||||
:SetFont("ITEM_BODY3")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetTextInfo("profitPct", private.GetPctText)
|
||||
:SetSortInfo("profitPct")
|
||||
:Commit()
|
||||
:Commit()
|
||||
:SetQuery(private.summaryQuery)
|
||||
:SetScript("OnRowClick", private.TableSelectionChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("Texture", "line")
|
||||
:SetHeight(2)
|
||||
:SetTexture("ACTIVE_BG")
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "footer")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(40)
|
||||
:SetPadding(8)
|
||||
:SetBackgroundColor("PRIMARY_BG")
|
||||
:AddChild(UIElements.New("Text", "num")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(format(L["%s Items Resold"], Theme.GetColor("INDICATOR"):ColorText(FormatLargeNumber(numItems))))
|
||||
)
|
||||
:AddChild(UIElements.New("Texture", "line")
|
||||
:SetMargin(4, 8, 0, 0)
|
||||
:SetWidth(2)
|
||||
:SetTexture("ACTIVE_BG")
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "profit")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(format(L["%s Total Profit"], Money.ToString(totalProfit)))
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Scrolling Table Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetMoneyText(value)
|
||||
return Money.ToString(value)
|
||||
end
|
||||
|
||||
function private.GetColoredMoneyText(value)
|
||||
return Money.ToString(value, Theme.GetFeedbackColor(value >= 0 and "GREEN" or "RED"):GetTextColorPrefix())
|
||||
end
|
||||
|
||||
function private.GetPctText(value)
|
||||
return Theme.GetFeedbackColor(value >= 0 and "GREEN" or "RED"):ColorText(value.."%")
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.FilterChangedCommon(dropdown)
|
||||
private.UpdateQuery()
|
||||
local totalProfit = 0
|
||||
local numItems = 0
|
||||
for _, row in private.summaryQuery:Iterator() do
|
||||
totalProfit = totalProfit + row:GetField("totalProfit")
|
||||
numItems = numItems + min(row:GetFields("sold", "bought"))
|
||||
end
|
||||
dropdown:GetElement("__parent.__parent.scrollingTable"):UpdateData(true)
|
||||
local footer = dropdown:GetElement("__parent.__parent.footer")
|
||||
footer:GetElement("num"):SetText(format(L["%s Items Resold"], Theme.GetColor("INDICATOR"):ColorText(FormatLargeNumber(numItems))))
|
||||
footer:GetElement("profit"):SetText(format(L["%s Total Profit"], Money.ToString(totalProfit)))
|
||||
footer:Draw()
|
||||
end
|
||||
|
||||
function private.SearchFilterChanged(input)
|
||||
private.searchFilter = input:GetValue()
|
||||
private.FilterChangedCommon(input)
|
||||
end
|
||||
|
||||
function private.GroupFilterChanged(groupSelector)
|
||||
wipe(private.groupFilter)
|
||||
for groupPath in groupSelector:SelectedGroupIterator() do
|
||||
private.groupFilter[groupPath] = true
|
||||
end
|
||||
private.FilterChangedCommon(groupSelector)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.UpdateQuery()
|
||||
private.summaryQuery:ResetFilters()
|
||||
local groupFilter = next(private.groupFilter) and private.groupFilter or nil
|
||||
local searchFilter = private.searchFilter ~= "" and private.searchFilter or nil
|
||||
local typeFilter = Table.Count(private.typeFilter) ~= #TYPE_KEYS and private.typeFilter or nil
|
||||
local characterFilter = Table.Count(private.characterFilter) ~= #private.characters and private.characterFilter or nil
|
||||
local minTime = private.timeFrameFilter ~= 0 and (time() - private.timeFrameFilter) or nil
|
||||
TSM.Accounting.Transactions.UpdateSummaryData(groupFilter, searchFilter, typeFilter, characterFilter, minTime)
|
||||
if Table.Count(private.rarityFilter) ~= #private.rarityList then
|
||||
private.summaryQuery:InTable("quality", private.rarityFilter)
|
||||
end
|
||||
end
|
||||
|
||||
function private.TableSelectionChanged(scrollingTable, row)
|
||||
TSM.MainUI.Ledger.ShowItemDetail(scrollingTable:GetParentElement():GetParentElement(), row:GetField("itemString"), "sale")
|
||||
end
|
||||
360
Core/UI/MainUI/Operations/Auctioning.lua
Normal file
360
Core/UI/MainUI/Operations/Auctioning.lua
Normal file
@@ -0,0 +1,360 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Auctioning = TSM.MainUI.Operations:NewPackage("Auctioning")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local String = TSM.Include("Util.String")
|
||||
local Money = TSM.Include("Util.Money")
|
||||
local Vararg = TSM.Include("Util.Vararg")
|
||||
local CustomPrice = TSM.Include("Service.CustomPrice")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local private = {
|
||||
currentOperationName = nil,
|
||||
currentTab = nil,
|
||||
}
|
||||
local IGNORE_DURATION_OPTIONS = {
|
||||
L["None"],
|
||||
AUCTION_TIME_LEFT1.." ("..AUCTION_TIME_LEFT1_DETAIL..")",
|
||||
AUCTION_TIME_LEFT2.." ("..AUCTION_TIME_LEFT2_DETAIL..")",
|
||||
AUCTION_TIME_LEFT3.." ("..AUCTION_TIME_LEFT3_DETAIL..")",
|
||||
}
|
||||
local BELOW_MIN_ITEMS = { L["Don't Post Items"], L["Post at Minimum Price"], L["Post at Maximum Price"], L["Post at Normal Price"], L["Ignore Auctions Below Min"] }
|
||||
local BELOW_MIN_KEYS = { "none", "minPrice", "maxPrice", "normalPrice", "ignore" }
|
||||
local ABOVE_MAX_ITEMS = { L["Don't Post Items"], L["Post at Minimum Price"], L["Post at Maximum Price"], L["Post at Normal Price"] }
|
||||
local ABOVE_MAX_KEYS = { "none", "minPrice", "maxPrice", "normalPrice" }
|
||||
local BAD_PRICE_SOURCES = { auctioningopmin = true, auctioningopmax = true, auctioningopnormal = true }
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Auctioning.OnInitialize()
|
||||
TSM.MainUI.Operations.RegisterModule("Auctioning", private.GetAuctioningOperationSettings)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Auctioning Operation Settings UI
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetAuctioningOperationSettings(operationName)
|
||||
TSM.UI.AnalyticsRecordPathChange("main", "operations", "auctioning")
|
||||
private.currentOperationName = operationName
|
||||
private.currentTab = private.currentTab or L["Details"]
|
||||
return UIElements.New("TabGroup", "tabs")
|
||||
:SetMargin(0, 0, 8, 0)
|
||||
:SetNavCallback(private.GetAuctioningSettings)
|
||||
:AddPath(L["Details"])
|
||||
:AddPath(L["Posting"])
|
||||
:AddPath(L["Canceling"])
|
||||
:SetPath(private.currentTab)
|
||||
end
|
||||
|
||||
function private.GetDetailsSettings()
|
||||
TSM.UI.AnalyticsRecordPathChange("main", "operations", "auctioning", "details")
|
||||
local operation = TSM.Operations.GetSettings("Auctioning", private.currentOperationName)
|
||||
return UIElements.New("ScrollFrame", "settings")
|
||||
:SetPadding(8, 8, 8, 0)
|
||||
:SetBackgroundColor("PRIMARY_BG")
|
||||
:AddChild(TSM.MainUI.Operations.CreateExpandableSection("Auctioning", "generalOptions", L["General Options"], L["Adjust some general settings."])
|
||||
:AddChild(UIElements.New("Frame", "content")
|
||||
:SetLayout("VERTICAL")
|
||||
:AddChildrenWithFunction(private.AddMaxStackSizeSetting)
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedSettingLine("ignoreLowDuration", L["Ignore auctions by duration"])
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(48)
|
||||
:SetMargin(0, 0, 0, 16)
|
||||
:AddChild(UIElements.New("SelectionDropdown", "dropdown")
|
||||
:SetHeight(24)
|
||||
:SetDisabled(TSM.Operations.HasRelationship("Auctioning", private.currentOperationName, "ignoreLowDuration"))
|
||||
:SetItems(IGNORE_DURATION_OPTIONS)
|
||||
:SetSelectedItemByKey(operation.ignoreLowDuration + 1, true)
|
||||
:SetScript("OnSelectionChanged", private.IgnoreLowDurationOnSelectionChanged)
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedSettingLine("blacklist", L["Blacklisted players"])
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(48)
|
||||
:AddChild(UIElements.New("Input", "input")
|
||||
:SetHeight(24)
|
||||
:SetBackgroundColor("ACTIVE_BG")
|
||||
:SetHintText(L["Enter player name"])
|
||||
:SetDisabled(TSM.Operations.HasRelationship("Auctioning", private.currentOperationName, "blacklist"))
|
||||
:SetScript("OnEnterPressed", private.BlacklistInputOnEnterPressed)
|
||||
)
|
||||
)
|
||||
:AddChildrenWithFunction(private.AddBlacklistPlayers)
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.GetOperationManagementElements("Auctioning", private.currentOperationName))
|
||||
end
|
||||
|
||||
function private.AddMaxStackSizeSetting(frame)
|
||||
if TSM.IsWowClassic() then
|
||||
frame:AddChild(private.CreateToggleLine("matchStackSize", L["Match stack size"]))
|
||||
end
|
||||
end
|
||||
|
||||
function private.GetPostingSettings()
|
||||
TSM.UI.AnalyticsRecordPathChange("main", "operations", "auctioning", "posting")
|
||||
local operation = TSM.Operations.GetSettings("Auctioning", private.currentOperationName)
|
||||
return UIElements.New("ScrollFrame", "settings")
|
||||
:SetPadding(8, 8, 8, 0)
|
||||
:SetBackgroundColor("PRIMARY_BG")
|
||||
:AddChild(TSM.MainUI.Operations.CreateExpandableSection("Auctioning", "postingSettings", L["Posting Options"], L["Adjust the settings below to set how groups attached to this operation will be auctioned."])
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedSettingLine("duration", L["Auction duration"])
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(48)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:AddChild(UIElements.New("SelectionDropdown", "dropdown")
|
||||
:SetHeight(24)
|
||||
:SetDisabled(TSM.Operations.HasRelationship("Auctioning", private.currentOperationName, "duration"))
|
||||
:SetItems(TSM.CONST.AUCTION_DURATIONS)
|
||||
:SetSelectedItemByKey(operation.duration)
|
||||
:SetScript("OnSelectionChanged", private.SetAuctioningDuration)
|
||||
)
|
||||
|
||||
)
|
||||
:AddChild(private.CreateInputLine("postCap", L["Post cap"], false, true))
|
||||
:AddChildrenWithFunction(private.AddStackSizeSettings)
|
||||
:AddChild(private.CreateInputLine("keepQuantity", L["Amount kept in bags"], false, true))
|
||||
:AddChild(private.CreateInputLine("maxExpires", L["Don't post after this many expires"]))
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.CreateExpandableSection("Auctioning", "priceSettings", L["Posting Price"], L["Adjust the settings below to set how groups attached to this operation will be priced."])
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedSettingLine("bidPercent", L["Set bid as percentage of buyout"])
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(48)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:AddChild(UIElements.New("Frame", "content")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:AddChild(UIElements.New("Input", "input")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetBackgroundColor("ACTIVE_BG")
|
||||
:SetValidateFunc(private.BidPercentValidateFunc)
|
||||
:SetDisabled(TSM.Operations.HasRelationship("Auctioning", private.currentOperationName, "bidPercent"))
|
||||
:SetValue((operation.bidPercent * 100).."%")
|
||||
:SetScript("OnValueChanged", private.BidPercentOnValueChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetFormattedText(L["Enter a value from %d - %d%%"], 0, 100)
|
||||
:SetTextColor(TSM.Operations.HasRelationship("Auctioning", private.currentOperationName, "bidPercent") and "TEXT_DISABLED" or "TEXT")
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedPriceInput("undercut", L["Undercut amount"], 66, private.CheckUndercut)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedPriceInput("minPrice", L["Minimum price"], 126, BAD_PRICE_SOURCES))
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedSettingLine("priceReset", L["When below minimum:"])
|
||||
:SetMargin(0, 0, 12, 12)
|
||||
:AddChild(UIElements.New("SelectionDropdown", "dropdown")
|
||||
:SetWidth(240)
|
||||
:SetDisabled(TSM.Operations.HasRelationship("Auctioning", private.currentOperationName, "priceReset"))
|
||||
:SetItems(BELOW_MIN_ITEMS, BELOW_MIN_KEYS)
|
||||
:SetSettingInfo(operation, "priceReset")
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedPriceInput("maxPrice", L["Maximum price"], 126, BAD_PRICE_SOURCES))
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedSettingLine("aboveMax", L["When above maximum:"])
|
||||
:SetMargin(0, 0, 12, 12)
|
||||
:AddChild(UIElements.New("SelectionDropdown", "dropdown")
|
||||
:SetWidth(240)
|
||||
:SetDisabled(TSM.Operations.HasRelationship("Auctioning", private.currentOperationName, "aboveMax"))
|
||||
:SetItems(ABOVE_MAX_ITEMS, ABOVE_MAX_KEYS)
|
||||
:SetSettingInfo(operation, "aboveMax")
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedPriceInput("normalPrice", L["Normal price"], 126, BAD_PRICE_SOURCES))
|
||||
)
|
||||
end
|
||||
|
||||
function private.AddStackSizeSettings(frame)
|
||||
if not TSM.IsWowClassic() then
|
||||
return
|
||||
end
|
||||
frame:AddChild(private.CreateInputLine("stackSize", L["Stack size"], false, true))
|
||||
frame:AddChild(private.CreateToggleLine("stackSizeIsCap", L["Allow partial stack"]))
|
||||
end
|
||||
|
||||
function private.GetCancelingSettings()
|
||||
TSM.UI.AnalyticsRecordPathChange("main", "operations", "auctioning", "canceling")
|
||||
return UIElements.New("ScrollFrame", "settings")
|
||||
:SetPadding(8, 8, 8, 0)
|
||||
:AddChild(TSM.MainUI.Operations.CreateExpandableSection("Auctioning", "priceSettings", L["Canceling Options"], L["Adjust the settings below to set how groups attached to this operation will be cancelled."])
|
||||
:AddChild(private.CreateToggleLine("cancelUndercut", L["Cancel undercut auctions"]))
|
||||
:AddChild(private.CreateToggleLine("cancelRepost", L["Cancel to repost higher"]))
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedPriceInput("cancelRepostThreshold", L["Repost threshold"], 66))
|
||||
)
|
||||
end
|
||||
|
||||
function private.GetAuctioningSettings(self, button)
|
||||
private.currentTab = button
|
||||
if button == L["Details"] then
|
||||
return private.GetDetailsSettings()
|
||||
elseif button == L["Posting"] then
|
||||
return private.GetPostingSettings()
|
||||
elseif button == L["Canceling"] then
|
||||
return private.GetCancelingSettings()
|
||||
else
|
||||
error("Unknown button!")
|
||||
end
|
||||
end
|
||||
|
||||
function private.AddBlacklistPlayers(frame)
|
||||
local operation = TSM.Operations.GetSettings("Auctioning", private.currentOperationName)
|
||||
if operation.blacklist == "" then return end
|
||||
local containerFrame = UIElements.New("Frame", "blacklistFrame")
|
||||
:SetLayout("FLOW")
|
||||
for index, player in Vararg.Iterator(strsplit(",", operation.blacklist)) do
|
||||
containerFrame:AddChild(UIElements.New("Frame", "blacklist" .. index)
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetSize(100, 20)
|
||||
:SetMargin(0, 12, 0, 0)
|
||||
:AddChild(UIElements.New("Text", "text")
|
||||
:SetWidth("AUTO")
|
||||
:SetMargin(0, 2, 0, 0)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(player)
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "removeBtn")
|
||||
:SetBackgroundAndSize("iconPack.14x14/Close/Circle")
|
||||
:SetContext(player)
|
||||
:SetScript("OnClick", private.RemoveBlacklistOnClick)
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
)
|
||||
end
|
||||
frame:AddChild(containerFrame)
|
||||
end
|
||||
|
||||
function private.CreateInputLine(key, label, disabled, margin)
|
||||
local operation = TSM.Operations.GetSettings("Auctioning", private.currentOperationName)
|
||||
local hasRelationship = TSM.Operations.HasRelationship("Auctioning", private.currentOperationName, key)
|
||||
return TSM.MainUI.Operations.CreateLinkedSettingLine(key, label, disabled)
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(48)
|
||||
:SetMargin(0, 0, 0, margin and 12 or 4)
|
||||
:AddChild(UIElements.New("Frame", "content")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:AddChild(UIElements.New("Input", "input")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetBackgroundColor("ACTIVE_BG")
|
||||
:SetContext(key)
|
||||
:SetDisabled(hasRelationship or disabled)
|
||||
:SetValidateFunc("CUSTOM_PRICE")
|
||||
:SetSettingInfo(operation, key)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetTextColor((hasRelationship or disabled) and "TEXT_DISABLED" or "TEXT")
|
||||
:SetFormattedText(L["Supported value range: %d - %d"], TSM.Operations.Auctioning.GetMinMaxValues(key))
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
function private.CreateToggleLine(key, label)
|
||||
local operation = TSM.Operations.GetSettings("Auctioning", private.currentOperationName)
|
||||
return TSM.MainUI.Operations.CreateLinkedSettingLine(key, label)
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(48)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:AddChild(UIElements.New("ToggleOnOff", "toggle")
|
||||
:SetHeight(18)
|
||||
:SetDisabled(TSM.Operations.HasRelationship("Auctioning", private.currentOperationName, key))
|
||||
:SetSettingInfo(operation, key)
|
||||
)
|
||||
end
|
||||
|
||||
function private.CheckUndercut(_, value)
|
||||
if not TSM.IsWowClassic() and Money.FromString(Money.ToString(value) or value) == 0 then
|
||||
return true
|
||||
elseif not TSM.IsWowClassic() and (Money.FromString(Money.ToString(value) or value) or math.huge) < COPPER_PER_SILVER then
|
||||
return false, L["Invalid undercut. To post below the cheapest auction without a significant undercut, set your undercut to 0c."]
|
||||
else
|
||||
local isValid, err = CustomPrice.Validate(value)
|
||||
if isValid then
|
||||
return true
|
||||
end
|
||||
return false, L["Invalid custom price."].." "..err
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.IgnoreLowDurationOnSelectionChanged(self)
|
||||
local operation = TSM.Operations.GetSettings("Auctioning", private.currentOperationName)
|
||||
operation.ignoreLowDuration = self:GetSelectedItemKey() - 1
|
||||
end
|
||||
|
||||
function private.BlacklistInputOnEnterPressed(input)
|
||||
local newPlayer = input:GetValue()
|
||||
if newPlayer == "" or strfind(newPlayer, ",") or newPlayer ~= String.Escape(newPlayer) then
|
||||
-- this is an invalid player name
|
||||
return
|
||||
end
|
||||
local operation = TSM.Operations.GetSettings("Auctioning", private.currentOperationName)
|
||||
local found = false
|
||||
for _, player in Vararg.Iterator(strsplit(",", operation.blacklist)) do
|
||||
if newPlayer == player then
|
||||
-- this player is already added
|
||||
input:SetValue("")
|
||||
found = true
|
||||
end
|
||||
end
|
||||
if found then
|
||||
return
|
||||
end
|
||||
operation.blacklist = (operation.blacklist == "") and newPlayer or (operation.blacklist..","..newPlayer)
|
||||
input:GetElement("__parent.__parent.__parent.__parent.__parent.__parent"):ReloadContent()
|
||||
end
|
||||
|
||||
function private.RemoveBlacklistOnClick(button)
|
||||
local player = button:GetContext()
|
||||
-- FIXME: This sort of logic should go within some Auctioning-specific operation setting wrapper code
|
||||
local operation = TSM.Operations.GetSettings("Auctioning", private.currentOperationName)
|
||||
if operation.blacklist == player then
|
||||
operation.blacklist = ""
|
||||
else
|
||||
-- handle cases where this entry is at the start, in the middle, and at the end
|
||||
operation.blacklist = gsub(operation.blacklist, "^"..player..",", "")
|
||||
operation.blacklist = gsub(operation.blacklist, ","..player..",", ",")
|
||||
operation.blacklist = gsub(operation.blacklist, ","..player.."$", "")
|
||||
end
|
||||
button:GetElement("__parent.__parent.__parent.__parent.__parent.__parent.__parent"):ReloadContent()
|
||||
end
|
||||
|
||||
function private.SetAuctioningDuration(dropdown)
|
||||
local operation = TSM.Operations.GetSettings("Auctioning", private.currentOperationName)
|
||||
operation.duration = dropdown:GetSelectedItemKey()
|
||||
end
|
||||
|
||||
function private.BidPercentValidateFunc(_, value)
|
||||
value = strmatch(value, "^([0-9]+) *%%?$")
|
||||
value = tonumber(value)
|
||||
if not value or value < 0 or value > 100 then
|
||||
return false, L["Bid percent must be between 0 and 100."]
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function private.BidPercentOnValueChanged(input)
|
||||
local operation = TSM.Operations.GetSettings("Auctioning", private.currentOperationName)
|
||||
local value = strmatch(input:GetValue(), "^([0-9]+) *%%?$")
|
||||
value = tonumber(value) / 100
|
||||
operation.bidPercent = value
|
||||
end
|
||||
867
Core/UI/MainUI/Operations/Core.lua
Normal file
867
Core/UI/MainUI/Operations/Core.lua
Normal file
@@ -0,0 +1,867 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Operations = TSM.MainUI:NewPackage("Operations")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local Log = TSM.Include("Util.Log")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local Money = TSM.Include("Util.Money")
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local Settings = TSM.Include("Service.Settings")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local private = {
|
||||
settings = nil,
|
||||
moduleNames = {},
|
||||
moduleCollapsed = {},
|
||||
moduleCallbacks = {},
|
||||
currentModule = nil,
|
||||
currentOperationName = nil,
|
||||
playerList = {},
|
||||
linkMenuEntries = {},
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Operations.OnInitialize()
|
||||
private.settings = Settings.NewView()
|
||||
:AddKey("global", "mainUIContext", "operationsDividedContainer")
|
||||
:AddKey("global", "mainUIContext", "operationsSummaryScrollingTable")
|
||||
TSM.MainUI.RegisterTopLevelPage(L["Operations"], private.GetOperationsFrame)
|
||||
end
|
||||
|
||||
function Operations.RegisterModule(name, callback)
|
||||
tinsert(private.moduleNames, name)
|
||||
private.moduleCallbacks[name] = callback
|
||||
end
|
||||
|
||||
function Operations.ShowOperationSettings(baseFrame, moduleName, operationName)
|
||||
baseFrame:SetSelectedNavButton(L["Operations"], true)
|
||||
baseFrame:GetElement("content.operations.selection.operationTree"):SetSelectedOperation(moduleName, operationName)
|
||||
end
|
||||
|
||||
function Operations.GetOperationManagementElements(moduleName, operationName)
|
||||
local operation = TSM.Operations.GetSettings(private.currentModule, private.currentOperationName)
|
||||
wipe(private.playerList)
|
||||
for factionrealm in TSM.db:GetConnectedRealmIterator("factionrealm") do
|
||||
for _, character in TSM.db:FactionrealmCharacterIterator(factionrealm) do
|
||||
tinsert(private.playerList, character.." - "..factionrealm)
|
||||
end
|
||||
end
|
||||
return UIElements.New("Frame", "management")
|
||||
:SetLayout("VERTICAL")
|
||||
:AddChild(Operations.CreateExpandableSection(moduleName, "managementOptions", L["Management Options"], L["Below you can ignore this operation on certain characters or realms."])
|
||||
:AddChild(Operations.CreateSettingLine("ignoreFactionRealms", L["Ignore operation on faction-realms"])
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(48)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:AddChild(UIElements.New("MultiselectionDropdown", "dropdown")
|
||||
:SetHeight(24)
|
||||
:SetItems(TSM.db:GetScopeKeys("factionrealm"), TSM.db:GetScopeKeys("factionrealm"))
|
||||
:SetSelectionText(L["No Faction-Realms"], L["%d Faction-Realms"], L["All Faction-Realms"])
|
||||
:SetSettingInfo(operation, "ignoreFactionrealm")
|
||||
)
|
||||
)
|
||||
:AddChild(Operations.CreateSettingLine("ignoreCharacters", L["Ignore operation on characters"])
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(48)
|
||||
:AddChild(UIElements.New("MultiselectionDropdown", "dropdown")
|
||||
:SetHeight(24)
|
||||
:SetItems(private.playerList, private.playerList)
|
||||
:SetSelectionText(L["No Characters"], L["%d Characters"], L["All Characters"])
|
||||
:SetSettingInfo(operation, "ignorePlayer")
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(Operations.CreateExpandableSection(moduleName, "groupManagement", L["Group Management"], L["Here you can add/remove what groups this operation is attached to."])
|
||||
:AddChild(Operations.CreateSettingLine("applyNewGroup", L["Apply operation to group"])
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(48)
|
||||
:AddChild(UIElements.New("GroupSelector", "group")
|
||||
:SetHintText(L["Add operation to groups"])
|
||||
:SetScript("OnSelectionChanged", private.GroupSelectionChanged)
|
||||
)
|
||||
)
|
||||
:AddChildrenWithFunction(private.AddOperationGroups)
|
||||
)
|
||||
end
|
||||
|
||||
function Operations.CreateExpandableSection(moduleName, id, text, description)
|
||||
return UIElements.New("CollapsibleContainer", id)
|
||||
:SetLayout("VERTICAL")
|
||||
:SetMargin(0, 0, 0, 8)
|
||||
:SetContextTable(private.moduleCollapsed, moduleName..text)
|
||||
:SetHeadingText(text)
|
||||
:AddChild(UIElements.New("Text", "description")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetText(description)
|
||||
)
|
||||
end
|
||||
|
||||
function Operations.CreateLinkedSettingLine(settingKey, labelText, disabled, alternateName)
|
||||
local relationshipSet = TSM.Operations.HasRelationship(private.currentModule, private.currentOperationName, settingKey)
|
||||
return UIElements.New("Frame", alternateName or settingKey)
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, 0, 4)
|
||||
:AddChild(UIElements.New("Frame", "line")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetTextColor((relationshipSet or disabled) and "TEXT_DISABLED" or "TEXT")
|
||||
:SetText(labelText)
|
||||
)
|
||||
:AddChild(private.CreateLinkButton(disabled, settingKey))
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
)
|
||||
end
|
||||
|
||||
function Operations.CreateSettingLine(id, labelText, disabled)
|
||||
return UIElements.New("Frame", id)
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, 0, 4)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetTextColor(disabled and "TEXT_DISABLED" or "TEXT")
|
||||
:SetText(labelText)
|
||||
)
|
||||
end
|
||||
|
||||
function Operations.CreateLinkedPriceInput(settingKey, label, height, validate, defaultValue)
|
||||
local isDisabled = TSM.Operations.HasRelationship(private.currentModule, private.currentOperationName, settingKey)
|
||||
local operation = TSM.Operations.GetSettings(private.currentModule, private.currentOperationName)
|
||||
local value = operation[settingKey]
|
||||
if defaultValue ~= nil and (not value or value == "") then
|
||||
isDisabled = true
|
||||
value = defaultValue
|
||||
end
|
||||
local validateFunc, validateContext = nil, nil
|
||||
if type(validate) == "table" then
|
||||
validateFunc = "CUSTOM_PRICE"
|
||||
validateContext = validate
|
||||
elseif type(validate) == "function" then
|
||||
validateFunc = validate
|
||||
elseif validate == nil then
|
||||
validateFunc = "CUSTOM_PRICE"
|
||||
else
|
||||
error("Invalid validate: "..tostring(validate))
|
||||
end
|
||||
return Operations.CreateLinkedSettingLine(settingKey, label)
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(height)
|
||||
:AddChild(UIElements.New("MultiLineInput", "input")
|
||||
:SetHeight(height - 24)
|
||||
:SetDisabled(isDisabled)
|
||||
:SetValidateFunc(validateFunc, validateContext)
|
||||
:SetSettingInfo(operation, settingKey)
|
||||
:SetValue(Money.ToString(value) or value)
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Operations UI
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetOperationsFrame()
|
||||
TSM.UI.AnalyticsRecordPathChange("main", "operations")
|
||||
local frame = UIElements.New("DividedContainer", "operations")
|
||||
:SetSettingsContext(private.settings, "operationsDividedContainer")
|
||||
:SetMinWidth(250, 250)
|
||||
:SetLeftChild(UIElements.New("Frame", "selection")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT")
|
||||
:AddChild(UIElements.New("Input", "search")
|
||||
:SetHeight(24)
|
||||
:SetMargin(8, 8, 8, 16)
|
||||
:SetIconTexture("iconPack.18x18/Search")
|
||||
:SetClearButtonEnabled(true)
|
||||
:AllowItemInsert(true)
|
||||
:SetHintText(L["Search Operations"])
|
||||
:SetScript("OnValueChanged", private.OperationSearchOnValueChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("OperationTree", "operationTree")
|
||||
:SetScript("OnOperationAdded", private.OperationTreeOnOperationAdded)
|
||||
:SetScript("OnOperationDeleted", private.OperationTreeOnOperationConfirmDelete)
|
||||
:SetScript("OnOperationSelected", private.OperationTreeOnOperationSelected)
|
||||
)
|
||||
)
|
||||
:SetRightChild(UIElements.New("ViewContainer", "content")
|
||||
:SetNavCallback(private.GetOperationsContent)
|
||||
:AddPath("none", true)
|
||||
:AddPath("summary")
|
||||
:AddPath("operation")
|
||||
)
|
||||
return frame
|
||||
end
|
||||
|
||||
function private.GetOperationsContent(_, path)
|
||||
if path == "none" then
|
||||
return UIElements.New("Frame", "settings")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetWidth("EXPAND")
|
||||
:SetBackgroundColor("PRIMARY_BG")
|
||||
:AddChild(UIElements.New("Frame", "title")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(40)
|
||||
:SetPadding(8)
|
||||
:AddChild(UIElements.New("Texture", "icon")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetTextureAndSize(TSM.UI.TexturePacks.GetColoredKey("iconPack.18x18/Operation", "TEXT"))
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "text")
|
||||
:SetFont("BODY_BODY1_BOLD")
|
||||
:SetText(L["No Operation Selected"])
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
elseif path == "summary" then
|
||||
return UIElements.New("Frame", "settings")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetWidth("EXPAND")
|
||||
:SetBackgroundColor("PRIMARY_BG")
|
||||
:AddChild(UIElements.New("Frame", "title")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(40)
|
||||
:SetPadding(8)
|
||||
:AddChild(UIElements.New("Text", "text")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY1_BOLD")
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer"))
|
||||
:AddChild(UIElements.New("Button", "addBtn")
|
||||
:SetWidth("AUTO")
|
||||
:SetMargin(12, 12, 0, 0)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetIcon("iconPack.14x14/Add/Circle", "LEFT")
|
||||
:SetText(L["Create New"])
|
||||
:SetScript("OnClick", private.CreateNewOperationOnClick)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "content")
|
||||
:SetLayout("VERTICAL")
|
||||
-- will be filled in by the operation selection callback
|
||||
)
|
||||
elseif path == "operation" then
|
||||
return UIElements.New("Frame", "settings")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetWidth("EXPAND")
|
||||
:SetBackgroundColor("PRIMARY_BG")
|
||||
:AddChild(UIElements.New("Frame", "title")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(40)
|
||||
:SetPadding(8)
|
||||
:AddChild(UIElements.New("Texture", "icon")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetTextureAndSize(TSM.UI.TexturePacks.GetColoredKey("iconPack.18x18/Operation", "TEXT"))
|
||||
)
|
||||
:AddChild(UIElements.New("EditableText", "text")
|
||||
:SetWidth("AUTO")
|
||||
:AllowItemInsert(true)
|
||||
:SetFont("BODY_BODY1_BOLD")
|
||||
:SetText(L["No Operation Selected"])
|
||||
:SetScript("OnValueChanged", private.OperationNameChanged)
|
||||
:SetScript("OnEditingChanged", private.NameOnEditingChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer"))
|
||||
:AddChild(UIElements.New("Button", "renameBtn")
|
||||
:SetWidth("AUTO")
|
||||
:SetMargin(12, 12, 0, 0)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetIcon("iconPack.14x14/Edit", "LEFT")
|
||||
:SetText(L["Rename"])
|
||||
:SetScript("OnClick", private.RenameOperationOnClick)
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "resetBtn")
|
||||
:SetWidth("AUTO")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetIcon("iconPack.14x14/Reset", "LEFT")
|
||||
:SetText(L["Reset"])
|
||||
:SetScript("OnClick", private.ResetOperationOnClick)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "content")
|
||||
:SetLayout("VERTICAL")
|
||||
-- will be filled in by the operation selection callback
|
||||
)
|
||||
else
|
||||
error("Invalid path: "..tostring(path))
|
||||
end
|
||||
end
|
||||
|
||||
function private.GetSummaryContent()
|
||||
local query = TSM.Operations.CreateQuery()
|
||||
:Equal("moduleName", private.currentModule)
|
||||
:VirtualField("numGroups", "number", private.NumGroupsVirtualField)
|
||||
:VirtualField("numItems", "number", private.NumItemsVirtualField)
|
||||
:OrderBy("operationName", true)
|
||||
local mostGroupsName, mostGroupsValue = "---", -math.huge
|
||||
local leastGroupsName, leastGroupsValue = "---", math.huge
|
||||
local mostItemsName, mostItemsValue = "---", -math.huge
|
||||
local leastItemsName, leastItemsValue = "---", math.huge
|
||||
for _, row in query:Iterator() do
|
||||
local operationName, numGroups, numItems = row:GetFields("operationName", "numGroups", "numItems")
|
||||
if numGroups > mostGroupsValue then
|
||||
mostGroupsValue = numGroups
|
||||
mostGroupsName = operationName
|
||||
end
|
||||
if numGroups < leastGroupsValue then
|
||||
leastGroupsValue = numGroups
|
||||
leastGroupsName = operationName
|
||||
end
|
||||
if numItems > mostItemsValue then
|
||||
mostItemsValue = numItems
|
||||
mostItemsName = operationName
|
||||
end
|
||||
if numItems < leastItemsValue then
|
||||
leastItemsValue = numItems
|
||||
leastItemsName = operationName
|
||||
end
|
||||
end
|
||||
return UIElements.New("Frame", "summary")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetBackgroundColor("PRIMARY_BG")
|
||||
:AddChild(UIElements.New("Frame", "summary")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(48)
|
||||
:SetMargin(8, 8, 0, 16)
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT", true)
|
||||
:AddChild(UIElements.New("Frame", "groups")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetPadding(8, 8, 2, 2)
|
||||
:AddChild(UIElements.New("Frame", "most")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 4)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetTextColor("ACTIVE_BG_ALT")
|
||||
:SetText(L["MOST GROUPS"])
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
:AddChild(UIElements.New("Text", "value")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetText(mostGroupsName)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "least")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetTextColor("ACTIVE_BG_ALT")
|
||||
:SetText(L["LEAST GROUPS"])
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
:AddChild(UIElements.New("Text", "value")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetText(leastGroupsName)
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Texture", "line1")
|
||||
:SetWidth(1)
|
||||
:SetTexture("ACTIVE_BG")
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "items")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetPadding(8, 8, 2, 2)
|
||||
:AddChild(UIElements.New("Frame", "most")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 4)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetTextColor("ACTIVE_BG_ALT")
|
||||
:SetText(L["MOST ITEMS"])
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
:AddChild(UIElements.New("Text", "value")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetText(mostItemsName)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "least")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetTextColor("ACTIVE_BG_ALT")
|
||||
:SetText(L["LEAST ITEMS"])
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
:AddChild(UIElements.New("Text", "value")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("RIGHT")
|
||||
:SetText(leastItemsName)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("SelectionScrollingTable", "list")
|
||||
:SetSettingsContext(private.settings, "operationsSummaryScrollingTable")
|
||||
:GetScrollingTableInfo()
|
||||
:NewColumn("name")
|
||||
:SetTitle(L["Operation"])
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo("operationName")
|
||||
:SetSortInfo("operationName")
|
||||
:SetActionIconInfo(1, 12, private.GetConfigureIcon, true)
|
||||
:SetActionIconClickHandler(private.OnConfigureIconClick)
|
||||
:DisableHiding()
|
||||
:Commit()
|
||||
:NewColumn("groups")
|
||||
:SetTitle(L["Groups Using"])
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo("numGroups")
|
||||
:SetSortInfo("numGroups")
|
||||
:Commit()
|
||||
:NewColumn("items")
|
||||
:SetTitle(L["Items Using"])
|
||||
:SetFont("TABLE_TABLE1")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo("numItems", private.GetNumItemsText)
|
||||
:SetSortInfo("numItems")
|
||||
:SetTooltipInfo("numItems", private.GetNumItemsTooltip)
|
||||
:Commit()
|
||||
:Commit()
|
||||
:SetQuery(query)
|
||||
:SetContext(query)
|
||||
:SetAutoReleaseQuery(true)
|
||||
:SetScript("OnSelectionChanged", private.OperationListOnSelectionChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("Texture", "line")
|
||||
:SetHeight(2)
|
||||
:SetTexture("ACTIVE_BG")
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "footer")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(40)
|
||||
:SetPadding(8)
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT")
|
||||
:AddChild(UIElements.New("ActionButton", "deleteSelected")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetDisabled(true)
|
||||
:SetText(L["Delete Operations"])
|
||||
:SetScript("OnClick", private.DeleteSelectedOnClick)
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "selectAll")
|
||||
:SetSize("AUTO", 20)
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetText(L["Select All"])
|
||||
:SetScript("OnClick", private.SelectAllOnClick)
|
||||
)
|
||||
:AddChild(UIElements.New("Texture", "line")
|
||||
:SetSize(2, 20)
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetTexture("ACTIVE_BG")
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "clearAll")
|
||||
:SetSize("AUTO", 20)
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetText(L["Clear All"])
|
||||
:SetDisabled(true)
|
||||
:SetScript("OnClick", private.ClearAllOnClick)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
function private.AddOperationGroups(frame)
|
||||
for _, groupPath in TSM.Operations.GroupIterator(private.currentModule, private.currentOperationName, true) do
|
||||
frame:AddChild(private.CreateGroupOperationLine(groupPath))
|
||||
end
|
||||
end
|
||||
|
||||
function private.CreateGroupOperationLine(groupPath)
|
||||
local groupName = groupPath == TSM.CONST.ROOT_GROUP_PATH and L["Base Group"] or TSM.Groups.Path.GetName(groupPath)
|
||||
local level = select('#', strsplit(TSM.CONST.GROUP_SEP, groupPath))
|
||||
return UIElements.New("Frame", "group")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:SetMargin(2, 0, 0, 0)
|
||||
:AddChild(UIElements.New("Text", "text")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY2")
|
||||
:SetTextColor(Theme.GetGroupColor(level))
|
||||
:SetText(groupName)
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "viewBtn")
|
||||
:SetMargin(2, 2, 0, 0)
|
||||
:SetBackgroundAndSize("iconPack.14x14/Groups")
|
||||
:SetContext(groupPath)
|
||||
:SetScript("OnClick", private.ViewGroupOnClick)
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "removeBtn")
|
||||
:SetBackgroundAndSize("iconPack.14x14/Close/Default")
|
||||
:SetContext(groupPath)
|
||||
:SetScript("OnClick", private.RemoveOperationGroupOnClick)
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
end
|
||||
|
||||
function private.CreateLinkButton(disabled, settingKey)
|
||||
local relationshipSet = TSM.Operations.HasRelationship(private.currentModule, private.currentOperationName, settingKey)
|
||||
local linkTexture = nil
|
||||
if disabled and relationshipSet then
|
||||
linkTexture = TSM.UI.TexturePacks.GetColoredKey("iconPack.14x14/Link", "INDICATOR_DISABLED")
|
||||
elseif disabled then
|
||||
linkTexture = TSM.UI.TexturePacks.GetColoredKey("iconPack.14x14/Link", "TEXT_DISABLED")
|
||||
elseif relationshipSet then
|
||||
linkTexture = TSM.UI.TexturePacks.GetColoredKey("iconPack.14x14/Link", "INDICATOR")
|
||||
else
|
||||
linkTexture = TSM.UI.TexturePacks.GetColoredKey("iconPack.14x14/Link", "TEXT")
|
||||
end
|
||||
return UIElements.New("Button", "linkBtn")
|
||||
:SetMargin(4, 4, 0, 0)
|
||||
:SetBackgroundAndSize(linkTexture)
|
||||
:SetDisabled(disabled)
|
||||
:SetContext(settingKey)
|
||||
:SetScript("OnClick", private.LinkBtnOnClick)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.OperationSearchOnValueChanged(input)
|
||||
local filter = strlower(input:GetValue())
|
||||
input:GetElement("__parent.operationTree"):SetOperationNameFilter(filter)
|
||||
end
|
||||
|
||||
function private.OperationTreeOnOperationAdded(operationTree, moduleName, operationName, copyOperationName)
|
||||
-- clear the filter
|
||||
operationTree:GetElement("__parent.search")
|
||||
:SetValue("")
|
||||
:Draw()
|
||||
operationTree:SetOperationNameFilter("")
|
||||
TSM.Operations.Create(moduleName, operationName)
|
||||
if copyOperationName then
|
||||
TSM.Operations.Copy(moduleName, operationName, copyOperationName)
|
||||
end
|
||||
end
|
||||
|
||||
function private.OperationTreeOnOperationConfirmDelete(self, moduleName, operationName)
|
||||
self:GetBaseElement():ShowConfirmationDialog(L["Delete Operation?"], L["Are you sure you want to delete this operation?"], private.OperationTreeOnOperationDeleted, self, moduleName, operationName)
|
||||
end
|
||||
|
||||
function private.OperationTreeOnOperationDeleted(self, moduleName, operationName)
|
||||
TSM.Operations.Delete(moduleName, operationName)
|
||||
local operationTree = self:GetElement("__parent.operationTree")
|
||||
operationTree:SetSelectedOperation(moduleName, nil)
|
||||
:Draw()
|
||||
end
|
||||
|
||||
function private.OperationTreeOnOperationSelected(self, moduleName, operationName)
|
||||
private.currentModule = moduleName
|
||||
private.currentOperationName = operationName
|
||||
|
||||
local viewContainer = self:GetParentElement():GetParentElement():GetElement("content")
|
||||
if moduleName and operationName then
|
||||
TSM.Operations.Update(moduleName, operationName)
|
||||
viewContainer:SetPath("operation")
|
||||
viewContainer:GetElement("settings.title.text"):SetText(operationName)
|
||||
local contentFrame = viewContainer:GetElement("settings.content")
|
||||
contentFrame:ReleaseAllChildren()
|
||||
contentFrame:AddChild(private.moduleCallbacks[moduleName](operationName))
|
||||
elseif moduleName then
|
||||
local numOperations = 0
|
||||
for _ in TSM.Operations.OperationIterator(moduleName) do
|
||||
numOperations = numOperations + 1
|
||||
end
|
||||
TSM.UI.AnalyticsRecordPathChange("main", "operations", "summary")
|
||||
viewContainer:SetPath("summary")
|
||||
viewContainer:GetElement("settings.title.text"):SetText(format(L["%s %s Operations"], Theme.GetColor("INDICATOR"):ColorText(numOperations), moduleName))
|
||||
local contentFrame = viewContainer:GetElement("settings.content")
|
||||
contentFrame:ReleaseAllChildren()
|
||||
contentFrame:AddChild(private.GetSummaryContent())
|
||||
else
|
||||
TSM.UI.AnalyticsRecordPathChange("main", "operations", "none")
|
||||
viewContainer:SetPath("none")
|
||||
viewContainer:GetElement("settings.title.text"):SetText(L["No Operation Selected"])
|
||||
end
|
||||
viewContainer:Draw()
|
||||
end
|
||||
|
||||
function private.CreateNewOperationOnClick(button)
|
||||
local operationName = "New Operation"
|
||||
local num = 1
|
||||
while TSM.Operations.Exists(private.currentModule, operationName.." "..num) do
|
||||
num = num + 1
|
||||
end
|
||||
operationName = operationName .. " " .. num
|
||||
TSM.Operations.Create(private.currentModule, operationName)
|
||||
button:GetElement("__parent.__parent.__parent.__parent.selection.operationTree")
|
||||
:SetSelectedOperation(private.currentModule, operationName)
|
||||
:Draw()
|
||||
end
|
||||
|
||||
function private.OperationNameChanged(text, newValue)
|
||||
newValue = strtrim(newValue)
|
||||
if newValue == private.currentOperationName then
|
||||
-- didn't change
|
||||
text:Draw()
|
||||
elseif newValue == "" then
|
||||
Log.PrintUser(L["Invalid operation name."])
|
||||
text:Draw()
|
||||
elseif TSM.Operations.Exists(private.currentModule, newValue) then
|
||||
Log.PrintUser(L["Group already exists."])
|
||||
text:Draw()
|
||||
else
|
||||
TSM.Operations.Rename(private.currentModule, private.currentOperationName, newValue)
|
||||
text:GetElement("__parent.__parent.__parent.__parent.selection.operationTree")
|
||||
:SetSelectedOperation(private.currentModule, newValue)
|
||||
:Draw()
|
||||
end
|
||||
end
|
||||
|
||||
function private.NameOnEditingChanged(text, editing)
|
||||
if editing then
|
||||
text:GetElement("__parent.renameBtn"):Hide()
|
||||
else
|
||||
text:GetElement("__parent.renameBtn"):Show()
|
||||
end
|
||||
end
|
||||
|
||||
function private.RenameOperationOnClick(button)
|
||||
button:GetElement("__parent.text"):SetEditing(true)
|
||||
end
|
||||
|
||||
function private.ResetOperationOnClick(button)
|
||||
button:GetBaseElement():ShowConfirmationDialog(L["Reset Operation?"], L["Resetting the operation will return all inputs back to default and cannot be unddone. Click confirm to reset."], private.ConfirmResetOnClick, button)
|
||||
end
|
||||
|
||||
function private.ConfirmResetOnClick(button)
|
||||
TSM.Operations.Reset(private.currentModule, private.currentOperationName)
|
||||
local settingsFrame = button:GetBaseElement():GetElement("content.operations.content.settings")
|
||||
local contentFrame = settingsFrame:GetElement("content")
|
||||
contentFrame:ReleaseAllChildren()
|
||||
TSM.Operations.Update(private.currentModule, private.currentOperationName)
|
||||
contentFrame:AddChild(private.moduleCallbacks[private.currentModule](private.currentOperationName))
|
||||
button:GetBaseElement():HideDialog()
|
||||
settingsFrame:Draw()
|
||||
Log.PrintfUser(L["%s - %s has been reset to default values."], private.currentModule, Theme.GetColor("INDICATOR_ALT"):ColorText(private.currentOperationName))
|
||||
end
|
||||
|
||||
function private.GroupSelectionChanged(groupSelector)
|
||||
for groupPath in groupSelector:SelectedGroupIterator() do
|
||||
if not TSM.Operations.GroupHasOperation(private.currentModule, groupPath, private.currentOperationName) then
|
||||
local parentElement = groupSelector:GetParentElement():GetParentElement()
|
||||
if groupPath ~= TSM.CONST.ROOT_GROUP_PATH then
|
||||
TSM.Groups.SetOperationOverride(groupPath, private.currentModule, true)
|
||||
end
|
||||
local numOperations = 0
|
||||
local lastOperationName = nil
|
||||
for _, groupOperationName in TSM.Groups.OperationIterator(groupPath, private.currentModule) do
|
||||
lastOperationName = groupOperationName
|
||||
numOperations = numOperations + 1
|
||||
end
|
||||
if numOperations == TSM.Operations.GetMaxNumber(private.currentModule) then
|
||||
-- replace the last operation since we're already at the max number of operations
|
||||
TSM.Groups.RemoveOperation(groupPath, private.currentModule, numOperations)
|
||||
Log.PrintfUser(L["%s previously had the max number of operations, so removed %s."], Log.ColorUserAccentText(TSM.Groups.Path.Format(groupPath)), Log.ColorUserAccentText(lastOperationName))
|
||||
end
|
||||
TSM.Groups.AppendOperation(groupPath, private.currentModule, private.currentOperationName)
|
||||
Log.PrintfUser(L["Added %s to %s."], Log.ColorUserAccentText(private.currentOperationName), Log.ColorUserAccentText(groupPath == TSM.CONST.ROOT_GROUP_PATH and L["Base Group"] or TSM.Groups.Path.Format(groupPath)))
|
||||
parentElement:AddChild(private.CreateGroupOperationLine(groupPath))
|
||||
end
|
||||
end
|
||||
groupSelector:ClearSelectedGroups(true)
|
||||
groupSelector:GetParentElement():GetParentElement():GetParentElement():GetParentElement():GetParentElement():GetParentElement():Draw()
|
||||
end
|
||||
|
||||
function private.ViewGroupOnClick(button)
|
||||
local baseFrame = button:GetBaseElement()
|
||||
TSM.MainUI.Groups.ShowGroupSettings(baseFrame, button:GetContext())
|
||||
end
|
||||
|
||||
function private.RemoveOperationGroupOnClick(self)
|
||||
local groupPath = self:GetContext()
|
||||
TSM.Groups.RemoveOperationByName(groupPath, private.currentModule, private.currentOperationName)
|
||||
|
||||
-- remove the line for this group
|
||||
local removeElement = self:GetParentElement()
|
||||
local removeElementParent = removeElement:GetParentElement()
|
||||
removeElementParent:RemoveChild(removeElement)
|
||||
removeElement:Release()
|
||||
removeElementParent:GetParentElement():GetParentElement():GetParentElement():Draw()
|
||||
end
|
||||
|
||||
function private.LinkBtnOnClick(button)
|
||||
local settingKey = button:GetContext()
|
||||
wipe(private.linkMenuEntries)
|
||||
for _, operationName in TSM.Operations.OperationIterator(private.currentModule) do
|
||||
if operationName ~= private.currentOperationName and not TSM.Operations.IsCircularRelationship(private.currentModule, private.currentOperationName, settingKey) then
|
||||
tinsert(private.linkMenuEntries, operationName)
|
||||
end
|
||||
end
|
||||
sort(private.linkMenuEntries)
|
||||
button:GetBaseElement():ShowDialogFrame(UIElements.New("PopupFrame", "linkDialog")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetSize(263, 243)
|
||||
:AddAnchor("TOPRIGHT", button:_GetBaseFrame(), "BOTTOM", 22, -16)
|
||||
:AddChild(UIElements.New("Frame", "titleFrame")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(37)
|
||||
:AddChild(UIElements.New("Text", "title")
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetJustifyH("CENTER")
|
||||
:SetText(L["Link to Another Operation"])
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Texture", "line")
|
||||
:SetHeight(2)
|
||||
:SetTexture("TEXT")
|
||||
)
|
||||
:AddChild(UIElements.New("SelectionList", "list")
|
||||
:SetContext(settingKey)
|
||||
:SetMargin(2, 2, 0, 3)
|
||||
:SetEntries(private.linkMenuEntries, TSM.Operations.GetRelationship(private.currentModule, private.currentOperationName, settingKey))
|
||||
:SetScript("OnEntrySelected", private.ListOnEntrySelected)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
function private.ListOnEntrySelected(list, operationName)
|
||||
local settingKey = list:GetContext()
|
||||
local previousValue = TSM.Operations.GetRelationship(private.currentModule, private.currentOperationName, settingKey)
|
||||
if operationName == previousValue then
|
||||
TSM.Operations.SetRelationship(private.currentModule, private.currentOperationName, settingKey, nil)
|
||||
else
|
||||
TSM.Operations.SetRelationship(private.currentModule, private.currentOperationName, settingKey, operationName)
|
||||
end
|
||||
|
||||
local baseFrame = list:GetBaseElement()
|
||||
baseFrame:HideDialog()
|
||||
Operations.ShowOperationSettings(baseFrame, private.currentModule, private.currentOperationName)
|
||||
end
|
||||
|
||||
function private.OperationListOnSelectionChanged(scrollingTable)
|
||||
local selectionCleared = scrollingTable:IsSelectionCleared()
|
||||
local numSelected = 0
|
||||
for _ in scrollingTable:SelectionIterator() do
|
||||
numSelected = numSelected + 1
|
||||
end
|
||||
local footer = scrollingTable:GetElement("__parent.footer")
|
||||
footer:GetElement("deleteSelected")
|
||||
:SetText(numSelected > 0 and format(L["Delete %d Operations"], numSelected) or L["Delete Operations"])
|
||||
:SetDisabled(selectionCleared)
|
||||
footer:GetElement("selectAll")
|
||||
:SetDisabled(scrollingTable:IsAllSelected())
|
||||
footer:GetElement("clearAll")
|
||||
:SetDisabled(selectionCleared)
|
||||
footer:Draw()
|
||||
end
|
||||
|
||||
function private.SelectAllOnClick(button)
|
||||
button:GetElement("__parent.__parent.list"):SelectAll()
|
||||
end
|
||||
|
||||
function private.ClearAllOnClick(button)
|
||||
button:GetElement("__parent.__parent.list"):ClearSelection()
|
||||
end
|
||||
|
||||
function private.DeleteSelectedOnClick(button)
|
||||
local scrollingTable = button:GetElement("__parent.__parent.list")
|
||||
button:GetBaseElement():ShowConfirmationDialog(L["Delete Operations?"], L["Are you sure you want to delete the selected operations?"], private.DeleteSelectedOperations, scrollingTable)
|
||||
end
|
||||
|
||||
function private.DeleteSelectedOperations(scrollingTable)
|
||||
local toDelete = TempTable.Acquire()
|
||||
for _, row in scrollingTable:SelectionIterator() do
|
||||
local moduleName, operationName = row:GetFields("moduleName", "operationName")
|
||||
assert(moduleName == private.currentModule)
|
||||
tinsert(toDelete, operationName)
|
||||
end
|
||||
TSM.Operations.DeleteList(private.currentModule, toDelete)
|
||||
TempTable.Release(toDelete)
|
||||
scrollingTable:UpdateData(true)
|
||||
private.OperationListOnSelectionChanged(scrollingTable)
|
||||
scrollingTable:GetElement("__parent.__parent.__parent.__parent.__parent.selection.operationTree"):UpdateData(true)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.NumGroupsVirtualField(row)
|
||||
local num = 0
|
||||
for _ in TSM.Operations.GroupIterator(row:GetField("moduleName"), row:GetField("operationName")) do
|
||||
num = num + 1
|
||||
end
|
||||
return num
|
||||
end
|
||||
|
||||
function private.NumItemsVirtualField(row)
|
||||
local includesBaseGroup = false
|
||||
local num = 0
|
||||
for _, groupPath in TSM.Operations.GroupIterator(row:GetField("moduleName"), row:GetField("operationName")) do
|
||||
if groupPath == TSM.CONST.ROOT_GROUP_PATH then
|
||||
includesBaseGroup = true
|
||||
else
|
||||
num = num + TSM.Groups.GetNumItems(groupPath)
|
||||
end
|
||||
end
|
||||
if includesBaseGroup then
|
||||
num = num + 0.9
|
||||
end
|
||||
return num
|
||||
end
|
||||
|
||||
function private.GetConfigureIcon(_, _, iconIndex)
|
||||
assert(iconIndex == 1)
|
||||
return true, "iconPack.12x12/Popout", false
|
||||
end
|
||||
|
||||
function private.OnConfigureIconClick(scrollingTable, data, iconIndex)
|
||||
assert(iconIndex == 1)
|
||||
local operationName = scrollingTable:GetContext():GetResultRowByUUID(data):GetField("operationName")
|
||||
scrollingTable:GetElement("__parent.__parent.__parent.__parent.__parent.selection.operationTree")
|
||||
:SetSelectedOperation(private.currentModule, operationName)
|
||||
end
|
||||
|
||||
function private.GetNumItemsText(numItems)
|
||||
if numItems == floor(numItems) then
|
||||
return numItems
|
||||
else
|
||||
return floor(numItems).."*"
|
||||
end
|
||||
end
|
||||
|
||||
function private.GetNumItemsTooltip(numItems)
|
||||
if numItems == floor(numItems) then
|
||||
return nil
|
||||
end
|
||||
return L["This operation is applied to the base group which includes every item not in another group."]
|
||||
end
|
||||
159
Core/UI/MainUI/Operations/Crafting.lua
Normal file
159
Core/UI/MainUI/Operations/Crafting.lua
Normal file
@@ -0,0 +1,159 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Crafting = TSM.MainUI.Operations:NewPackage("Crafting")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local private = {
|
||||
currentOperationName = nil,
|
||||
}
|
||||
local BAD_CRAFT_VALUE_PRICE_SOURCES = {
|
||||
crafting = true,
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Crafting.OnInitialize()
|
||||
TSM.MainUI.Operations.RegisterModule("Crafting", private.GetCraftingOperationSettings)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Crafting Operation Settings UI
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetCraftingOperationSettings(operationName)
|
||||
TSM.UI.AnalyticsRecordPathChange("main", "operations", "crafting")
|
||||
private.currentOperationName = operationName
|
||||
local operation = TSM.Operations.GetSettings("Crafting", private.currentOperationName)
|
||||
local frame = UIElements.New("ScrollFrame", "settings")
|
||||
:SetPadding(8, 8, 8, 0)
|
||||
:AddChild(TSM.MainUI.Operations.CreateExpandableSection("Crafting", "restockQuantity", L["Restock Options"], L["Adjust how crafted items are restocked."])
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedSettingLine("minRestock", L["Min restock quantity"])
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(48)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:AddChild(UIElements.New("Frame", "content")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:AddChild(UIElements.New("Input", "input")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetBackgroundColor("ACTIVE_BG")
|
||||
:SetValidateFunc("CUSTOM_PRICE")
|
||||
:SetSettingInfo(operation, "minRestock")
|
||||
:SetDisabled(TSM.Operations.HasRelationship("Crafting", private.currentOperationName, "minRestock"))
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetTextColor(TSM.Operations.HasRelationship("Crafting", private.currentOperationName, "minRestock") and "TEXT_DISABLED" or "TEXT")
|
||||
:SetFormattedText(L["Supported range: %d - %d"], TSM.Operations.Crafting.GetRestockRange())
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedSettingLine("maxRestock", L["Max restock quantity"])
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(48)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:AddChild(UIElements.New("Frame", "content")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:AddChild(UIElements.New("Input", "input")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetBackgroundColor("ACTIVE_BG")
|
||||
:SetValidateFunc("CUSTOM_PRICE")
|
||||
:SetSettingInfo(operation, "maxRestock")
|
||||
:SetDisabled(TSM.Operations.HasRelationship("Crafting", private.currentOperationName, "maxRestock"))
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetTextColor(TSM.Operations.HasRelationship("Crafting", private.currentOperationName, "maxRestock") and "TEXT_DISABLED" or "TEXT")
|
||||
:SetFormattedText(L["Supported range: %d - %d"], TSM.Operations.Crafting.GetRestockRange())
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedSettingLine("minProfit", L["Set min profit"], nil, "minProfitToggle")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(42)
|
||||
:AddChild(UIElements.New("ToggleOnOff", "toggle")
|
||||
:SetHeight(18)
|
||||
:SetValue(operation.minProfit ~= "")
|
||||
:SetDisabled(TSM.Operations.HasRelationship("Crafting", private.currentOperationName, "minProfit"))
|
||||
:SetScript("OnValueChanged", private.MinProfitToggleOnValueChanged)
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.CreateExpandableSection("Crafting", "priceSettings", L["Crafting Value"], L["Adjust how TSM values crafted items when calculating profit."])
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedSettingLine("craftPriceMethod", L["Override default craft value"], nil, "craftPriceMethodToggle")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(42)
|
||||
:AddChild(UIElements.New("ToggleOnOff", "toggle")
|
||||
:SetHeight(18)
|
||||
:SetValue(operation.craftPriceMethod ~= "")
|
||||
:SetDisabled(TSM.Operations.HasRelationship("Crafting", private.currentOperationName, "craftPriceMethod"))
|
||||
:SetScript("OnValueChanged", private.CraftPriceToggleOnValueChanged)
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.GetOperationManagementElements("Crafting", private.currentOperationName))
|
||||
|
||||
if operation.minProfit ~= "" then
|
||||
frame:GetElement("restockQuantity.content.minProfitToggle"):SetMargin(0, 0, 0, 12)
|
||||
frame:GetElement("restockQuantity"):AddChild(TSM.MainUI.Operations.CreateLinkedPriceInput("minProfit", L["Min profit amount"], 80))
|
||||
end
|
||||
if operation.craftPriceMethod ~= "" then
|
||||
frame:GetElement("priceSettings.content.craftPriceMethodToggle"):SetMargin(0, 0, 0, 12)
|
||||
frame:GetElement("priceSettings"):AddChild(TSM.MainUI.Operations.CreateLinkedPriceInput("craftPriceMethod", L["Craft Value"], 80, BAD_CRAFT_VALUE_PRICE_SOURCES, TSM.db.global.craftingOptions.defaultCraftPriceMethod))
|
||||
end
|
||||
|
||||
return frame
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.MinProfitToggleOnValueChanged(toggle, value)
|
||||
local operation = TSM.Operations.GetSettings("Crafting", private.currentOperationName)
|
||||
local defaultValue = TSM.Operations.GetSettingDefault("Crafting", "minProfit")
|
||||
operation.minProfit = value and defaultValue or ""
|
||||
local settingsFrame = toggle:GetParentElement():GetParentElement()
|
||||
if value then
|
||||
settingsFrame:GetElement("minProfitToggle"):SetMargin(0, 0, 0, 12)
|
||||
settingsFrame:GetParentElement():AddChild(TSM.MainUI.Operations.CreateLinkedPriceInput("minProfit", L["Min profit amount"], 80))
|
||||
else
|
||||
settingsFrame:GetElement("minProfitToggle"):SetMargin(0, 0, 0, 0)
|
||||
local linkedPriceLine = settingsFrame:GetElement("minProfit")
|
||||
settingsFrame:RemoveChild(linkedPriceLine)
|
||||
linkedPriceLine:Release()
|
||||
end
|
||||
settingsFrame:GetParentElement():GetParentElement():Draw()
|
||||
end
|
||||
|
||||
function private.CraftPriceToggleOnValueChanged(toggle, value)
|
||||
local operation = TSM.Operations.GetSettings("Crafting", private.currentOperationName)
|
||||
operation.craftPriceMethod = value and TSM.db.global.craftingOptions.defaultCraftPriceMethod or ""
|
||||
local settingsFrame = toggle:GetParentElement():GetParentElement()
|
||||
if value then
|
||||
settingsFrame:GetElement("craftPriceMethodToggle"):SetMargin(0, 0, 0, 12)
|
||||
settingsFrame:GetParentElement():AddChild(TSM.MainUI.Operations.CreateLinkedPriceInput("craftPriceMethod", L["Craft Value"], 80, BAD_CRAFT_VALUE_PRICE_SOURCES, TSM.db.global.craftingOptions.defaultCraftPriceMethod))
|
||||
else
|
||||
settingsFrame:GetElement("craftPriceMethodToggle"):SetMargin(0, 0, 0, 0)
|
||||
local linkedPriceLine = settingsFrame:GetElement("craftPriceMethod")
|
||||
settingsFrame:RemoveChild(linkedPriceLine)
|
||||
linkedPriceLine:Release()
|
||||
end
|
||||
settingsFrame:GetParentElement():GetParentElement():Draw()
|
||||
end
|
||||
220
Core/UI/MainUI/Operations/Mailing.lua
Normal file
220
Core/UI/MainUI/Operations/Mailing.lua
Normal file
@@ -0,0 +1,220 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Mailing = TSM.MainUI.Operations:NewPackage("Mailing")
|
||||
local PlayerInfo = TSM.Include("Service.PlayerInfo")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local private = {
|
||||
currentOperationName = nil,
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Mailing.OnInitialize()
|
||||
TSM.MainUI.Operations.RegisterModule("Mailing", private.GetMailingOperationSettings)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Mailing Operation Settings UI
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetMailingOperationSettings(operationName)
|
||||
TSM.UI.AnalyticsRecordPathChange("main", "operations", "mailing")
|
||||
private.currentOperationName = operationName
|
||||
local operation = TSM.Operations.GetSettings("Mailing", private.currentOperationName)
|
||||
return UIElements.New("ScrollFrame", "content")
|
||||
:SetPadding(8, 8, 8, 0)
|
||||
:AddChild(TSM.MainUI.Operations.CreateExpandableSection("Mailing", "generalOptions", L["General Options"], L["Adjust how items are mailed."])
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedSettingLine("target", L["Target character"])
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(48)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:AddChild(UIElements.New("Frame", "content")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:AddChild(UIElements.New("Input", "input")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetHintText(L["Enter player name"])
|
||||
:SetAutoComplete(PlayerInfo.GetConnectedAlts())
|
||||
:SetClearButtonEnabled(true)
|
||||
:SetDisabled(TSM.Operations.HasRelationship("Mailing", private.currentOperationName, "target"))
|
||||
:SetSettingInfo(operation, "target")
|
||||
)
|
||||
:AddChild(UIElements.New("ActionButton", "contacts")
|
||||
:SetSize(152, 24)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Contacts"])
|
||||
:SetScript("OnClick", private.ContactsBtnOnClick)
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedSettingLine("keepQty", L["Keep this amount"])
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(48)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:AddChild(UIElements.New("Frame", "content")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:AddChild(UIElements.New("Input", "input")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetBackgroundColor("ACTIVE_BG")
|
||||
:SetValidateFunc("NUMBER", "0:50000")
|
||||
:SetSettingInfo(operation, "keepQty")
|
||||
:SetDisabled(TSM.Operations.HasRelationship("Mailing", private.currentOperationName, "keepQty"))
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetTextColor(TSM.Operations.HasRelationship("Mailing", private.currentOperationName, "keepQty") and "TEXT_DISABLED" or "TEXT")
|
||||
:SetFormattedText(L["Enter a value from %d - %d"], 0, 50000)
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedSettingLine("maxQtyEnabled", L["Set max quantity"])
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(42)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:AddChild(UIElements.New("ToggleOnOff", "toggle")
|
||||
:SetHeight(18)
|
||||
:SetSettingInfo(operation, "maxQtyEnabled")
|
||||
:SetDisabled(TSM.Operations.HasRelationship("Mailing", private.currentOperationName, "maxQtyEnabled"))
|
||||
:SetScript("OnValueChanged", private.MaxQuantityToggleOnValueChanged)
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedSettingLine("maxQty", L["Max quantity"], not operation.maxQtyEnabled)
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(48)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:AddChild(UIElements.New("Frame", "content")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:AddChild(UIElements.New("Input", "input")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetBackgroundColor("ACTIVE_BG")
|
||||
:SetValidateFunc("NUMBER", "1:50000")
|
||||
:SetDisabled(TSM.Operations.HasRelationship("Mailing", private.currentOperationName, "maxQty") or not operation.maxQtyEnabled)
|
||||
:SetSettingInfo(operation, "maxQty")
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetTextColor((TSM.Operations.HasRelationship("Mailing", private.currentOperationName, "maxQty") or not operation.maxQtyEnabled) and "TEXT_DISABLED" or "TEXT")
|
||||
:SetFormattedText(L["Enter a value from %d - %d"], 1, 50000)
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedSettingLine("restock", L["Restock target to max quantity"], not operation.maxQtyEnabled)
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(42)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:AddChild(UIElements.New("ToggleOnOff", "toggle")
|
||||
:SetHeight(18)
|
||||
:SetSettingInfo(operation, "restock")
|
||||
:SetDisabled(TSM.Operations.HasRelationship("Mailing", private.currentOperationName, "restock") or not operation.maxQtyEnabled)
|
||||
:SetScript("OnValueChanged", private.RestockToggleOnValueChanged)
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedSettingLine("restockSources", L["Sources to include for restock"], not operation.restock or not operation.maxQtyEnabled)
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(48)
|
||||
:AddChild(UIElements.New("MultiselectionDropdown", "dropdown")
|
||||
:SetHeight(24)
|
||||
:AddItem(BANK, "bank")
|
||||
:AddItem(GUILD, "guild")
|
||||
:SetSettingInfo(operation, "restockSources")
|
||||
:SetSelectionText(L["No Sources"], L["%d Sources"], L["All Sources"])
|
||||
:SetDisabled(TSM.Operations.HasRelationship("Mailing", private.currentOperationName, "restockSources") or not operation.restock or not operation.maxQtyEnabled)
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.GetOperationManagementElements("Mailing", private.currentOperationName))
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.ContactsBtnOnClick(button)
|
||||
TSM.UI.Util.Contacts.ShowDialog(button, button:GetElement("__parent.input"))
|
||||
end
|
||||
|
||||
|
||||
function private.MaxQuantityToggleOnValueChanged(toggle, value)
|
||||
local settingsFrame = toggle:GetElement("__parent.__parent")
|
||||
local restockValue = settingsFrame:GetElement("restock.toggle"):GetValue()
|
||||
local relationshipSet, linkTexture, textColor = TSM.Operations.GetRelationshipColors("Mailing", private.currentOperationName, "maxQty", value)
|
||||
settingsFrame:GetElement("maxQty.line.linkBtn")
|
||||
:SetBackground(linkTexture)
|
||||
:SetDisabled(not value)
|
||||
settingsFrame:GetElement("maxQty.line.label")
|
||||
:SetTextColor(textColor)
|
||||
settingsFrame:GetElement("maxQty.content.label")
|
||||
:SetTextColor(textColor)
|
||||
settingsFrame:GetElement("maxQty.content.input")
|
||||
:SetDisabled(relationshipSet or not value)
|
||||
|
||||
relationshipSet, linkTexture, textColor = TSM.Operations.GetRelationshipColors("Mailing", private.currentOperationName, "restock", value)
|
||||
settingsFrame:GetElement("restock.line.linkBtn")
|
||||
:SetBackground(linkTexture)
|
||||
:SetDisabled(not value)
|
||||
settingsFrame:GetElement("restock.line.label")
|
||||
:SetTextColor(textColor)
|
||||
settingsFrame:GetElement("restock.toggle")
|
||||
:SetDisabled(relationshipSet or not value)
|
||||
|
||||
relationshipSet = TSM.Operations.HasRelationship("Mailing", private.currentOperationName, "restockSources")
|
||||
if relationshipSet and value and restockValue then
|
||||
linkTexture = TSM.UI.TexturePacks.GetColoredKey("iconPack.14x14/Link", "INDICATOR")
|
||||
elseif (relationshipSet and not value and restockValue) or (relationshipSet and value and not restockValue) or (relationshipSet and not value and not restockValue) then
|
||||
linkTexture = TSM.UI.TexturePacks.GetColoredKey("iconPack.14x14/Link", "INDICATOR_DISABLED")
|
||||
elseif (value and not restockValue) or (not value and restockValue) or (not value and not restockValue) then
|
||||
linkTexture = TSM.UI.TexturePacks.GetColoredKey("iconPack.14x14/Link", "TEXT_DISABLED")
|
||||
else
|
||||
linkTexture = TSM.UI.TexturePacks.GetColoredKey("iconPack.14x14/Link", "TEXT")
|
||||
end
|
||||
settingsFrame:GetElement("restockSources.line.linkBtn")
|
||||
:SetBackground(linkTexture)
|
||||
:SetDisabled(not value)
|
||||
settingsFrame:GetElement("restockSources.line.label")
|
||||
:SetTextColor(relationshipSet and "TEXT_DISABLED" or ((value and restockValue) and "TEXT" or "TEXT_DISABLED"))
|
||||
settingsFrame:GetElement("restockSources.dropdown")
|
||||
:SetDisabled(relationshipSet or not value or not restockValue)
|
||||
settingsFrame:Draw()
|
||||
end
|
||||
|
||||
function private.RestockToggleOnValueChanged(toggle, value)
|
||||
local settingsFrame = toggle:GetElement("__parent.__parent")
|
||||
local maxQtyEnabled = settingsFrame:GetElement("maxQtyEnabled.toggle"):GetValue()
|
||||
local relationshipSet = TSM.Operations.HasRelationship("Mailing", private.currentOperationName, "restockSources")
|
||||
local linkTexture = nil
|
||||
if relationshipSet and value and maxQtyEnabled then
|
||||
linkTexture = TSM.UI.TexturePacks.GetColoredKey("iconPack.14x14/Link", "INDICATOR")
|
||||
elseif (relationshipSet and not value and maxQtyEnabled) or (relationshipSet and value and not maxQtyEnabled) or (relationshipSet and not value and not maxQtyEnabled) then
|
||||
linkTexture = TSM.UI.TexturePacks.GetColoredKey("iconPack.14x14/Link", "INDICATOR_DISABLED")
|
||||
elseif (value and not maxQtyEnabled) or (not value and maxQtyEnabled) or (not value and not maxQtyEnabled) then
|
||||
linkTexture = TSM.UI.TexturePacks.GetColoredKey("iconPack.14x14/Link", "TEXT_DISABLED")
|
||||
else
|
||||
linkTexture = TSM.UI.TexturePacks.GetColoredKey("iconPack.14x14/Link", "TEXT")
|
||||
end
|
||||
settingsFrame:GetElement("restockSources.line.linkBtn")
|
||||
:SetBackground(linkTexture)
|
||||
:SetDisabled(not value)
|
||||
settingsFrame:GetElement("restockSources.line.label")
|
||||
:SetTextColor(relationshipSet and "TEXT_DISABLED" or ((value and maxQtyEnabled) and "TEXT" or "TEXT_DISABLED"))
|
||||
settingsFrame:GetElement("restockSources.dropdown")
|
||||
:SetDisabled(relationshipSet or not value or not maxQtyEnabled)
|
||||
settingsFrame:Draw()
|
||||
end
|
||||
88
Core/UI/MainUI/Operations/Shopping.lua
Normal file
88
Core/UI/MainUI/Operations/Shopping.lua
Normal file
@@ -0,0 +1,88 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Shopping = TSM.MainUI.Operations:NewPackage("Shopping")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local private = {
|
||||
currentOperationName = nil,
|
||||
}
|
||||
local RESTOCK_SOURCES = { L["Alts"], L["Auctions"], BANK, GUILD }
|
||||
local RESTOCK_SOURCES_KEYS = { "alts", "auctions", "bank", "guild" }
|
||||
local BAD_PRICE_SOURCES = { shoppingopmax = true }
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Shopping.OnInitialize()
|
||||
TSM.MainUI.Operations.RegisterModule("Shopping", private.GetShoppingOperationSettings)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Shopping Operation Settings UI
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetShoppingOperationSettings(operationName)
|
||||
TSM.UI.AnalyticsRecordPathChange("main", "operations", "shopping")
|
||||
private.currentOperationName = operationName
|
||||
local operation = TSM.Operations.GetSettings("Shopping", private.currentOperationName)
|
||||
return UIElements.New("ScrollFrame", "settings")
|
||||
:SetPadding(8, 8, 8, 0)
|
||||
:SetBackgroundColor("PRIMARY_BG")
|
||||
:AddChild(TSM.MainUI.Operations.CreateExpandableSection("Shopping", "generalOptions", L["General Options"], L["Set what items are shown during a Shopping scan."])
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedPriceInput("maxPrice", L["Maximum auction price"], 124, BAD_PRICE_SOURCES))
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedSettingLine("showAboveMaxPrice", L["Show auctions above max price"])
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(48)
|
||||
:SetMargin(0, 0, 12, 12)
|
||||
:AddChild(UIElements.New("ToggleOnOff", "toggle")
|
||||
:SetHeight(18)
|
||||
:SetSettingInfo(operation, "showAboveMaxPrice")
|
||||
:SetDisabled(TSM.Operations.HasRelationship("Shopping", private.currentOperationName, "showAboveMaxPrice"))
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedSettingLine("restockQuantity", L["Maximum restock quantity"])
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(48)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:AddChild(UIElements.New("Frame", "content")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:AddChild(UIElements.New("Input", "input")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetBackgroundColor("ACTIVE_BG")
|
||||
:SetValidateFunc("CUSTOM_PRICE")
|
||||
:SetSettingInfo(operation, "restockQuantity")
|
||||
:SetDisabled(TSM.Operations.HasRelationship("Shopping", private.currentOperationName, "restockQuantity"))
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetTextColor(TSM.Operations.HasRelationship("Shopping", private.currentOperationName, "restockQuantity") and "TEXT_DISABLED" or "TEXT")
|
||||
:SetFormattedText(L["Supported range: %d - %d"], TSM.Operations.Shopping.GetRestockRange())
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedSettingLine("restockSources", L["Sources to include for restock"])
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(48)
|
||||
:AddChild(UIElements.New("MultiselectionDropdown", "dropdown")
|
||||
:SetHeight(24)
|
||||
:SetItems(RESTOCK_SOURCES, RESTOCK_SOURCES_KEYS)
|
||||
:SetSettingInfo(operation, "restockSources")
|
||||
:SetSelectionText(L["No Sources"], L["%d Sources"], L["All Sources"])
|
||||
:SetDisabled(TSM.Operations.HasRelationship("Shopping", private.currentOperationName, "restockSources"))
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.GetOperationManagementElements("Shopping", private.currentOperationName))
|
||||
end
|
||||
42
Core/UI/MainUI/Operations/Sniper.lua
Normal file
42
Core/UI/MainUI/Operations/Sniper.lua
Normal file
@@ -0,0 +1,42 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Sniper = TSM.MainUI.Operations:NewPackage("Sniper")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local private = {
|
||||
currentOperationName = nil,
|
||||
}
|
||||
local BAD_PRICE_SOURCES = { sniperopmax = true }
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Sniper.OnInitialize()
|
||||
TSM.MainUI.Operations.RegisterModule("Sniper", private.GetSniperOperationSettings)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Sniper Operation Settings UI
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetSniperOperationSettings(operationName)
|
||||
TSM.UI.AnalyticsRecordPathChange("main", "operations", "sniper")
|
||||
private.currentOperationName = operationName
|
||||
return UIElements.New("ScrollFrame", "settings")
|
||||
:SetPadding(8, 8, 8, 0)
|
||||
:SetBackgroundColor("PRIMARY_BG")
|
||||
:AddChild(TSM.MainUI.Operations.CreateExpandableSection("Sniper", "settings", L["General Options"], L["Set what items are shown during a Sniper scan."])
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedPriceInput("belowPrice", L["Maximum price"], 124, BAD_PRICE_SOURCES))
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.GetOperationManagementElements("Sniper", private.currentOperationName))
|
||||
end
|
||||
268
Core/UI/MainUI/Operations/Vendoring.lua
Normal file
268
Core/UI/MainUI/Operations/Vendoring.lua
Normal file
@@ -0,0 +1,268 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Vendoring = TSM.MainUI.Operations:NewPackage("Vendoring")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local private = {
|
||||
currentOperationName = nil,
|
||||
}
|
||||
local RESTOCK_SOURCES = { BANK, GUILD, L["Alts"], L["Alts AH"], L["AH"], L["Mail"] }
|
||||
local RESTOCK_SOURCES_KEYS = { "bank", "guild", "alts", "alts_ah", "ah", "mail" }
|
||||
local SETTING_INFO = {
|
||||
restockQty = "INPUT_LABEL",
|
||||
restockSources = "DROPDOWN",
|
||||
keepQty = "INPUT_LABEL",
|
||||
sellAfterExpired = "INPUT_LABEL",
|
||||
vsMarketValue = "INPUT",
|
||||
vsMaxMarketValue = "INPUT",
|
||||
vsDestroyValue = "INPUT",
|
||||
vsMaxDestroyValue = "INPUT",
|
||||
sellSoulbound = "TOGGLE",
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Vendoring.OnInitialize()
|
||||
TSM.MainUI.Operations.RegisterModule("Vendoring", private.GetVendoringOperationSettings)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Vendoring Operation Settings UI
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetVendoringOperationSettings(operationName)
|
||||
TSM.UI.AnalyticsRecordPathChange("main", "operations", "vendoring")
|
||||
private.currentOperationName = operationName
|
||||
|
||||
local operation = TSM.Operations.GetSettings("Vendoring", private.currentOperationName)
|
||||
return UIElements.New("ScrollFrame", "settings")
|
||||
:SetPadding(8, 8, 8, 0)
|
||||
:AddChild(TSM.MainUI.Operations.CreateExpandableSection("Vendoring", "buyOptionsHeading", L["Buy Options"], L["Set what is bought from a vendor."])
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedSettingLine("enableBuy", L["Enable buying"])
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(48)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:AddChild(UIElements.New("ToggleOnOff", "toggle")
|
||||
:SetHeight(18)
|
||||
:SetSettingInfo(operation, "enableBuy")
|
||||
:SetDisabled(TSM.Operations.HasRelationship("Vendoring", private.currentOperationName, "enableBuy"))
|
||||
:SetScript("OnValueChanged", private.EnableBuyingToggleOnValueChanged)
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedSettingLine("restockQty", L["Restock quantity"], not operation.enableBuy)
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(48)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:AddChild(UIElements.New("Frame", "content")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:AddChild(UIElements.New("Input", "input")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetBackgroundColor("ACTIVE_BG")
|
||||
:SetValidateFunc("NUMBER", "0:50000")
|
||||
:SetSettingInfo(operation, "restockQty")
|
||||
:SetDisabled(TSM.Operations.HasRelationship("Vendoring", private.currentOperationName, "restockQty") or not operation.enableBuy)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetTextColor((TSM.Operations.HasRelationship("Vendoring", private.currentOperationName, "restockQty") or not operation.enableBuy) and "TEXT_DISABLED" or "TEXT")
|
||||
:SetFormattedText(L["Enter a value from %d - %d"], 0, 50000)
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedSettingLine("restockSources", L["Sources to include for restock"], not operation.enableBuy)
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(48)
|
||||
:AddChild(UIElements.New("MultiselectionDropdown", "dropdown")
|
||||
:SetHeight(24)
|
||||
:SetItems(RESTOCK_SOURCES, RESTOCK_SOURCES_KEYS)
|
||||
:SetSettingInfo(operation, "restockSources")
|
||||
:SetSelectionText(L["No Sources"], L["%d Sources"], L["All Sources"])
|
||||
:SetDisabled(TSM.Operations.HasRelationship("Vendoring", private.currentOperationName, "restockSources") or not operation.enableBuy)
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.CreateExpandableSection("Vendoring", "sellOptionsHeading", L["Sell Options"], L["Set what is sold to a vendor."])
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedSettingLine("enableSell", L["Enable selling"])
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(48)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:AddChild(UIElements.New("ToggleOnOff", "toggle")
|
||||
:SetHeight(18)
|
||||
:SetSettingInfo(operation, "enableSell")
|
||||
:SetScript("OnValueChanged", private.EnableSellingToggleOnValueChanged)
|
||||
:SetDisabled(TSM.Operations.HasRelationship("Vendoring", private.currentOperationName, "enableSell"))
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedSettingLine("keepQty", L["Keep quantity"], not operation.enableSell)
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(48)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:AddChild(UIElements.New("Frame", "content")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:AddChild(UIElements.New("Input", "input")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetBackgroundColor("ACTIVE_BG")
|
||||
:SetValidateFunc("NUMBER", "0:50000")
|
||||
:SetSettingInfo(operation, "keepQty")
|
||||
:SetDisabled(TSM.Operations.HasRelationship("Vendoring", private.currentOperationName, "keepQty") or not operation.enableSell)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetTextColor((TSM.Operations.HasRelationship("Vendoring", private.currentOperationName, "keepQty") or not operation.enableSell) and "TEXT_DISABLED" or "TEXT")
|
||||
:SetFormattedText(L["Enter a value from %d - %d"], 0, 50000)
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedSettingLine("sellAfterExpired", L["Min number of expires"], not operation.enableSell)
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(48)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:AddChild(UIElements.New("Frame", "content")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:AddChild(UIElements.New("Input", "input")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetBackgroundColor("ACTIVE_BG")
|
||||
:SetValidateFunc("NUMBER", "0:50000")
|
||||
:SetSettingInfo(operation, "sellAfterExpired")
|
||||
:SetDisabled(TSM.Operations.HasRelationship("Vendoring", private.currentOperationName, "sellAfterExpired") or not operation.enableSell)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetTextColor((TSM.Operations.HasRelationship("Vendoring", private.currentOperationName, "sellAfterExpired") or not operation.enableSell) and "TEXT_DISABLED" or "TEXT")
|
||||
:SetFormattedText(L["Enter a value from %d - %d"], 0, 50000)
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedSettingLine("vsMarketValue", L["Market value"], not operation.enableSell)
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(48)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:AddChild(UIElements.New("Input", "input")
|
||||
:SetHeight(24)
|
||||
:SetBackgroundColor("ACTIVE_BG")
|
||||
:SetValidateFunc("CUSTOM_PRICE")
|
||||
:SetSettingInfo(operation, "vsMarketValue")
|
||||
:SetDisabled(TSM.Operations.HasRelationship("Vendoring", private.currentOperationName, "vsMarketValue") or not operation.enableSell)
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedSettingLine("vsMaxMarketValue", L["Max market value (Enter '0c' to disable)"], not operation.enableSell)
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(48)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:AddChild(UIElements.New("Input", "input")
|
||||
:SetHeight(24)
|
||||
:SetBackgroundColor("ACTIVE_BG")
|
||||
:SetValidateFunc("CUSTOM_PRICE")
|
||||
:SetSettingInfo(operation, "vsMaxMarketValue")
|
||||
:SetDisabled(TSM.Operations.HasRelationship("Vendoring", private.currentOperationName, "vsMaxMarketValue") or not operation.enableSell)
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedSettingLine("vsDestroyValue", L["Destroy value"], not operation.enableSell)
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(48)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:AddChild(UIElements.New("Input", "input")
|
||||
:SetHeight(24)
|
||||
:SetBackgroundColor("ACTIVE_BG")
|
||||
:SetValidateFunc("CUSTOM_PRICE")
|
||||
:SetSettingInfo(operation, "vsDestroyValue")
|
||||
:SetDisabled(TSM.Operations.HasRelationship("Vendoring", private.currentOperationName, "vsDestroyValue") or not operation.enableSell)
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedSettingLine("vsMaxDestroyValue", L["Max destroy value (Enter '0c' to disable)"], not operation.enableSell)
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(48)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:AddChild(UIElements.New("Input", "input")
|
||||
:SetHeight(24)
|
||||
:SetBackgroundColor("ACTIVE_BG")
|
||||
:SetValidateFunc("CUSTOM_PRICE")
|
||||
:SetSettingInfo(operation, "vsMaxDestroyValue")
|
||||
:SetDisabled(TSM.Operations.HasRelationship("Vendoring", private.currentOperationName, "vsMaxDestroyValue") or not operation.enableSell)
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedSettingLine("sellSoulbound", L["Sell soulbound items"], not operation.enableSell)
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(48)
|
||||
:SetMargin(0, 0, 0, 0)
|
||||
:AddChild(UIElements.New("ToggleOnOff", "toggle")
|
||||
:SetHeight(18)
|
||||
:SetSettingInfo(operation, "sellSoulbound")
|
||||
:SetDisabled(TSM.Operations.HasRelationship("Vendoring", private.currentOperationName, "sellSoulbound") or not operation.enableSell)
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.GetOperationManagementElements("Vendoring", private.currentOperationName))
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.UpdateSettingState(settingsFrame, key, operation, value)
|
||||
local relationshipSet, linkTexture, textColor = TSM.Operations.GetRelationshipColors("Vendoring", private.currentOperationName, key, value)
|
||||
local settingKeyFrame = settingsFrame:GetElement(key)
|
||||
settingKeyFrame:GetElement("line.linkBtn")
|
||||
:SetBackground(linkTexture)
|
||||
:SetDisabled(not value)
|
||||
settingKeyFrame:GetElement("line.label")
|
||||
:SetTextColor(textColor)
|
||||
local settingType = SETTING_INFO[key]
|
||||
if settingType == "INPUT_LABEL" then
|
||||
settingKeyFrame:GetElement("content.input")
|
||||
:SetDisabled(relationshipSet or not value)
|
||||
:SetValue(operation[key] or "")
|
||||
settingKeyFrame:GetElement("content.label")
|
||||
:SetTextColor(textColor)
|
||||
elseif settingType == "INPUT" then
|
||||
settingKeyFrame:GetElement("input")
|
||||
:SetDisabled(relationshipSet or not value)
|
||||
elseif settingType == "TOGGLE" then
|
||||
settingKeyFrame:GetElement("toggle")
|
||||
:SetDisabled(relationshipSet or not value)
|
||||
elseif settingType == "DROPDOWN" then
|
||||
settingKeyFrame:GetElement("dropdown")
|
||||
:SetDisabled(relationshipSet or not value)
|
||||
else
|
||||
error("Invalid settingType: "..tostring(settingType))
|
||||
end
|
||||
end
|
||||
|
||||
function private.EnableBuyingToggleOnValueChanged(toggle, value)
|
||||
local operation = TSM.Operations.GetSettings("Vendoring", private.currentOperationName)
|
||||
local settingsFrame = toggle:GetElement("__parent.__parent")
|
||||
private.UpdateSettingState(settingsFrame, "restockQty", operation, value)
|
||||
private.UpdateSettingState(settingsFrame, "restockSources", operation, value)
|
||||
settingsFrame:Draw()
|
||||
end
|
||||
|
||||
function private.EnableSellingToggleOnValueChanged(toggle, value)
|
||||
local operation = TSM.Operations.GetSettings("Vendoring", private.currentOperationName)
|
||||
local settingsFrame = toggle:GetElement("__parent.__parent")
|
||||
for key in pairs(SETTING_INFO) do
|
||||
if key ~= "restockQty" and key ~= "restockSources" then
|
||||
private.UpdateSettingState(settingsFrame, key, operation, value)
|
||||
end
|
||||
end
|
||||
settingsFrame:Draw()
|
||||
end
|
||||
108
Core/UI/MainUI/Operations/Warehousing.lua
Normal file
108
Core/UI/MainUI/Operations/Warehousing.lua
Normal file
@@ -0,0 +1,108 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Warehousing = TSM.MainUI.Operations:NewPackage("Warehousing")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local private = { currentOperationName = nil }
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Warehousing.OnInitialize()
|
||||
TSM.MainUI.Operations.RegisterModule("Warehousing", private.GetWarehousingOperationSettings)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Warehousing Operation Settings UI
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetWarehousingOperationSettings(operationName)
|
||||
TSM.UI.AnalyticsRecordPathChange("main", "operations", "warehousing")
|
||||
private.currentOperationName = operationName
|
||||
return UIElements.New("ScrollFrame", "settings")
|
||||
:SetPadding(8, 8, 8, 0)
|
||||
:AddChild(TSM.MainUI.Operations.CreateExpandableSection("Warehousing", "moveSettings", L["Move Quantity Options"], L["Set how items are moved out of the bank."])
|
||||
:AddChild(private.CreateEnabledSettingLine("moveQuantity", L["Set move quantity"], L["Quantity to move"], 0, 50000, true))
|
||||
:AddChild(private.CreateEnabledSettingLine("stackSize", L["Set stack size"], L["Stack size multiple"], 0, 200, true))
|
||||
:AddChild(private.CreateEnabledSettingLine("keepBagQuantity", L["Set keep in bags quantity"], L["Keep in bags quantity"], 0, 50000, true))
|
||||
:AddChild(private.CreateEnabledSettingLine("keepBankQuantity", L["Set keep in bank quantity"], L["Keep in bank quantity"], 0, 50000))
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.CreateExpandableSection("Warehousing", "restockSettings", L["Restock Options"], L["Set how items are restocked from the bank."])
|
||||
:AddChild(private.CreateEnabledSettingLine("restockQuantity", L["Enable restock"], L["Restock quantity"], 0, 50000, true))
|
||||
:AddChild(private.CreateEnabledSettingLine("restockStackSize", L["Set stack size for restock"], L["Stack size multiple"], 0, 200, true))
|
||||
:AddChild(private.CreateEnabledSettingLine("restockKeepBankQuantity", L["Set keep in bank quantity"], L["Keep in bank quantity"], 0, 50000))
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.GetOperationManagementElements("Warehousing", private.currentOperationName))
|
||||
end
|
||||
|
||||
function private.CreateEnabledSettingLine(key, enableText, text, minValue, maxValue, margin)
|
||||
local operation = TSM.Operations.GetSettings("Warehousing", private.currentOperationName)
|
||||
local hasRelationship = TSM.Operations.HasRelationship("Warehousing", private.currentOperationName, key)
|
||||
return UIElements.New("Frame", "content")
|
||||
:SetLayout("VERTICAL")
|
||||
:AddChild(TSM.MainUI.Operations.CreateLinkedSettingLine(key, text)
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(48)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:AddChild(UIElements.New("ToggleOnOff", "toggle")
|
||||
:SetHeight(18)
|
||||
:SetValue(operation[key] ~= 0)
|
||||
:SetDisabled(hasRelationship)
|
||||
:SetContext(key)
|
||||
:SetScript("OnValueChanged", private.EnabledSettingEnableOnValueChanged)
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Operations.CreateSettingLine("content", text, hasRelationship or operation[key] == 0)
|
||||
:SetLayout("VERTICAL")
|
||||
:SetHeight(48)
|
||||
:SetMargin(0, 0, 0, margin and 12 or 4)
|
||||
:AddChild(UIElements.New("Frame", "content")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:AddChild(UIElements.New("Input", "input")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetBackgroundColor("ACTIVE_BG")
|
||||
:SetValidateFunc("NUMBER", minValue..":"..maxValue)
|
||||
:SetSettingInfo(operation, key)
|
||||
:SetDisabled(hasRelationship or operation[key] == 0)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetTextColor((hasRelationship or operation[key] == 0) and "TEXT_DISABLED" or "TEXT")
|
||||
:SetFormattedText(L["Enter a value from %d - %d"], minValue, maxValue)
|
||||
)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.EnabledSettingEnableOnValueChanged(toggle, value)
|
||||
local key = toggle:GetContext()
|
||||
local operation = TSM.Operations.GetSettings("Warehousing", private.currentOperationName)
|
||||
operation[key] = value and 1 or 0
|
||||
local settingFrame = toggle:GetElement("__parent.__parent.content")
|
||||
settingFrame:GetElement("label")
|
||||
:SetTextColor(value and "TEXT" or "TEXT_DISABLED")
|
||||
settingFrame:GetElement("content.input")
|
||||
:SetDisabled(not value)
|
||||
:SetValue(operation[key])
|
||||
settingFrame:GetElement("content.label")
|
||||
:SetTextColor(value and "TEXT" or "TEXT_DISABLED")
|
||||
settingFrame:Draw()
|
||||
end
|
||||
105
Core/UI/MainUI/Settings/Accounting.lua
Normal file
105
Core/UI/MainUI/Settings/Accounting.lua
Normal file
@@ -0,0 +1,105 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Accounting = TSM.MainUI.Settings:NewPackage("Accounting")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local Log = TSM.Include("Util.Log")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local private = {}
|
||||
local DAYS_OLD_OPTIONS = { 0, 15, 30, 45, 60, 75, 90, 180, 360 }
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Accounting.OnInitialize()
|
||||
TSM.MainUI.Settings.RegisterSettingPage(L["Accounting"], "middle", private.GetAccountingSettingsFrame)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Accounting Settings UI
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetAccountingSettingsFrame()
|
||||
TSM.UI.AnalyticsRecordPathChange("main", "settings", "accounting")
|
||||
return UIElements.New("ScrollFrame", "accountingSettings")
|
||||
:SetPadding(8, 8, 8, 0)
|
||||
:AddChild(TSM.MainUI.Settings.CreateExpandableSection("Accounting", "accounting", L["General Options"], L["Some general Accounting options are below."])
|
||||
:AddChild(UIElements.New("Frame", "check1")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:AddChild(UIElements.New("Checkbox", "tradeCheckbox")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Track Sales / Purchases via trade"])
|
||||
:SetSettingInfo(TSM.db.global.accountingOptions, "trackTrades")
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "check2")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:AddChild(UIElements.New("Checkbox", "tradePromptCheckbox")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Don't prompt to record trades"])
|
||||
:SetSettingInfo(TSM.db.global.accountingOptions, "autoTrackTrades")
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Settings.CreateExpandableSection("Accounting", "accounting", L["Clear Old Data"], L["You can clear old Accounting data below to keep things running smoothly."])
|
||||
:AddChild(UIElements.New("Text", "daysOldLabel")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 4)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Remove Data Older Than (Days)"])
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "daysOld")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:AddChild(UIElements.New("SelectionDropdown", "dropdown")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetHintText(L["None Selected"])
|
||||
:SetItems(DAYS_OLD_OPTIONS)
|
||||
:SetScript("OnSelectionChanged", private.DaysOldDropdownOnSelectionChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("ActionButton", "clearBtn")
|
||||
:SetWidth(107)
|
||||
:SetDisabled(true)
|
||||
:SetText(L["Clear Data"])
|
||||
:SetScript("OnClick", private.ClearBtnOnClick)
|
||||
)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.DaysOldDropdownOnSelectionChanged(dropdown)
|
||||
dropdown:GetElement("__parent.clearBtn")
|
||||
:SetDisabled(false)
|
||||
:Draw()
|
||||
end
|
||||
|
||||
function private.ClearBtnOnClick(button)
|
||||
local days = button:GetElement("__parent.dropdown"):GetSelectedItem()
|
||||
button:GetBaseElement():ShowConfirmationDialog(L["Clear Old Data?"], L["Are you sure you want to clear old accounting data?"], private.ClearDataConfirmed, days)
|
||||
end
|
||||
|
||||
function private.ClearDataConfirmed(days)
|
||||
Log.PrintfUser(L["Removed a total of %s old records."], TSM.Accounting.Transactions.RemoveOldData(days) + TSM.Accounting.Money.RemoveOldData(days) + TSM.Accounting.Auctions.RemoveOldData(days))
|
||||
end
|
||||
182
Core/UI/MainUI/Settings/Appearance.lua
Normal file
182
Core/UI/MainUI/Settings/Appearance.lua
Normal file
@@ -0,0 +1,182 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Appearance = TSM.MainUI.Settings:NewPackage("Appearance")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local LibDBIcon = LibStub("LibDBIcon-1.0")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local private = {
|
||||
colorSetKeys = {},
|
||||
colorSetNames = {},
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Appearance.OnInitialize()
|
||||
for _, key, name in TSM.UI.Util.ColorSetIterator() do
|
||||
tinsert(private.colorSetKeys, key)
|
||||
tinsert(private.colorSetNames, name)
|
||||
end
|
||||
TSM.MainUI.Settings.RegisterSettingPage(L["Appearance"], "middle", private.GetSettingsFrame)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Appearance Settings UI
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetSettingsFrame()
|
||||
TSM.UI.AnalyticsRecordPathChange("main", "settings", "appearance")
|
||||
return UIElements.New("ScrollFrame", "generalSettings")
|
||||
:SetPadding(8, 8, 8, 0)
|
||||
:AddChild(TSM.MainUI.Settings.CreateExpandableSection("Appearance", "appearance", L["General Options"], L["Some general appearance options are below."])
|
||||
:AddChild(UIElements.New("Frame", "content")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:AddChild(UIElements.New("Checkbox", "minimapCheckbox")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Hide minimap icon"])
|
||||
:SetSettingInfo(TSM.db.global.coreOptions.minimapIcon, "hide")
|
||||
:SetScript("OnValueChanged", private.MinimapOnValueChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "content")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:AddChild(UIElements.New("Checkbox", "taskListLockCheckbox")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Lock task list's background"])
|
||||
:SetSettingInfo(TSM.db.global.appearanceOptions, "taskListBackgroundLock")
|
||||
:SetScript("OnValueChanged", private.TaskListLockOnValueChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "content")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:AddChild(UIElements.New("Checkbox", "showTotalMoneyCheckbox")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Show total gold in header"])
|
||||
:SetSettingInfo(TSM.db.global.appearanceOptions, "showTotalMoney")
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetHeight(24)
|
||||
:SetMargin(12, 0, 4, 12)
|
||||
:SetFont("BODY_BODY1_BOLD")
|
||||
:SetText(L["Themes"])
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "theme")
|
||||
:SetLayout("FLOW")
|
||||
:AddChildrenWithFunction(private.AddTheme)
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.MinimapOnValueChanged(_, value)
|
||||
if value then
|
||||
LibDBIcon:Hide("TradeSkillMaster")
|
||||
else
|
||||
LibDBIcon:Show("TradeSkillMaster")
|
||||
end
|
||||
end
|
||||
|
||||
function private.TaskListLockOnValueChanged(_, value)
|
||||
TSM.db.global.appearanceOptions.taskListBackgroundLock = value
|
||||
if TSM.UI.TaskListUI.IsVisible() then
|
||||
TSM.UI.TaskListUI.UpdateFrame()
|
||||
end
|
||||
end
|
||||
|
||||
function private.AddTheme(frame)
|
||||
for _, key, name in TSM.UI.Util.ColorSetIterator() do
|
||||
frame:AddChild(UIElements.New("Frame", name)
|
||||
:SetLayout("VERTICAL")
|
||||
:SetSize(198, 140)
|
||||
:SetPadding(0, 0, 12, 8)
|
||||
:SetMargin(0, 12, 0, 8)
|
||||
:SetBackgroundColor(Theme.GetColor("FRAME_BG", key), true)
|
||||
:SetBorderColor(Theme.GetColor("ACTIVE_BG_ALT", key))
|
||||
:SetContext(key)
|
||||
:AddChild(UIElements.New("Frame", "top")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(36)
|
||||
:SetMargin(8, 8, 0, 12)
|
||||
:AddChild(UIElements.New("Frame", "left")
|
||||
:SetSize(36, 36)
|
||||
:SetMargin(0, 12, 0, 0)
|
||||
:SetBackgroundColor(Theme.GetColor("ACTIVE_BG_ALT", key), true)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "right")
|
||||
:SetLayout("VERTICAL")
|
||||
:AddChild(UIElements.New("Frame", "line1")
|
||||
:SetHeight(12)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:SetBackgroundColor(Theme.GetColor("ACTIVE_BG", key), true)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "line2")
|
||||
:SetHeight(12)
|
||||
:SetBackgroundColor(Theme.GetColor("PRIMARY_BG_ALT", key), true)
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "line3")
|
||||
:SetMargin(8, 8, 0, 12)
|
||||
:SetBackgroundColor(Theme.GetColor("PRIMARY_BG", key), true)
|
||||
)
|
||||
:AddChild(UIElements.New("Texture", "divider")
|
||||
:SetHeight(1)
|
||||
:SetTexture(Theme.GetColor("ACTIVE_BG_ALT", key))
|
||||
)
|
||||
:AddChild(UIElements.New("Toggle", "toggle")
|
||||
:SetHeight(20)
|
||||
:SetMargin(8, 0, 8, 0)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:AddOption(Theme.GetThemeName(key), TSM.db.global.appearanceOptions.colorSet == key)
|
||||
:SetScript("OnValueChanged", private.ThemeButtonOnClick)
|
||||
)
|
||||
:AddChildNoLayout(UIElements.New("Button", "btn")
|
||||
:AddAnchor("TOPLEFT")
|
||||
:AddAnchor("BOTTOMRIGHT")
|
||||
:SetScript("OnClick", private.ThemeButtonOnClick)
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
function private.ThemeButtonOnClick(buttonToggle)
|
||||
local selectedKey = buttonToggle:GetParentElement():GetContext()
|
||||
for _, key, name in TSM.UI.Util.ColorSetIterator() do
|
||||
local toggle = buttonToggle:GetElement("__parent.__parent."..name..".toggle")
|
||||
if key == selectedKey then
|
||||
toggle:SetOption(name, true)
|
||||
else
|
||||
toggle:ClearOption(true)
|
||||
end
|
||||
end
|
||||
TSM.db.global.appearanceOptions.colorSet = selectedKey
|
||||
Theme.SetActiveColorSet(selectedKey)
|
||||
end
|
||||
225
Core/UI/MainUI/Settings/Auctioning.lua
Normal file
225
Core/UI/MainUI/Settings/Auctioning.lua
Normal file
@@ -0,0 +1,225 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Auctioning = TSM.MainUI.Settings:NewPackage("Auctioning")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local Sound = TSM.Include("Util.Sound")
|
||||
local String = TSM.Include("Util.String")
|
||||
local Log = TSM.Include("Util.Log")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local private = {
|
||||
sounds = {},
|
||||
soundkeys = {},
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Auctioning.OnInitialize()
|
||||
TSM.MainUI.Settings.RegisterSettingPage(L["Auctioning"], "middle", private.GetAuctioningSettingsFrame)
|
||||
for key, name in pairs(Sound.GetSounds()) do
|
||||
tinsert(private.sounds, name)
|
||||
tinsert(private.soundkeys, key)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Auctioning Settings UI
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetAuctioningSettingsFrame()
|
||||
TSM.UI.AnalyticsRecordPathChange("main", "settings", "auctioning")
|
||||
return UIElements.New("ScrollFrame", "auctioningSettings")
|
||||
:SetPadding(8, 8, 8, 0)
|
||||
:AddChild(TSM.MainUI.Settings.CreateExpandableSection("Auctioning", "auctioning", L["General Options"], L["Some general Auctioning options are below."])
|
||||
:AddChild(UIElements.New("Frame", "check1")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:AddChild(UIElements.New("Checkbox", "cancelBids")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Cancel auctions with bids"])
|
||||
:SetSettingInfo(TSM.db.global.auctioningOptions, "cancelWithBid")
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "check2")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:AddChild(UIElements.New("Checkbox", "invalidPrice")
|
||||
:SetWidth("AUTO")
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Disable invalid price warnings"])
|
||||
:SetSettingInfo(TSM.db.global.auctioningOptions, "disableInvalidMsg")
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Settings.CreateExpandableSection("Auctioning", "sounds", L["Auction House Sounds"], L["Setup various sounds that play when doing Auctioning scans."])
|
||||
:AddChild(UIElements.New("Frame", "labelLine1")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 4)
|
||||
:AddChild(UIElements.New("Text", "scan")
|
||||
:SetMargin(0, 12, 0, 0)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Scan complete sound"])
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "confirm")
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Confirm complete sound"])
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "dropdownLine1")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:AddChild(UIElements.New("SelectionDropdown", "scanComplete")
|
||||
:SetMargin(0, 12, 0, 0)
|
||||
:SetItems(private.sounds, private.soundkeys)
|
||||
:SetSettingInfo(TSM.db.global.auctioningOptions, "scanCompleteSound")
|
||||
:SetScript("OnSelectionChanged", private.SoundOnSelectionChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("SelectionDropdown", "confirmComplete")
|
||||
:SetItems(private.sounds, private.soundkeys)
|
||||
:SetSettingInfo(TSM.db.global.auctioningOptions, "confirmCompleteSound")
|
||||
:SetScript("OnSelectionChanged", private.SoundOnSelectionChanged)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "saleLabel")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 4)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Auction sale sound"])
|
||||
)
|
||||
:AddChild(UIElements.New("SelectionDropdown", "saleDropdown")
|
||||
:SetHeight(24)
|
||||
:SetItems(private.sounds, private.soundkeys)
|
||||
:SetSettingInfo(TSM.db.global.coreOptions, "auctionSaleSound")
|
||||
:SetScript("OnSelectionChanged", private.SoundOnSelectionChanged)
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Settings.CreateExpandableSection("Auctioning", "whitelist", L["Whitelist"], L["TSM will not undercut any players you add to your whitelist."])
|
||||
:AddChild(UIElements.New("Text", "matchLabel")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 4)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Match whitelisted players"])
|
||||
)
|
||||
:AddChild(UIElements.New("ToggleOnOff", "matchToggle")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:SetSettingInfo(TSM.db.global.auctioningOptions, "matchWhitelist")
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "addLabel")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 4)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Whitelisted characters"])
|
||||
)
|
||||
:AddChild(UIElements.New("Input", "newPlayerInput")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, 0, 4)
|
||||
:SetBackgroundColor("ACTIVE_BG")
|
||||
:SetHintText(L["Enter player name"])
|
||||
:SetScript("OnEnterPressed", private.NewPlayerOnEnterPressed)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "whitelistFrame")
|
||||
:SetLayout("FLOW")
|
||||
:SetHeight(60)
|
||||
:AddChildrenWithFunction(private.AddWhitelistRows)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
function private.AddWhitelistRows(containerFrame)
|
||||
for player in pairs(TSM.db.factionrealm.auctioningOptions.whitelist) do
|
||||
private.AddWhitelistRow(containerFrame, player)
|
||||
end
|
||||
end
|
||||
|
||||
function private.AddWhitelistRow(frame, player)
|
||||
frame:AddChild(UIElements.New("Frame", "whitelist_"..player)
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetSize(100, 20)
|
||||
:SetMargin(0, 12, 0, 0)
|
||||
:AddChild(UIElements.New("Text", "text")
|
||||
:SetWidth("AUTO")
|
||||
:SetMargin(0, 2, 0, 0)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(player)
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "removeBtn")
|
||||
:SetBackgroundAndSize("iconPack.14x14/Close/Circle")
|
||||
:SetContext(player)
|
||||
:SetScript("OnClick", private.RemoveWhitelistOnClick)
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer"))
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.SoundOnSelectionChanged(self)
|
||||
Sound.PlaySound(self:GetSelectedItemKey())
|
||||
end
|
||||
|
||||
function private.NewPlayerOnEnterPressed(input)
|
||||
local newPlayer = strlower(input:GetValue())
|
||||
input:SetValue("")
|
||||
input:Draw()
|
||||
if newPlayer == "" or strfind(newPlayer, ",") or newPlayer ~= String.Escape(newPlayer) then
|
||||
Log.PrintfUser(L["Invalid player name."])
|
||||
return
|
||||
elseif TSM.db.factionrealm.auctioningOptions.whitelist[newPlayer] then
|
||||
Log.PrintfUser(L["The player \"%s\" is already on your whitelist."], TSM.db.factionrealm.auctioningOptions.whitelist[newPlayer])
|
||||
return
|
||||
end
|
||||
|
||||
local isAlt = false
|
||||
for factionrealm in TSM.db:GetConnectedRealmIterator("factionrealm") do
|
||||
for _, character in TSM.db:FactionrealmCharacterIterator(factionrealm) do
|
||||
if strlower(newPlayer) == strlower(character) then
|
||||
Log.PrintfUser(L["You do not need to add \"%s\", alts are whitelisted automatically."], newPlayer)
|
||||
isAlt = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if isAlt then
|
||||
return
|
||||
end
|
||||
|
||||
TSM.db.factionrealm.auctioningOptions.whitelist[newPlayer] = newPlayer
|
||||
|
||||
-- add a new row to the UI
|
||||
local frame = input:GetElement("__parent.whitelistFrame")
|
||||
private.AddWhitelistRow(frame, newPlayer)
|
||||
frame:Draw()
|
||||
end
|
||||
|
||||
function private.RemoveWhitelistOnClick(self)
|
||||
local player = self:GetContext()
|
||||
TSM.db.factionrealm.auctioningOptions.whitelist[player] = nil
|
||||
|
||||
-- remove this row
|
||||
local row = self:GetParentElement()
|
||||
local frame = row:GetParentElement()
|
||||
frame:RemoveChild(row)
|
||||
row:Release()
|
||||
frame:Draw()
|
||||
end
|
||||
287
Core/UI/MainUI/Settings/Core.lua
Normal file
287
Core/UI/MainUI/Settings/Core.lua
Normal file
@@ -0,0 +1,287 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Settings = TSM.MainUI:NewPackage("Settings")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local Wow = TSM.Include("Util.Wow")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local private = {
|
||||
settingPages = {
|
||||
top = {},
|
||||
middle = {},
|
||||
bottom = {},
|
||||
},
|
||||
callback = {},
|
||||
childSettingsPages = {},
|
||||
sectionCollapsed = {},
|
||||
}
|
||||
local SECTIONS = { "top", "middle" }
|
||||
local SETTING_PATH_SEP = "`"
|
||||
local SETTING_LABEL_WIDTH = 400
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Settings.OnInitialize()
|
||||
TSM.MainUI.RegisterTopLevelPage(L["Settings"], private.GetSettingsFrame)
|
||||
end
|
||||
|
||||
function Settings.RegisterSettingPage(name, section, callback)
|
||||
assert(tContains(SECTIONS, section))
|
||||
tinsert(private.settingPages[section], name)
|
||||
private.callback[name] = callback
|
||||
end
|
||||
|
||||
function Settings.RegisterChildSettingPage(parentName, childName, callback)
|
||||
local path = parentName..SETTING_PATH_SEP..childName
|
||||
private.childSettingsPages[parentName] = private.childSettingsPages[parentName] or {}
|
||||
tinsert(private.childSettingsPages[parentName], childName)
|
||||
private.callback[path] = callback
|
||||
end
|
||||
|
||||
function Settings.CreateSettingLine(id, labelText, width)
|
||||
width = width or SETTING_LABEL_WIDTH
|
||||
|
||||
return UIElements.New("Frame", id)
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(26)
|
||||
:SetMargin(0, 0, 0, 16)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetWidth(width)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetTextColor("TEXT_ALT")
|
||||
:SetText(labelText)
|
||||
)
|
||||
end
|
||||
|
||||
function Settings.CreateHeading(id, text)
|
||||
return UIElements.New("Text", id)
|
||||
:SetHeight(19)
|
||||
:SetMargin(0, 0, 0, 4)
|
||||
:SetFont("BODY_BODY1_BOLD")
|
||||
:SetText(text)
|
||||
end
|
||||
|
||||
function Settings.CreateInputWithReset(id, label, context, validate)
|
||||
local scope, namespace, key = strsplit(".", context)
|
||||
local validateFunc, validateContext = nil, nil
|
||||
if type(validate) == "table" then
|
||||
validateFunc = "CUSTOM_PRICE"
|
||||
validateContext = validate
|
||||
elseif type(validate) == "function" then
|
||||
validateFunc = validate
|
||||
elseif validate == nil then
|
||||
validateFunc = "CUSTOM_PRICE"
|
||||
else
|
||||
error("Invalid validate: "..tostring(validate))
|
||||
end
|
||||
return UIElements.New("Frame", id)
|
||||
:SetLayout("VERTICAL")
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetHeight(18)
|
||||
:SetMargin(0, 0, 0, 4)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetTextColor("TEXT_ALT")
|
||||
:SetText(label)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "content")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:AddChild(UIElements.New("Input", "input")
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetBackgroundColor("ACTIVE_BG")
|
||||
:SetValidateFunc(validateFunc, validateContext)
|
||||
:SetSettingInfo(TSM.db[scope][namespace], key)
|
||||
:SetScript("OnValueChanged", private.InputOnValueChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("ActionButton", "resetButton")
|
||||
:SetWidth(108)
|
||||
:SetText(L["Reset"])
|
||||
:SetDisabled(TSM.db[scope][namespace][key] == TSM.db:GetDefault(scope, namespace, key))
|
||||
:SetScript("OnClick", private.ResetBtnOnClick)
|
||||
:SetContext(context)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
function Settings.CreateExpandableSection(pageName, id, text, description, descriptionHeight)
|
||||
return UIElements.New("CollapsibleContainer", id)
|
||||
:SetLayout("VERTICAL")
|
||||
:SetMargin(0, 0, 0, 8)
|
||||
:SetContextTable(private.sectionCollapsed, pageName..text)
|
||||
:SetHeadingText(text)
|
||||
:AddChild(UIElements.New("Text", "description")
|
||||
:SetHeight(descriptionHeight or 20)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetText(description)
|
||||
)
|
||||
end
|
||||
|
||||
function Settings.PromptToReload()
|
||||
StaticPopupDialogs["TSMReloadPrompt"] = StaticPopupDialogs["TSMReloadPrompt"] or {
|
||||
text = L["You must reload your UI for these settings to take effect. Reload now?"],
|
||||
button1 = YES,
|
||||
button2 = NO,
|
||||
timeout = 0,
|
||||
OnAccept = ReloadUI,
|
||||
}
|
||||
Wow.ShowStaticPopupDialog("TSMReloadPrompt")
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Settings UI
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetSettingsFrame()
|
||||
TSM.UI.AnalyticsRecordPathChange("main", "settings")
|
||||
local defaultPage = private.settingPages.top[1]
|
||||
|
||||
local frame = UIElements.New("Frame", "settings")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT")
|
||||
:AddChild(UIElements.New("Frame", "settingNavigation")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetWidth(160)
|
||||
:SetPadding(12, 12, 1, 9)
|
||||
:AddChild(UIElements.New("Frame", "top")
|
||||
:SetLayout("VERTICAL")
|
||||
)
|
||||
:AddChild(UIElements.New("Texture", "vline")
|
||||
:SetHeight(1)
|
||||
:SetMargin(0, 0, 8, 8)
|
||||
:SetTexture("ACTIVE_BG_ALT")
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "middle")
|
||||
:SetLayout("VERTICAL")
|
||||
)
|
||||
:AddChild(UIElements.New("Spacer", "spacer")
|
||||
-- make all the navigation align to the top
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Texture", "divider")
|
||||
:SetWidth(2)
|
||||
:SetTexture("ACTIVE_BG")
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "contentFrame")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetBackgroundColor("PRIMARY_BG")
|
||||
:AddChild(UIElements.New("ViewContainer", "content")
|
||||
:SetNavCallback(private.ContentNavCallback)
|
||||
)
|
||||
)
|
||||
|
||||
local content = frame:GetElement("contentFrame.content")
|
||||
local settingNav = frame:GetElement("settingNavigation")
|
||||
for _, location in ipairs(SECTIONS) do
|
||||
local navFrame = settingNav:GetElement(location)
|
||||
for _, settingName in ipairs(private.settingPages[location]) do
|
||||
navFrame:AddChild(UIElements.New("Button", settingName)
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 8, 0)
|
||||
:SetFont("BODY_BODY2_BOLD")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetContext(settingName)
|
||||
:SetText(settingName)
|
||||
:SetScript("OnClick", private.NavButtonOnClick)
|
||||
)
|
||||
content:AddPath(settingName, settingName == defaultPage)
|
||||
if private.childSettingsPages[settingName] then
|
||||
for _, childSettingName in ipairs(private.childSettingsPages[settingName]) do
|
||||
local path = settingName..SETTING_PATH_SEP..childSettingName
|
||||
navFrame:AddChild(UIElements.New("Button", path)
|
||||
:SetHeight(20)
|
||||
:SetMargin(9, 0, 8, 0)
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetContext(path)
|
||||
:SetText(strupper(childSettingName))
|
||||
:SetScript("OnClick", private.NavButtonOnClick)
|
||||
)
|
||||
content:AddPath(path, path == defaultPage)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
private.UpdateNavFrame(settingNav, defaultPage)
|
||||
return frame
|
||||
end
|
||||
|
||||
function private.ContentNavCallback(content, path)
|
||||
return private.callback[path]()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.NavButtonOnClick(button)
|
||||
local path = button:GetContext()
|
||||
if private.childSettingsPages[path] then
|
||||
-- select the first child
|
||||
path = path..SETTING_PATH_SEP..private.childSettingsPages[path][1]
|
||||
end
|
||||
|
||||
local contentFrame = button:GetElement("__parent.__parent.__parent.contentFrame")
|
||||
local navFrame = contentFrame:GetElement("__parent.settingNavigation")
|
||||
private.UpdateNavFrame(navFrame, path)
|
||||
navFrame:Draw()
|
||||
contentFrame:GetElement("content"):SetPath(path, true)
|
||||
end
|
||||
|
||||
function private.InputOnValueChanged(input)
|
||||
local button = input:GetElement("__parent.resetButton")
|
||||
local scope, namespace, key = strsplit(".", button:GetContext())
|
||||
button:SetDisabled(TSM.db[scope][namespace][key] == TSM.db:GetDefault(scope, namespace, key))
|
||||
:Draw()
|
||||
end
|
||||
|
||||
function private.ResetBtnOnClick(button)
|
||||
local scope, namespace, key = strsplit(".", button:GetContext())
|
||||
local defaultValue = TSM.db:GetDefault(scope, namespace, key)
|
||||
TSM.db:Set(scope, nil, namespace, key, defaultValue)
|
||||
button:GetElement("__parent.input")
|
||||
:SetValue(defaultValue)
|
||||
:Draw()
|
||||
button:SetDisabled(true)
|
||||
:Draw()
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.UpdateNavFrame(navFrame, selectedPath)
|
||||
local selectedSetting = strsplit(SETTING_PATH_SEP, selectedPath)
|
||||
for _, location in ipairs(SECTIONS) do
|
||||
for _, settingName in ipairs(private.settingPages[location]) do
|
||||
navFrame:GetElement(location ..".".. settingName)
|
||||
:SetTextColor(settingName == selectedSetting and "TEXT" or "ACTIVE_BG_ALT")
|
||||
if private.childSettingsPages[settingName] then
|
||||
for _, childSettingName in ipairs(private.childSettingsPages[settingName]) do
|
||||
local path = settingName..SETTING_PATH_SEP..childSettingName
|
||||
if settingName == selectedSetting then
|
||||
navFrame:GetElement(location ..".".. path)
|
||||
:SetTextColor(path == selectedPath and "INDICATOR" or "TEXT")
|
||||
:Show()
|
||||
else
|
||||
navFrame:GetElement(location ..".".. path):Hide()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
142
Core/UI/MainUI/Settings/Crafting.lua
Normal file
142
Core/UI/MainUI/Settings/Crafting.lua
Normal file
@@ -0,0 +1,142 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local Crafting = TSM.MainUI.Settings:NewPackage("Crafting")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local PlayerInfo = TSM.Include("Service.PlayerInfo")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local private = {
|
||||
altCharacters = {},
|
||||
altGuilds = {},
|
||||
}
|
||||
local BAD_MAT_PRICE_SOURCES = {
|
||||
matprice = true,
|
||||
}
|
||||
local BAD_CRAFT_VALUE_PRICE_SOURCES = {
|
||||
crafting = true,
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Crafting.OnInitialize()
|
||||
TSM.MainUI.Settings.RegisterSettingPage(L["Crafting"], "middle", private.GetCraftingSettingsFrame)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Crafting Settings UI
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetCraftingSettingsFrame()
|
||||
TSM.UI.AnalyticsRecordPathChange("main", "settings", "crafting")
|
||||
wipe(private.altCharacters)
|
||||
wipe(private.altGuilds)
|
||||
for _, character in PlayerInfo.CharacterIterator(true) do
|
||||
tinsert(private.altCharacters, character)
|
||||
end
|
||||
for name in PlayerInfo.GuildIterator() do
|
||||
tinsert(private.altGuilds, name)
|
||||
end
|
||||
|
||||
return UIElements.New("ScrollFrame", "craftingSettings")
|
||||
:SetPadding(8, 8, 8, 0)
|
||||
:AddChild(TSM.MainUI.Settings.CreateExpandableSection("Crafting", "inventory", L["Inventory Options"], "")
|
||||
:AddChild(UIElements.New("Frame", "inventoryOptionsLabels")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetMargin(0, 0, 0, 4)
|
||||
:SetHeight(20)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetMargin(0, 12, 0, 0)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Ignore Characters"])
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "label")
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Ignore Guilds"])
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Frame", "inventoryOptionsDropdowns")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:AddChild(UIElements.New("MultiselectionDropdown", "charDropdown")
|
||||
:SetMargin(0, 12, 0, 0)
|
||||
:SetItems(private.altCharacters, private.altCharacters)
|
||||
:SetSettingInfo(TSM.db.global.craftingOptions, "ignoreCharacters")
|
||||
:SetSelectionText(L["No Characters"], L["%d Characters"], L["All Characters"])
|
||||
)
|
||||
:AddChild(UIElements.New("MultiselectionDropdown", "guildDropdown")
|
||||
:SetItems(private.altGuilds, private.altGuilds)
|
||||
:SetSettingInfo(TSM.db.global.craftingOptions, "ignoreGuilds")
|
||||
:SetSelectionText(L["No Guilds"], L["%d Guilds"], L["All Guilds"])
|
||||
)
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Settings.CreateExpandableSection("Crafting", "price", L["Default price configuration"], "")
|
||||
:AddChild(UIElements.New("Text", "matCostLabel")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 4)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetTextColor("TEXT_ALT")
|
||||
:SetText(L["Default material cost method"])
|
||||
)
|
||||
:AddChild(UIElements.New("MultiLineInput", "matCostInput")
|
||||
:SetHeight(70)
|
||||
:SetMargin(0, 0, 0, 12)
|
||||
:SetValidateFunc("CUSTOM_PRICE", BAD_MAT_PRICE_SOURCES)
|
||||
:SetSettingInfo(TSM.db.global.craftingOptions, "defaultMatCostMethod")
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "craftValueLabel")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 0, 4)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetTextColor("TEXT_ALT")
|
||||
:SetText(L["Default craft value method"])
|
||||
)
|
||||
:AddChild(UIElements.New("MultiLineInput", "matCostInput")
|
||||
:SetHeight(70)
|
||||
:SetValidateFunc("CUSTOM_PRICE", BAD_CRAFT_VALUE_PRICE_SOURCES)
|
||||
:SetSettingInfo(TSM.db.global.craftingOptions, "defaultCraftPriceMethod")
|
||||
)
|
||||
)
|
||||
:AddChild(TSM.MainUI.Settings.CreateExpandableSection("Crafting", "cooldowns", L["Ignored Cooldowns"], L["Use this list to manage what cooldowns you'd like TSM to ignore from crafting."])
|
||||
:AddChild(UIElements.New("QueryScrollingTable", "items")
|
||||
:SetHeight(126)
|
||||
:GetScrollingTableInfo()
|
||||
:NewColumn("item")
|
||||
:SetTitle(L["Cooldown"])
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetTextInfo(nil, private.CooldownGetText)
|
||||
:DisableHiding()
|
||||
:Commit()
|
||||
:Commit()
|
||||
:SetQuery(TSM.Crafting.CreateIgnoredCooldownQuery())
|
||||
:SetAutoReleaseQuery(true)
|
||||
:SetSelectionDisabled(true)
|
||||
:SetScript("OnRowClick", private.IgnoredCooldownOnRowClick)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.CooldownGetText(row)
|
||||
return row:GetField("characterKey").." - "..TSM.Crafting.GetName(row:GetField("spellId"))
|
||||
end
|
||||
|
||||
function private.IgnoredCooldownOnRowClick(_, row)
|
||||
TSM.Crafting.RemoveIgnoredCooldown(row:GetFields("characterKey", "spellId"))
|
||||
end
|
||||
297
Core/UI/MainUI/Settings/CustomSources.lua
Normal file
297
Core/UI/MainUI/Settings/CustomSources.lua
Normal file
@@ -0,0 +1,297 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
local _, TSM = ...
|
||||
local CustomSources = TSM.MainUI.Settings:NewPackage("CustomSources")
|
||||
local L = TSM.Include("Locale").GetTable()
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local Theme = TSM.Include("Util.Theme")
|
||||
local CustomPrice = TSM.Include("Service.CustomPrice")
|
||||
local UIElements = TSM.Include("UI.UIElements")
|
||||
local private = {}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function CustomSources.OnInitialize()
|
||||
TSM.MainUI.Settings.RegisterSettingPage(L["Custom Sources"], "middle", private.GetCustomSourcesSettingsFrame)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Custom Sources Settings UI
|
||||
-- ============================================================================
|
||||
|
||||
function private.GetCustomSourcesSettingsFrame()
|
||||
TSM.UI.AnalyticsRecordPathChange("main", "settings", "custom_sources")
|
||||
return UIElements.New("ScrollFrame", "content")
|
||||
:SetPadding(8, 8, 8, 0)
|
||||
:AddChild(TSM.MainUI.Settings.CreateExpandableSection("Custom Price", "general", L["Custom Sources"], format(L["Custom sources allow you to create more advanced prices for use throughout the addon. You'll be able to use these new variables in the same way you can use the built-in price sources such as %s and %s."], Theme.GetColor("INDICATOR"):ColorText("vendorsell"), Theme.GetColor("INDICATOR"):ColorText("vendorbuy")), 60)
|
||||
:AddChild(UIElements.New("Frame", "tableHeading")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(20)
|
||||
:AddChild(UIElements.New("Text", "col1")
|
||||
:SetWidth(162)
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetText(L["Name"])
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "col2")
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetText(L["String"])
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Texture", "line1")
|
||||
:SetHeight(1)
|
||||
:SetTexture("ACTIVE_BG")
|
||||
)
|
||||
:AddChildrenWithFunction(private.AddCustomPriceRows)
|
||||
:AddChild(UIElements.New("ActionButton", "addNewBtn")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, 32, 0)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Add a new custom source"])
|
||||
:SetScript("OnClick", private.AddNewButtonOnClick)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
function private.CreateCustomPriceRow(name)
|
||||
local priceString = TSM.db.global.userData.customPriceSources[name]
|
||||
local row = UIElements.New("Frame", "row_"..name)
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(28)
|
||||
:SetMargin(-12, -12, 0, 0)
|
||||
:SetPadding(12, 12, 0, 0)
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT")
|
||||
:SetContext(name)
|
||||
:SetScript("OnEnter", private.CustomPriceRowOnEnter)
|
||||
:SetScript("OnLeave", private.CustomPriceRowOnLeave)
|
||||
:AddChild(UIElements.New("Text", "nameText")
|
||||
:SetWidth(162)
|
||||
:SetMargin(0, 8, 0, 0)
|
||||
:SetFont("BODY_BODY3_MEDIUM")
|
||||
:SetText(name)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "valueText")
|
||||
:SetFont("BODY_BODY3")
|
||||
:SetText(priceString)
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "editBtn")
|
||||
:SetMargin(4, 0, 0, 0)
|
||||
:SetBackgroundAndSize("iconPack.18x18/Edit")
|
||||
:SetScript("OnClick", private.EditCustomPriceOnClick)
|
||||
:PropagateScript("OnEnter")
|
||||
:PropagateScript("OnLeave")
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "deleteBtn")
|
||||
:SetMargin(4, 0, 0, 0)
|
||||
:SetBackgroundAndSize("iconPack.18x18/Delete")
|
||||
:SetScript("OnClick", private.DeleteCustomPriceOnClick)
|
||||
:PropagateScript("OnEnter")
|
||||
:PropagateScript("OnLeave")
|
||||
)
|
||||
row:GetElement("editBtn"):Hide()
|
||||
row:GetElement("deleteBtn"):Hide()
|
||||
return row
|
||||
end
|
||||
|
||||
function private.AddCustomPriceRows(frame)
|
||||
local names = TempTable.Acquire()
|
||||
for name in pairs(TSM.db.global.userData.customPriceSources) do
|
||||
tinsert(names, name)
|
||||
end
|
||||
sort(names)
|
||||
for _, name in ipairs(names) do
|
||||
frame:AddChild(private.CreateCustomPriceRow(name))
|
||||
end
|
||||
TempTable.Release(names)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Local Script Handlers
|
||||
-- ============================================================================
|
||||
|
||||
function private.CustomPriceRowOnEnter(frame)
|
||||
frame:SetBackgroundColor("FRAME_BG")
|
||||
frame:GetElement("editBtn"):Show()
|
||||
frame:GetElement("deleteBtn"):Show()
|
||||
frame:Draw()
|
||||
end
|
||||
|
||||
function private.CustomPriceRowOnLeave(frame)
|
||||
frame:SetBackgroundColor("PRIMARY_BG_ALT")
|
||||
frame:GetElement("editBtn"):Hide()
|
||||
frame:GetElement("deleteBtn"):Hide()
|
||||
frame:Draw()
|
||||
end
|
||||
|
||||
function private.EditCustomPriceOnClick(button)
|
||||
private.ShowEditDialog(button)
|
||||
end
|
||||
|
||||
function private.ShowEditDialog(editBtn)
|
||||
local dialogFrame = UIElements.New("Frame", "frame")
|
||||
:SetLayout("VERTICAL")
|
||||
:SetSize(478, 314)
|
||||
:SetPadding(12)
|
||||
:AddAnchor("CENTER")
|
||||
:SetMouseEnabled(true)
|
||||
:SetBackgroundColor("FRAME_BG", true)
|
||||
:SetContext(editBtn)
|
||||
:AddChild(UIElements.New("Frame", "header")
|
||||
:SetLayout("HORIZONTAL")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, -4, 14)
|
||||
:AddChild(UIElements.New("Spacer", "spacer")
|
||||
:SetWidth(20)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "title")
|
||||
:SetJustifyH("CENTER")
|
||||
:SetFont("BODY_BODY1_BOLD")
|
||||
:SetText(L["Edit Custom Source"])
|
||||
)
|
||||
:AddChild(UIElements.New("Button", "closeBtn")
|
||||
:SetMargin(0, -4, 0, 0)
|
||||
:SetBackgroundAndSize("iconPack.24x24/Close/Default")
|
||||
:SetScript("OnClick", private.EditPriceCloseBtnOnClick)
|
||||
)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "name")
|
||||
:SetHeight(20)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetText(L["Name"])
|
||||
)
|
||||
:AddChild(UIElements.New("Input", "nameInput")
|
||||
:SetHeight(24)
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT")
|
||||
:SetValue(editBtn:GetElement("__parent.nameText"):GetText())
|
||||
:SetTabPaths("__parent.valueInput", "__parent.valueInput")
|
||||
:SetValidateFunc(private.NameValidateFunc)
|
||||
:SetScript("OnValidationChanged", private.ConfirmOnValidationChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("Text", "string")
|
||||
:SetHeight(20)
|
||||
:SetMargin(0, 0, 6, 0)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetJustifyH("LEFT")
|
||||
:SetText(L["String"])
|
||||
)
|
||||
:AddChild(UIElements.New("MultiLineInput", "valueInput")
|
||||
:SetBackgroundColor("PRIMARY_BG_ALT")
|
||||
:SetValue(editBtn:GetElement("__parent.valueText"):GetText())
|
||||
:SetTabPaths("__parent.nameInput", "__parent.nameInput")
|
||||
:SetValidateFunc(private.ValueValidateFunc)
|
||||
:SetScript("OnValidationChanged", private.ConfirmOnValidationChanged)
|
||||
)
|
||||
:AddChild(UIElements.New("ActionButton", "confirm")
|
||||
:SetHeight(24)
|
||||
:SetMargin(0, 0, 12, 0)
|
||||
:SetFont("BODY_BODY2_MEDIUM")
|
||||
:SetText(L["Confirm"])
|
||||
:SetContext(editBtn:GetElement("__parent.__parent"))
|
||||
:SetScript("OnClick", private.ConfirmOnClick)
|
||||
)
|
||||
editBtn:GetBaseElement():ShowDialogFrame(dialogFrame)
|
||||
return dialogFrame
|
||||
end
|
||||
|
||||
function private.EditPriceCloseBtnOnClick(button)
|
||||
button:GetBaseElement():HideDialog()
|
||||
end
|
||||
|
||||
function private.NameValidateFunc(input, value)
|
||||
if value == "" then
|
||||
return false
|
||||
elseif gsub(value, "([a-z]+)", "") ~= "" then
|
||||
return false, L["Custom price names can only contain lowercase letters."]
|
||||
elseif value ~= input:GetParentElement():GetContext():GetParentElement():GetContext() then
|
||||
return CustomPrice.ValidateName(value)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function private.ConfirmOnValidationChanged(input)
|
||||
input:GetElement("__parent.confirm")
|
||||
:SetDisabled(not input:IsValid())
|
||||
:Draw()
|
||||
end
|
||||
|
||||
function private.ValueValidateFunc(input, value)
|
||||
value = strlower(strtrim(value))
|
||||
local isValid, errMsg = CustomPrice.Validate(value)
|
||||
if not isValid and value ~= "" then
|
||||
return false, errMsg
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function private.ConfirmOnClick(button)
|
||||
local baseElement = button:GetBaseElement()
|
||||
local oldName = button:GetParentElement():GetContext():GetParentElement():GetContext()
|
||||
local newName = button:GetElement("__parent.nameInput"):GetValue()
|
||||
if oldName ~= newName then
|
||||
CustomPrice.RenameCustomPriceSource(oldName, newName)
|
||||
CustomPrice.SetCustomPriceSource(newName, button:GetElement("__parent.valueInput"):GetValue())
|
||||
local generalContainer = button:GetParentElement():GetContext():GetParentElement():GetParentElement()
|
||||
local rowFrame = button:GetParentElement():GetContext():GetParentElement()
|
||||
generalContainer:AddChildBeforeById("addNewBtn", private.CreateCustomPriceRow(newName))
|
||||
generalContainer:RemoveChild(rowFrame)
|
||||
rowFrame:Release()
|
||||
generalContainer:GetElement("__parent.__parent")
|
||||
:Draw()
|
||||
else
|
||||
CustomPrice.SetCustomPriceSource(newName, button:GetElement("__parent.valueInput"):GetValue())
|
||||
button:GetParentElement():GetContext():GetElement("__parent.nameText")
|
||||
:SetText(newName)
|
||||
:Draw()
|
||||
button:GetParentElement():GetContext():GetElement("__parent.valueText")
|
||||
:SetText(button:GetElement("__parent.valueInput"):GetValue())
|
||||
:Draw()
|
||||
end
|
||||
baseElement:HideDialog()
|
||||
end
|
||||
|
||||
function private.DeleteCustomPriceOnClick(button)
|
||||
CustomPrice.DeleteCustomPriceSource(button:GetParentElement():GetContext())
|
||||
local rowFrame = button:GetParentElement()
|
||||
local parentFrame = rowFrame:GetParentElement()
|
||||
parentFrame:RemoveChild(rowFrame)
|
||||
rowFrame:Release()
|
||||
parentFrame:GetElement("__parent.__parent")
|
||||
:Draw()
|
||||
end
|
||||
|
||||
function private.AddNewButtonOnClick(button)
|
||||
-- generate a placeholder name
|
||||
local newName = nil
|
||||
local suffix = ""
|
||||
while not newName do
|
||||
for i = strbyte("a"), strbyte("z") do
|
||||
newName = "customprice"..suffix..strchar(i)
|
||||
if not TSM.db.global.userData.customPriceSources[newName] then
|
||||
break
|
||||
end
|
||||
newName = nil
|
||||
end
|
||||
suffix = suffix..strchar(random(strbyte("a"), strbyte("z")))
|
||||
end
|
||||
|
||||
CustomPrice.CreateCustomPriceSource(newName, "")
|
||||
button:GetParentElement()
|
||||
:AddChildBeforeById("addNewBtn", private.CreateCustomPriceRow(newName))
|
||||
button:GetElement("__parent.__parent.__parent")
|
||||
:Draw()
|
||||
local dialogFrame = private.ShowEditDialog(button:GetElement("__parent.row_"..newName..".editBtn"))
|
||||
dialogFrame:GetElement("valueInput"):SetFocused(true)
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user