8 Commits

Author SHA1 Message Date
Brues fe22dfc456 Detect nameplate mouseover by GUID compare, not Blizzard glow texture
Both mouseover checks relied on original.glow:IsShown() (a fragile proxy for
the hovered plate, on a Blizzard texture pfUI hides/restyles) gated by
UnitExists("mouseover"). Cache the engine mouseover unit's GUID once per
central-loop tick (frameState.mouseoverGuid) and have each plate compare its
cachedGuid against it.

More correct for the data path: the "mouseover" unit token is only chosen
for the plate whose GUID actually matches the engine's mouseover unit, so
overlapping plates can't double-match and paint the wrong unit's data. Also
cheaper -- one UnitGUID("mouseover") per tick plus a per-plate table compare,
versus the old per-plate IsShown gated on a per-tick UnitExists.
2026-07-29 16:57:25 -05:00
Brues 5eaab2784f Resolve target castbar plate via cached GUID, not per-frame lookup
The unthrottled target castbar frame called C_NamePlate.GetNamePlateForUnit
every frame. Stash the target's GUID on PLAYER_TARGET_CHANGED (seeded on
PLAYER_ENTERING_WORLD for a target held across reload) and resolve the plate
through plateByGuid instead -- a table lookup plus a real idle skip when
there's no target. The plate is looked up per frame (not cached) so a plate
spawning/despawning while the unit stays targeted still resolves correctly.
2026-07-29 16:36:29 -05:00
Brues f41d5ac6d0 Make nameplate castbars event-driven; unthrottle target bar
Previously every visible plate polled C_Spell.UnitCastingInfo each throttled
tick just to detect casts. Now cast state is event-driven:

- SPELL_START_OTHER (nampower) stamps a per-GUID castState cache (spellId,
  timing, channel flag from spellType); SPELL_FAILED_OTHER clears it; normal
  completion expires at endTime. GetCastInfo just reads the cache, so all
  call sites (castbar update, non-target detection, casting name-color) stop
  polling.
- plateByGuid routes events to plates in O(1) and bounds the cache to on-screen
  casters; NAME_PLATE_UNIT_ADDED seeds an already-casting unit with one poll.
- The vestigial nameplate.castUpdate flag now fires on cast start to bypass the
  throttle for an immediate bar.

With the poll gone, per-tick work is a cache read + SetValue, so the dedicated
target castbar frame now runs unthrottled (every frame) for the smoothest
sweep, and the non-target nameplates_castbar default rises 50 -> 100 FPS.

Known limits: other-unit cast pushback isn't reported by nampower, and a
mob-cancelled channel with no fail event lingers until endTime.
2026-07-29 16:29:12 -05:00
Brues af8780bb41 Use GetStringColor for newitem glow color
Replace CreateColor/Color:GetRGBA usage with pfUI.api.GetStringColor in modules/newitem.lua
2026-07-29 14:39:25 -05:00
Brues ea37f41db1 Check r[3] instead of table.getn in rgbhex
Replace table.getn(r) >= 3 with r[3] ~= nil in api/api.lua
2026-07-29 14:35:44 -05:00
Brues 6f36da7aa1 Cache and streamline color helpers in api
- GetStringColor: memoize via a metatable __index cache (single lookup on
  hits) and store numeric components so ColorMixin comparisons/arithmetic
  behave, not just setter coercion.
- GetStringColorObject: new accessor returning a cached, shared read-only
  ColorMixin for callers that want an object instead of raw values.
- rgbhex: memoize the markup keyed on the byte values that actually determine
  the output (Round(x*255)), so continuously-varying inputs like health
  gradients collapse onto a bounded set instead of leaking a cache entry per
  shade. Build misses via C_ColorUtil.GenerateTextColorCode on a plain table
  instead of allocating a throwaway ColorMixin. Fix a latent bug where the
  r/g/b/a temporaries were file-scoped, so a malformed input returned the
  previous call's color instead of an empty string.
