TradeSkillMaster/Core/UI/FrameStack.lua

340 lines
10 KiB
Lua

-- ------------------------------------------------------------------------------ --
-- 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