mirror of
https://github.com/brues-code/pfUI.git
synced 2026-09-21 23:26:56 +00:00
8cdaf1fc00
If the global GetCoinTextureString exists, use it to format money (wrapped in white color codes) for CreateGoldString. Keeps the existing numeric fallback formatting for environments without GetCoinTextureString.
1718 lines
59 KiB
Lua
1718 lines
59 KiB
Lua
pfUI.api = { }
|
|
|
|
-- load pfUI environment
|
|
setfenv(1, pfUI:GetEnvironment())
|
|
|
|
-- [ DLL Detection Helpers ]
|
|
-- Detects presence of various DLL extensions for enhanced functionality
|
|
|
|
-- [ HasSuperWoW ]
|
|
-- Returns true if SuperWoW DLL is active
|
|
-- SuperWoW provides: UNIT_CASTEVENT, UnitPosition, SetMouseoverUnit, SpellInfo, etc.
|
|
function pfUI.api.HasSuperWoW()
|
|
return SUPERWOW_VERSION or (SetAutoloot and SpellInfo)
|
|
end
|
|
|
|
-- [ HasUnitXP ]
|
|
-- Returns true if UnitXP_SP3 DLL is active
|
|
-- UnitXP provides: distance, line of sight, behind detection, targeting helpers
|
|
function pfUI.api.HasUnitXP()
|
|
local success = pcall(UnitXP, "nop", "nop")
|
|
return success
|
|
end
|
|
|
|
-- [ HasNampower ]
|
|
-- Returns true if Nampower DLL is active
|
|
-- Nampower provides: spell queuing, GetCastInfo, GetSpellIdCooldown, IsSpellInRange, etc.
|
|
function pfUI.api.HasNampower()
|
|
return GetNampowerVersion and true or false
|
|
end
|
|
|
|
-- [ GetUnitDistance ]
|
|
-- Returns distance to unit using best available method
|
|
-- 'unit1' [string] first unit (default: "player")
|
|
-- 'unit2' [string] second unit
|
|
-- returns: [number] distance in yards, or nil if unavailable
|
|
function pfUI.api.GetUnitDistance(unit1, unit2)
|
|
if not unit2 then
|
|
unit2 = unit1
|
|
unit1 = "player"
|
|
end
|
|
|
|
if not UnitExists(unit2) then return nil end
|
|
|
|
-- Try UnitXP first (most accurate)
|
|
if pfUI.api.HasUnitXP() then
|
|
local success, distance = pcall(UnitXP, "distanceBetween", unit1, unit2)
|
|
if success and distance then return distance end
|
|
end
|
|
|
|
-- Try SuperWoW UnitPosition
|
|
if pfUI.api.HasSuperWoW() and UnitPosition then
|
|
local x1, y1, z1 = UnitPosition(unit1)
|
|
local x2, y2, z2 = UnitPosition(unit2)
|
|
if x1 and y1 and z1 and x2 and y2 and z2 then
|
|
return ((x2 - x1)^2 + (y2 - y1)^2 + (z2 - z1)^2)^0.5
|
|
end
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
-- [ UnitInLineOfSight ]
|
|
-- Returns true if unit1 has line of sight to unit2
|
|
-- Requires UnitXP_SP3
|
|
function pfUI.api.UnitInLineOfSight(unit1, unit2)
|
|
if not pfUI.api.HasUnitXP() then return nil end
|
|
if not unit2 then
|
|
unit2 = unit1
|
|
unit1 = "player"
|
|
end
|
|
local success, inSight = pcall(UnitXP, "inSight", unit1, unit2)
|
|
if success then return inSight end
|
|
return nil
|
|
end
|
|
|
|
-- [ UnitIsBehind ]
|
|
-- Returns true if unit1 is behind unit2
|
|
-- Requires UnitXP_SP3
|
|
function pfUI.api.UnitIsBehind(unit1, unit2)
|
|
if not pfUI.api.HasUnitXP() then return nil end
|
|
if not unit2 then
|
|
unit2 = unit1
|
|
unit1 = "player"
|
|
end
|
|
local success, behind = pcall(UnitXP, "behind", unit1, unit2)
|
|
if success then return behind end
|
|
return nil
|
|
end
|
|
|
|
-- Client API shortcuts
|
|
gfind = string.gmatch or string.gfind
|
|
mod = math.mod or mod
|
|
|
|
-- [ strsplit ]
|
|
-- Splits a string using a delimiter. Self-contained on purpose: it does NOT
|
|
-- delegate to the global strsplit / string.split, because third-party addons
|
|
-- clobber those (e.g. BigWigs' SpellRequests redefines string:split to return
|
|
-- a table, which would make this return a single table instead of r,g,b,a and
|
|
-- break color/version parsing depending on load order). Delimiter chars are
|
|
-- treated as a set (any one char splits), and empty fields are preserved
|
|
-- ("a,,b" -> "a", "", "b"), matching real strsplit semantics.
|
|
-- 'delimiter' [string] characters that will be interpreted as delimiter
|
|
-- characters (bytes) in the string.
|
|
-- 'subject' [string] String to split.
|
|
-- return: [list] a list of strings.
|
|
local format, sgsub = string.format, string.gsub
|
|
function pfUI.api.strsplit(delimiter, subject)
|
|
if not subject then return nil end
|
|
local fields = {}
|
|
delimiter = delimiter or ":"
|
|
local pattern = format("([^%s]+)", delimiter)
|
|
sgsub(subject, pattern, function(c) fields[table.getn(fields)+1] = c end)
|
|
return unpack(fields)
|
|
end
|
|
|
|
-- [ isempty ]
|
|
-- Returns true if a table is empty or not existing, otherwise false.
|
|
-- 'tbl' [table] the table that shall be checked
|
|
-- return: [boolean] result of the check.
|
|
function pfUI.api.isempty(tbl)
|
|
return next(tbl or {}) == nil
|
|
end
|
|
|
|
-- [ checkversion ]
|
|
-- Compares a given version (major,minor,fix) and compares it to the current
|
|
-- 'chkmajor' [number] the major number to check
|
|
-- 'chkminor' [number] the minor number to check
|
|
-- 'chkfix' [number] the fix number to check
|
|
--
|
|
-- return: [boolean] true when the current version is smaller or equal
|
|
-- to the given value, otherwise returns nil.
|
|
local major, minor, fix = nil, nil, nil
|
|
function pfUI.api.checkversion(chkmajor, chkminor, chkfix)
|
|
if not major and not minor and not fix then
|
|
-- load and convert current version
|
|
major, minor, fix = pfUI.api.strsplit(".", tostring(pfUI_config.version))
|
|
major, minor, fix = tonumber(major) or 0, tonumber(minor) or 0, tonumber(fix) or 0
|
|
end
|
|
|
|
local chkversion = chkmajor + chkminor/100 + chkfix/10000
|
|
local curversion = major + minor/100 + fix/10000
|
|
return curversion <= chkversion and true or nil
|
|
end
|
|
|
|
-- [ UnitInRange ]
|
|
-- Returns whether a party/raid member is nearby.
|
|
-- It takes care of the rangecheck module if existing.
|
|
-- unit [string] A unit to query (string, unitID)
|
|
-- return: [bool] "1" if in range otherwise "nil"
|
|
function pfUI.api.UnitInRange(unit)
|
|
if not UnitExists(unit) or not UnitIsVisible(unit) then
|
|
return nil
|
|
elseif CheckInteractDistance(unit, 4) then
|
|
return 1
|
|
end
|
|
|
|
-- master switch: with the 40y check off, a visible unit beyond interact
|
|
-- range counts as in range (nothing fades). Invisible units already
|
|
-- returned nil above, matching the pre-collapse behavior.
|
|
if C.unitframes.rangecheck == "0" then return 1 end
|
|
|
|
-- UnitXP precise mode: skip librange entirely, use direct distance check
|
|
if C.unitframes.rangecheck_mode == "unitxp" and _G.UnitXP then
|
|
local threshold = tonumber(C.unitframes.rangecheck_distance) or 40
|
|
local success, distance = pcall(_G.UnitXP, "distanceBetween", "player", unit)
|
|
if success and distance then
|
|
return distance <= threshold and 1 or nil
|
|
end
|
|
-- fallthrough to librange if UnitXP fails
|
|
end
|
|
|
|
if pfUI.api.librange then
|
|
return pfUI.api.librange:UnitInSpellRange(unit)
|
|
end
|
|
return nil
|
|
end
|
|
|
|
-- [ RunOOC ]
|
|
-- Runs a function once, as soon as the combat lockdown fades.
|
|
-- func [function] The function that shall run ooc.
|
|
-- return: [bool] true if the function was added,
|
|
-- nil if the function already exists in queue
|
|
local queue, frame = {}
|
|
function pfUI.api.RunOOC(func)
|
|
if not frame then
|
|
frame = CreateFrame("Frame")
|
|
frame:SetScript("OnUpdate", function()
|
|
for key, func in pairs(queue) do func(); queue[key] = nil end
|
|
end)
|
|
end
|
|
|
|
if not queue[tostring(func)] then
|
|
queue[tostring(func)] = func
|
|
return true
|
|
end
|
|
end
|
|
|
|
-- [ UnitHasBuff ]
|
|
-- Returns whether a unit has the named buff or not.
|
|
-- unit [string] A unit to query (string, unitID)
|
|
-- name [string] The localized name of the buff.
|
|
-- return: [bool] true if unit has buff otherwise "nil"
|
|
function pfUI.api.UnitHasBuff(unit, name)
|
|
return C_UnitAuras.GetAuraDataBySpellName(unit, name, "HELPFUL") ~= nil or nil
|
|
end
|
|
|
|
-- [ IsPlayerGuid ]
|
|
-- Returns whether a GUID or unit token refers to the local player.
|
|
-- guid [string] A unit GUID (or unitID) to test.
|
|
-- return: [bool] true if it is the player otherwise "false"
|
|
pfUI.api.IsPlayerGuid = _G.IsPlayerGuid
|
|
|
|
-- [ GetUnbuffedRoster ]
|
|
-- Returns a comma-joined, colored list of group members missing the named aura.
|
|
-- name [string] the localized aura name to check for
|
|
-- return: [string] comma-joined list, empty string if everyone has it
|
|
function pfUI.api.GetUnbuffedRoster(name)
|
|
local missing = {}
|
|
local function check(unit)
|
|
if UnitName(unit) and not pfUI.api.UnitHasBuff(unit, name) then
|
|
table.insert(missing, GetUnitColor(unit) .. UnitName(unit) .. "|r")
|
|
end
|
|
end
|
|
|
|
if IsInRaid() then
|
|
for i=1,40 do check("raid"..i) end
|
|
else
|
|
check("player")
|
|
for i=1,4 do check("party"..i) end
|
|
end
|
|
|
|
return table.concat(missing, ", ")
|
|
end
|
|
|
|
-- [[ GetUnitColor ]]
|
|
-- Returns an escape string for the unit aswell as the RGB values
|
|
-- unit [string] the unitstring
|
|
-- return: [table] string, r, g, b
|
|
function pfUI.api.GetUnitColor(unitstr)
|
|
local class = UnitClassBase(unitstr)
|
|
local classColor = PFUI_CLASS_COLORS[class]
|
|
return classColor:GenerateHexColorMarkup(), classColor:GetRGB()
|
|
end
|
|
|
|
-- [ strvertical ]
|
|
-- Creates vertical text using linebreaks. Multibyte char friendly.
|
|
-- 'str' [string] String to columnize.
|
|
-- return: [string] the string tranformed to a column.
|
|
function pfUI.api.strvertical(str)
|
|
local _, len = string.gsub(str,"[^\128-\193]", "")
|
|
if (len == string.len(str)) then
|
|
return string.gsub(str, "(.)", "%1\n")
|
|
else
|
|
return string.gsub(str,"([%z\1-\127\194-\244][\128-\191]*)", "%1\n")
|
|
end
|
|
end
|
|
|
|
-- [ round ]
|
|
-- Rounds a float number into specified places after comma.
|
|
-- 'input' [float] the number that should be rounded.
|
|
-- 'places' [int] amount of places after the comma.
|
|
-- returns: [float] rounded number.
|
|
function pfUI.api.round(input, places)
|
|
if not places then places = 0 end
|
|
if type(input) == "number" and type(places) == "number" then
|
|
local pow = 1
|
|
for i = 1, places do pow = pow * 10 end
|
|
return floor(input * pow + 0.5) / pow
|
|
end
|
|
end
|
|
|
|
-- [ clamp ]
|
|
-- Clamps a number between given range.
|
|
-- 'x' [number] the number that should be clamped.
|
|
-- 'min' [number] minimum value.
|
|
-- 'max' [number] maximum value.
|
|
-- returns: [number] clamped value: 'x', 'min' or 'max' value itself.
|
|
function pfUI.api.clamp(x, min, max)
|
|
if type(x) == "number" and type(min) == "number" and type(max) == "number" then
|
|
return x < min and min or x > max and max or x
|
|
else
|
|
return x
|
|
end
|
|
end
|
|
|
|
-- [ modf ]
|
|
-- Returns integral and fractional part of a number.
|
|
-- 'f' [float] the number to breakdown.
|
|
-- returns: [int],[float] whole and fractional part.
|
|
function pfUI.api.modf(f)
|
|
return math.modf(f)
|
|
end
|
|
|
|
-- [ GetServerEpoch ]
|
|
-- Returns a Unix epoch (seconds) representing the current server wall
|
|
-- clock, packed in the player's local timezone so that date(fmt, epoch)
|
|
-- prints server-time strings regardless of where the player sits.
|
|
-- Returns nil before login; date(fmt, nil) silently falls back to local time.
|
|
pfUI.api.GetServerEpoch = C_DateAndTime.GetServerTimeLocal
|
|
|
|
-- [ GetSlashCommands ]
|
|
-- Lists all registeres slash commands
|
|
-- 'text' [string] optional, a specific command to find
|
|
-- return: [list] a list of all matching slash commands
|
|
function pfUI.api.GetSlashCommands(text)
|
|
local cmds
|
|
for k, v in pairs(_G) do
|
|
if strfind(k, "^SLASH_") and (not text or v == text) then
|
|
cmds = cmds or {}
|
|
cmds[k] = v
|
|
end
|
|
end
|
|
|
|
return cmds
|
|
end
|
|
|
|
-- [ RegisterSlashCommand ]
|
|
-- Lists all registeres slash commands
|
|
-- 'name' [string] name of the command
|
|
-- 'cmds' [table] table containing all slash command strings
|
|
-- 'func' [function] the function that should be assigned
|
|
-- 'force' [boolean] force assign the command even if aleady provided
|
|
-- by another function/addon.
|
|
function pfUI.api.RegisterSlashCommand(name, cmds, func, force)
|
|
local counter = 1
|
|
|
|
for _, cmd in pairs(cmds) do
|
|
if force or not pfUI.api.GetSlashCommands(cmd) then
|
|
_G["SLASH_"..name..counter] = cmd
|
|
counter = counter + 1
|
|
end
|
|
end
|
|
|
|
_G.SlashCmdList[name] = func
|
|
end
|
|
|
|
-- [ GetCaptures ]
|
|
-- Returns the indexes of a given regex pattern
|
|
-- 'pat' [string] unformatted pattern
|
|
-- returns: [numbers] capture indexes
|
|
local capture_cache = {}
|
|
function pfUI.api.GetCaptures(pat)
|
|
local r = capture_cache
|
|
|
|
if not r[pat] then
|
|
for a, b, c, d, e in gfind(gsub(pat, "%((.+)%)", "%1"), gsub(pat, "%d%$", "%%(.-)$")) do
|
|
r[pat] = { a, b, c, d, e}
|
|
end
|
|
|
|
r[pat] = r[pat] or {}
|
|
end
|
|
|
|
return r[pat][1], r[pat][2], r[pat][3], r[pat][4], r[pat][5]
|
|
end
|
|
|
|
-- [ SanitizePattern ]
|
|
-- Sanitizes and convert patterns into gfind compatible ones.
|
|
-- 'pattern' [string] unformatted pattern
|
|
-- returns: [string] simplified gfind compatible pattern
|
|
local sanitize_cache = {}
|
|
function pfUI.api.SanitizePattern(pattern)
|
|
if not sanitize_cache[pattern] then
|
|
local ret = pattern
|
|
-- escape magic characters
|
|
ret = gsub(ret, "([%+%-%*%(%)%?%[%]%^])", "%%%1")
|
|
-- remove capture indexes
|
|
ret = gsub(ret, "%d%$","")
|
|
-- catch all characters
|
|
ret = gsub(ret, "(%%%a)","%(%1+%)")
|
|
-- convert all %s to .+
|
|
ret = gsub(ret, "%%s%+",".+")
|
|
-- set priority to numbers over strings
|
|
ret = gsub(ret, "%(.%+%)%(%%d%+%)","%(.-%)%(%%d%+%)")
|
|
-- cache it
|
|
sanitize_cache[pattern] = ret
|
|
end
|
|
|
|
return sanitize_cache[pattern]
|
|
end
|
|
|
|
-- [ cmatch ]
|
|
-- Same as string.match but aware of capture indexes (up to 5)
|
|
-- 'str' [string] input string that should be matched
|
|
-- 'pat' [string] unformatted pattern
|
|
-- returns: [strings] matched string in capture order
|
|
function pfUI.api.cmatch(str, pat)
|
|
-- idx* = the logical %N index of each physical capture slot (nil when the
|
|
-- pattern has no %N$ markers); val* = the matched values in physical order.
|
|
local idx1, idx2, idx3, idx4, idx5 = GetCaptures(pat)
|
|
local _, _, val1, val2, val3, val4, val5 = string.find(str, pfUI.api.SanitizePattern(pat))
|
|
|
|
-- reorder the physical matches into logical (%1..%5) order
|
|
local out1 = idx5 == 1 and val5 or idx4 == 1 and val4 or idx3 == 1 and val3 or idx2 == 1 and val2 or val1
|
|
local out2 = idx5 == 2 and val5 or idx4 == 2 and val4 or idx3 == 2 and val3 or idx1 == 2 and val1 or val2
|
|
local out3 = idx5 == 3 and val5 or idx4 == 3 and val4 or idx1 == 3 and val1 or idx2 == 3 and val2 or val3
|
|
local out4 = idx5 == 4 and val5 or idx1 == 4 and val1 or idx3 == 4 and val3 or idx2 == 4 and val2 or val4
|
|
local out5 = idx1 == 5 and val1 or idx4 == 5 and val4 or idx3 == 5 and val3 or idx2 == 5 and val2 or val5
|
|
|
|
return out1, out2, out3, out4, out5
|
|
end
|
|
|
|
-- [ GetItemLinkByName ]
|
|
-- Returns an itemLink for the given itemname
|
|
-- 'name' [string] name of the item
|
|
-- returns: [string] entire itemLink for the given item
|
|
local itemLinkCache = {}
|
|
local itemLinkMisses = {}
|
|
local ITEMLINK_MAX_SCANS = 2
|
|
function pfUI.api.GetItemLinkByName(name)
|
|
-- GetInboxItem() hands us a nil name for attachment-less mail
|
|
if not name then return end
|
|
-- cache successful resolutions so repeated lookups (e.g. per inbox click) are free
|
|
if itemLinkCache[name] then return itemLinkCache[name] end
|
|
|
|
-- Failures have to be counted, not just retried. An unresolvable name walks
|
|
-- the entire id range and finds nothing -- a visible hitch on every tooltip
|
|
-- hover. Allow a couple of attempts (rendering the tooltip caches the item,
|
|
-- so the next hover usually resolves), then stop scanning for that name.
|
|
local misses = itemLinkMisses[name] or 0
|
|
if misses >= ITEMLINK_MAX_SCANS then return end
|
|
|
|
-- Octo/Turtle custom items live well past the 25818 vanilla ceiling
|
|
for itemID = 1, 61000 do
|
|
local itemName = C_Item.GetItemNameByID(itemID)
|
|
if itemName and itemName == name then
|
|
local _, itemLink = C_Item.GetItemInfo(itemID)
|
|
itemLinkCache[name] = itemLink
|
|
return itemLink
|
|
end
|
|
end
|
|
|
|
itemLinkMisses[name] = misses + 1
|
|
end
|
|
|
|
-- [ FindItem ]
|
|
-- Returns the bag and slot position of an item based on the name.
|
|
-- 'item' [string] name of the item
|
|
-- returns: [int] bag
|
|
-- [int] slot
|
|
function pfUI.api.FindItem(item)
|
|
for bag = 4, 0, -1 do
|
|
for slot = 1, GetContainerNumSlots(bag) do
|
|
local query = C_Item.GetItemName(ItemLocation:CreateFromBagAndSlot(bag, slot))
|
|
if query and query ~= "" and string.lower(query) == string.lower(item) then
|
|
return bag, slot
|
|
end
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
-- [ GetBagFamily ]
|
|
-- Returns information about the type of a bag such as Soul Bags or Quivers.
|
|
-- Available bagtypes are "BAG", "KEYRING", "SOULBAG", "QUIVER" and "SPECIAL"
|
|
-- 'bag' [int] the bag id
|
|
-- returns: [string] the type of the bag, e.g "QUIVER"
|
|
function pfUI.api.GetBagFamily(bag)
|
|
if bag == -2 then return "KEYRING" end
|
|
if bag == 0 then return "BAG" end -- backpack
|
|
if bag == -1 then return "BAG" end -- bank
|
|
|
|
local id = GetInventoryItemID("player", ContainerIDToInventoryID(bag))
|
|
if id then
|
|
-- classID 1 = Container (bags), 11 = Quiver
|
|
-- Container subclasses: 0 = Bag (default), 1 = Soul Bag, 2+ = specialty (herb/enchanting/etc.)
|
|
-- Quiver subclasses: 2 = Quiver (arrows), 3 = Ammo Pouch (bullets)
|
|
local _, _, _, _, _, classID, subClassID = C_Item.GetItemInfoInstant(id)
|
|
if classID == 1 then
|
|
if subClassID == 0 then return "BAG" end
|
|
if subClassID == 1 then return "SOULBAG" end
|
|
return "SPECIAL"
|
|
elseif classID == 11 then
|
|
return "QUIVER"
|
|
end
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
-- [ Abbreviate ]
|
|
-- Abbreviates a number from 1234 to 1.23k
|
|
-- 'number' [number] the number that should be abbreviated
|
|
-- 'returns: [string] the abbreviated value
|
|
function pfUI.api.Abbreviate(number)
|
|
local mode = pfUI_config.unitframes.abbrevnum
|
|
-- mode "0" = disabled (full numbers)
|
|
-- mode "1" = 2 decimals (4250 -> 4.25k) [legacy/default]
|
|
-- mode "2" = 1 decimal (4250 -> 4.2k) - always rounds DOWN
|
|
|
|
if mode == "1" or mode == "2" then
|
|
local sign = number < 0 and -1 or 1
|
|
number = math.abs(number)
|
|
|
|
if number > 1000000 then
|
|
if mode == "2" then
|
|
-- 1 decimal, round DOWN: 4.18m -> 4.1m
|
|
return (floor(number/100000) / 10 * sign) .. "m"
|
|
else
|
|
return pfUI.api.round(number/1000000*sign, 2) .. "m"
|
|
end
|
|
elseif number > 1000 then
|
|
if mode == "2" then
|
|
-- 1 decimal, round DOWN: 4180 -> 4.1k (not 4.2k)
|
|
return (floor(number/100) / 10 * sign) .. "k"
|
|
else
|
|
return pfUI.api.round(number/1000*sign, 2) .. "k"
|
|
end
|
|
end
|
|
end
|
|
|
|
return math.floor(number)
|
|
end
|
|
|
|
-- [ SendChatMessageWide ]
|
|
-- Sends a message to widest audience the player can broadcast to
|
|
-- 'msg' [string] the message to send
|
|
function pfUI.api.SendChatMessageWide(msg)
|
|
local channel = "SAY"
|
|
if IsInRaid() then
|
|
if ( IsRaidLeader() or IsRaidOfficer() ) then
|
|
channel = "RAID_WARNING"
|
|
else
|
|
channel = "RAID"
|
|
end
|
|
elseif UnitExists("party1") then
|
|
channel = "PARTY"
|
|
end
|
|
SendChatMessage(msg,channel)
|
|
end
|
|
|
|
-- [ GroupInfoByName ]
|
|
-- Gets the unit id by name
|
|
-- 'name' [string] party or raid member
|
|
-- 'group' [string] "raid" or "party"
|
|
-- returns: [table] {name='name',unitId='unitId',Id=Id,lclass='lclass',class='class'}
|
|
do -- create a scope so we don't have to worry about upvalue collisions
|
|
local party, raid, unitinfo = {}, {}, {}
|
|
party[0] = "player" -- fake unit
|
|
for i=1, MAX_PARTY_MEMBERS do
|
|
party[i] = "party"..i
|
|
end
|
|
for i=1, MAX_RAID_MEMBERS do
|
|
raid[i] = "raid"..i
|
|
end
|
|
function pfUI.api.GroupInfoByName(name,group)
|
|
unitinfo = pfUI.api.wipe(unitinfo)
|
|
if group == "party" then
|
|
for i=0, MAX_PARTY_MEMBERS do
|
|
local unitName = UnitName(party[i])
|
|
if unitName == name then
|
|
local lclass,class = UnitClass(party[i])
|
|
if not (lclass and class) then
|
|
lclass,class = _G.UNKNOWN, "UNKNOWN"
|
|
end
|
|
unitinfo.name,unitinfo.unitId,unitinfo.Id,unitinfo.lclass,unitinfo.class =
|
|
unitName,party[i],i,lclass,class
|
|
return unitinfo
|
|
end
|
|
end
|
|
elseif group == "raid" then
|
|
for i=1, MAX_RAID_MEMBERS do
|
|
local unitName = UnitName(raid[i])
|
|
if unitName == name then
|
|
local lclass,class = UnitClass(raid[i])
|
|
if not (lclass and class) then
|
|
lclass,class = _G.UNKNOWN, "UNKNOWN"
|
|
end
|
|
unitinfo.name,unitinfo.unitId,unitinfo.Id,unitinfo.lclass,unitinfo.class =
|
|
unitName,raid[i],i,lclass,class
|
|
return unitinfo
|
|
end
|
|
end
|
|
end
|
|
-- fallback for GetMasterLootCandidate not updating immediately for leavers
|
|
unitinfo.lclass,unitinfo.class = _G.UNKNOWN, "UNKNOWN"
|
|
return unitinfo
|
|
end
|
|
end
|
|
|
|
-- [ HookScript ]
|
|
-- Securely post-hooks a script handler.
|
|
-- 'f' [frame] the frame which needs a hook
|
|
-- 'script' [string] the handler to hook
|
|
-- 'func' [function] the function that should be added
|
|
function HookScript(f, script, func)
|
|
f:HookScript(script, func)
|
|
end
|
|
|
|
function hooksecurefunc(tbl, name, func)
|
|
if type(tbl) == "string" then tbl, name, func = _G, tbl, name end
|
|
if not tbl or type(tbl[name]) ~= "function" then return end
|
|
return _G.hooksecurefunc(tbl, name, func)
|
|
end
|
|
|
|
-- [ HookAddonOrVariable ]
|
|
-- Sets a function to be called automatically once an addon gets loaded
|
|
-- 'addon' [string] addon or variable name
|
|
-- 'func' [function] function that should run
|
|
do
|
|
local lurker
|
|
local pending = {}
|
|
|
|
local function ProcessPending()
|
|
if not lurker.foundConfig then return end
|
|
for i = table.getn(pending), 1, -1 do
|
|
local hook = pending[i]
|
|
if IsAddOnLoaded(hook.addon) or _G[hook.addon] then
|
|
hook.func()
|
|
table.remove(pending, i)
|
|
end
|
|
end
|
|
if table.getn(pending) == 0 then
|
|
lurker:UnregisterAllEvents()
|
|
end
|
|
end
|
|
|
|
function pfUI.api.HookAddonOrVariable(addon, func)
|
|
if not lurker then
|
|
lurker = CreateFrame("Frame", nil)
|
|
lurker:SetScript("OnEvent", function()
|
|
if event == "VARIABLES_LOADED" or event == "PLAYER_ENTERING_WORLD" then
|
|
this.foundConfig = true
|
|
end
|
|
ProcessPending()
|
|
end)
|
|
end
|
|
|
|
table.insert(pending, { addon = addon, func = func })
|
|
lurker:RegisterEvent("ADDON_LOADED")
|
|
lurker:RegisterEvent("VARIABLES_LOADED")
|
|
lurker:RegisterEvent("PLAYER_ENTERING_WORLD")
|
|
ProcessPending()
|
|
end
|
|
end
|
|
|
|
-- [ QueueFunction ]
|
|
-- Add functions to a FIFO queue for execution after a short delay.
|
|
-- '...' [vararg] function, [arguments]
|
|
local timer
|
|
function pfUI.api.QueueFunction(a1,a2,a3,a4,a5,a6,a7,a8,a9)
|
|
if not timer then
|
|
timer = CreateFrame("Frame")
|
|
timer.queue = {}
|
|
timer.interval = TOOLTIP_UPDATE_TIME
|
|
timer.DeQueue = function()
|
|
local item = table.remove(timer.queue,1)
|
|
if item then
|
|
item[1](item[2],item[3],item[4],item[5],item[6],item[7],item[8],item[9])
|
|
end
|
|
if table.getn(timer.queue) == 0 then
|
|
timer:Hide() -- no need to run the OnUpdate when the queue is empty
|
|
end
|
|
end
|
|
timer:SetScript("OnUpdate",function()
|
|
this.sinceLast = (this.sinceLast or 0) + arg1
|
|
while (this.sinceLast > this.interval) do
|
|
this.DeQueue()
|
|
this.sinceLast = this.sinceLast - this.interval
|
|
end
|
|
end)
|
|
end
|
|
table.insert(timer.queue,{a1,a2,a3,a4,a5,a6,a7,a8,a9})
|
|
timer:Show() -- start the OnUpdate
|
|
end
|
|
|
|
-- [ Create Gold String ]
|
|
-- Transforms a amount of copper into a fully fledged gold string
|
|
-- 'money' [int] the amount of coppy (GetMoney())
|
|
-- return: [string] a colorized string which is split into
|
|
-- gold,silver and copper values.
|
|
function pfUI.api.CreateGoldString(money)
|
|
if type(money) ~= "number" then return "-" end
|
|
|
|
if _G.GetCoinTextureString then
|
|
return "|cffffffff" .. _G.GetCoinTextureString(money) .. "|r"
|
|
end
|
|
|
|
local gold = floor(money/ 100 / 100)
|
|
local silver = floor(mod((money/100),100))
|
|
local copper = floor(mod(money,100))
|
|
|
|
local string = ""
|
|
if gold > 0 then string = string .. "|cffffffff" .. gold .. "|cffffd700g" end
|
|
if silver > 0 or gold > 0 then string = string .. "|cffffffff " .. silver .. "|cffc7c7cfs" end
|
|
string = string .. "|cffffffff " .. copper .. "|cffeda55fc"
|
|
|
|
return string
|
|
end
|
|
|
|
-- [ Enable Movable ]
|
|
-- Set all necessary functions to make a already existing frame movable.
|
|
-- 'name' [frame/string] Name of the Frame that should be movable
|
|
-- 'addon' [string] Addon that must be loaded before being able to access the frame
|
|
-- 'blacklist' [table] A list of frames that should be deactivated for mouse usage
|
|
local function OnDragStart() this:StartMoving() end
|
|
local function OnDragStop() this:StopMovingOrSizing() end
|
|
function pfUI.api.EnableMovable(name, addon, blacklist)
|
|
if addon then
|
|
local scan = CreateFrame("Frame")
|
|
scan:RegisterEvent("ADDON_LOADED")
|
|
scan:SetScript("OnEvent", function()
|
|
if arg1 == addon then
|
|
local frame = _G[name]
|
|
|
|
if blacklist then
|
|
for _, disable in pairs(blacklist) do
|
|
_G[disable]:EnableMouse(false)
|
|
end
|
|
end
|
|
|
|
frame:SetMovable(true)
|
|
frame:EnableMouse(true)
|
|
frame:RegisterForDrag("LeftButton")
|
|
frame:SetScript("OnDragStart", OnDragStart)
|
|
frame:SetScript("OnDragStop", OnDragStop)
|
|
|
|
this:UnregisterAllEvents()
|
|
end
|
|
end)
|
|
else
|
|
if blacklist then
|
|
for _, disable in pairs(blacklist) do
|
|
_G[disable]:EnableMouse(false)
|
|
end
|
|
end
|
|
|
|
local frame = name
|
|
if type(name) == "string" then frame = _G[name] end
|
|
frame:SetMovable(true)
|
|
frame:EnableMouse(true)
|
|
frame:RegisterForDrag("LeftButton")
|
|
frame:SetScript("OnDragStart", OnDragStart)
|
|
frame:SetScript("OnDragStop", OnDragStop)
|
|
end
|
|
end
|
|
|
|
-- [ Copy Table ]
|
|
-- By default a table assignment only will be a reference instead of a copy.
|
|
-- This is used to create a replicate of the actual table.
|
|
-- 'src' [table] the table that should be copied.
|
|
-- return: [table] the replicated table.
|
|
function pfUI.api.CopyTable(src)
|
|
local lookup_table = {}
|
|
local function _copy(src)
|
|
if type(src) ~= "table" then
|
|
return src
|
|
elseif lookup_table[src] then
|
|
return lookup_table[src]
|
|
end
|
|
local new_table = {}
|
|
lookup_table[src] = new_table
|
|
for index, value in pairs(src) do
|
|
new_table[_copy(index)] = _copy(value)
|
|
end
|
|
return setmetatable(new_table, getmetatable(src))
|
|
end
|
|
return _copy(src)
|
|
end
|
|
|
|
-- [ Wipe Table ]
|
|
-- Empties a table and returns it.
|
|
-- 'src' [table] the table that should be emptied.
|
|
-- return: [table] the emptied table.
|
|
-- Delegates to ClassicAPI's table.wipe, which also resets the Lua 5.0 getn
|
|
-- length (luaL_setn(t,0)) so table.insert on a wiped table resumes at [1].
|
|
-- Append to wiped arrays with table.insert -- NOT the t[table.getn(t)+1]=v
|
|
-- idiom, which needs an unmanaged length counter and won't work here.
|
|
function pfUI.api.wipe(src)
|
|
return table.wipe(src)
|
|
end
|
|
|
|
-- [ Load Movable ]
|
|
-- Loads the positions of a Frame.
|
|
-- 'frame' [frame] the frame that should be positioned.
|
|
-- 'init' [bool] treats the current position as initial data
|
|
function pfUI.api.LoadMovable(frame, init)
|
|
-- update position data
|
|
if not frame.posdata or init then
|
|
frame.posdata = { scale = frame:GetScale(), pos = {} }
|
|
for i=1,frame:GetNumPoints() do
|
|
frame.posdata.pos[i] = { frame:GetPoint(i) }
|
|
end
|
|
end
|
|
|
|
if pfUI_config["position"][frame:GetName()] then
|
|
if pfUI_config["position"][frame:GetName()]["parent"] then
|
|
frame:SetParent(_G[pfUI_config["position"][frame:GetName()]["parent"]])
|
|
end
|
|
|
|
if pfUI_config["position"][frame:GetName()]["scale"] then
|
|
frame:SetScale(pfUI_config["position"][frame:GetName()].scale)
|
|
end
|
|
|
|
if pfUI_config["position"][frame:GetName()]["xpos"] then
|
|
local anchor = pfUI_config["position"][frame:GetName()]["anchor"] or "TOPLEFT"
|
|
frame:ClearAllPoints()
|
|
frame:SetPoint(anchor, pfUI_config["position"][frame:GetName()].xpos, pfUI_config["position"][frame:GetName()].ypos)
|
|
end
|
|
elseif frame.posdata and frame.posdata.pos[1] then
|
|
frame:ClearAllPoints()
|
|
frame:SetScale(frame.posdata.scale)
|
|
|
|
for id, point in pairs(frame.posdata.pos) do
|
|
local a, b, c, d, e = unpack(point)
|
|
-- skip if anchoring to itself (would cause WoW error)
|
|
if a and b and b ~= frame and b ~= frame:GetName() then
|
|
frame:SetPoint(a,b,c,d,e)
|
|
elseif a and (not b or b == frame or b == frame:GetName()) then
|
|
frame:SetPoint(a,d,e)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- [ Save Movable ]
|
|
-- Save the positions of a Frame.
|
|
-- 'frame' [frame] the frame that should be saved.
|
|
function pfUI.api.SaveMovable(frame, scale)
|
|
local anchor, _, _, xpos, ypos = frame:GetPoint()
|
|
C.position[frame:GetName()] = C.position[frame:GetName()] or {}
|
|
C.position[frame:GetName()]["xpos"] = round(xpos)
|
|
C.position[frame:GetName()]["ypos"] = round(ypos)
|
|
C.position[frame:GetName()]["anchor"] = anchor
|
|
C.position[frame:GetName()]["parent"] = frame:GetParent() and frame:GetParent():GetName() or nil
|
|
if scale then
|
|
C.position[frame:GetName()]["scale"] = frame:GetScale()
|
|
end
|
|
end
|
|
|
|
-- [ Update Movable ]
|
|
-- Loads and update the configured position of the specified frame.
|
|
-- It also creates an entry in the movables table.
|
|
-- 'frame' [frame] the frame that should be updated.
|
|
-- 'init' [bool] treats the current position as initial data
|
|
function pfUI.api.UpdateMovable(frame, init)
|
|
local name = frame:GetName()
|
|
|
|
if pfUI_config.global.offscreen == "0" then
|
|
frame:SetClampedToScreen(true)
|
|
end
|
|
|
|
if not pfUI.movables[name] then
|
|
pfUI.movables[name] = frame
|
|
end
|
|
|
|
LoadMovable(frame, init)
|
|
end
|
|
|
|
-- [ Remove Movable ]
|
|
-- Removes a Frame from the movable list.
|
|
-- 'frame' [frame] the frame that should be removed.
|
|
function pfUI.api.RemoveMovable(frame)
|
|
local name = frame:GetName()
|
|
pfUI.movables[name] = nil
|
|
end
|
|
|
|
-- [ AlignToPosition ]
|
|
-- Sets a frame to the selected anchor
|
|
-- 'frame' [frame] the frame that should be aligned
|
|
-- 'anchor' [frame] the frame where it should be aligned to
|
|
-- 'position' [string] where it should appear, takes the following:
|
|
-- "TOP", "RIGHT", "BOTTOM", "LEFT"
|
|
function pfUI.api.AlignToPosition(frame, anchor, position, spacing)
|
|
if frame == anchor then return end
|
|
frame:ClearAllPoints()
|
|
if position == "TOP" and anchor then
|
|
frame:SetPoint("BOTTOMLEFT", anchor, "TOPLEFT", 0, (spacing or 0))
|
|
frame:SetPoint("BOTTOMRIGHT", anchor, "TOPRIGHT", 0, (spacing or 0))
|
|
elseif position == "RIGHT" and anchor then
|
|
frame:SetPoint("TOPLEFT", anchor, "TOPRIGHT", (spacing or 0), 0)
|
|
frame:SetPoint("BOTTOMLEFT", anchor, "BOTTOMRIGHT", (spacing or 0), 0)
|
|
elseif position == "BOTTOM" and anchor then
|
|
frame:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", 0, -(spacing or 0))
|
|
frame:SetPoint("TOPRIGHT", anchor, "BOTTOMRIGHT", 0, -(spacing or 0))
|
|
elseif position == "LEFT" and anchor then
|
|
frame:SetPoint("TOPRIGHT", anchor, "TOPLEFT", -(spacing or 0), 0)
|
|
frame:SetPoint("BOTTOMRIGHT", anchor, "BOTTOMLEFT", -(spacing or 0), 0)
|
|
else
|
|
frame:SetPoint("TOPRIGHT", UIParent, "TOPRIGHT", 0, 0)
|
|
frame:SetPoint("BOTTOMRIGHT", UIParent, "BOTTOMRIGHT", 0, 0)
|
|
end
|
|
end
|
|
|
|
-- [ SetAutoPoint ]
|
|
-- Automatically places the frame according to screen position of the parent.
|
|
-- 'frame' [frame] the frame that should be moved.
|
|
-- 'parent' [frame] the frame's anchor point
|
|
-- 'spacing' [number] the padding that should be used between the
|
|
-- frame and its parent frame
|
|
function pfUI.api.SetAutoPoint(frame, parent, spacing)
|
|
--[[
|
|
|
|
a b max
|
|
+-----------------+
|
|
| 1 | 2 | 3 |
|
|
|-----+-----+-----| c
|
|
| 4 | 5 | 6 |
|
|
|-----+-----+-----| d
|
|
| 7 | 8 | 9 |
|
|
+-----------------+
|
|
0
|
|
|
|
]]--
|
|
|
|
local a = GetScreenWidth() / 3
|
|
local b = GetScreenWidth() / 3 * 2
|
|
|
|
local c = GetScreenHeight() / 3 * 2
|
|
local d = GetScreenHeight() / 3
|
|
|
|
local x, y = parent:GetCenter()
|
|
|
|
if not x or not y then return end
|
|
|
|
local off = spacing or 0
|
|
|
|
frame:ClearAllPoints()
|
|
|
|
if x < a and y > c then
|
|
-- TOPLEFT
|
|
frame:SetPoint("TOPLEFT", parent, "BOTTOMLEFT", 0, -off)
|
|
elseif x > a and x < b and y > c then
|
|
-- TOP
|
|
frame:SetPoint("TOP", parent, "BOTTOM", 0, -off)
|
|
elseif x > b and y > c then
|
|
-- TOPRIGHT
|
|
frame:SetPoint("TOPRIGHT", parent, "BOTTOMRIGHT", 0, -off)
|
|
|
|
elseif x < a and y > d and y < c then
|
|
-- LEFT
|
|
frame:SetPoint("LEFT", parent, "RIGHT", off, 0)
|
|
|
|
elseif x > a and x < b and y > d and y < c then
|
|
-- CENTER
|
|
frame:SetPoint("BOTTOM", parent, "TOP", 0, off)
|
|
|
|
elseif x > b and y > d and y < c then
|
|
-- RIGHT
|
|
frame:SetPoint("RIGHT", parent, "LEFT", -off, 0)
|
|
|
|
elseif x < a and y < d then
|
|
-- BOTTOMLEFT
|
|
frame:SetPoint("BOTTOMLEFT", parent, "TOPLEFT", 0, off)
|
|
|
|
elseif x > a and x < b and y < d then
|
|
-- BOTTOM
|
|
frame:SetPoint("BOTTOM", parent, "TOP", 0, off)
|
|
|
|
elseif x > b and y < d then
|
|
-- BOTTOMRIGHT
|
|
frame:SetPoint("BOTTOMRIGHT", parent, "TOPRIGHT", 0, off)
|
|
end
|
|
end
|
|
|
|
-- [ GetBestAnchor ]
|
|
-- Returns the best anchor of a frame, based on its position
|
|
-- 'self' [frame] the frame that should be checked
|
|
-- returns: [string] the name of the best anchor
|
|
function pfUI.api.GetBestAnchor(self)
|
|
local scale = self:GetScale()
|
|
local x, y = self:GetCenter()
|
|
local a = GetScreenWidth() / scale / 3
|
|
local b = GetScreenWidth() / scale / 3 * 2
|
|
local c = GetScreenHeight() / scale / 3 * 2
|
|
local d = GetScreenHeight() / scale / 3
|
|
if not x or not y then return end
|
|
|
|
if x < a and y > c then
|
|
return "TOPLEFT"
|
|
elseif x > a and x < b and y > c then
|
|
return "TOP"
|
|
elseif x > b and y > c then
|
|
return "TOPRIGHT"
|
|
elseif x < a and y > d and y < c then
|
|
return "LEFT"
|
|
elseif x > a and x < b and y > d and y < c then
|
|
return "CENTER"
|
|
elseif x > b and y > d and y < c then
|
|
return "RIGHT"
|
|
elseif x < a and y < d then
|
|
return "BOTTOMLEFT"
|
|
elseif x > a and x < b and y < d then
|
|
return "BOTTOM"
|
|
elseif x > b and y < d then
|
|
return "BOTTOMRIGHT"
|
|
end
|
|
end
|
|
|
|
-- [ ConvertFrameAnchor ]
|
|
-- Converts a frame anchor into another one while preserving the frame position
|
|
-- 'self' [frame] the frame that should get another anchor.
|
|
-- 'anchor' [string] the new anchor that shall be used
|
|
-- returns: anchor, x, y can directly be used in SetPoint()
|
|
function pfUI.api.ConvertFrameAnchor(self, anchor)
|
|
local scale, x, y, _ = self:GetScale(), nil, nil, nil
|
|
|
|
if anchor == "CENTER" then
|
|
x, y = self:GetCenter()
|
|
x, y = x - GetScreenWidth()/2/scale, y - GetScreenHeight()/2/scale
|
|
elseif anchor == "TOPLEFT" then
|
|
x, y = self:GetLeft(), self:GetTop() - GetScreenHeight()/scale
|
|
elseif anchor == "TOP" then
|
|
x, _ = self:GetCenter()
|
|
x, y = x - GetScreenWidth()/2/scale, self:GetTop() - GetScreenHeight()/scale
|
|
elseif anchor == "TOPRIGHT" then
|
|
x, y = self:GetRight() - GetScreenWidth()/scale, self:GetTop() - GetScreenHeight()/scale
|
|
elseif anchor == "RIGHT" then
|
|
_, y = self:GetCenter()
|
|
x, y = self:GetRight() - GetScreenWidth()/scale, y - GetScreenHeight()/2/scale
|
|
elseif anchor == "BOTTOMRIGHT" then
|
|
x, y = self:GetRight() - GetScreenWidth()/scale, self:GetBottom()
|
|
elseif anchor == "BOTTOM" then
|
|
x, _ = self:GetCenter()
|
|
x, y = x - GetScreenWidth()/2/scale, self:GetBottom()
|
|
elseif anchor == "BOTTOMLEFT" then
|
|
x, y = self:GetLeft(), self:GetBottom()
|
|
elseif anchor == "LEFT" then
|
|
_, y = self:GetCenter()
|
|
x, y = self:GetLeft(), y - GetScreenHeight()/2/scale
|
|
end
|
|
|
|
return anchor, round(x, 2), round(y, 2)
|
|
end
|
|
|
|
-- [ GetStringColor ]
|
|
-- Queries the pfUI setting strings and extract its color codes
|
|
-- returns r,g,b,a as strings
|
|
local color_cache = setmetatable({}, {
|
|
__index = function(t, k)
|
|
local color = { pfUI.api.strsplit(",", k) }
|
|
rawset(t, k, color)
|
|
return color
|
|
end
|
|
})
|
|
function pfUI.api.GetStringColor(colorstr)
|
|
return unpack(color_cache[colorstr])
|
|
end
|
|
|
|
-- [ GetStringColorObject ]
|
|
-- Like GetStringColor, but returns a cached ColorMixin instead of raw values.
|
|
-- The object is a shared per-string singleton, so treat it as read-only.
|
|
-- returns a ColorMixin
|
|
local color_object_cache = setmetatable({}, {
|
|
__index = function(t, k)
|
|
local r,g,b,a = pfUI.api.GetStringColor(k)
|
|
local color = CreateColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
|
|
rawset(t, k, color)
|
|
return color
|
|
end
|
|
})
|
|
function pfUI.api.GetStringColorObject(colorstr)
|
|
return color_object_cache[colorstr]
|
|
end
|
|
|
|
-- [ rgbhex ]
|
|
-- Returns color format from color info
|
|
-- 'r' [table | number] color table or r color component
|
|
-- 'g' [number] optional g color component
|
|
-- 'b' [number] optional b color component
|
|
-- 'a' [number] optional alpha component
|
|
-- returns color string in the form of '|caarrggbb'
|
|
local rgbhex_cache = {}
|
|
function pfUI.api.rgbhex(r, g, b, a)
|
|
local _r, _g, _b, _a
|
|
if type(r) == "table" then
|
|
if r.r then
|
|
_r, _g, _b, _a = r.r, r.g, r.b, (r.a or 1)
|
|
elseif r[3] ~= nil then
|
|
_r, _g, _b, _a = r[1], r[2], r[3], (r[4] or 1)
|
|
end
|
|
elseif tonumber(r) then
|
|
_r, _g, _b, _a = r, g, b, (a or 1)
|
|
end
|
|
|
|
if _r and _g and _b and _a then
|
|
local key = ((Round(_r*255)*256 + Round(_g*255))*256 + Round(_b*255))*256 + Round(_a*255)
|
|
local hex = rgbhex_cache[key]
|
|
if not hex then
|
|
hex = "|c" .. C_ColorUtil.GenerateTextColorCode({ r = _r, g = _g, b = _b, a = _a })
|
|
rgbhex_cache[key] = hex
|
|
end
|
|
return hex
|
|
end
|
|
|
|
return ""
|
|
end
|
|
|
|
-- [ GetBorderSize ]
|
|
-- Returns the configure value of a border and its pixel scaled version.
|
|
-- 'pref' allows to specifiy a custom border (i.e unitframes, panel)
|
|
function pfUI.api.GetBorderSize(pref)
|
|
if not pfUI.borders then pfUI.borders = {} end
|
|
|
|
-- set to default border if accessing a wrong border type
|
|
if not pref or not pfUI_config.appearance.border[pref] or pfUI_config.appearance.border[pref] == "-1" then
|
|
pref = "default"
|
|
end
|
|
|
|
if pfUI.borders[pref] then
|
|
-- return already cached values
|
|
return pfUI.borders[pref][1], pfUI.borders[pref][2]
|
|
else
|
|
-- add new borders to the pfUI tree
|
|
local raw = tonumber(pfUI_config.appearance.border[pref])
|
|
if raw == -1 then raw = 3 end
|
|
|
|
local scaled = raw * GetPerfectPixel()
|
|
pfUI.borders[pref] = { raw, scaled }
|
|
|
|
return raw, scaled
|
|
end
|
|
end
|
|
|
|
-- [ GetPerfectPixel ]
|
|
-- Returns a number that equals a real pixel on regular scaled frames.
|
|
-- Respects the current UI-scale and calculates a real pixel based on
|
|
-- the screen resolution and the 768px sized drawlayer.
|
|
function pfUI.api.GetPerfectPixel()
|
|
if pfUI.pixel then return pfUI.pixel end
|
|
|
|
if pfUI_config.appearance.border.pixelperfect == "1" then
|
|
local scale = GetCVar("useUiScale") == "1" and GetCVar("uiScale") or "1"
|
|
local resolution = GetCVar("gxResolution")
|
|
local _, _, screenwidth, screenheight = strfind(resolution, "(.+)x(.+)")
|
|
|
|
pfUI.pixel = 768 / screenheight / scale
|
|
pfUI.pixel = pfUI.pixel > 1 and 1 or pfUI.pixel
|
|
|
|
-- autodetect and zoom for HiDPI displays
|
|
if pfUI_config.appearance.border.hidpi == "1" then
|
|
pfUI.pixel = pfUI.pixel < .5 and pfUI.pixel * 2 or pfUI.pixel
|
|
end
|
|
else
|
|
pfUI.pixel = .7
|
|
end
|
|
|
|
pfUI.backdrop = {
|
|
bgFile = "Interface\\BUTTONS\\WHITE8X8", tile = false, tileSize = 0,
|
|
edgeFile = "Interface\\BUTTONS\\WHITE8X8", edgeSize = pfUI.pixel,
|
|
insets = {left = -pfUI.pixel, right = -pfUI.pixel, top = -pfUI.pixel, bottom = -pfUI.pixel},
|
|
}
|
|
|
|
pfUI.backdrop_thin = {
|
|
bgFile = "Interface\\BUTTONS\\WHITE8X8", tile = false, tileSize = 0,
|
|
edgeFile = "Interface\\BUTTONS\\WHITE8X8", edgeSize = pfUI.pixel,
|
|
insets = {left = 0, right = 0, top = 0, bottom = 0},
|
|
}
|
|
|
|
return pfUI.pixel
|
|
end
|
|
|
|
-- [ Create Backdrop ]
|
|
-- Creates a pfUI compatible frame as backdrop element
|
|
-- 'f' [frame] the frame which should get a backdrop.
|
|
-- 'inset' [int] backdrop inset, defaults to border size.
|
|
-- 'legacy' [bool] use legacy backdrop instead of creating frames.
|
|
-- 'transp' [number] set default transparency
|
|
local backdrop, b, level, rawborder, border, br, bg, bb, ba, er, eg, eb, ea
|
|
function pfUI.api.CreateBackdrop(f, inset, legacy, transp, backdropSetting)
|
|
-- exit if now frame was given
|
|
if not f then return end
|
|
|
|
-- load raw and pixel perfect scaled border
|
|
rawborder, border = GetBorderSize()
|
|
|
|
-- load custom border if existing
|
|
if inset then
|
|
rawborder = inset / GetPerfectPixel()
|
|
border = inset
|
|
end
|
|
|
|
-- detect if blizzard backdrops shall be used
|
|
local blizz = C.appearance.border.force_blizz == "1" and true or nil
|
|
backdrop = blizz and pfUI.backdrop_blizz_full or rawborder == 1 and pfUI.backdrop_thin or pfUI.backdrop
|
|
border = blizz and math.max(border, 3) or border
|
|
|
|
-- get the color settings
|
|
br, bg, bb, ba = pfUI.api.GetStringColor(pfUI_config.appearance.border.background)
|
|
er, eg, eb, ea = pfUI.api.GetStringColor(pfUI_config.appearance.border.color)
|
|
|
|
if transp and transp < tonumber(ba) then ba = transp end
|
|
|
|
-- use legacy backdrop handling
|
|
if legacy then
|
|
if backdropSetting then f:SetBackdrop(backdropSetting) end
|
|
f:SetBackdrop(backdrop)
|
|
f:SetBackdropColor(br, bg, bb, ba)
|
|
f:SetBackdropBorderColor(er, eg, eb , ea)
|
|
else
|
|
-- increase clickable area if available
|
|
if f.SetHitRectInsets then
|
|
f:SetHitRectInsets(-border,-border,-border,-border)
|
|
end
|
|
|
|
-- use new backdrop behaviour
|
|
if not f.backdrop then
|
|
if f:GetBackdrop() then f:SetBackdrop(nil) end
|
|
|
|
local b = CreateFrame("Frame", nil, f)
|
|
level = f:GetFrameLevel()
|
|
if level < 1 then
|
|
b:SetFrameLevel(level)
|
|
else
|
|
b:SetFrameLevel(level - 1)
|
|
end
|
|
|
|
f.backdrop = b
|
|
end
|
|
|
|
f.backdrop:SetPoint("TOPLEFT", f, "TOPLEFT", -border, border)
|
|
f.backdrop:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", border, -border)
|
|
f.backdrop:SetBackdrop(backdrop)
|
|
f.backdrop:SetBackdropColor(br, bg, bb, ba)
|
|
f.backdrop:SetBackdropBorderColor(er, eg, eb , ea)
|
|
|
|
if blizz then
|
|
if not f.backdrop_border then
|
|
local border = CreateFrame("Frame", nil, f.backdrop)
|
|
border:SetFrameLevel(level + 2)
|
|
f.backdrop_border = border
|
|
|
|
local hookSetBackdropBorderColor = f.backdrop.SetBackdropBorderColor
|
|
f.backdrop.SetBackdropBorderColor = function(self, r, g, b, a)
|
|
f.backdrop_border:SetBackdropBorderColor(r, g, b, a)
|
|
hookSetBackdropBorderColor(f.backdrop, r, g, b, a)
|
|
end
|
|
end
|
|
|
|
f.backdrop_border:SetAllPoints(f.backdrop)
|
|
f.backdrop_border:SetBackdrop(pfUI.backdrop_blizz_border)
|
|
f.backdrop_border:SetBackdropBorderColor(er, eg, eb , ea)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- [ Create FontString ]
|
|
-- Get-or-create a FontString attached to `f` as the named field. Idempotent —
|
|
-- subsequent calls return the existing FontString. Defaults to pfUI's typical
|
|
-- font style: default font at config size, OUTLINE flag, OVERLAY layer.
|
|
-- 'f' [frame] parent frame
|
|
-- 'key' [string] field name on `f` to stash the FontString (e.g. "scoreText")
|
|
-- 'layer' [string] draw layer. Defaults to "OVERLAY".
|
|
-- 'size' [int] font size. Defaults to C.global.font_size.
|
|
-- 'flags' [string] font flags. Defaults to "OUTLINE".
|
|
-- 'font' [string] font path. Defaults to pfUI.font_default.
|
|
function pfUI.api.CreateFontString(f, key, layer, size, flags, font)
|
|
if not f or not key then return end
|
|
if f[key] then return f[key] end
|
|
|
|
local fs = f:CreateFontString(nil, layer or "OVERLAY")
|
|
fs:SetFont(font or pfUI.font_default, size or C.global.font_size, flags or "OUTLINE")
|
|
f[key] = fs
|
|
return fs
|
|
end
|
|
|
|
-- [ Create Shadow ]
|
|
-- Creates a pfUI compatible frame as shadow element
|
|
-- 'f' [frame] the frame which should get a backdrop.
|
|
function pfUI.api.CreateBackdropShadow(f)
|
|
-- exit if now frame was given
|
|
if not f then return end
|
|
|
|
if f.backdrop_shadow or pfUI_config.appearance.border.shadow ~= "1" then
|
|
return
|
|
end
|
|
|
|
local anchor = f.backdrop or f
|
|
f.backdrop_shadow = CreateFrame("Frame", nil, anchor)
|
|
f.backdrop_shadow:SetFrameStrata("BACKGROUND")
|
|
f.backdrop_shadow:SetFrameLevel(1)
|
|
f.backdrop_shadow:SetPoint("TOPLEFT", anchor, "TOPLEFT", -5, 5)
|
|
f.backdrop_shadow:SetPoint("BOTTOMRIGHT", anchor, "BOTTOMRIGHT", 5, -5)
|
|
f.backdrop_shadow:SetBackdrop(pfUI.backdrop_shadow)
|
|
f.backdrop_shadow:SetBackdropBorderColor(0, 0, 0, tonumber(pfUI_config.appearance.border.shadow_intensity))
|
|
end
|
|
|
|
-- [ Bar Layout Options ] --
|
|
-- 'barsize' size of bar in number of buttons
|
|
-- returns: array of options as strings for pfUI.gui.bar
|
|
function pfUI.api.BarLayoutOptions(barsize)
|
|
assert(barsize > 0 and barsize <= NUM_ACTIONBAR_BUTTONS,"BarLayoutOptions: barsize "..tostring(barsize).." is invalid")
|
|
local options, seen = {}, {}
|
|
local function add(option)
|
|
if not seen[option] then table.insert(options, option); seen[option] = true end
|
|
end
|
|
for i,layout in ipairs(pfGridmath[barsize]) do
|
|
add(string.format("%d x %d",layout[1],layout[2]))
|
|
end
|
|
for _, option in ipairs({
|
|
"1 x 12", "1 x 10", "2 x 10", "2 x 5", "3 x 10", "3 x 5", "3 x 3",
|
|
"5 x 3", "10 x 3", "5 x 2", "10 x 2", "10 x 1", "12 x 1"
|
|
}) do add(option) end
|
|
table.sort(options, function(a, b)
|
|
local _, _, ac, ar = string.find(a, "(%d+)%s*x%s*(%d+)")
|
|
local _, _, bc, br = string.find(b, "(%d+)%s*x%s*(%d+)")
|
|
ac, ar = tonumber(ac) or 1, tonumber(ar) or 1
|
|
bc, br = tonumber(bc) or 1, tonumber(br) or 1
|
|
local ah = ac >= ar and 0 or 1
|
|
local bh = bc >= br and 0 or 1
|
|
if ah ~= bh then return ah < bh end
|
|
if ah == 0 then if ac ~= bc then return ac > bc end; return ar < br
|
|
else if ar ~= br then return ar > br end; return ac < bc end
|
|
end)
|
|
return options
|
|
end
|
|
|
|
-- [ Bar Layout Formfactor ] --
|
|
-- 'option' string option as used in pfUI_config.bars[bar].option
|
|
-- returns: integer formfactor
|
|
local formfactors = {}
|
|
setmetatable(formfactors, {__mode = "v"}) -- weak table so values not referenced are collected on next gc
|
|
function pfUI.api.BarLayoutFormfactor(option)
|
|
if formfactors[option] then
|
|
return formfactors[option]
|
|
else
|
|
for barsize,_ in ipairs(pfGridmath) do
|
|
local options = pfUI.api.BarLayoutOptions(barsize)
|
|
for i,opt in ipairs(options) do
|
|
if opt == option then
|
|
formfactors[option] = i
|
|
return formfactors[option]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function ResolveBarLayout(barsize, formfactor, uneven)
|
|
local layout = tostring(formfactor or "")
|
|
local _, _, a, b = string.find(layout, "(%d+)%s*x%s*(%d+)")
|
|
if a and b then
|
|
local cols, rows = tonumber(a), tonumber(b)
|
|
cols = math.min(NUM_ACTIONBAR_BUTTONS, math.max(1, cols))
|
|
rows = math.min(NUM_ACTIONBAR_BUTTONS, math.max(1, rows))
|
|
local orientation = string.upper(uneven or "DOWN")
|
|
local mode = (orientation == "LEFT" or orientation == "RIGHT") and "cols" or "rows"
|
|
if mode == "rows" then
|
|
cols = math.max(1, math.min(cols, barsize))
|
|
rows = math.max(1, math.ceil(barsize / cols))
|
|
else
|
|
rows = math.max(1, math.min(rows, barsize))
|
|
cols = math.max(1, math.ceil(barsize / rows))
|
|
end
|
|
return cols, rows, mode, orientation
|
|
end
|
|
local index = pfUI.api.BarLayoutFormfactor(layout)
|
|
if not index then return nil end
|
|
local cols, rows = unpack(pfGridmath[barsize][index])
|
|
return cols, rows, "rows", "DOWN"
|
|
end
|
|
|
|
-- [ Bar Layout Size ] --
|
|
-- 'bar' frame reference,
|
|
-- 'barsize' integer number of buttons,
|
|
-- 'formfactor' string formfactor in cols x rows,
|
|
-- 'padding' the spacing between buttons
|
|
function pfUI.api.BarLayoutSize(bar,barsize,formfactor,iconsize,bordersize,padding,uneven,fillmode)
|
|
assert(barsize > 0 and barsize <= NUM_ACTIONBAR_BUTTONS,"BarLayoutSize: barsize "..tostring(barsize).." is invalid")
|
|
local cols, rows = ResolveBarLayout(barsize, formfactor, uneven)
|
|
if not cols or not rows then cols, rows = unpack(pfGridmath[barsize][1]) end
|
|
local width = (iconsize + bordersize*2+padding) * cols + padding
|
|
local height = (iconsize + bordersize*2+padding) * rows + padding
|
|
bar._size = {width,height}
|
|
return bar._size
|
|
end
|
|
|
|
-- [ Bar Button Anchor ] --
|
|
-- 'button' frame reference
|
|
-- 'basename' name of button frame without index
|
|
-- 'buttonindex' index number of button on bar
|
|
-- 'formfactor' string formfactor in cols x rows
|
|
-- 'iconsize' size of the button
|
|
-- 'bordersize' default bordersize
|
|
-- 'padding' the spacing between buttons
|
|
function pfUI.api.BarButtonAnchor(button,basename,buttonindex,barsize,formfactor,iconsize,bordersize,padding,uneven,fillmode)
|
|
assert(barsize > 0 and barsize <= NUM_ACTIONBAR_BUTTONS,"BarButtonAnchor: barsize "..tostring(barsize).." is invalid")
|
|
local cols, rows, mode, orientation = ResolveBarLayout(barsize, formfactor, uneven)
|
|
if not cols or not rows then
|
|
cols, rows, mode, orientation = unpack(pfGridmath[barsize][1]), "rows", "DOWN"
|
|
end
|
|
|
|
-- fillmode: remap buttonindex to slotindex (grid position in native fill order)
|
|
-- only needed when fill direction differs from layout mode
|
|
local slotindex = buttonindex
|
|
if fillmode == "row" and mode == "cols" then
|
|
local full = math.floor(barsize / rows)
|
|
local rem = barsize - full * rows
|
|
local n = 0
|
|
for r = 1, rows do
|
|
local w = full + ((r <= rem) and 1 or 0)
|
|
if n + w >= buttonindex then
|
|
local c = buttonindex - n
|
|
slotindex = (c - 1) * rows + r
|
|
break
|
|
end
|
|
n = n + w
|
|
end
|
|
elseif fillmode == "col" and mode == "rows" then
|
|
local full = math.floor(barsize / cols)
|
|
local rem = barsize - full * cols
|
|
local n = 0
|
|
for c = 1, cols do
|
|
local h = full + ((c <= rem) and 1 or 0)
|
|
if n + h >= buttonindex then
|
|
local r = buttonindex - n
|
|
slotindex = (r - 1) * cols + c
|
|
break
|
|
end
|
|
n = n + h
|
|
end
|
|
end
|
|
|
|
local parent = button:GetParent()
|
|
local step = iconsize + bordersize*2 + padding
|
|
local row, col
|
|
if mode == "cols" then
|
|
col = math.ceil(slotindex / rows)
|
|
row = slotindex - ((col-1) * rows)
|
|
else
|
|
row = math.ceil(slotindex / cols)
|
|
col = slotindex - ((row-1) * cols)
|
|
end
|
|
if mode == "cols" and orientation == "LEFT" then
|
|
local final_col = math.ceil(barsize / rows)
|
|
local count = barsize - (final_col - 1) * rows
|
|
if count > 0 and count < rows then
|
|
if col == final_col then col = 1 else col = col + 1 end
|
|
end
|
|
elseif mode == "rows" and orientation == "UP" then
|
|
local final_row = math.ceil(barsize / cols)
|
|
local count = barsize - (final_row - 1) * cols
|
|
if count > 0 and count < cols then
|
|
if row == final_row then row = 1 else row = row + 1 end
|
|
end
|
|
end
|
|
local x = bordersize + padding + (col - 1) * step
|
|
local y = -bordersize - padding - (row - 1) * step
|
|
if slotindex == 1 then
|
|
button._anchor = {"TOPLEFT", parent, "TOPLEFT", x, y}
|
|
else
|
|
button._anchor = {"TOPLEFT", parent, "TOPLEFT", x, y}
|
|
if mode == "cols" then
|
|
local final_col = math.ceil(barsize / rows)
|
|
local count = barsize - (final_col - 1) * rows
|
|
local target_col = (orientation == "LEFT") and 1 or cols
|
|
if count > 0 and count < rows and col == target_col and row == 1 then
|
|
button._anchor[5] = button._anchor[5] - (rows - count) * step / 2
|
|
end
|
|
else
|
|
local final_row = math.ceil(barsize / cols)
|
|
local count = barsize - (final_row - 1) * cols
|
|
local target_row = (orientation == "UP") and 1 or rows
|
|
if count > 0 and count < cols and row == target_row and col == 1 then
|
|
button._anchor[4] = button._anchor[4] + (cols - count) * step / 2
|
|
end
|
|
end
|
|
end
|
|
return button._anchor
|
|
end
|
|
|
|
-- [ Enable Autohide ] --
|
|
-- 'frame' the frame that should be hidden
|
|
function pfUI.api.EnableAutohide(frame, timeout, combat)
|
|
if not frame then return end
|
|
local timeout = timeout
|
|
|
|
frame.hover = frame.hover or CreateFrame("Frame", frame:GetName() .. "Autohide", frame)
|
|
frame.hover:SetParent(frame)
|
|
frame.hover:SetAllPoints(frame)
|
|
frame.hover.parent = frame
|
|
frame.hover:Show()
|
|
|
|
if combat then
|
|
frame.hover:RegisterEvent("PLAYER_REGEN_ENABLED")
|
|
frame.hover:RegisterEvent("PLAYER_REGEN_DISABLED")
|
|
frame.hover:SetScript("OnEvent", function()
|
|
if event == "PLAYER_REGEN_DISABLED" then
|
|
this.parent:SetAlpha(1)
|
|
this.activeTo = "keep"
|
|
elseif event == "PLAYER_REGEN_ENABLED" then
|
|
this.activeTo = GetTime() + timeout
|
|
end
|
|
end)
|
|
end
|
|
|
|
frame.hover:SetScript("OnUpdate", function()
|
|
-- throttle to 0.05s
|
|
if (this.tick or 0) > GetTime() then return end
|
|
this.tick = GetTime() + 0.05
|
|
|
|
if this.activeTo == "keep" then return end
|
|
|
|
if MouseIsOver(this, 10, -10, -10, 10) then
|
|
this.activeTo = GetTime() + timeout
|
|
this.parent:SetAlpha(1)
|
|
elseif this.activeTo then
|
|
if this.activeTo < GetTime() and this.parent:GetAlpha() > 0 then
|
|
local fps = (60 / math.max(GetFramerate(), 1))
|
|
this.parent:SetAlpha(this.parent:GetAlpha() - 0.05*fps)
|
|
end
|
|
else
|
|
this.activeTo = GetTime() + timeout
|
|
end
|
|
end)
|
|
end
|
|
|
|
-- [ Disable Autohide ] --
|
|
-- 'frame' the frame that should get the autohide removed
|
|
function pfUI.api.DisableAutohide(frame)
|
|
if not frame then return end
|
|
if not frame.hover then return end
|
|
|
|
frame.hover:SetScript("OnEvent", nil)
|
|
frame.hover:SetScript("OnUpdate", nil)
|
|
frame.hover:Hide()
|
|
frame:SetAlpha(1)
|
|
end
|
|
|
|
-- [ GetColoredTime ] --
|
|
-- 'remaining' the time in seconds that should be converted
|
|
-- return a colored string including a time unit (m/h/d)
|
|
local color_day, color_hour, color_minute, color_low, color_normal
|
|
function pfUI.api.GetColoredTimeString(remaining)
|
|
if not remaining then return "" end
|
|
|
|
-- Show days if remaining is > 99 Hours (99 * 60 * 60)
|
|
if remaining > 356400 then
|
|
if not color_day then
|
|
local r,g,b,a = pfUI.api.GetStringColor(C.appearance.cd.daycolor)
|
|
color_day = pfUI.api.rgbhex(r,g,b)
|
|
end
|
|
|
|
return color_day .. round(remaining / 86400) .. "|rd"
|
|
|
|
-- Show hours if remaining is > 99 Minutes (99 * 60)
|
|
elseif remaining > 5940 then
|
|
if not color_hour then
|
|
local r,g,b,a = pfUI.api.GetStringColor(C.appearance.cd.hourcolor)
|
|
color_hour = pfUI.api.rgbhex(r,g,b)
|
|
end
|
|
|
|
return color_hour .. round(remaining / 3600) .. "|rh"
|
|
|
|
-- Show minutes if remaining is > 99 Seconds (99)
|
|
elseif remaining > 99 then
|
|
if not color_minute then
|
|
local r,g,b,a = pfUI.api.GetStringColor(C.appearance.cd.minutecolor)
|
|
color_minute = pfUI.api.rgbhex(r,g,b)
|
|
end
|
|
|
|
return color_minute .. round(remaining / 60) .. "|rm"
|
|
|
|
-- Show milliseconds on low
|
|
elseif remaining <= 5 and pfUI_config.appearance.cd.milliseconds == "1" then
|
|
if not color_low then
|
|
local r,g,b,a = pfUI.api.GetStringColor(C.appearance.cd.lowcolor)
|
|
color_low = pfUI.api.rgbhex(r,g,b)
|
|
end
|
|
|
|
return color_low .. string.format("%.1f", round(remaining,1))
|
|
|
|
-- Show seconds on low
|
|
elseif remaining <= 5 then
|
|
if not color_low then
|
|
local r,g,b,a = pfUI.api.GetStringColor(C.appearance.cd.lowcolor)
|
|
color_low = pfUI.api.rgbhex(r,g,b)
|
|
end
|
|
|
|
return color_low .. round(remaining)
|
|
|
|
-- Show seconds on normal
|
|
elseif remaining >= 0 then
|
|
if not color_normal then
|
|
local r, g, b, a = pfUI.api.GetStringColor(C.appearance.cd.normalcolor)
|
|
color_normal = pfUI.api.rgbhex(r,g,b)
|
|
end
|
|
return color_normal .. round(remaining)
|
|
|
|
-- Return empty
|
|
else
|
|
return ""
|
|
end
|
|
end
|
|
|
|
-- [ GetColorGradient ] --
|
|
-- 'perc' percentage (0-1)
|
|
-- return r,g,b and hexcolor
|
|
local gradientcolors = {}
|
|
function pfUI.api.GetColorGradient(perc)
|
|
if not perc or perc ~= perc then perc = 0 end -- NaN guard: NaN ~= NaN is true in Lua
|
|
perc = perc > 1 and 1 or perc
|
|
perc = perc < 0 and 0 or perc
|
|
perc = floor(perc*100)/100
|
|
|
|
local index = perc
|
|
if not gradientcolors[index] then
|
|
local r1, g1, b1, r2, g2, b2
|
|
|
|
if perc <= 0.5 then
|
|
perc = perc * 2
|
|
r1, g1, b1 = 1, 0, 0
|
|
r2, g2, b2 = 1, 1, 0
|
|
else
|
|
perc = perc * 2 - 1
|
|
r1, g1, b1 = 1, 1, 0
|
|
r2, g2, b2 = 0, 1, 0
|
|
end
|
|
|
|
local r = round(r1 + (r2 - r1) * perc, 4)
|
|
local g = round(g1 + (g2 - g1) * perc, 4)
|
|
local b = round(b1 + (b2 - b1) * perc, 4)
|
|
local h = pfUI.api.rgbhex(r,g,b)
|
|
|
|
gradientcolors[index] = {}
|
|
gradientcolors[index].r = r
|
|
gradientcolors[index].g = g
|
|
gradientcolors[index].b = b
|
|
gradientcolors[index].h = h
|
|
end
|
|
|
|
return gradientcolors[index].r,
|
|
gradientcolors[index].g,
|
|
gradientcolors[index].b,
|
|
gradientcolors[index].h
|
|
end
|
|
|
|
-- [ GetNoNameObject ] --
|
|
-- 'frame' [string] parent frame
|
|
-- 'scantype' [string] scanning Region or Children
|
|
-- 'objtype' [string]
|
|
-- 'layer' [string]
|
|
-- 'arg1' [string]
|
|
-- return object
|
|
function pfUI.api.GetNoNameObject(frame, objtype, layer, arg1, arg2)
|
|
-- A nil/non-frame parent otherwise dies on frame:GetRegions()/:GetChildren()
|
|
-- below, and the traceback stops here — useless, since all callers share this
|
|
-- line. pfUI shadows Lua's `error` (dropping the level arg and not throwing),
|
|
-- so fold the caller frame in via debugstack to name the offending skin, then
|
|
-- bail so we don't fall through and crash on frame:GetRegions() anyway.
|
|
if type(frame) ~= "table" or not frame.GetRegions then
|
|
error("GetNoNameObject: invalid parent frame\n" .. debugstack(2, 3, 0))
|
|
return
|
|
end
|
|
|
|
local arg1 = arg1 and gsub(arg1, "([%+%-%*%(%)%?%[%]%^])", "%%%1")
|
|
local arg2 = arg2 and gsub(arg2, "([%+%-%*%(%)%?%[%]%^])", "%%%1")
|
|
|
|
local objects
|
|
if objtype == "Texture" or objtype == "FontString" then
|
|
objects = {frame:GetRegions()}
|
|
else
|
|
objects = {frame:GetChildren()}
|
|
end
|
|
|
|
for _, object in ipairs(objects) do
|
|
local check = true
|
|
if object:GetObjectType() ~= objtype or (layer and object:GetDrawLayer() ~= layer) then check = false end
|
|
|
|
if check then
|
|
if objtype == "Texture" and object.SetTexture and object:GetTexture() ~= "Interface\\BUTTONS\\WHITE8X8" then
|
|
if arg1 then
|
|
local texture = object:GetTexture()
|
|
if texture and not string.find(texture, arg1, 1) then check = false end
|
|
end
|
|
|
|
if check then return object end
|
|
elseif objtype == "FontString" and object.SetText then
|
|
if arg1 then
|
|
local text = object:GetText()
|
|
if text and not string.find(text, arg1, 1) then check = false end
|
|
end
|
|
|
|
if check then return object end
|
|
elseif objtype == "Button" and object.GetNormalTexture and object:GetNormalTexture() then
|
|
if arg1 then
|
|
local texture = object:GetNormalTexture():GetTexture()
|
|
if texture and not string.find(texture, arg1, 1) then check = false end
|
|
end
|
|
if arg2 then
|
|
local text = object:GetText()
|
|
if text and not string.find(text, arg2, 1) then check = false end
|
|
end
|
|
|
|
if check then return object end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- [ TryMemoizedFuncLoadstringForSpellCasts ]
|
|
-- Memoizes lua function strings for spell casts to improve performance.
|
|
-- Supports both string functions and direct function values.
|
|
-- 'funcOrStr' [function|string] Either a function or a lua string to execute
|
|
-- return: [function|nil] The function to execute, or nil on error
|
|
local memoizedFuncs = {}
|
|
function pfUI.api.TryMemoizedFuncLoadstringForSpellCasts(funcOrStr)
|
|
-- If it's already a function, return it directly
|
|
if type(funcOrStr) == "function" then
|
|
return funcOrStr
|
|
end
|
|
|
|
-- If it's a string, try to memoize it
|
|
if type(funcOrStr) == "string" then
|
|
-- Check if we've already compiled this string
|
|
if memoizedFuncs[funcOrStr] then
|
|
return memoizedFuncs[funcOrStr]
|
|
end
|
|
|
|
-- Try to compile the string
|
|
local func = loadstring(funcOrStr)
|
|
if func then
|
|
memoizedFuncs[funcOrStr] = func
|
|
return func
|
|
end
|
|
end
|
|
|
|
return nil
|
|
end |