2026-07-29 14:06:25 -05:00
Brues 149d5dd362 Scope tooltip cursor-follow to when shown; default it to smooth
The cursor-follow OnUpdate polled GetCursorPosition() 10x/second forever,
even with no tooltip visible. Rework it so the follower frame is created
once, hidden, and only shown while a tooltip is up -- an OnUpdate fires only
while its frame is shown, so the poll now runs solely during tooltip display.
Position the follower immediately on show to avoid a one-frame flash.

Also bump the tooltip_cursor throttle default from custom/10 FPS to the
fastest preset (50 FPS) so cursor tracking is smooth out of the box while
the Throttling tooltip knob stays available for low-end machines.
2026-07-29 11:42:18 -05:00
Brues 0156d9dfec Harden strsplit against third-party global clobbering
BigWigs (SpellRequests) redefines the global string:split to return a table,
and another addon clobbers the strsplit global too. pfUI.api.strsplit and the
bare strsplit callers inherited the broken versions depending on load order,
producing 'attempt to compare number with nil' from GetStringColor.

- Make pfUI.api.strsplit fully self-contained (no delegation to global
  strsplit / string.split), so no override can reach it.
- Optimize the hot path: single-char delimiters use plain-text find (no
  pattern compilation, no per-call char-class string); localize string.find
  and string.sub.
- Route the per-frame castbar/nameplate color splits through GetStringColor,
  which caches, instead of re-splitting a constant string every update.
