mirror of
https://github.com/brues-code/pfUI.git
synced 2026-09-21 23:26:56 +00:00
00292ca3b4
commit70b1e66c2dAuthor: Brues <5278969+brues-code@users.noreply.github.com> Date: Sun Jul 26 18:30:18 2026 -0500 Use SetSize/SetShown and simplify minimap/map commit7b6bfe9975Author: Brues <5278969+brues-code@users.noreply.github.com> Date: Sat Jul 25 11:27:14 2026 -0500 Include raidpet frames in /pftest test mode The unit-frame test toggle (showall) only previews frames that exist and are positioned. Raidpet frames are created when raidpet.visible == "1", but LayoutPets' collapse mode only positions pets whose raidpet<N> unit actually exists, so solo/in test mode they stayed hidden. Add a showall branch to LayoutPets that mirror-lays every pet cell and shows it (bypassing collapse and the roster gate), still guarded by the existing showpets check so nothing happens when raidpet is disabled. Call LayoutPets from the /pftest handler so the grid updates immediately on toggle-on and restores to the normal layout on toggle-off. commit4c65c38647Author: Brues <5278969+brues-code@users.noreply.github.com> Date: Sat Jul 25 10:34:33 2026 -0500 Remove unused RangeCache local in UnitInRange commit6c96bbbe6cAuthor: Brues <5278969+brues-code@users.noreply.github.com> Date: Sat Jul 25 10:34:33 2026 -0500 Use Clamp() for the two-sided clamps in ui-widgets Replace the hand-rolled math.min/math.max and if/elseif clamp idioms in the status bar and scroll frame with Clamp(). Equivalent for every value seen (min <= max always holds), just clearer. commit830e0a0be1Author: Brues <5278969+brues-code@users.noreply.github.com> Date: Sat Jul 25 10:34:33 2026 -0500 Drop vanilla GetItemInfo shim for C_Item.GetItemInfo The compat/vanilla.lua GetItemInfo override reshaped vanilla's tuple into retail order by inserting nil at slot 4 (itemLevel), but truncated at 8 fields, dropping itemEquipLoc/itemTexture. ClassicAPI's global GetItemInfo keeps the vanilla shape (its hook only warms the item cache), so the shim stayed necessary but incomplete. Remove it and point the three callers at ClassicAPI's C_Item.GetItemInfo, which returns the full 18-field retail tuple. Positions 1-8 are identical to the old shim output, so bags/roll are unchanged; character.lua's itemSlot (equipLoc, field 9) was always nil under the truncating shim and now resolves correctly for ShaguScore. commitc0da63657dAuthor: Brues <5278969+brues-code@users.noreply.github.com> Date: Sat Jul 25 10:09:34 2026 -0500 Various cleanup Removes version compatibility checks for TBC/Vanilla, consolidating code to target a single WoW version. Refactors repetitive SetWidth/SetHeight calls to SetSize for cleaner code. Removes duplicate function definitions from unitxp module and simplifies conditional show/hide operations using SetShown. commit11a6302c2dAuthor: Brues <5278969+brues-code@users.noreply.github.com> Date: Sat Jul 25 09:53:37 2026 -0500 Delegate pfUI.api.strsplit to ClassicAPI's strsplit Replace the Lua pattern-based implementation with a thin wrapper around ClassicAPI's C-level strsplit. Keeps the pfUI.api.strsplit entry point for backwards compatibility with addons that call it, while dropping the redundant reimplementation. Behavioral note: the old version used ([^delim]+) which silently collapsed empty fields; delegating to real strsplit now preserves them ("a,,b" -> "a", "", "b"). All in-repo callers split numeric color tuples, version strings, or build name-keyed lookup tables, none of which are affected by empty-field preservation. commit67c126eac8Author: Brues <5278969+brues-code@users.noreply.github.com> Date: Sat Jul 25 01:01:02 2026 -0500 Bump ClassicAPI minimum version to 1.8.0 commita6cf157518Author: Brues <5278969+brues-code@users.noreply.github.com> Date: Sat Jul 25 00:49:50 2026 -0500 read spell rank via C_Spell.GetSpellSubtext The rank string comes from the spell subtext ("Rank N"), which ClassicAPI's C_Spell.GetSpellSubtext returns directly -- so drop nampower's GetSpellRecField(spellId, "rank") here. The existing "Rank " gsub parse is unchanged. commit4b1ba99b4fAuthor: Brues <5278969+brues-code@users.noreply.github.com> Date: Sat Jul 25 00:33:20 2026 -0500 Move player info overlay onto ClassicAPI (drop Nampower) The haste/spell-power overlay read both values through Nampower and disabled itself entirely without it. Move both to ClassicAPI so it works on any ClassicAPI client: - Spell power: GetSpellPower("net") (nampower) -> GetSpellBonusDamage(i) (ClassicAPI, same per-school field). Merged the build+scan into one loop; the default school seeds the tiebreak so equal schools still favor it. - Haste: GetUnitField("player", "modCastSpeed") -> UnitSpellHaste("player"), which returns the haste percentage directly (the (1/modCastSpeed-1)*100 conversion is now baked in, off the same UNIT_MOD_CAST_SPEED field, signed). - Dropped the "if not GetUnitField then return" gate -- UpdateInfoText no longer touches Nampower, so the overlay runs everywhere. commit7df4aa6d50Author: Brues <5278969+brues-code@users.noreply.github.com> Date: Sat Jul 25 00:32:44 2026 -0500 Hook the real global _G.UnitHealth for feign death Inside a RegisterModule body, `function UnitHealth(...)` defines UnitHealth on the pfUI environment, not the real global -- so the feign-death real-HP fix only reached callers that resolve UnitHealth through pfUI's env, and missed _G consumers (Blizzard frames, other addons). Hook _G.UnitHealth explicitly (and capture oldUnitHealth from _G) so the un-gate applies everywhere. commitb0bf2fd869Author: Brues <5278969+brues-code@users.noreply.github.com> Date: Fri Jul 24 21:10:58 2026 -0500 Refactor nampower module Simplify reactive spell storage by using spell IDs instead of texture/name tables. Update to use modern C_Spell APIs (GetSpellTexture, GetSpellName) instead of manual texture paths. Consolidate SetWidth/SetHeight calls to SetSize and use print() instead of DEFAULT_CHAT_FRAME:AddMessage(). commit0b06961333Author: Brues <5278969+brues-code@users.noreply.github.com> Date: Fri Jul 24 20:54:23 2026 -0500 Use GetNamePlateForUnit for target lookups; drop dead ScanGuid block - Replace the three GetNamePlateForGUID(UnitGUID("target")) round-trips with GetNamePlateForUnit("target"), which resolves the token to a GUID internally -- no UnitGUID string detour, and it returns nil for no-target/no-plate so the UnitExists guards collapse. GetNamePlateForGUID is left for raw-GUID cases. - Remove the dead libunitscan.ScanGuid nameplate block (ScanGuid was deleted from libunitscan long ago, so the guard was never true) -- it carried the last GetUnitField("npcFlags") read. commit3e6210086eAuthor: Brues <5278969+brues-code@users.noreply.github.com> Date: Fri Jul 24 20:54:23 2026 -0500 Drop Nampower stats system and polling from unit frames GetUnitStats now reads health/power straight from UnitHealth/UnitPower (the descriptor fields the server broadcasts) -- for a real unit token GetUnitField read nothing different, so the whole Nampower-vs-fallback apparatus was measuring a distinction that no longer exists. Removed: - The stats system: pfUI.uf.stats, pfUIStatsFrame + UpdateStatsDisplay, the lastUnitStats change-cache, the /pfuistats slash command, and every counter increment (event/heartbeat/earlyReturn/throttledSkip/nampower/fallback). - All GetUnitField health reads (GetUnitStats, heal-prediction, health-gradient color) -- collapsed to the UnitHealth/UnitHealthMax they already fell back to. - The heartbeat-polling backstop and its fallbackThrottle/lastEventUpdate deps. Frames now refresh on events only; eventless frames still use their own tick, and range/aggro still run in the eventless-actions block. commit63001b7b0cAuthor: Brues <5278969+brues-code@users.noreply.github.com> Date: Fri Jul 24 20:09:42 2026 -0500 Move nameplates onto ClassicAPI stable nameplate tokens ClassicAPI now assigns retail-exact, per-plate-stable "nameplateN" tokens and fires vanilla UNIT_* events for them, so the nameplate module can key off the token instead of Nampower/SuperWoW GUID primitives: - Events: UNIT_AURA_GUID/UNIT_FLAGS_GUID -> UNIT_AURA/UNIT_FLAGS, matched by the "nameplateN" token (guarded on the token prefix, since these also fire for target/party/raid). Registered unconditionally -- no GetUnitField gate. - Health: GetUnitField(guid, "health"/"maxHealth") -> UnitHealth/UnitHealthMax on the plate's cached token. Same UNIT_FIELD_HEALTH the server broadcasts (real HP on Turtle; the ~= 100 scaled-vs-real guard is unchanged). - Combat/target in GetCombatStateColor: GetUnitField("flags") + HasFlag -> UnitAffectingCombat(token); GetUnitField("target") + SuperWoW "<guid>target" -> the "nameplateNtarget" chain. Dropped the now-dead HasFlag, UNIT_FLAG_IN_COMBAT, and guidTargetTokenCache. - GetCastInfo now takes the token directly; every caller already holds it, so the UnitTokenFromGUID(guid) round-trip is gone (unused module-wide now). NAME_PLATE_UNIT_ADDED caches the token as plate.nameplate.unit alongside the GUID; REMOVED clears both. GUID is retained only as the stable cache key (debuffCache/threatMemory/combatColorCache). Only GetUnitField("npcFlags") remains -- it has no token/vanilla equivalent. commitce1c49fcbbAuthor: Brues <5278969+brues-code@users.noreply.github.com> Date: Fri Jul 24 18:20:06 2026 -0500 Make focus/focustarget event-driven via ClassicAPI unit events ClassicAPI now fires UNIT_* (health/mana/aura/...) with arg1 == "focus" and arg1 == "focustarget", observed per-unit like target/party/raid. Both frames already registered those events (focus/focustarget are in pfValidUnits) and their OnEvent already matches arg1 == label, so the 0.2s polling ticks were pure workarounds for the missing events. Drop both ticks; the frames now refresh on-event like target, with range/glow still on the shared 0.5s state pass and PLAYER_FOCUS_CHANGED still driving assign/clear.
1659 lines
56 KiB
Lua
1659 lines
56 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. Thin wrapper that delegates to
|
|
-- ClassicAPI's C-level strsplit, kept as a pfUI.api entry point for
|
|
-- backwards compatibility with addons that call pfUI.api.strsplit.
|
|
-- Note: unlike the old Lua implementation, empty fields are preserved
|
|
-- (e.g. "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.
|
|
function pfUI.api.strsplit(delimiter, subject)
|
|
if not subject then return nil end
|
|
return _G.strsplit(delimiter or ":", subject)
|
|
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
|
|
|
|
-- [ 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 = UnitClass(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
|
|
local a, b, c, d, e
|
|
local _, va, vb, vc, vd, ve
|
|
local ra, rb, rc, rd, re
|
|
function pfUI.api.cmatch(str, pat)
|
|
-- read capture indexes
|
|
a, b, c, d, e = GetCaptures(pat)
|
|
_, _, va, vb, vc, vd, ve = string.find(str, pfUI.api.SanitizePattern(pat))
|
|
|
|
-- put entries into the proper return values
|
|
ra = e == 1 and ve or d == 1 and vd or c == 1 and vc or b == 1 and vb or va
|
|
rb = e == 2 and ve or d == 2 and vd or c == 2 and vc or a == 2 and va or vb
|
|
rc = e == 3 and ve or d == 3 and vd or a == 3 and va or b == 3 and vb or vc
|
|
rd = e == 4 and ve or a == 4 and va or c == 4 and vc or b == 4 and vb or vd
|
|
re = a == 5 and va or d == 5 and vd or c == 5 and vc or b == 5 and vb or ve
|
|
|
|
return ra, rb, rc, rd, re
|
|
end
|
|
|
|
-- [ GetItemLinkByName ]
|
|
-- Returns an itemLink for the given itemname
|
|
-- 'name' [string] name of the item
|
|
-- returns: [string] entire itemLink for the given item
|
|
function pfUI.api.GetItemLinkByName(name)
|
|
for itemID = 1, 25818 do
|
|
local itemName = C_Item.GetItemNameByID(itemID)
|
|
if itemName and itemName == name then
|
|
local _, itemLink = C_Item.GetItemInfo(itemID)
|
|
return itemLink
|
|
end
|
|
end
|
|
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
|
|
|
|
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
|
|
local color_cache = {}
|
|
function pfUI.api.GetStringColor(colorstr)
|
|
if not color_cache[colorstr] then
|
|
local r, g, b, a = pfUI.api.strsplit(",", colorstr)
|
|
color_cache[colorstr] = { r, g, b, a }
|
|
end
|
|
return unpack(color_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 _r, _g, _b, _a
|
|
function pfUI.api.rgbhex(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 table.getn(r) >= 3 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
|
|
return CreateColor(_r, _g, _b, _a):GenerateHexColorMarkup()
|
|
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 |