2026-07-29 10:37:29 -05:00
6 changed files with 197 additions and 87 deletions
+47 -21
View File
@@ -92,25 +92,24 @@ 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.
-- 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 stringsplit = _G.string.split
local format, sgsub = string.format, string.gsub
function pfUI.api.strsplit(delimiter, subject)
if not subject then return nil end
delimiter = delimiter or ":"
if stringsplit then
return stringsplit(delimiter, subject)
end
local fields = {}
local pattern = string.format("([^%s]+)", delimiter)
string.gsub(subject, pattern, function(c) fields[table.getn(fields)+1] = c end)
delimiter = delimiter or ":"
local pattern = format("([^%s]+)", delimiter)
sgsub(subject, pattern, function(c) fields[table.getn(fields)+1] = c end)
return unpack(fields)
end
@@ -998,16 +997,36 @@ 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 }
-- returns r,g,b,a as numbers
local color_cache = setmetatable({}, {
__index = function(t, k)
local color = { pfUI.api.strsplit(",", k) }
for i = 1, table.getn(color) do
color[i] = tonumber(color[i])
end
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 color = CreateColor(pfUI.api.GetStringColor(k))
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
@@ -1015,12 +1034,13 @@ end
-- 'b' [number] optional b color component
-- 'a' [number] optional alpha component
-- returns color string in the form of '|caarrggbb'
local _r, _g, _b, _a
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 table.getn(r) >= 3 then
elseif r[3] ~= nil then
_r, _g, _b, _a = r[1], r[2], r[3], (r[4] or 1)
end
elseif tonumber(r) then
@@ -1028,7 +1048,13 @@ function pfUI.api.rgbhex(r, g, b, a)
end
if _r and _g and _b and _a then
return CreateColor(_r, _g, _b, _a):GenerateHexColorMarkup()
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 ""
+2 -2
View File
@@ -35,7 +35,7 @@ libthrottle.defaults = {
nameplates_target = "custom",
nameplates_castbar = "custom",
nameplates_mass = "custom",
tooltip_cursor = "custom",
tooltip_cursor = "fastest",
chat_tab = "custom",
swingtimer = "custom",
}
@@ -160,7 +160,7 @@ libthrottle:SetScript("OnEvent", function()
-- Set defaults for custom fields if missing
if not _G.pfUI_throttle.nameplates_target_custom then _G.pfUI_throttle.nameplates_target_custom = "50" end
if not _G.pfUI_throttle.nameplates_custom then _G.pfUI_throttle.nameplates_custom = "10" end
if not _G.pfUI_throttle.nameplates_castbar_custom then _G.pfUI_throttle.nameplates_castbar_custom = "50" end
if not _G.pfUI_throttle.nameplates_castbar_custom then _G.pfUI_throttle.nameplates_castbar_custom = "100" end
if not _G.pfUI_throttle.nameplates_mass_custom then _G.pfUI_throttle.nameplates_mass_custom = "7" end
if not _G.pfUI_throttle.tooltip_cursor_custom then _G.pfUI_throttle.tooltip_cursor_custom = "10" end
if not _G.pfUI_throttle.chat_tab_custom then _G.pfUI_throttle.chat_tab_custom = "10" end
+3 -5
View File
@@ -90,7 +90,7 @@ pfUI:RegisterModule("castbar", function ()
cb:SetAlpha(1)
cb.fadeout = nil
cb.bar:SetStatusBarColor(strsplit(",", C.appearance.castbar[isChannel and "channelcolor" or "castbarcolor"]))
cb.bar:SetStatusBarColor(GetStringColor(C.appearance.castbar[isChannel and "channelcolor" or "castbarcolor"]))
local rank = ""
if spellID then
@@ -103,8 +103,7 @@ pfUI:RegisterModule("castbar", function ()
if tex and cb.showicon then
local size = cb:GetHeight()
cb.icon:Show()
cb.icon:SetHeight(size)
cb.icon:SetWidth(size)
cb.icon:SetSize(size, size)
cb.icon.texture:SetTexture(tex)
cb.bar:SetPoint("TOPLEFT", cb.icon, "TOPRIGHT", cb.spacing, 0)
else
@@ -169,8 +168,7 @@ pfUI:RegisterModule("castbar", function ()
-- icon
cb.icon = CreateFrame("Frame", nil, cb)
cb.icon:SetPoint("TOPLEFT", 0, 0)
cb.icon:SetHeight(16)
cb.icon:SetWidth(16)
cb.icon:SetSize(16, 16)
cb.icon.texture = cb.icon:CreateTexture(nil, "OVERLAY")
cb.icon.texture:SetAllPoints()
+99 -31
View File
@@ -61,11 +61,20 @@ pfUI:RegisterModule("nameplates", function ()
local raidGuidCache = {} -- guid -> name (rebuilt on RAID_ROSTER_UPDATE/PARTY_MEMBERS_CHANGED)
-- Resolve a unit token to its cast/channel info via C_Spell. Returns a
-- compact struct (spellName / icon / startTime / endTime / duration /
-- isChannel) or nil when the unit isn't casting. Callers already hold the
-- nameplate token, so there's no GUID->token round-trip.
local function GetCastInfo(unit)
-- Per-GUID cast state, populated by nampower's SPELL_START_OTHER events and
-- cleared on SPELL_FAILED_OTHER / plate removal / expiry. This replaces the
-- old per-tick C_Spell poll on every visible plate: cast detection is now
-- event driven, and GetCastInfo just reads this cache.
local castState = {}
-- guid -> nameplate, maintained on NAME_PLATE_UNIT_ADDED/_REMOVED so a cast
-- event can find its plate in O(1) and only cache casts we actually show.
local plateByGuid = {}
local targetPlateGuid = nil
-- One-shot C_Spell poll. Only used to seed a plate that spawns while its
-- unit is already mid-cast (its SPELL_START_OTHER fired before the plate
-- existed). Never called per frame.
local function PollCastInfo(unit)
if not unit then return nil end
local name, _, texture, startMs, endMs, _, _, _, spellID = C_Spell.UnitCastingInfo(unit)
local isChannel
@@ -84,6 +93,20 @@ pfUI:RegisterModule("nameplates", function ()
isChannel = isChannel,
}
end
-- Read a unit's current cast from the event-driven cache (keyed by GUID).
-- Returns the cached struct while the cast is still active, else nil (and
-- prunes the expired entry). Same struct shape and callers as before, minus
-- the per-tick poll.
local function GetCastInfo(unit)
if not unit then return nil end
local guid = UnitGUID(unit)
if not guid then return nil end
local info = castState[guid]
if info and info.endTime > GetTime() then return info end
if info then castState[guid] = nil end
return nil
end
local debuffCache = {} -- guid -> { [spellID] = { start, duration } }
-- Reusable per-plate debuff display buffer (avoid GC churn from per-call table creation)
@@ -156,7 +179,7 @@ pfUI:RegisterModule("nameplates", function ()
now = 0,
hasTarget = false,
targetGuid = nil,
hasMouseover = false,
mouseoverGuid = nil,
}
-- cache default border color
@@ -453,6 +476,11 @@ nameplates:RegisterEvent("NAME_PLATE_UNIT_ADDED")
nameplates:RegisterEvent("NAME_PLATE_UNIT_REMOVED")
nameplates:RegisterEvent("UNIT_AURA")
nameplates:RegisterEvent("UNIT_FLAGS")
-- nampower cast lifecycle for other units (gated by NP_EnableSpell{Start,Go}
-- Events, enabled by libdebuff). Drives castbars event-first instead of
-- polling C_Spell on every plate each tick. Mirrors castbar.lua's target bar.
nameplates:RegisterEvent("SPELL_START_OTHER")
nameplates:RegisterEvent("SPELL_FAILED_OTHER")
nameplates:SetScript("OnEvent", function()
-- Stop event handling during logout to prevent crash 132
@@ -470,6 +498,7 @@ nameplates:RegisterEvent("UNIT_FLAGS")
CacheConfig()
this:SetGameVariables()
RebuildRaidGuidCache()
targetPlateGuid = UnitExists("target") and UnitGUID("target") or nil
end
-- Handle friendly zone nameplate disable feature
@@ -529,8 +558,16 @@ nameplates:RegisterEvent("UNIT_FLAGS")
-- token itself for token-based UnitX reads (stable per plate lifetime).
local plate = C_NamePlate.GetNamePlateForUnit(arg1)
if plate and plate.nameplate then
plate.nameplate.cachedGuid = UnitGUID(arg1)
local guid = UnitGUID(arg1)
plate.nameplate.cachedGuid = guid
plate.nameplate.unit = arg1
if guid then
plateByGuid[guid] = plate.nameplate
-- Seed: the unit may already be mid-cast (its SPELL_START_OTHER fired
-- before this plate existed). One poll here catches that; ongoing
-- casts arrive via the event.
castState[guid] = PollCastInfo(arg1)
end
nameplates.OnShow(plate)
end
visiblePlateCount = visiblePlateCount + 1
@@ -544,6 +581,8 @@ nameplates:RegisterEvent("UNIT_FLAGS")
if debuffCache[guid] then debuffCache[guid] = nil end
if threatMemory[guid] then threatMemory[guid] = nil end
if combatColorCache[guid] then combatColorCache[guid] = nil end
if castState[guid] then castState[guid] = nil end
if plateByGuid[guid] then plateByGuid[guid] = nil end
local plate = C_NamePlate.GetNamePlateForUnit(arg1)
if plate and plate.nameplate and plate.nameplate.cachedGuid == guid then
plate.nameplate.cachedGuid = nil
@@ -563,6 +602,41 @@ nameplates:RegisterEvent("UNIT_FLAGS")
end
end
elseif event == "SPELL_START_OTHER" then
-- nampower: arg2=spellId, arg3=casterGuid, arg6=castTime(ms),
-- arg7=channel duration(ms, 0 if not a channel), arg8=spellType
-- (1 = channel). Cache the cast only for a unit we have a plate for, so
-- the table stays bounded to on-screen casters.
local casterGuid = arg3
local plate = casterGuid and plateByGuid[casterGuid]
if plate then
local isChannel = arg8 == 1
local durationMs = isChannel and arg7 or arg6
if durationMs and durationMs > 0 then
local spellId = arg2
local now = GetTime()
castState[casterGuid] = {
spellName = C_Spell.GetSpellName(spellId),
spellID = spellId,
icon = C_Spell.GetSpellTexture(spellId),
startTime = now,
endTime = now + durationMs / 1000,
duration = durationMs / 1000,
isChannel = isChannel,
}
plate.castUpdate = true -- bypass the throttle so the bar shows now
end
end
elseif event == "SPELL_FAILED_OTHER" then
-- nampower: arg1=casterGuid, arg2=spellId. Clear on interrupt/failure.
local casterGuid = arg1
if casterGuid and castState[casterGuid] then
castState[casterGuid] = nil
local plate = plateByGuid[casterGuid]
if plate then plate.castUpdate = true end
end
elseif event == "UNIT_AURA" then
-- ClassicAPI: fires with arg1 == "nameplateN" when a unit's aura set
-- changes (add/remove/modify). Flag the matching plate so OnUpdate does a
@@ -578,6 +652,7 @@ nameplates:RegisterEvent("UNIT_FLAGS")
end
elseif event == "PLAYER_TARGET_CHANGED" then
targetPlateGuid = UnitExists("target") and UnitGUID("target") or nil
-- Flag the target's plate for update
local plate = C_NamePlate.GetNamePlateForUnit("target")
if plate and plate.nameplate then
@@ -607,7 +682,7 @@ nameplates:RegisterEvent("UNIT_FLAGS")
-- PERF: Cache GetTime() once per frame
frameState.now = now
frameState.hasTarget, frameState.targetGuid = UnitExists("target")
frameState.hasMouseover = UnitExists("mouseover")
frameState.mouseoverGuid = UnitGUID("mouseover")
-- propagate events to all nameplates
if this.eventcache then
@@ -824,8 +899,7 @@ nameplates:RegisterEvent("UNIT_FLAGS")
RebuildOfftanks()
nameplate:SetWidth(plate_width)
nameplate:SetHeight(plate_height)
nameplate:SetSize(plate_width, plate_height)
nameplate:SetPoint("TOP", parent, "TOP", 0, 0)
nameplate.name:SetFont(font, font_size, font_style)
@@ -854,23 +928,20 @@ nameplates:RegisterEvent("UNIT_FLAGS")
nameplate.guild:SetFont(font, font_size, font_style)
nameplate.glow:SetWidth(C.nameplates.width + 60)
nameplate.glow:SetHeight(C.nameplates.heighthealth + 30)
nameplate.glow:SetSize(C.nameplates.width + 60, C.nameplates.heighthealth + 30)
nameplate.glow:SetVertexColor(glowr, glowg, glowb, glowa)
nameplate.raidicon:ClearAllPoints()
nameplate.raidicon:SetPoint("BOTTOM", nameplate.health, "TOP", C.nameplates.raidiconoffx, C.nameplates.raidiconoffy)
nameplate.level:SetFont(font, font_size, font_style)
nameplate.raidicon:SetWidth(C.nameplates.raidiconsize)
nameplate.raidicon:SetHeight(C.nameplates.raidiconsize)
nameplate.raidicon:SetSize(C.nameplates.raidiconsize, C.nameplates.raidiconsize)
for i=1,16 do
UpdateDebuffConfig(nameplate, i)
end
for i=1,5 do
nameplate.combopoints[i]:SetWidth(combo_size)
nameplate.combopoints[i]:SetHeight(combo_size)
nameplate.combopoints[i]:SetSize(combo_size, combo_size)
nameplate.combopoints[i]:SetPoint("TOPRIGHT", nameplate.health, "BOTTOMRIGHT", -(i-1)*(combo_size+default_border*3), -default_border*3)
CreateBackdrop(nameplate.combopoints[i], default_border)
end
@@ -928,7 +999,7 @@ nameplates:RegisterEvent("UNIT_FLAGS")
end
local target = plate.istarget
local mouseover = UnitExists("mouseover") and plate.original.glow:IsShown() or nil
local mouseover = plate.cachedGuid and plate.cachedGuid == frameState.mouseoverGuid or nil
local unitstr = target and "target" or mouseover and "mouseover" or plate.cachedGuid or nil
-- resolve player vs npc from plate's own unit so libunitscan can't return
@@ -1316,7 +1387,7 @@ nameplates:RegisterEvent("UNIT_FLAGS")
local update
local original = nameplate.original
local name = original.name:GetText()
local mouseover = state and state.hasMouseover and original.glow:IsShown() or nil
local mouseover = nameplate.cachedGuid and nameplate.cachedGuid == frameState.mouseoverGuid or nil
-- trigger queued event update
if hasEventUpdate then
@@ -1570,7 +1641,7 @@ nameplates:RegisterEvent("UNIT_FLAGS")
-- Relative 0..duration range to avoid float precision loss with large
-- absolute timestamps.
nameplate.castbar:SetMinMaxValues(0, duration)
nameplate.castbar:SetStatusBarColor(strsplit(",", C.appearance.castbar[(isChannel and "channelcolor" or "castbarcolor")]))
nameplate.castbar:SetStatusBarColor(GetStringColor(C.appearance.castbar[(isChannel and "channelcolor" or "castbarcolor")]))
if castInfo.icon then
nameplate.castbar.icon.tex:SetTexture(castInfo.icon)
nameplate.castbar.icon.tex:SetTexCoord(.1,.9,.1,.9)
@@ -1590,20 +1661,17 @@ nameplates:RegisterEvent("UNIT_FLAGS")
if not nameplate.castbar.isShown then nameplate.castbar.isShown = true; nameplate.castbar:Show() end
end
-- Dedicated frame that updates ONLY the target plate castbar.
-- Uses nameplates_castbar throttle from libthrottle.
-- Dedicated frame that updates ONLY the target plate castbar. Unthrottled:
-- now that casts are event-driven, this just reads the cache + SetValue, so
-- it animates the fill every frame for the smoothest sweep on the bar the
-- player watches most. (Non-target plates stay throttled via the central
-- loop's nameplates_castbar gate.)
nameplates.castbarFrame = CreateFrame("Frame", nil, UIParent)
nameplates.castbarFrame:SetScript("OnUpdate", function()
if not cfg.showcastbar then return end
local now = GetTime()
local throttle = pfUI.throttle:Get("nameplates_castbar")
if (this.tick or 0) > now then return end
this.tick = now + throttle
local frame = C_NamePlate.GetNamePlateForUnit("target")
if not frame or not frame.nameplate then return end
nameplates.UpdateCastbar(frame.nameplate, now)
if not cfg.showcastbar or not targetPlateGuid then return end
local nameplate = plateByGuid[targetPlateGuid]
if not nameplate then return end
nameplates.UpdateCastbar(nameplate, GetTime())
end)
-- set nameplate game settings
+2 -2
View File
@@ -4,7 +4,7 @@ pfUI:RegisterModule("newitem", function ()
pfUI.newitem = {}
local color = CreateColor(strsplit(",", C.appearance.bags.newitem_color))
local r, g, b, a = pfUI.api.GetStringColor(C.appearance.bags.newitem_color)
function pfUI.newitem:UpdateSlot(bag, slot)
if bag < 0 or bag > 4 then return end
@@ -18,7 +18,7 @@ pfUI:RegisterModule("newitem", function ()
local glow = frame:CreateTexture(nil, "OVERLAY")
glow:SetTexture("Interface\\Buttons\\UI-ActionButton-Border")
glow:SetBlendMode("ADD")
glow:SetVertexColor(color:GetRGBA())
glow:SetVertexColor(r, g, b, a)
glow:SetPoint("CENTER", frame, "CENTER")
glow:Hide()
glow.RefreshSize = function(g)
+44 -26
View File
@@ -23,36 +23,54 @@ pfUI:RegisterModule("tooltip", function ()
end
if C.tooltip.position == "cursor" then
-- Cursor mode makes the tooltip follow the mouse. The client has no
-- mouse-move event, so following means polling GetCursorPosition() via an
-- invisible follower frame that the tooltip anchors to. The follower is
-- only shown while a tooltip is visible -- an OnUpdate fires only while its
-- frame is shown, so the poll stops the moment the tooltip hides instead
-- of running forever.
local follower, Reposition
if C.tooltip.cursoralign ~= "native" then
local size = tonumber(C.tooltip.cursoroffset) * 2
follower = CreateFrame("Frame", nil, UIParent)
follower:SetSize(size, size)
follower:Hide()
Reposition = function()
local scale = UIParent:GetScale()
local x, y = GetCursorPosition()
follower:SetPoint("CENTER", UIParent, "BOTTOMLEFT", x/scale, y/scale)
if C.tooltip.cursoralign == "top" then
follower:SetWidth(GameTooltip:GetWidth())
end
end
follower:SetScript("OnUpdate", function()
-- throttle - cursor following doesn't need to be every frame
if (this.tick or 0) > GetTime() then return end
this.tick = GetTime() + (pfUI.throttle and pfUI.throttle:Get("tooltip_cursor") or 0.1)
Reposition()
end)
-- stop polling as soon as the tooltip is gone
pfUI.tooltip:SetScript("OnHide", function() follower:Hide() end)
end
function _G.GameTooltip_SetDefaultAnchor(tooltip, parent)
tooltip:SetOwner(parent, "ANCHOR_CURSOR")
if C.tooltip.cursoralign ~= "native" then
-- create mouse follow frame
if not tooltip.cursor then
tooltip.cursor = CreateFrame("Frame", nil, UIParent)
local size = tonumber(C.tooltip.cursoroffset) * 2
tooltip.cursor:SetSize(size, size)
tooltip.cursor:SetScript("OnUpdate", function()
-- throttle - cursor following doesn't need to be every frame
if (this.tick or 0) > GetTime() then return end
this.tick = GetTime() + (pfUI.throttle and pfUI.throttle:Get("tooltip_cursor") or 0.1)
if not follower then return end
local scale = UIParent:GetScale()
local x, y = GetCursorPosition()
this:SetPoint("CENTER", UIParent, "BOTTOMLEFT", x/scale, y/scale)
if C.tooltip.cursoralign == "top" then
tooltip.cursor:SetWidth(tooltip:GetWidth())
end
end)
end
-- position the follower right away so the tooltip doesn't flash at a
-- stale spot before the first OnUpdate tick
follower:Show()
Reposition()
-- adjust tooltip to mouse frame
if C.tooltip.cursoralign == "top" then
tooltip:SetPoint("BOTTOMLEFT", tooltip.cursor, "TOPLEFT", 0, 0)
elseif C.tooltip.cursoralign == "left" then
tooltip:SetPoint("BOTTOMRIGHT", tooltip.cursor, "LEFT", 0, 0)
elseif C.tooltip.cursoralign == "right" then
tooltip:SetPoint("BOTTOMLEFT", tooltip.cursor, "RIGHT", 0, 0)
end
if C.tooltip.cursoralign == "top" then
tooltip:SetPoint("BOTTOMLEFT", follower, "TOPLEFT", 0, 0)
elseif C.tooltip.cursoralign == "left" then
tooltip:SetPoint("BOTTOMRIGHT", follower, "LEFT", 0, 0)
elseif C.tooltip.cursoralign == "right" then
tooltip:SetPoint("BOTTOMLEFT", follower, "RIGHT", 0, 0)
end
end
end