15 Commits

Author SHA1 Message Date
Brues 1c7c91fdf7 Fix power bar rendering black behind its backdrop
The power bar's frame level was pinned to f.power's live level
(f.power:GetFrameLevel() + 1). CreateBackdrop caches the backdrop at
f.power's level when it first builds it, but f.power's level can shift
afterward as strata changes propagate across UpdateConfig re-runs. When
it shifts below the cached backdrop, the fill drops behind the dark
backdrop and the bar renders black -- which reproduced on some clients
but not others depending on how the levels resolved.

Anchor the fill to the backdrop's own level instead
(f.power.backdrop:GetFrameLevel() + 1). The backdrop's level is fixed
once created, so the fill is always exactly one level above it
regardless of f.power's value, and the bar can never fall behind it.
2026-08-06 19:16:49 -05:00
brues-code 7f5d18bc30 Update README to simplify project description 2026-08-06 13:29:49 -05:00
Brues 7efc3334ae Add an aura caster line to tooltips
Hook GameTooltip:SetUnitAura to append the caster's name; every pfUI aura
tooltip (player buffs, buffwatch, unitframes) routes through it, so one hook
covers them all. Resolve the name from the live sourceUnit token, falling
back to the sourceGUID name cache (players only) when the token is gone.

Gated behind a new tooltip.aurasource option (off by default) with a matching
"Show Aura Caster" checkbox. The toggle reloads the UI, so the hook is
installed conditionally at load rather than re-checking the flag per tooltip.
2026-08-06 02:51:08 -05:00
Brues f322fbd6c6 CancelItemTempEnchantment can't exist in vanilla without server change 2026-08-06 02:03:21 -05:00
brues-code 41b77a5728 Add the macro icon picker and improve the equipment manager (#35)
* Add pfUI-native macro icon picker

Replace Blizzard's MacroPopupFrame (the name + icon dialog) with a
custom pfUI picker driven by IconDataProviderMixin, so the full spell +
item + loose icon set is available instead of the stock spell-only list.
It has a searchable, filterable icon grid and saves through
C_Macro.CreateMacro / C_Macro.EditMacro, which take the icon as a texture
string -- so arbitrary icons (including index-less INV_* item icons)
persist. The macro-frame API is identical on the modern (Turtle/Octo)
and stock-vanilla macro UIs, so the picker works on both with no client
detection.

The new macroicons module takes over the New / Change-Icon buttons and
retires the stock popup; the blizzard macro skin now only skins
MacroPopupFrame when that module is disabled.

* Gate the macro icon hook behind a config flag

Run the Blizzard_MacroUI hook only when C.disabled["macroicons"] is
"1".

* Add search to the icon pickers and fix set editing

Equipment manager:
- Move the OK button next to Cancel at the bottom-right.
- Add an icon search box in the freed bottom-left space.
- Shift the popup contents up and shrink the window after the title
  was removed.
- Show the current icon when you edit a set. Keep a full icon path as
  is instead of adding a second prefix, and match grid icons by name.
- Close the name/icon popup when the manager frame closes. Remove the
  duplicate OnHide hook.

Both pickers now use Blizzard global strings for the labels and the
icon filters.

* Replace the row hover poll with OnLeave handlers

The gear button sits inside the set row's rectangle, so MouseIsOver(row)
is true for both. Show the gear on row enter. Hide it from a shared
OnLeave on the row and the gear when the cursor leaves the row. This
removes the per-frame OnUpdate poll.

* Handle missing C_Macro.CreateMacro API

Add guards to detect if C_Macro.CreateMacro is unavailable. The macroicons module now returns early if the API doesn't exist, and turtle-wow.lua uses the alternative Blizzard_MacroUI fallback path in such cases.

* Remove the macro picker title and shrink the window

Delete the title font string and its SetText calls. Move every
top-anchored element up 14px and shrink the window height from 498 to
484. The OK, Cancel, and search controls are bottom-anchored, so they
keep their spacing to the grid.

* bump ClassicAPI min version to 1.9.3

* Extract a shared icon picker widget

The macro icon picker and the equipment manager's name/icon popup carried
near-identical copies of the icon grid, All/Spells/Items filter, name search,
and the IconDataProvider state machine. Pull that into a single
pfUI.api.CreateIconPicker helper; each module keeps only its own frame shell,
buttons, and save flow.

Also fix the grid tooltip: pass white to GameTooltip:SetText (it defaults to
red) and derive the icon name with the case-insensitive basename key so
mixed-case provider paths show a clean name instead of the full path.
2026-08-05 22:33:37 -05:00
Brues 06fea6c889 Cache the player guild name for nameplate coloring
Compare each plate's guild name against a cached myGuild value instead
of calling UnitIsInMyGuild for every plate. Refresh the cache on
PLAYER_GUILD_UPDATE and PLAYER_ENTERING_WORLD. This replaces a per-plate
C call with a Lua string compare.
2026-08-05 21:43:08 -05:00
Brues 940a1adae0 Restore Clique support on unit frames
Fixes #33.

Clique's pfUI plugin replaces pfUI.uf.ClickAction and calls it from an
OnClick script. The move to secure attribute clicks removed that
function, so Clique stopped working.

Add the legacy Lua click path back for Clique. When Clique is loaded,
EnableScripts sets an OnClick script that runs ClickAction. When Clique
is not loaded, the frames keep the secure attribute path.

Skip the secure attributes in Clique mode. The type1 target attribute
switches the target before Clique can cast. Right-click opens the unit
menu through ClassicAPI_ToggleUnitMenu.
2026-08-05 21:43:08 -05:00
Brues d13710c5a4 Rebuild macro icon list from the icon data provider
Hook Blizzard_MacroUI and replace UpdateMacroIconFilenames. The new
function reads each icon from IconDataProviderMixin, strips the path
prefix, and adds the name to MACRO_ICON_FILENAMES. A seen table skips
duplicate names.
2026-08-05 03:02:43 -05:00
Brues c3dd7cb601 Refactor player info: simplify haste and SP display
Minor readability and style cleanup.
2026-08-05 00:28:55 -05:00
Brues 9190b88974 Cache player color codes and use full ARGB hex
Replace the cfgColorToHex function with a memoized table. The table
caches the color code for each config string on first use.

Build the code with C_ColorUtil.GenerateTextColorCode instead of
string.format. Change the spell-school colors and the format strings
to 8-character ARGB hex with the |c prefix.
2026-08-04 23:00:51 -05:00
Brues fe0ec82ede Route config color strings through GetStringColor cache
Replace raw strsplit(",", ...) color parsing across action bars, chat,
nameplates, third-party skins, player, and roll with the cached
GetStringColor / GetStringColorObject helpers, dropping the per-build
string-table allocations they created.
2026-08-04 22:27:43 -05:00
Brues 350b1d63ae Revert breaking change for WorldMapFrame 2026-08-04 21:29:03 -05:00
Brues c7f42491ef Update README.md 2026-08-04 16:28:08 -05:00
brues-code 6b458c9e27 Modernize cast bars, unit-frame clicks, and mark tracking with ClassicAPI (#32)
* Migrate player castbar to ClassicAPI UNIT_SPELLCAST_* events

Drive the player cast path off ClassicAPI's synthesized, player-only
UNIT_SPELLCAST_* events instead of the vanilla SPELLCAST_* plus nampower
SPELL_{START,GO,DELAYED}_SELF mix. START/CHANNEL_START poll UnitCastingInfo/
UnitChannelInfo and stamp; STOP/CHANNEL_STOP/FAILED/INTERRUPTED clear;
SUCCEEDED drives tradeskill craft counting; DELAYED/CHANNEL_UPDATE re-poll.
Since UNIT_SPELLCAST_START fires for chained same-spell recasts, the nampower
SELF deps and their RunNextFrame co-hook-ordering workarounds are gone.
Remote target/focus bars keep SPELL_*_OTHER (the new events are player-only).

Fix the cast-start flash: StampBar now primes the fill on the stamp frame,
and ClearBar no longer snaps the bar to full (that snap lingered in the fade
tail and flashed when the next cast stamped).

Add a red flash on a cancelled cast: new appearance.castbar.failcolor,
flashed by ClearBar only when a cast was actually in progress.

Handle pushback Quartz-style: DELAYED/CHANNEL_UPDATE re-poll just the times
and accumulate the endMs shift into this.delay for the +/- indicator (cast
+X, channel -X), rather than a full restamp.

* Route target/focus castbars through remote UNIT_SPELLCAST_*

ClassicAPI now fires UNIT_SPELLCAST_* for remote tokens (target/focus/...)
via PollRemote, so drop the nampower SPELL_*_OTHER path and drive all three
bars off one event model. The gate is now token-based (arg1 == this.unitstr,
plus the arg1=="player" + UnitIsUnit case for target=self); the RunNextFrame
defer is gone since the remote poll fires after UnitCastingInfo is readable.
Cast detection is now nampower-free across every bar.

Use the event's rank payload (arg5): thread it through RefreshBar into
StampBar, which now only calls C_Spell.GetSpellSubtext as a fallback on the
retarget re-poll (PLAYER_TARGET/FOCUS_CHANGED) where no event is in hand.

* Drive nameplate castbars off UNIT_SPELLCAST_*

ClassicAPI now fires UNIT_SPELLCAST_* for nameplate tokens, so populate the
per-GUID castState cache from UNIT_SPELLCAST_{START,CHANNEL_START} (poll
PollCastInfo for the timing the payload omits) and clear it on
{STOP,CHANNEL_STOP} -- the remote poll fires STOP for natural end, interrupt,
and cancel alike. Replaces the nampower SPELL_{START,FAILED}_OTHER path; the
^nameplate arg1 gate ignores the target/focus/party fires of the same events,
and the NAME_PLATE_UNIT_ADDED seed still catches a plate spawning mid-cast.
Nameplate cast detection is now nampower-free.

* We no longer use these features from SuperWoW/Nampower

* Use secure unit attributes; remove mouseover scripts

Switch unitframes to attribute-driven clicks and remove mouseover tooltip handlers. Adds SetAttribute("unit") in UpdateVisibility and sets frame attributes (unit and type1='target') when creating frames. Removes OnEnter/OnLeave functions and their SetScript registrations and eliminates direct TargetUnit calls in ClickAction

* Use SetShown and simplify component default

Replace explicit Show/Hide conditionals in api/unitframes.lua with SetShown(...) for ressIcon, leaderIcon, lootIcon, pvpIcon, and restIcon to reduce branching and improve readability. Also remove the local shadowing of the parameter 'component' by using assignment (component = component or ""). No intended behavior changes.

* Drive unit-frame clicks via secure attributes

Replace the Lua OnClick dispatch (OnClick/ClickAction/RightClickAction) with
secure frame attributes: default type1=target / type2=menu, and EnableClickCast
now writes type/spell/macrotext/menu/target/focus attributes per button+modifier
instead of caching a clickactions table read at click time. Drops the now-dead
clickactions table and buttons list.

Also set a "unit" attribute on each frame (at creation and re-synced in
UpdateVisibility to the live token, so a party shown on the raid grid reports
partyN, not raidN) so the hovered unit resolves from the frame.

Match the target/focus/menu click keywords exactly rather than by prefix, so a
spell whose name starts with "focus"/"target"/"menu" (e.g. Focus Magic) casts
instead of being swallowed as the keyword action.

* Simplify marktracking colors and refresh per mark

Replace the hand-rolled ParseColor plus its default-color table with
GetStringColor -- the raidmarkercolor_* config keys already carry those same
defaults, so the fallback was dead code.

Restructure the refresh off observed mark tokens: UNIT_HEALTH/UNIT_MAXHEALTH
now arrive as arg1 == "markN", so refresh just that one row (UpdateRow) instead
of rescanning all eight on every nearby unit's health tick. A visibility flip
(into range / death / hp crossing 0) re-packs the rows, so UpdateRow hands off
to a full UpdateDisplay; RAID_TARGET_UPDATE / PLAYER_ENTERING_WORLD stay full
refreshes, with the 1s poll as the range-change backstop.

* not true anymore

* Poll marktracking fallback via C_Timer instead of OnUpdate

The 1s range-change safety net ran a per-frame OnUpdate that no-oped ~59 of
every 60 frames. Replace it with C_Timer.NewTicker(FALLBACK_INTERVAL,
UpdateDisplay) -- one wakeup per second off the shared timer driver -- and drop
the elapsed accumulator. The scanner frame is now purely event-driven.

* SuperWoW is now optional

* Update README feature list

Add 'Mouseover Unit Frames' and 'Click-casting' to the main features list. Update the SuperWoW entry to reflect that it tracks party/raid units on the minimap (replacing the prior SetMouseoverUnit note).

* Replace fixed-interval OnUpdate polls with C_Timer/RunNextFrame

Swap hand-rolled per-frame throttles for the modern timer primitives:

- turtle-wow: one-shot next-frame defer (self-hiding OnUpdate frame) -> RunNextFrame
- panel: clock, combat, and fps widgets -> NewTicker(1); guild roster -> NewTicker(60)
- minimap: coordinates text -> NewTicker(0.1)
- addonbuttons: one-shot init -> RunNextFrame; 5s button rescan -> NewTicker(5),
  guarded by IsShown() to preserve the old "paused while hidden" behavior

Ticker callbacks reference frames via upvalue since 'this' is unbound outside
OnUpdate. Genuine per-frame work (bar fills, fades, drag) and polls already
coordinated through pfUI.throttle are left as-is.

* update pfQuest link

* Use SetShown/SetSize in panel module

Replace manual Show/Hide toggles with SetShown(not ... ) and replace SetWidth/SetHeight with SetSize in modules/panel.lua. Changes simplify toggle logic (timer, WorldMap, chat hide buttons) and unify sizing calls for timer, frames, textures, and microbutton. No behavior changes intended; purely refactor for conciseness and consistency.

* Bump ClassicAPI min version to 1.9.0

* Drive reagent counter from events instead of a polling OnUpdate

The pfReagentCounter frame ran an OnUpdate that (1) progressively rescanned
all 120 action slots one-per-100ms on any slot change and (2) recounted
reagent inventory on a 1s throttle. Replace both with direct event handling:

- ACTIONBAR_SLOT_CHANGED updates just the changed slot (arg1), full-scanning
  only when arg1 is 0/nil, instead of restarting a ~12s rescan on every edit
- BAG_UPDATE_DELAYED recounts tracked reagents directly (it is already
  Blizzard's coalesced bag event, so the extra 1s throttle was redundant)
- PLAYER_ENTERING_WORLD seeds the full reagent map once

UpdateSlot now seeds a new reagent's real count via GetItemCount so a freshly
placed reagent spell shows the correct number immediately. The updatecache ->
BarsUpdate render path and the IsReagentAction/GetReagentCount accessors are
unchanged.
2026-08-04 14:27:15 -05:00
Brues f338deebe7 UNITFRAME_SECURE_TEMPLATE is always nil in vanilla 2026-08-01 13:12:14 -05:00
27 changed files with 954 additions and 878 deletions
+16 -14
View File
@@ -2,11 +2,11 @@
[![Octo WoW](https://img.shields.io/badge/Octo%20WoW-1.18.1-brightgreen.svg)](https://octowow.st/)
[![ClassicAPI](https://img.shields.io/badge/ClassicAPI-Required-purple.svg)](https://github.com/brues-code/ClassicAPI)
[![SuperWoW](https://img.shields.io/badge/SuperWoW-Required-purple.svg)](https://github.com/balakethelock/SuperWoW)
[![Nampower](https://img.shields.io/badge/Nampower-Required-purple.svg)](https://github.com/brues-code/nampower)
[![UnitXP](https://img.shields.io/badge/UnitXP__SP3-Optional-yellow.svg)](https://codeberg.org/konaka/UnitXP_SP3)
[![SuperWoW](https://img.shields.io/badge/SuperWoW-Optional-yellow.svg)](https://github.com/balakethelock/SuperWoW)
[![UnitXP](https://img.shields.io/badge/UnitXP__SP3-Optional-yellow.svg)](https://github.com/brues-code/UnitXP_SP3)
**A pfUI fork specifically optimized for ClassicAPI on [Octo WoW](https://octowow.st/) which requires Nampower and SuperWoW with optional UnitXP_SP3 DLL integration.**
**A pfUI fork specifically optimized for ClassicAPI on [Octo WoW](https://octowow.st/)**
This version includes significant performance improvements and DLL-enhanced features.
@@ -16,7 +16,7 @@ This version includes significant performance improvements and DLL-enhanced feat
4. Copy "pfUI" into Wow-Directory\Interface\AddOns
5. Restart Wow
## Optional DLL Enhancements
## DLL Enhancements
Since pfUI 6.0.0 includes integrations with client-side DLLs for enhanced functionality. These DLLs are permitted on Octo WoW:
@@ -36,23 +36,24 @@ Provides:
- Faster, safer profile sharing - Engine-side serialize/compress/base64 via C_EncodingUtil
- C_Timer - After / NewTicker replacing hand-rolled OnUpdate throttles
- Feign death, shapeshift and quest-item detection via real API calls
- Mouseover Unit Frames
- Click-casting
- Loot Roll History (/loothistory)
- New item highlighting
- Plenty other functions
### [SuperWoW](https://github.com/balakethelock/SuperWoW)
Provides:
- UnitPosition for distance calculations
- SetMouseoverUnit for improved targeting
### [Nampower](https://github.com/brues-code/nampower)
Provides:
- Spell queue indicator
- GCD indicator
- Reactive spell detection
- Enhanced cast information
### [UnitXP_SP3](https://codeberg.org/konaka/UnitXP_SP3)
### [SuperWoW](https://github.com/balakethelock/SuperWoW)
Provides:
- Tracks party/raid units on the minimap
### [UnitXP_SP3](https://github.com/brues-code/UnitXP_SP3)
Provides:
- Line of Sight detection
@@ -79,6 +80,7 @@ Use `/pfdll` in-game to check which DLLs are detected.
/swapfocus Toggle Focus and Target-Frame
/pftest Toggle pfUI Unitframe Test Mode
/abp Addon Button Panel
/loothistory Show Loot Roll History
## Languages
pfUI supports and contains language specific code for the following gameclients.
@@ -91,7 +93,7 @@ pfUI supports and contains language specific code for the following gameclients.
* Russian (ruRU)
## Recommended Addons
* [pfQuest](https://shagu.org/pfQuest) A simple database and quest helper
* [pfQuest](https://github.com/brues-code/pfQuest) A simple database and quest helper
* [SuperCleveRoidMacros](https://github.com/brues-code/SuperCleveRoidMacros) Supports modern macro formats
## Plugins
+2
View File
@@ -155,6 +155,7 @@ function pfUI:LoadConfig()
pfUI:UpdateConfig("appearance", "cd", "dynamicsize", "1")
pfUI:UpdateConfig("appearance", "castbar", "castbarcolor", ".7,.7,.9,.8")
pfUI:UpdateConfig("appearance", "castbar", "channelcolor", ".9,.9,.7,.8")
pfUI:UpdateConfig("appearance", "castbar", "failcolor", "1,.2,.2,.8")
pfUI:UpdateConfig("appearance", "castbar", "texture", "Interface\\AddOns\\pfUI\\img\\bar")
pfUI:UpdateConfig("appearance", "infight", "screen", "0")
pfUI:UpdateConfig("appearance", "infight", "aggro", "0")
@@ -784,6 +785,7 @@ function pfUI:LoadConfig()
pfUI:UpdateConfig("tooltip", nil, "extguild", "1")
pfUI:UpdateConfig("tooltip", nil, "itemid", "0")
pfUI:UpdateConfig("tooltip", nil, "movespeed", "0")
pfUI:UpdateConfig("tooltip", nil, "aurasource", "0")
pfUI:UpdateConfig("tooltip", nil, "alpha", "0.8")
pfUI:UpdateConfig("tooltip", nil, "alwaysperc", "0")
pfUI:UpdateConfig("tooltip", "compare", "basestats", "1")
+246
View File
@@ -1246,3 +1246,249 @@ function pfUI.api.SkinMoneyInputFrame(frame)
copperIcon:ClearAllPoints()
copperIcon:SetPoint("LEFT", copper_editbox, "RIGHT", 2, 0)
end
-- Shared icon picker widget. Builds a name field, a "currently selected"
-- preview, and a 10x8 icon grid backed by IconDataProviderMixin with an
-- All/Spells/Items filter and a name search. Used by the macro icon picker
-- and the equipment manager's name/icon popup. The widgets attach to
-- `parent`; the caller owns the surrounding frame, its OK/Cancel buttons,
-- and the save flow. Selection is tracked by texture PATH (not index) so it
-- survives filter changes -- a spell icon you picked still saves after you
-- switch the filter to "Items" and it drops out of the visible grid.
--
-- name global-name prefix for the scroll frame and editboxes
-- parent the popup frame to attach the widgets to
-- extraType IconDataProviderExtraType.* seed (Spellbook / Equipment)
-- nameLabelText header shown above the name field
--
-- Returns a handle:
-- .editbox name EditBox (caller wires enter/escape)
-- .search search EditBox
-- .GetIcon() current selected texture path
-- .SetIcon(path) set selection (nil -> question mark)
-- .Refresh() ensure provider, redraw grid + preview
-- .SetIconAreaShown(shown) show/hide the grid, filter, search, and labels
function pfUI.api.CreateIconPicker(name, parent, extraType, nameLabelText)
local QUESTION_MARK = "INTERFACE\\ICONS\\INV_MISC_QUESTIONMARK"
local ICON_GRID_COLS = 10
local ICON_GRID_ROWS = 8
local ICON_BTN_SIZE = 36
local ICON_BTN_PAD = 6
local provider = nil
local selectedIconPath = QUESTION_MARK
local currentFilter = "all"
local searchText = ""
local filtered = nil -- provider indices matching the search, or nil when empty
local RefreshIconGrid, RebuildFilter
local function EnsureProvider()
if not provider then
provider = CreateAndInitFromMixin(IconDataProviderMixin, extraType)
end
end
-- Normalize a texture to an uppercase basename so matching works across
-- sources: GetMacroInfo / stored set icons return "Interface\Icons\<name>"
-- while provider paths are uppercase-prefixed for base icons and
-- mixed-case for spellbook extras.
local function IconKey(path)
if type(path) ~= "string" then return path end
return string.gsub(string.upper(path), "^.*[\\/]", "")
end
parent.nameLabel = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal")
parent.nameLabel:SetPoint("TOPLEFT", parent, "TOPLEFT", 14, -20)
parent.nameLabel:SetText(nameLabelText)
parent.editbox = CreateFrame("EditBox", name.."Name", parent, "InputBoxTemplate")
parent.editbox:SetSize(280, 20)
parent.editbox:SetPoint("TOPLEFT", parent, "TOPLEFT", 14, -38)
parent.editbox:SetAutoFocus(false)
parent.editbox:SetMaxLetters(16)
CreateBackdrop(parent.editbox)
parent.selectedLabel = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal")
parent.selectedLabel:SetPoint("TOPRIGHT", parent, "TOPRIGHT", -14, -14)
parent.selectedLabel:SetText(ICON_SELECTION_TITLE_CURRENT)
parent.selectedLabel:SetTextColor(1, 0.82, 0)
parent.selectedPreview = CreateFrame("Frame", nil, parent)
parent.selectedPreview:SetSize(42, 42)
parent.selectedPreview:SetPoint("TOPRIGHT", parent, "TOPRIGHT", -14, -30)
CreateBackdrop(parent.selectedPreview)
parent.selectedPreview.tex = parent.selectedPreview:CreateTexture(nil, "ARTWORK")
parent.selectedPreview.tex:SetAllPoints(parent.selectedPreview)
parent.selectedPreview.tex:SetTexCoord(.08, .92, .08, .92)
parent.iconLabel = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal")
parent.iconLabel:SetPoint("TOPLEFT", parent, "TOPLEFT", 14, -86)
parent.iconLabel:SetText(MACRO_POPUP_CHOOSE_ICON)
-- Icon grid + scroll
local iconScroll = CreateFrame("ScrollFrame", name.."Scroll", parent, "FauxScrollFrameTemplate")
iconScroll:SetPoint("TOPLEFT", parent, "TOPLEFT", 14, -116)
iconScroll:SetWidth(ICON_GRID_COLS * (ICON_BTN_SIZE + ICON_BTN_PAD) - ICON_BTN_PAD)
iconScroll:SetHeight(ICON_GRID_ROWS * (ICON_BTN_SIZE + ICON_BTN_PAD) - ICON_BTN_PAD)
local scrollbar = _G[name.."ScrollScrollBar"]
scrollbar:ClearAllPoints()
scrollbar:SetPoint("TOPLEFT", iconScroll, "TOPRIGHT", 8, -16)
scrollbar:SetPoint("BOTTOMLEFT", iconScroll, "BOTTOMRIGHT", 8, 16)
SkinScrollbar(scrollbar)
-- Filter dropdown: All / Spells / Items
local filterDropdown = CreateFrame("Frame", name.."Filter", parent, "UIDropDownMenuTemplate")
filterDropdown:SetPoint("TOPRIGHT", parent, "TOPRIGHT", 0, -80)
local function ApplyFilter(value)
currentFilter = value
UIDropDownMenu_SetSelectedValue(filterDropdown, value)
if provider then
if value == "spells" then provider:SetIconTypes({ IconDataProviderIconType.Spell })
elseif value == "items" then provider:SetIconTypes({ IconDataProviderIconType.Item })
else provider:SetIconTypes(nil) end
RebuildFilter()
RefreshIconGrid()
end
end
UIDropDownMenu_Initialize(filterDropdown, function()
local info
info = {}; info.text = ICON_FILTER_ALL; info.value = "all"
info.func = function() ApplyFilter("all") end
info.checked = currentFilter == "all"
UIDropDownMenu_AddButton(info)
info = {}; info.text = ICON_FILTER_SPELL; info.value = "spells"
info.func = function() ApplyFilter("spells") end
info.checked = currentFilter == "spells"
UIDropDownMenu_AddButton(info)
info = {}; info.text = ICON_FILTER_ITEM; info.value = "items"
info.func = function() ApplyFilter("items") end
info.checked = currentFilter == "items"
UIDropDownMenu_AddButton(info)
end)
UIDropDownMenu_SetWidth(120, filterDropdown)
UIDropDownMenu_SetSelectedValue(filterDropdown, "all")
SkinDropDown(filterDropdown)
local iconButtons = {}
for r = 1, ICON_GRID_ROWS do
for c = 1, ICON_GRID_COLS do
local i = (r - 1) * ICON_GRID_COLS + c
local btn = CreateFrame("Button", nil, parent)
btn:SetSize(ICON_BTN_SIZE, ICON_BTN_SIZE)
btn:SetPoint("TOPLEFT", iconScroll, "TOPLEFT", (c-1) * (ICON_BTN_SIZE + ICON_BTN_PAD), -(r-1) * (ICON_BTN_SIZE + ICON_BTN_PAD))
CreateBackdrop(btn)
btn.texture = btn:CreateTexture(nil, "ARTWORK")
btn.texture:SetAllPoints(btn)
btn.texture:SetTexCoord(.08, .92, .08, .92)
btn:SetScript("OnClick", function()
if this.iconIndex and provider then
local path = provider:GetIconByIndex(this.iconIndex)
if path then selectedIconPath = path end
RefreshIconGrid()
end
end)
btn:SetScript("OnEnter", function()
if not this.iconIndex or not provider then return end
local path = provider:GetIconByIndex(this.iconIndex)
if type(path) == "string" then
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText(IconKey(path), 1, 1, 1)
GameTooltip:Show()
end
end)
btn:SetScript("OnLeave", GameTooltip_Hide)
iconButtons[i] = btn
end
end
-- Rebuild the search-filtered index list (provider indices whose icon
-- basename contains the search text); nil when the search box is empty.
function RebuildFilter()
EnsureProvider()
if searchText == "" then
filtered = nil
return
end
filtered = {}
for i = 1, provider:GetNumIcons() do
local path = provider:GetIconByIndex(i)
if type(path) == "string" and string.find(IconKey(path), searchText, 1, true) then
table.insert(filtered, i)
end
end
end
function RefreshIconGrid()
EnsureProvider()
local numIcons = filtered and table.getn(filtered) or provider:GetNumIcons()
local numRows = math.ceil(numIcons / ICON_GRID_COLS)
FauxScrollFrame_Update(iconScroll, numRows, ICON_GRID_ROWS, ICON_BTN_SIZE + ICON_BTN_PAD)
local offset = FauxScrollFrame_GetOffset(iconScroll)
for i = 1, ICON_GRID_ROWS * ICON_GRID_COLS do
local listIdx = i + offset * ICON_GRID_COLS
local btn = iconButtons[i]
if listIdx <= numIcons then
btn:Show()
local providerIdx = filtered and filtered[listIdx] or listIdx
btn.iconIndex = providerIdx
local path = provider:GetIconByIndex(providerIdx)
btn.texture:SetTexture(path)
if IconKey(path) == IconKey(selectedIconPath) then
btn.backdrop:SetBackdropBorderColor(1, 0.82, 0, 1)
else
btn.backdrop:SetBackdropBorderColor(pfUI.cache.er, pfUI.cache.eg, pfUI.cache.eb, pfUI.cache.ea)
end
else
btn:Hide()
btn.iconIndex = nil
end
end
parent.selectedPreview.tex:SetTexture(selectedIconPath)
end
iconScroll:SetScript("OnVerticalScroll", function()
FauxScrollFrame_OnVerticalScroll(ICON_BTN_SIZE + ICON_BTN_PAD, function() RefreshIconGrid() end)
end)
-- Search box (bottom-left) filters the grid by icon name.
parent.searchLabel = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal")
parent.searchLabel:SetPoint("BOTTOMLEFT", parent, "BOTTOMLEFT", 16, 18)
parent.searchLabel:SetText(SEARCH)
parent.search = CreateFrame("EditBox", name.."Search", parent, "InputBoxTemplate")
parent.search:SetSize(180, 20)
parent.search:SetPoint("LEFT", parent.searchLabel, "RIGHT", 10, 0)
parent.search:SetAutoFocus(false)
CreateBackdrop(parent.search)
parent.search:SetScript("OnEscapePressed", function() this:ClearFocus() end)
parent.search:SetScript("OnTextChanged", function()
searchText = strupper(strtrim(this:GetText()))
RebuildFilter()
scrollbar:SetValue(0)
RefreshIconGrid()
end)
-- Release the provider when the popup closes so its icon cache GCs.
parent:HookScript("OnHide", function()
if provider then provider:Release(); provider = nil end
end)
local handle = { editbox = parent.editbox, search = parent.search }
function handle.GetIcon() return selectedIconPath end
function handle.SetIcon(path) selectedIconPath = path or QUESTION_MARK end
function handle.Refresh() RefreshIconGrid() end
function handle.SetIconAreaShown(shown)
local method = shown and "Show" or "Hide"
iconScroll[method](iconScroll)
for _, b in ipairs(iconButtons) do b[method](b) end
parent.iconLabel[method](parent.iconLabel)
parent.selectedLabel[method](parent.selectedLabel)
parent.selectedPreview[method](parent.selectedPreview)
filterDropdown[method](filterDropdown)
parent.searchLabel[method](parent.searchLabel)
parent.search[method](parent.search)
end
return handle
end
+90 -160
View File
@@ -238,6 +238,7 @@ function pfUI.uf:UpdateVisibility()
end
local unitstr = string.format("%s%s", self.label or "", self.id or "")
self:SetAttribute("unit", unitstr ~= "" and unitstr or nil)
local visibility = string.format("[target=%s,exists] show; hide", unitstr)
-- Group frames are redundant when the group is already shown as a raid grid:
@@ -401,7 +402,7 @@ function pfUI.uf:UpdateConfig()
if tonumber(f.config.pheight) < 0 then f.power:Hide() end
pfUI.api.CreateBackdrop(f.power, default_border)
f.power.bar:SetFrameLevel(f.power:GetFrameLevel() + 1)
f.power.bar:SetFrameLevel(f.power.backdrop:GetFrameLevel() + 1)
f.power.bar:SetStatusBarTexture(pfUI.media[f.config.pbartexture])
f.power.bar:SetAllPoints(f.power)
@@ -1110,11 +1111,7 @@ function pfUI.uf.OnUpdate()
-- update ressurections
local ress = libpredict:UnitHasIncomingResurrection(unit)
if ress and UnitIsDeadOrGhost(unit) then
this.ressIcon:Show()
else
this.ressIcon:Hide()
end
this.ressIcon:SetShown(ress and UnitIsDeadOrGhost(unit))
end
end
end
@@ -1177,52 +1174,6 @@ function pfUI.uf.OnUpdate()
end
end
function pfUI.uf.OnEnter()
if not this.label then return end
local unitstr = this.label .. this.id
if UnitExists(unitstr) then
SetMouseoverUnit(unitstr)
end
if this.config.showtooltip == "0" then return end
GameTooltip_SetDefaultAnchor(GameTooltip, this)
GameTooltip:SetUnit(this.label .. this.id)
GameTooltip:Show()
end
function pfUI.uf.OnLeave()
SetMouseoverUnit("")
GameTooltip:FadeOut()
end
function pfUI.uf.OnClick()
if not this.label and this.unitname then
TargetByName(this.unitname, true)
else
pfUI.uf:ClickAction(arg1)
end
end
function pfUI.uf:RightClickAction(unit)
if unit == "player" then
ToggleDropDownMenu(1, nil, PlayerFrameDropDown, "cursor")
elseif unit == "target" then
ToggleDropDownMenu(1, nil, TargetFrameDropDown, "cursor")
elseif unit == "pet" then
ToggleDropDownMenu(1, nil, PetFrameDropDown, "cursor")
elseif unit == "party" or strfind(unit, "party%d") then
ToggleDropDownMenu(1, nil, getglobal("PartyMemberFrame" .. this.id .. "DropDown"), "cursor")
elseif unit == "raid" or strfind(unit, "raid%d") then
local name = this.lastUnit
local unitstr = this.label .. this.id
FriendsDropDown.displayMode = "MENU"
FriendsDropDown.initialize = function() UnitPopup_ShowMenu(_G[UIDROPDOWNMENU_OPEN_MENU], "PARTY", unitstr, name, id) end
ToggleDropDownMenu(1, nil, FriendsDropDown, "cursor")
end
end
function pfUI.uf:EnableEvents()
local f = self
@@ -1258,17 +1209,72 @@ function pfUI.uf:EnableEvents()
'MiddleButtonUp', 'Button4Up', 'Button5Up')
end
-- ============================================================
-- Clique compatibility
-- Clique's pfUI plugin replaces pfUI.uf.ClickAction and expects an
-- OnClick script to call it (with `this` = frame, `arg1` = button). The
-- secure attribute path replaced that dispatch, so when Clique is loaded
-- EnableScripts routes clicks through this legacy Lua path instead. The
-- secure path is skipped there because type1="target" switches the target
-- before Clique can cast.
-- ============================================================
function pfUI.uf.OnClick()
if not this.label and this.unitname then
TargetByName(this.unitname, true)
else
pfUI.uf:ClickAction(arg1)
end
end
function pfUI.uf:ClickAction(button)
local label = this.label or ""
local id = this.id or ""
local unitstr = label .. id
if SpellIsTargeting() and button == "RightButton" then
SpellStopTargeting()
return
end
if SpellIsTargeting() and button == "LeftButton" then
SpellTargetUnit(unitstr)
elseif CursorHasItem() then
DropItemOnUnit(unitstr)
end
-- right-click opens the standard unit menu (ClassicAPI resolves the type)
if button == "RightButton" then
ClassicAPI_ToggleUnitMenu(unitstr)
return
end
-- drop food on petframe
if label == "pet" and CursorHasItem() then
if UnitClassBase("player") == "HUNTER" then
DropItemOnUnit("pet")
return
end
end
-- default click
TargetUnit(unitstr)
end
function pfUI.uf:EnableScripts()
local f = self
f:SetScript("OnClick", pfUI.uf.OnClick)
if IsAddOnLoaded("Clique") then
f:SetScript("OnClick", pfUI.uf.OnClick)
else
f:SetAttribute("type1", "target")
f:SetAttribute("type2", "menu")
f:EnableClickCast()
end
f:SetScript("OnShow", pfUI.uf.OnShow)
f:SetScript("OnEvent", pfUI.uf.OnEvent)
f:SetScript("OnUpdate", pfUI.uf.OnUpdate)
f:SetScript("OnEnter", pfUI.uf.OnEnter)
f:SetScript("OnLeave", pfUI.uf.OnLeave)
f:EnableClickCast()
-- add frame to visibility refresh handler
visibilityscan.frames[f] = true
@@ -1296,7 +1302,7 @@ function pfUI.uf:CreateUnitFrame(unit, id, config, tick)
unit, id = "target", ""
end
local f = CreateFrame("Button", "pf" .. fname, UIParent, UNITFRAME_SECURE_TEMPLATE)
local f = CreateFrame("Button", "pf" .. fname, UIParent)
-- add unitframe functions
f.UpdateFrameSize = pfUI.uf.UpdateFrameSize
@@ -1423,6 +1429,10 @@ function pfUI.uf:CreateUnitFrame(unit, id, config, tick)
f:Hide()
end
if f.label ~= "" then
f:SetAttribute("unit", f.label .. f.id)
end
-- register frame for clique
_G.ClickCastFrames = ClickCastFrames or {}
ClickCastFrames[f] = true
@@ -1477,11 +1487,7 @@ function pfUI.uf:RefreshIndicators(unit)
local unitstr = unit.label .. unit.id
if unit.leaderIcon then -- Leader Icon
if unit.config.leadericon == "1" and UnitIsPartyLeader(unitstr) and IsInGroup() then
unit.leaderIcon:Show()
else
unit.leaderIcon:Hide()
end
unit.leaderIcon:SetShown(unit.config.leadericon == "1" and UnitIsPartyLeader(unitstr) and IsInGroup())
end
if unit.lootIcon then -- Loot Icon
@@ -1491,29 +1497,16 @@ function pfUI.uf:RefreshIndicators(unit)
-- no third return value here.. but leaving this as a hint
local method, group, raid = GetLootMethod()
local name = group and UnitName(group == 0 and "player" or "party"..group) or raid and UnitName("raid"..raid) or nil
if name and name == UnitName(unitstr) then
unit.lootIcon:Show()
else
unit.lootIcon:Hide()
end
unit.lootIcon:SetShown(name and name == UnitName(unitstr))
end
end
if unit.pvpIcon then -- PvP Icon
if unit.config.showPVP == "1" and UnitIsPVP(unitstr) then
unit.pvpIcon:Show()
else
unit.pvpIcon:Hide()
end
unit.pvpIcon:SetShown(unit.config.showPVP == "1" and UnitIsPVP(unitstr))
end
if unit.restIcon and unit:GetName() == "pfPlayer" then -- Rest Icon
if C.unitframes.player.showRest == "1" and UnitIsUnit(unitstr, "player") and IsResting() then
unit.restIcon:Show()
else
unit.restIcon:Hide()
end
unit.restIcon:SetShown(C.unitframes.player.showRest == "1" and UnitIsUnit(unitstr, "player") and IsResting())
end
if unit.happinessIcon and unit:GetName() == "pfPet" then -- Happiness Icon
@@ -1583,7 +1576,7 @@ function pfUI.uf:RefreshUnit(unit, component)
if not unit.hp then return end
if not unit.power then return end
if not unit.id then unit.id = "" end
local component = component or ""
component = component or ""
-- don't update scanner activity
if unit.label == "target" or unit.label == "targettarget" or unit.label == "targettargettarget" then
@@ -2126,14 +2119,6 @@ function pfUI.uf:RefreshUnit(unit, component)
end
end
local buttons = {
[1] = "LeftButton",
[2] = "RightButton",
[3] = "MiddleButton",
[4] = "Button4",
[5] = "Button5",
}
local modifiers = {
[""] = "",
["alt"] = "_alt",
@@ -2143,86 +2128,31 @@ local modifiers = {
function pfUI.uf:EnableClickCast()
if self.config.clickcast ~= "1" then return end
for bid, button in pairs(buttons) do
for bid = 1, 5 do -- LeftButton, RightButton, MiddleButton, Button4, Button5
for modifier, mconf in pairs(modifiers) do
local bconf = bid == 1 and "" or bid
if pfUI_config.unitframes["clickcast"..bconf..mconf] ~= "" then
-- fill clickaction table
self.clickactions = self.clickactions or {}
self.clickactions[modifier..button] = pfUI_config.unitframes["clickcast"..bconf..mconf]
local action = pfUI_config.unitframes["clickcast"..bconf..mconf]
if action and action ~= "" then
local prefix = modifier ~= "" and (modifier .. "-") or ""
local low = string.lower(action)
if low == "menu" then
self:SetAttribute(prefix .. "type" .. bid, "menu")
elseif low == "target" then
self:SetAttribute(prefix .. "type" .. bid, "target")
elseif low == "focus" then
self:SetAttribute(prefix .. "type" .. bid, "focus")
elseif string.find(action, "^/") then
self:SetAttribute(prefix .. "type" .. bid, "macro")
self:SetAttribute(prefix .. "macrotext" .. bid, action)
else
self:SetAttribute(prefix .. "type" .. bid, "spell")
self:SetAttribute(prefix .. "spell" .. bid, action)
end
end
end
end
end
function pfUI.uf:ClickAction(button)
local label = this.label or ""
local id = this.id or ""
local unitstr = label .. id
local showmenu = button == "RightButton" and true or nil
if SpellIsTargeting() and button == "RightButton" then
SpellStopTargeting()
return
end
if SpellIsTargeting() and button == "LeftButton" then
SpellTargetUnit(unitstr)
elseif CursorHasItem() then
DropItemOnUnit(unitstr)
end
-- run click casting if enabled
local modstring = ""
modstring = IsAltKeyDown() and modstring.."alt" or modstring
modstring = IsControlKeyDown() and modstring.."ctrl" or modstring
modstring = IsShiftKeyDown() and modstring.."shift" or modstring
modstring = modstring..button
if this.clickactions and this.clickactions[modstring] then
local action_lower = string.lower(this.clickactions[modstring])
if string.find(action_lower, "^menu") then
-- show menu
showmenu = true
elseif string.find(action_lower, "^target") then
-- target unit
showmenu = nil
else
-- run click cast action
local is_macro = string.find(this.clickactions[modstring], "^%/(.+)")
local tswitch = UnitIsUnit(unitstr, "target")
TargetUnit(unitstr)
if is_macro then
RunMacroText(this.clickactions[modstring])
else
CastSpellByName(this.clickactions[modstring])
end
if not tswitch then TargetLastTarget() end
return
end
end
-- dropdown menus
if showmenu then
pfUI.uf:RightClickAction(label)
return
end
-- drop food on petframe
if label == "pet" and CursorHasItem() then
local playerClass = UnitClassBase("player")
if playerClass == "HUNTER" then
DropItemOnUnit("pet")
return
end
end
-- default click
TargetUnit(unitstr)
end
function pfUI.uf:AddIcon(frame, pos, icon, timeleft, stacks, start, duration)
local showtime = frame.config.indicator_time == "1" and true or nil
local showstacks = frame.config.indicator_stacks == "1" and true or nil
-1
View File
@@ -25,7 +25,6 @@ PLAYER_BUFF_START_ID = -1
ACTIONBAR_SECURE_TEMPLATE_BAR = nil
ACTIONBAR_SECURE_TEMPLATE_BUTTON = nil
UNITFRAME_SECURE_TEMPLATE = nil
--[[ Vanilla API Extensions ]]--
+1
View File
@@ -71,6 +71,7 @@
<Include file="..\modules\energytick.lua"/>
<Include file="..\modules\totems.lua"/>
<Include file="..\modules\macrotweak.lua"/>
<Include file="..\modules\macroicons.lua"/>
<Include file="..\modules\turtle-wow.lua"/>
<Include file="..\modules\superwow.lua"/>
<Include file="..\modules\innervatecall.lua"/>
+35 -51
View File
@@ -600,17 +600,17 @@ pfUI:RegisterModule("actionbar", function ()
-- update usable [out-of-range = 1, oom = 2, not-usable = 3, default = 0]
if self.outofrange and C.bars.glowrange == "1" then
if self.vertexstate ~= 1 then
self.icon:SetVertexColor(self.rangeColor[1], self.rangeColor[2], self.rangeColor[3], self.rangeColor[4])
self.icon:SetVertexColor(self.rangeColor:GetRGBA())
self.vertexstate = 1
end
elseif oom and C.bars.showoom == "1" then
if self.vertexstate ~= 2 then
self.icon:SetVertexColor(self.oomColor[1], self.oomColor[2], self.oomColor[3], self.oomColor[4])
self.icon:SetVertexColor(self.oomColor:GetRGBA())
self.vertexstate = 2
end
elseif not usable and C.bars.showna == "1" then
if self.vertexstate ~= 3 then
self.icon:SetVertexColor(self.naColor[1], self.naColor[2], self.naColor[3], self.naColor[4])
self.icon:SetVertexColor(self.naColor:GetRGBA())
self.vertexstate = 3
end
else
@@ -992,13 +992,13 @@ pfUI:RegisterModule("actionbar", function ()
local font_offset = tonumber(C.bars.font_offset)
local macro_size = tonumber(C.bars.macro_size)
local macro_color = { strsplit(",", C.bars.macro_color) }
local macro_color = { GetStringColor(C.bars.macro_color) }
local count_size = tonumber(C.bars.count_size)
local count_color = { strsplit(",", C.bars.count_color) }
local count_color = { GetStringColor(C.bars.count_color) }
local bind_size = tonumber(C.bars.bind_size)
local bind_color = { strsplit(",", C.bars.bind_color) }
local bind_color = { GetStringColor(C.bars.bind_color) }
local cd_size = tonumber(C.bars.cd_size)
@@ -1157,17 +1157,17 @@ pfUI:RegisterModule("actionbar", function ()
end
-- range glow color
f.rangeColor = { strsplit(",", C.bars.rangecolor) }
f.rangeColor = GetStringColorObject(C.bars.rangecolor)
-- out of mana color
f.oomColor = { strsplit(",", C.bars.oomcolor) }
f.oomColor = GetStringColorObject(C.bars.oomcolor)
-- not usable color
f.naColor = { strsplit(",", C.bars.nacolor) }
f.naColor = GetStringColorObject(C.bars.nacolor)
-- equipped color
if f.equipped then
f.equipped:SetTexture(strsplit(",", C.bars.eqcolor))
f.equipped:SetTexture(GetStringColor(C.bars.eqcolor))
end
-- general appearance
@@ -1546,7 +1546,7 @@ pfUI:RegisterModule("actionbar", function ()
-- via GetActionInfo + C_Spell.GetSpellReagents. Macro actions and
-- bag-item actions are skipped (their reagent resolution would need a
-- macro-body parse / item-effect lookup that we don't bother with).
local UpdateSlot = function(slot)
local function UpdateSlot(slot)
local newID = nil
if HasAction(slot) then
local kind, spellID = GetActionInfo(slot)
@@ -1561,60 +1561,44 @@ pfUI:RegisterModule("actionbar", function ()
if reagent_slots[slot] ~= newID then
reagent_slots[slot] = newID
if newID then
reagent_counts[newID] = reagent_counts[newID] or 0
reagent_counts[newID] = reagent_counts[newID] or C_Item.GetItemCount(newID)
end
updatecache[slot] = true
end
end
-- Recount every tracked reagent and flag its buttons for a refresh.
local RecountReagents = function()
for itemID in pairs(reagent_counts) do
reagent_counts[itemID] = C_Item.GetItemCount(itemID)
end
for slot in pairs(reagent_slots) do
updatecache[slot] = true
end
end
local reagentcounter = CreateFrame("Frame", "pfReagentCounter", UIParent)
reagentcounter:RegisterEvent("PLAYER_ENTERING_WORLD")
reagentcounter:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
reagentcounter:RegisterEvent("BAG_UPDATE_DELAYED")
reagentcounter:SetScript("OnEvent", function()
if event == "BAG_UPDATE_DELAYED" then
this.event = true
if event == "ACTIONBAR_SLOT_CHANGED" then
-- arg1 is the changed slot; 0 (or nil) means "all slots"
if arg1 and arg1 > 0 then
UpdateSlot(arg1)
else
for slot = 1, 120 do UpdateSlot(slot) end
end
elseif event == "BAG_UPDATE_DELAYED" then
-- inventory changed: refresh the counts we already track
RecountReagents()
else
this.scan = 1
-- PLAYER_ENTERING_WORLD: seed the full reagent map (UpdateSlot seeds
-- each new reagent's count; a BAG_UPDATE_DELAYED follows during login)
for slot = 1, 120 do UpdateSlot(slot) end
end
end)
-- Reagent counter update with throttle for performance optimization
reagentcounter:SetScript("OnUpdate", function()
-- Throttle entire function to 10 FPS for smooth scanning
if (this.tick_update or 0) > GetTime() then return end
this.tick_update = GetTime() + 0.1
-- scan one action slot per update
if this.scan and this.scan <= 120 then
UpdateSlot(this.scan)
this.scan = this.scan + 1
end
-- trigger reagent count updates after action scans
if this.scan and this.scan >= 120 then
this.event = true
this.scan = nil
end
-- queue events to fire only once per second
if not this.event then return end
if ( this.tick or 1) > GetTime() then return else this.tick = GetTime() + 1 end
-- scan for all reagent item counts
for itemID in pairs(reagent_counts) do
reagent_counts[itemID] = C_Item.GetItemCount(itemID)
end
-- update all actionbar buttons
for slot in pairs(reagent_slots) do
updatecache[slot] = true
end
-- remove event trigger
this.event = nil
end)
function IsReagentAction(slot)
return reagent_slots[slot] and true or nil
end
+16 -13
View File
@@ -397,22 +397,25 @@ pfUI:RegisterModule("addonbuttons", function ()
pfUI.addonbuttons:ProcessButtons()
end)
pfUI.addonbuttons:SetScript("OnUpdate", function()
-- Initial setup on the next frame, once other addons' minimap buttons exist
RunNextFrame(function()
-- check if the panel should be shown by default
if not this.initialized then
if C.abuttons.showdefault == "1" and GetNumButtons() > 0 then
pfUI.addonbuttons:Show()
else
pfUI.addonbuttons:Hide()
end
-- update all buttons
pfUI.addonbuttons:ProcessButtons()
this.initialized = true
if C.abuttons.showdefault == "1" and GetNumButtons() > 0 then
pfUI.addonbuttons:Show()
else
pfUI.addonbuttons:Hide()
end
-- throttle updates to once per 5 seconds
if ( this.tick or 1) > GetTime() then return else this.tick = GetTime() + 5 end
-- update all buttons and apply workarounds
pfUI.addonbuttons:ProcessButtons()
for k, v in pairs(pfUI.addonbuttons.overrides) do
_G[k] = v
end
end)
-- Rescan minimap buttons every 5 seconds while the panel is shown
C_Timer.NewTicker(5, function()
if not pfUI.addonbuttons:IsShown() then return end
-- reload/rescan minimap buttons
pfUI.addonbuttons:ProcessButtons()
+2 -8
View File
@@ -138,16 +138,10 @@ pfUI:RegisterModule("buff", function ()
end
end)
buff:SetScript("OnLeave", function()
GameTooltip:Hide()
end)
buff:SetScript("OnLeave", GameTooltip_Hide)
buff:SetScript("OnClick", function()
if CancelItemTempEnchantment and this.mode and this.mode == "MAINHAND" then
CancelItemTempEnchantment(1)
elseif CancelItemTempEnchantment and this.mode and this.mode == "OFFHAND" then
CancelItemTempEnchantment(2)
elseif this.spellId then
if this.spellId then
C_Spell.CancelSpellByID(this.spellId)
end
end)
+112 -137
View File
@@ -16,9 +16,13 @@ pfUI:RegisterModule("castbar", function ()
end
end
-- Clear cast state on the bar. Shows the bar full for one frame, then
-- OnUpdate fades it out.
local function ClearBar(cb)
-- Clear cast state on the bar and start the fade-out. On a normal end the bar
-- is left at its current fill (a completed cast is already ~full; nothing
-- snaps to full, which used to flash for a frame when the next cast stamped
-- the bar). When `failed` is set for a cast that was actually in progress, the
-- bar flashes full red before fading — the cancelled-cast indicator.
local function ClearBar(cb, failed)
local wasActive = cb.endTime ~= nil
cb.startTime, cb.endTime, cb.isChannel = nil, nil, nil
cb.activeName, cb.spellID = nil, nil
cb.isTradeskill = nil
@@ -26,8 +30,11 @@ pfUI:RegisterModule("castbar", function ()
cb.tradeskillSingleMs, cb.currentCraftStart = nil, nil
cb.lastMax = nil
cb.delay = 0
cb.bar:SetMinMaxValues(1, 100)
cb.bar:SetValue(100)
if failed and wasActive then
cb.bar:SetStatusBarColor(GetStringColor(C.appearance.castbar.failcolor))
cb.bar:SetMinMaxValues(0, 1)
cb.bar:SetValue(1)
end
if cb.bar.spark then cb.bar.spark:Hide() end
cb.fadeout = 1
end
@@ -65,8 +72,8 @@ pfUI:RegisterModule("castbar", function ()
end
-- Mark the start of craft 2..N within an active merge (called from the
-- SPELLCAST_START / SPELL_START_SELF handlers). Resets the per-craft
-- spark to the left edge of the bar.
-- UNIT_SPELLCAST_START handler). Resets the per-craft spark to the left
-- edge of the bar.
local function StartTradeskillCraft(cb)
cb.currentCraftStart = GetTime() * 1000
local remaining = cb.tradeskillTotal - (cb.tradeskillCompleted or 0)
@@ -79,7 +86,7 @@ pfUI:RegisterModule("castbar", function ()
-- Stamp the bar with cast data and render text/icon/lag once. OnUpdate
-- then animates the fill from this state without touching C_Spell.
local function StampBar(cb, name, tex, startMs, endMs, spellID, isChannel, delayMs, isTradeskill)
local function StampBar(cb, name, tex, startMs, endMs, spellID, isChannel, delayMs, isTradeskill, rank)
cb.startTime = startMs
cb.endTime = endMs
cb.isChannel = isChannel
@@ -92,10 +99,13 @@ pfUI:RegisterModule("castbar", function ()
cb.bar:SetStatusBarColor(GetStringColor(C.appearance.castbar[isChannel and "channelcolor" or "castbarcolor"]))
local rank = ""
if spellID then
-- Rank: prefer the value the UNIT_SPELLCAST_* event delivered (arg5, passed
-- through by RefreshBar). Only the retarget re-poll has no event in hand, so
-- it falls back to a lookup.
if not rank and spellID then
rank = C_Spell.GetSpellSubtext(spellID) or ""
end
rank = rank or ""
local spellname = (cb.showname and name) and (name .. " ") or ""
local rankstr = (cb.showrank and rank ~= "") and string.format("|cffaaffcc[%s]|r", rank) or ""
cb.bar.left:SetText(spellname .. rankstr)
@@ -122,11 +132,21 @@ pfUI:RegisterModule("castbar", function ()
cb.bar:SetMinMaxValues(0, duration)
cb.lastMax = duration
-- Prime the fill on this frame. StampBar otherwise leaves the previous
-- value in place (ClearBar, run on the prior cast's STOP, leaves it full),
-- so the bar would flash full for the frame between here and the next
-- OnUpdate tick. Reset the throttle too so the timer text updates promptly.
local nowSec = GetTime()
local cur = isChannel and (endMs / 1000 - nowSec) or (nowSec - startMs / 1000)
if cur < 0 then cur = 0 elseif cur > duration then cur = duration end
cb.bar:SetValue(cur)
cb.tick = 0
end
-- One-shot poll: read C_Spell for the bar's unit, stamp or clear. Called
-- from event handlers (cast start, target/focus change), never per-frame.
local function RefreshBar(cb)
local function RefreshBar(cb, rank)
local query = cb.unitstr ~= "" and cb.unitstr or cb.unitname
if not query or (cb.unitstr ~= "" and not UnitExists(cb.unitstr)) then
ClearBar(cb)
@@ -149,7 +169,7 @@ pfUI:RegisterModule("castbar", function ()
end
end
if name and startMs and endMs then
StampBar(cb, name, tex, startMs, endMs, spellID, isChan, delayMs, isTradeskill)
StampBar(cb, name, tex, startMs, endMs, spellID, isChan, delayMs, isTradeskill, rank)
else
ClearBar(cb)
end
@@ -294,35 +314,27 @@ pfUI:RegisterModule("castbar", function ()
end
end)
-- Cast lifecycle events. Player bars react to vanilla SPELLCAST_*; non-
-- player bars also react to Nampower SPELL_*_OTHER (gated by the
-- NP_EnableSpell{Start,Go}Events CVars, enabled by libdebuff) plus the
-- retarget event. Player events also feed non-player bars for the
-- target=self case.
cb:RegisterEvent("SPELLCAST_START")
cb:RegisterEvent("SPELLCAST_STOP")
cb:RegisterEvent("SPELLCAST_FAILED")
cb:RegisterEvent("SPELLCAST_INTERRUPTED")
cb:RegisterEvent("SPELLCAST_CHANNEL_START")
cb:RegisterEvent("SPELLCAST_CHANNEL_STOP")
cb:RegisterEvent("SPELLCAST_CHANNEL_UPDATE")
cb:RegisterEvent("SPELL_DELAYED_SELF")
-- Chained same-spell recasts never run the client cast path (the 1.12
-- engine short-circuits at spellID == current-cast), so vanilla
-- SPELLCAST_START never fires for them. nampower's SPELL_START_SELF
-- (server-driven) is the only signal that shows them.
cb:RegisterEvent("SPELL_START_SELF")
if unitstr == "player" then
cb:RegisterEvent("SPELL_GO_SELF")
end
if unitstr ~= "player" and unitstr ~= "" then
cb:RegisterEvent("SPELL_START_OTHER")
cb:RegisterEvent("SPELL_FAILED_OTHER")
if unitstr == "target" then
cb:RegisterEvent("PLAYER_TARGET_CHANGED")
elseif unitstr == "focus" then
cb:RegisterEvent("PLAYER_FOCUS_CHANGED")
end
-- Cast lifecycle, entirely on ClassicAPI's UNIT_SPELLCAST_* events. They
-- fire per unit token: arg1=="player" for the player's own casts, and the
-- remote token(s) ("target", "focus", ...) for other units -- so one set of
-- events drives every bar with no Nampower dependency. The handler routes an
-- event to this bar when arg1 matches its unit, or -- since the player's own
-- casts only ever fire arg1=="player" -- when the bar's unit resolves to the
-- player (target=self). PLAYER_TARGET/FOCUS_CHANGED re-polls so a unit
-- already mid-cast when it becomes the target/focus still shows.
cb:RegisterEvent("UNIT_SPELLCAST_START")
cb:RegisterEvent("UNIT_SPELLCAST_STOP")
cb:RegisterEvent("UNIT_SPELLCAST_FAILED")
cb:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED")
cb:RegisterEvent("UNIT_SPELLCAST_DELAYED")
cb:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
cb:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START")
cb:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP")
cb:RegisterEvent("UNIT_SPELLCAST_CHANNEL_UPDATE")
if unitstr == "target" then
cb:RegisterEvent("PLAYER_TARGET_CHANGED")
elseif unitstr == "focus" then
cb:RegisterEvent("PLAYER_FOCUS_CHANGED")
end
cb:SetScript("OnEvent", function()
@@ -333,83 +345,33 @@ pfUI:RegisterModule("castbar", function ()
return
end
if event == "SPELL_START_OTHER" then
-- arg3=casterGuid. Defer one frame so ClassicAPI's UnitChannelInfo
-- can see the engine's +0x228 broadcast for remote-unit channels
-- (the cohook+packet handler runs in the same frame; the broadcast
-- propagates after).
if arg3 == UnitGUID(unit) then
local target = this
RunNextFrame(function() RefreshBar(target) end)
end
-- UNIT_SPELLCAST_* fire per unit token (arg1). Handle an event when it's
-- for this bar's unit, or -- since the player's own casts only ever fire
-- arg1=="player" -- when this bar's unit currently resolves to the player
-- (target=self / focus=self).
-- Args: arg1=unit, arg2=castGUID, arg3=spellID, arg4=name, arg5=rank.
if arg1 ~= unit and not (arg1 == "player" and UnitIsUnit(unit, "player")) then
return
end
if event == "SPELL_FAILED_OTHER" then
if arg1 == UnitGUID(unit) then ClearBar(this) end
return
end
-- Vanilla SPELLCAST_* + SPELL_DELAYED_SELF fire only for the local
-- player. Non-player bars handle them only when their unit currently
-- resolves to the player (target=self / focus=self).
if not UnitIsUnit(unit, 'player') then return end
if event == "SPELLCAST_START" or event == "SPELLCAST_CHANNEL_START" then
if event == "UNIT_SPELLCAST_START" or event == "UNIT_SPELLCAST_CHANNEL_START" then
-- START also fires per craft in a same-spell chain, so an active merge
-- just resyncs the current craft's spark/label instead of restamping.
if this.tradeskillTotal then
-- Mid-chain craft N+1 of N. Keep the merged bar; resync the spark
-- to the new craft's start and refresh the "(N)" count label.
StartTradeskillCraft(this)
else
RefreshBar(this)
RefreshBar(this, arg5)
if this.isTradeskill and (this.pendingTradeskillCount or 0) > 1
and C.castbar.player.mergetradeskill == "1" then
EnterTradeskillMerge(this, this.startTime, this.endTime, this.pendingTradeskillCount)
end
this.pendingTradeskillCount = nil
end
elseif event == "SPELL_START_SELF" then
-- Catches chained same-spell recasts (no SPELLCAST_START fires) —
-- including tradeskill chaining where craft 2..N reuse one spell.
-- Defer one frame so ClassicAPI's SMSG_SPELL_START co-hook has
-- stamped g_cast before we poll, regardless of co-hook order.
if this.tradeskillTotal then
StartTradeskillCraft(this)
else
local target = this
RunNextFrame(function()
-- Re-check: SPELL_START_SELF (nampower co-hook) fires before vanilla
-- SPELLCAST_START on the same packet, so SPELLCAST_START may have
-- entered merge in this same frame. Don't restamp over it.
if target.tradeskillTotal then
StartTradeskillCraft(target)
return
end
RefreshBar(target)
if target.unitstr == "player" and target.isTradeskill
and (target.pendingTradeskillCount or 0) > 1
and C.castbar.player.mergetradeskill == "1" then
EnterTradeskillMerge(target, target.startTime, target.endTime, target.pendingTradeskillCount)
end
target.pendingTradeskillCount = nil
end)
end
elseif event == "SPELLCAST_CHANNEL_STOP" then
-- A channel's stop can arrive after a following cast already claimed
-- the bar (channel->cast transition); only clear if a channel is
-- actually being shown, so it doesn't wipe an active cast bar.
if this.isChannel then ClearBar(this) end
elseif event == "SPELLCAST_STOP" then
-- During a tradeskill chain, SPELL_GO_SELF already counted this craft
-- and either cleared the bar (chain done) or kept it running. Only a
-- non-merge cast clears here.
if not this.tradeskillTotal then ClearBar(this) end
elseif event == "SPELLCAST_FAILED" or event == "SPELLCAST_INTERRUPTED" then
ClearBar(this)
elseif event == "SPELL_GO_SELF" then
-- arg2 = spellId. In a tradeskill merge, count each successful craft
-- and clear when the chain is done.
if this.tradeskillTotal and arg2 == this.tradeskillSpellID then
elseif event == "UNIT_SPELLCAST_SUCCEEDED" then
-- Per-craft completion during a tradeskill merge (arg3 = spellID):
-- count and clear when the chain is done. A no-op for normal casts,
-- which are cleared by UNIT_SPELLCAST_STOP.
if this.tradeskillTotal and arg3 == this.tradeskillSpellID then
this.tradeskillCompleted = (this.tradeskillCompleted or 0) + 1
if this.tradeskillCompleted >= this.tradeskillTotal then
ClearBar(this)
@@ -417,33 +379,46 @@ pfUI:RegisterModule("castbar", function ()
UpdateTradeskillLabel(this)
end
end
elseif event == "SPELL_DELAYED_SELF" then
-- Cast pushback. nampower's event carries the delay (arg2); apply it
-- locally rather than re-polling, so the bar doesn't depend on
-- ClassicAPI's SMSG_SPELL_DELAYED co-hook having bumped g_cast before
-- this fires (co-hook order vs nampower is not guaranteed).
if not this.endTime or not arg2 then return end
local delayMs = tonumber(arg2) or 0
if delayMs > 0 then
this.delay = (this.delay or 0) + delayMs / 1000
this.endTime = this.endTime + delayMs
local newDuration = (this.endTime - this.startTime) / 1000
this.bar:SetMinMaxValues(0, newDuration)
this.lastMax = newDuration
end
elseif event == "SPELLCAST_CHANNEL_UPDATE" then
-- Channel pushback. ClassicAPI doesn't track channel delay in
-- g_channel, so we adjust endTime + delay locally and resize the
-- bar so OnUpdate animates against the new total.
if not this.endTime or not arg1 then return end
local newEndMs = GetTime() * 1000 + arg1
local diff = this.endTime - newEndMs
if diff > 50 then
this.delay = (this.delay or 0) + diff / 1000
this.endTime = newEndMs
local newDuration = (this.endTime - this.startTime) / 1000
this.bar:SetMinMaxValues(0, newDuration)
this.lastMax = newDuration
elseif event == "UNIT_SPELLCAST_CHANNEL_STOP" then
-- A channel's stop can arrive after a following cast already claimed
-- the bar (channel->cast transition); only clear if a channel is
-- actually being shown, so it doesn't wipe an active cast bar.
if this.isChannel then ClearBar(this) end
elseif event == "UNIT_SPELLCAST_STOP" then
-- STOP fires between crafts in a merge too (each craft is a new cast);
-- UNIT_SPELLCAST_SUCCEEDED owns the count, so only a non-merge cast
-- clears here.
if not this.tradeskillTotal then ClearBar(this) end
elseif event == "UNIT_SPELLCAST_FAILED" or event == "UNIT_SPELLCAST_INTERRUPTED" then
ClearBar(this, true)
elseif event == "UNIT_SPELLCAST_DELAYED" or event == "UNIT_SPELLCAST_CHANNEL_UPDATE" then
-- Pushback: a cast delayed later or a channel shortened. Following
-- Quartz, re-poll just the times and accumulate the shift into a running
-- this.delay for the +/- indicator, rather than a full restamp (which
-- would reset that total and re-render icon/text). ClassicAPI moves
-- endMs (SpellDelayed_h bumps g_cast.endMs; the MSG_CHANNEL_UPDATE
-- co-hook rewrites g_channel.endMs) while startMs stays put, so we diff
-- endMs. delay is a positive magnitude; OnUpdate signs it "+" for casts
-- and "-" for channels. Skipped during a tradeskill merge.
if this.endTime and not this.tradeskillTotal then
local query = this.unitstr ~= "" and this.unitstr or this.unitname
local startMs, endMs
if this.isChannel then
local _, _, _, s, e = C_Spell.UnitChannelInfo(query)
startMs, endMs = s, e
else
local _, _, _, s, e = C_Spell.UnitCastingInfo(query)
startMs, endMs = s, e
end
if startMs and endMs then
local shift = this.isChannel and (this.endTime - endMs) or (endMs - this.endTime)
this.delay = (this.delay or 0) + shift / 1000
this.startTime = startMs
this.endTime = endMs
local newDuration = (endMs - startMs) / 1000
this.bar:SetMinMaxValues(0, newDuration)
this.lastMax = newDuration
end
end
end
end)
@@ -489,10 +464,10 @@ pfUI:RegisterModule("castbar", function ()
UpdateMovable(pfUI.castbar.player)
-- Tradeskill merge: hook DoTradeSkill so the player castbar knows the
-- requested count before the first SPELLCAST_START fires. Always-on hook
-- (the config knob is read at event time so toggling takes effect on the
-- next craft without a /reload). DoTradeSkill is synchronous; the server
-- roundtrip to SPELLCAST_START gives us plenty of time after this hook.
-- requested count before the first UNIT_SPELLCAST_START fires. Always-on
-- hook (the config knob is read at event time so toggling takes effect on
-- the next craft without a /reload). DoTradeSkill is synchronous; the
-- server roundtrip to UNIT_SPELLCAST_START gives us plenty of time.
hooksecurefunc("DoTradeSkill", function(index, num)
if pfUI.castbar.player then
pfUI.castbar.player.pendingTradeskillCount = tonumber(num) or 1
+7 -7
View File
@@ -82,10 +82,10 @@ pfUI:RegisterModule("chat", function ()
end
if C.chat.global.custombg == "1" then
local r, g, b, a = strsplit(",", C.chat.global.background)
local r, g, b, a = GetStringColor(C.chat.global.background)
pfUI.chat.left.backdrop:SetBackdropColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
local r, g, b, a = strsplit(",", C.chat.global.border)
local r, g, b, a = GetStringColor(C.chat.global.border)
pfUI.chat.left.backdrop:SetBackdropBorderColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
end
@@ -257,10 +257,10 @@ pfUI:RegisterModule("chat", function ()
end
if C.chat.global.custombg == "1" then
local r, g, b, a = strsplit(",", C.chat.global.background)
local r, g, b, a = GetStringColor(C.chat.global.background)
pfUI.chat.right.backdrop:SetBackdropColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
local r, g, b, a = strsplit(",", C.chat.global.border)
local r, g, b, a = GetStringColor(C.chat.global.border)
pfUI.chat.right.backdrop:SetBackdropBorderColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
end
@@ -675,7 +675,7 @@ pfUI:RegisterModule("chat", function ()
end
-- read and parse whisper color settings
local cr, cg, cb, ca = strsplit(",", C.chat.global.whisper)
local cr, cg, cb, ca = GetStringColor(C.chat.global.whisper)
cr, cg, cb = tonumber(cr), tonumber(cg), tonumber(cb)
local wcol = rgbhex(cr, cg, cb)
@@ -706,10 +706,10 @@ pfUI:RegisterModule("chat", function ()
_G.CHAT_WHISPER_INFORM_GET = '[W]' .. default
end
local r,g,b,a = strsplit(",", C.chat.text.timecolor)
local r,g,b,a = GetStringColor(C.chat.text.timecolor)
local timecolorhex = rgbhex(r,g,b,a)
local r,g,b = strsplit(",", C.chat.text.unknowncolor)
local r,g,b = GetStringColor(C.chat.text.unknowncolor)
local unknowncolorhex = rgbhex(r,g,b)
-- Suppress FriendsFrame's WHO_LIST_UPDATE handling for our DLL-issued
+5 -5
View File
@@ -1,10 +1,10 @@
pfUI:RegisterModule("cooldown", function ()
-- cache values
local lowcolor = {strsplit(",", C.appearance.cd.lowcolor)}
local normalcolor = {strsplit(",", C.appearance.cd.normalcolor)}
local minutecolor = {strsplit(",", C.appearance.cd.minutecolor)}
local hourcolor = {strsplit(",", C.appearance.cd.hourcolor)}
local daycolor = {strsplit(",", C.appearance.cd.daycolor)}
-- local lowcolor = {strsplit(",", C.appearance.cd.lowcolor)}
-- local normalcolor = {strsplit(",", C.appearance.cd.normalcolor)}
-- local minutecolor = {strsplit(",", C.appearance.cd.minutecolor)}
-- local hourcolor = {strsplit(",", C.appearance.cd.hourcolor)}
-- local daycolor = {strsplit(",", C.appearance.cd.daycolor)}
local parent, parent_name
local function pfCooldownOnUpdate()
+70 -268
View File
@@ -1,11 +1,10 @@
-- Equipment Manager module
-- Backport of the 4.3.4 GearManagerDialog UI on top of ClassicAPI's
-- C_EquipmentSet.* API. Adds a 6th tab to CharacterFrame.
-- Backport of the 4.3.4 GearManagerDialog UI on top of ClassicAPI's C_EquipmentSet.* API.
pfUI:RegisterModule("equipmentmanager", function()
if not C_EquipmentSet or not C_EquipmentSet.CanUseEquipmentSets() then return end
pfUI.equipmentmanager = pfUI.equipmentmanager or {}
pfUI.equipmentmanager = {}
local SET_ROW_HEIGHT = 36
@@ -55,7 +54,7 @@ pfUI:RegisterModule("equipmentmanager", function()
local function EquipSet(setID)
if not setID then return end
if C_EquipmentSet.EquipmentSetContainsLockedItems(setID) then
UIErrorsFrame:AddMessage(ERR_CLIENT_LOCKED_OUT or "Locked items in set", 1, .1, .1, 1)
UIErrorsFrame:AddMessage(ERR_CLIENT_LOCKED_OUT, 1, .1, .1, 1)
return
end
ClearCursor()
@@ -68,8 +67,7 @@ pfUI:RegisterModule("equipmentmanager", function()
-- ============================================================
local frame = CreateFrame("Frame", "pfEquipmentManagerFrame", CharacterFrame)
frame:SetWidth(220)
frame:SetHeight(350)
frame:SetSize(220, 350)
frame:SetFrameStrata("HIGH")
frame:SetScript("OnShow", function()
this:ClearAllPoints()
@@ -86,7 +84,7 @@ pfUI:RegisterModule("equipmentmanager", function()
frame.title = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
frame.title:SetPoint("TOP", frame, "TOP", 0, -10)
frame.title:SetText(T["Equipment Manager"] or "Equipment Manager")
frame.title:SetText(EQUIPMENT_MANAGER)
local closeBtn = CreateFrame("Button", nil, frame, "UIPanelCloseButton")
closeBtn:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -4, -4)
@@ -98,15 +96,14 @@ pfUI:RegisterModule("equipmentmanager", function()
-- ============================================================
local toggleBtn = CreateFrame("Button", "pfEqMgrToggleButton", PaperDollFrame)
toggleBtn:SetWidth(28)
toggleBtn:SetHeight(28)
toggleBtn:SetSize(28, 28)
toggleBtn:SetPoint("BOTTOM", CharacterHandsSlot, "TOP", 0, 4)
toggleBtn:SetNormalTexture(pfUI.path.."\\img\\UI-GearManager-Button")
toggleBtn:SetPushedTexture(pfUI.path.."\\img\\UI-GearManager-Button-Pushed")
toggleBtn:SetHighlightTexture("Interface\\Buttons\\ButtonHilight-Square", "ADD")
toggleBtn:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText(T["Equipment Manager"] or "Equipment Manager")
GameTooltip:SetText(PAPERDOLL_EQUIPMENTMANAGER)
GameTooltip:Show()
end)
toggleBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
@@ -126,13 +123,13 @@ pfUI:RegisterModule("equipmentmanager", function()
-- ============================================================
local btnEquip = CreateFrame("Button", "pfEqMgrEquip", frame, "UIPanelButtonTemplate")
btnEquip:SetWidth(86); btnEquip:SetHeight(22); btnEquip:SetText(T["Equip"] or "Equip")
btnEquip:SetSize(86, 22); btnEquip:SetText(EQUIPSET_EQUIP)
btnEquip:SetPoint("TOPLEFT", frame, "TOPLEFT", 16, -30)
SkinButton(btnEquip)
btnEquip:SetScript("OnClick", function() EquipSet(selectedSetID) end)
local btnSave = CreateFrame("Button", "pfEqMgrSave", frame, "UIPanelButtonTemplate")
btnSave:SetWidth(86); btnSave:SetHeight(22); btnSave:SetText(T["Save"] or "Save")
btnSave:SetSize(86, 22); btnSave:SetText(SAVE)
btnSave:SetPoint("LEFT", btnEquip, "RIGHT", 6, 0)
SkinButton(btnSave)
btnSave:SetScript("OnClick", function()
@@ -141,7 +138,7 @@ pfUI:RegisterModule("equipmentmanager", function()
if not name then return end
local targetID = selectedSetID
StaticPopupDialogs["PFUI_EQMGR_SAVE_CONFIRM"] = {
text = string.format(T["Would you like to save the equipment set '%s'?"] or "Would you like to save the equipment set '%s'?", name),
text = string.format(CONFIRM_SAVE_EQUIPMENT_SET, name),
button1 = YES, button2 = NO,
OnAccept = function()
C_EquipmentSet.ClearIgnoredSlotsForSave()
@@ -166,7 +163,7 @@ pfUI:RegisterModule("equipmentmanager", function()
local rowMenu = CreateFrame("Frame", "pfEqMgrRowMenu", UIParent)
rowMenu:SetFrameStrata("DIALOG")
rowMenu:SetWidth(140); rowMenu:SetHeight(50)
rowMenu:SetSize(140, 50);
rowMenu:Hide()
CreateBackdrop(rowMenu, nil, nil, .95)
CreateBackdropShadow(rowMenu)
@@ -185,9 +182,9 @@ pfUI:RegisterModule("equipmentmanager", function()
tinsert(UISpecialFrames, "pfEqMgrRowMenu") -- Escape closes it
rowMenu.changeBtn = CreateFrame("Button", nil, rowMenu, "UIPanelButtonTemplate")
rowMenu.changeBtn:SetWidth(130); rowMenu.changeBtn:SetHeight(20)
rowMenu.changeBtn:SetSize(130, 20);
rowMenu.changeBtn:SetPoint("TOPLEFT", rowMenu, "TOPLEFT", 5, -3)
rowMenu.changeBtn:SetText(T["Change Name/Icon"] or "Change Name/Icon")
rowMenu.changeBtn:SetText(EQUIPMENT_SET_EDIT)
SkinButton(rowMenu.changeBtn)
rowMenu.changeBtn:SetScript("OnClick", function()
rowMenu:Hide()
@@ -198,9 +195,9 @@ pfUI:RegisterModule("equipmentmanager", function()
end)
rowMenu.deleteBtn = CreateFrame("Button", nil, rowMenu, "UIPanelButtonTemplate")
rowMenu.deleteBtn:SetWidth(130); rowMenu.deleteBtn:SetHeight(20)
rowMenu.deleteBtn:SetSize(130, 20);
rowMenu.deleteBtn:SetPoint("TOP", rowMenu.changeBtn, "BOTTOM", 0, -2)
rowMenu.deleteBtn:SetText(T["Delete"] or "Delete")
rowMenu.deleteBtn:SetText(DELETE)
SkinButton(rowMenu.deleteBtn)
rowMenu.deleteBtn:SetScript("OnClick", function()
rowMenu:Hide()
@@ -208,7 +205,7 @@ pfUI:RegisterModule("equipmentmanager", function()
local targetID = rowMenu.targetSetID
local name = C_EquipmentSet.GetEquipmentSetInfo(targetID)
StaticPopupDialogs["PFUI_EQMGR_DELETE"] = {
text = string.format(T["Delete equipment set '%s'?"] or "Delete equipment set '%s'?", name or "?"),
text = string.format(CONFIRM_DELETE_EQUIPMENT_SET, name or "?"),
button1 = YES, button2 = NO,
OnAccept = function()
pendingIgnoredToggles[targetID] = nil
@@ -229,9 +226,8 @@ pfUI:RegisterModule("equipmentmanager", function()
local LIST_ROW_STRIDE = SET_ROW_HEIGHT + 2
local listFrame = CreateFrame("Frame", nil, frame)
listFrame:SetPoint("TOPLEFT", frame, "TOPLEFT", 16, -58)
listFrame:SetWidth(180)
-- Height fits N rows + (N-1) inter-row gaps + 3px top/bottom padding.
listFrame:SetHeight(LIST_VISIBLE_ROWS * LIST_ROW_STRIDE + 4)
listFrame:SetSize(180, LIST_VISIBLE_ROWS * LIST_ROW_STRIDE + 4)
CreateBackdrop(listFrame, nil, nil, .75)
-- Mouse-wheel scroll: list of [sets..., newSetRow] is virtualized
@@ -247,15 +243,13 @@ pfUI:RegisterModule("equipmentmanager", function()
local setRows = {}
local function CreateSetRow()
local row = CreateFrame("Button", nil, listFrame)
row:SetWidth(170)
row:SetHeight(SET_ROW_HEIGHT)
row:SetSize(170, SET_ROW_HEIGHT)
-- Position is set per-Refresh based on the row's visible slot;
-- start anchored to avoid uninitialized geometry before first Refresh.
row:SetPoint("TOPLEFT", listFrame, "TOPLEFT", 5, -3)
row.icon = row:CreateTexture(nil, "ARTWORK")
row.icon:SetWidth(30)
row.icon:SetHeight(30)
row.icon:SetSize(30, 30)
row.icon:SetPoint("LEFT", row, "LEFT", 2, 0)
row.icon:SetTexCoord(.08, .92, .08, .92)
@@ -270,7 +264,7 @@ pfUI:RegisterModule("equipmentmanager", function()
row.highlight:Hide()
row.gear = CreateFrame("Button", nil, row)
row.gear:SetWidth(16); row.gear:SetHeight(16)
row.gear:SetSize(16, 16);
row.gear:SetPoint("RIGHT", row, "RIGHT", -4, 0)
row.gear.tex = row.gear:CreateTexture(nil, "ARTWORK")
row.gear.tex:SetAllPoints(row.gear)
@@ -306,27 +300,27 @@ pfUI:RegisterModule("equipmentmanager", function()
pfUI.equipmentmanager.Refresh()
end)
-- Hover handling: OnLeave fires when the cursor moves onto a child
-- (the gear button becomes the topmost mouse target). Use an
-- OnUpdate poll so the gear stays shown while the cursor is over
-- the row OR the gear itself.
-- Show the gear and tooltip while the cursor is over the row or its
-- gear child. The gear sits inside the row's rectangle, so a single
-- MouseIsOver(row) check covers both. OnLeave on the row and the gear
-- catches every exit path, so no OnUpdate poll is needed.
local function HideRowHover()
if MouseIsOver(row) then return end
GameTooltip:Hide()
row.gear:Hide()
end
row:SetScript("OnEnter", function()
if not this.setID then return end
local name = C_EquipmentSet.GetEquipmentSetInfo(this.setID)
if not row.setID then return end
local name = C_EquipmentSet.GetEquipmentSetInfo(row.setID)
if name then
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetOwner(row, "ANCHOR_RIGHT")
GameTooltip:SetEquipmentSet(name)
GameTooltip:Show()
end
row.gear:Show()
this:SetScript("OnUpdate", function()
if not MouseIsOver(this) and not MouseIsOver(row.gear) then
this:SetScript("OnUpdate", nil)
GameTooltip:Hide()
row.gear:Hide()
end
end)
end)
row:SetScript("OnLeave", HideRowHover)
row.gear:SetScript("OnLeave", HideRowHover)
return row
end
@@ -338,10 +332,10 @@ pfUI:RegisterModule("equipmentmanager", function()
-- specific set index (row[i] always shows ids[i]).
local newSetRow = CreateFrame("Button", nil, listFrame)
newSetRow:SetWidth(170); newSetRow:SetHeight(SET_ROW_HEIGHT)
newSetRow:SetSize(170, SET_ROW_HEIGHT);
newSetRow.icon = newSetRow:CreateTexture(nil, "ARTWORK")
newSetRow.icon:SetWidth(24); newSetRow.icon:SetHeight(24)
newSetRow.icon:SetSize(24, 24);
newSetRow.icon:SetPoint("LEFT", newSetRow, "LEFT", 5, 0)
newSetRow.icon:SetTexture(pfUI.path.."\\img\\Character-Plus")
@@ -349,7 +343,7 @@ pfUI:RegisterModule("equipmentmanager", function()
newSetRow.text:SetPoint("LEFT", newSetRow.icon, "RIGHT", 8, 0)
newSetRow.text:SetPoint("RIGHT", newSetRow, "RIGHT", -4, 0)
newSetRow.text:SetJustifyH("LEFT")
newSetRow.text:SetText(T["New Set"] or "New Set")
newSetRow.text:SetText(PAPERDOLL_NEWEQUIPMENTSET)
newSetRow.text:SetTextColor(0.2, 1, 0.2)
newSetRow.highlight = newSetRow:CreateTexture(nil, "BACKGROUND")
@@ -362,8 +356,7 @@ pfUI:RegisterModule("equipmentmanager", function()
local function MakeButton(name, label, parent, anchor, ax, ay, width)
local b = CreateFrame("Button", name, parent, "UIPanelButtonTemplate")
b:SetWidth(width or 70)
b:SetHeight(22)
b:SetSize(width or 70, 22)
b:SetText(label)
b:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", ax, ay)
SkinButton(b)
@@ -376,8 +369,7 @@ pfUI:RegisterModule("equipmentmanager", function()
local namePopup = CreateFrame("Frame", "pfEqMgrNamePopup", UIParent)
namePopup:SetFrameStrata("DIALOG")
namePopup:SetWidth(472)
namePopup:SetHeight(498)
namePopup:SetSize(472, 484)
namePopup:SetPoint("CENTER", UIParent, "CENTER")
namePopup:Hide()
CreateBackdrop(namePopup, nil, nil, .9)
@@ -388,200 +380,28 @@ pfUI:RegisterModule("equipmentmanager", function()
namePopup:SetScript("OnDragStart", function() this:StartMoving() end)
namePopup:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
namePopup.title = namePopup:CreateFontString(nil, "OVERLAY", "GameFontNormal")
namePopup.title:SetPoint("TOP", namePopup, "TOP", 0, -10)
namePopup.title:SetText(T["Save Set"] or "Save Set")
-- Equipment seed leads the grid with gear-relevant item icons. The widget
-- owns the name field, preview, grid, filter, and search; this module keeps
-- the OK/Cancel buttons and the create/save/rename flow below.
local iconPicker = CreateIconPicker("pfEqMgrIcon", namePopup,
IconDataProviderExtraType.Equipment, GEARSETS_POPUP_TEXT)
namePopup.nameLabel = namePopup:CreateFontString(nil, "OVERLAY", "GameFontNormal")
namePopup.nameLabel:SetPoint("TOPLEFT", namePopup, "TOPLEFT", 14, -34)
namePopup.nameLabel:SetText(T["Enter Set Name (Max 16 Characters):"] or "Enter Set Name (Max 16 Characters):")
local btnPopupOK = MakeButton("pfEqMgrPopupOK", OKAY, namePopup, namePopup, 14, -340, 80)
namePopup.editbox = CreateFrame("EditBox", "pfEqMgrNameEdit", namePopup, "InputBoxTemplate")
namePopup.editbox:SetWidth(280)
namePopup.editbox:SetHeight(20)
namePopup.editbox:SetPoint("TOPLEFT", namePopup, "TOPLEFT", 14, -52)
namePopup.editbox:SetAutoFocus(false)
namePopup.editbox:SetMaxLetters(16)
CreateBackdrop(namePopup.editbox)
namePopup.selectedLabel = namePopup:CreateFontString(nil, "OVERLAY", "GameFontNormal")
namePopup.selectedLabel:SetPoint("TOPRIGHT", namePopup, "TOPRIGHT", -14, -28)
namePopup.selectedLabel:SetText(T["Currently Selected"] or "Currently Selected")
namePopup.selectedLabel:SetTextColor(1, 0.82, 0)
namePopup.selectedPreview = CreateFrame("Frame", nil, namePopup)
namePopup.selectedPreview:SetWidth(42)
namePopup.selectedPreview:SetHeight(42)
namePopup.selectedPreview:SetPoint("TOPRIGHT", namePopup, "TOPRIGHT", -14, -44)
CreateBackdrop(namePopup.selectedPreview)
namePopup.selectedPreview.tex = namePopup.selectedPreview:CreateTexture(nil, "ARTWORK")
namePopup.selectedPreview.tex:SetAllPoints(namePopup.selectedPreview)
namePopup.selectedPreview.tex:SetTexCoord(.08, .92, .08, .92)
namePopup.iconLabel = namePopup:CreateFontString(nil, "OVERLAY", "GameFontNormal")
namePopup.iconLabel:SetPoint("TOPLEFT", namePopup, "TOPLEFT", 14, -100)
namePopup.iconLabel:SetText(T["Choose an Icon:"] or "Choose an Icon:")
-- Icon picker: 10×8 grid of buttons + scroll
local ICON_GRID_COLS = 10
local ICON_GRID_ROWS = 8
local ICON_BTN_SIZE = 36
local ICON_BTN_PAD = 6
local iconScroll = CreateFrame("ScrollFrame", "pfEqMgrIconScroll", namePopup, "FauxScrollFrameTemplate")
iconScroll:SetPoint("TOPLEFT", namePopup, "TOPLEFT", 14, -130)
iconScroll:SetWidth(ICON_GRID_COLS * (ICON_BTN_SIZE + ICON_BTN_PAD) - ICON_BTN_PAD)
iconScroll:SetHeight(ICON_GRID_ROWS * (ICON_BTN_SIZE + ICON_BTN_PAD) - ICON_BTN_PAD)
-- IconDataProviderMixin owns the icon DB, dedup, and lazy load.
-- Init on first picker open; release on hide so the cache GCs.
local provider = nil
-- Selection is tracked by PATH (not index) so it survives filter
-- changes: a spell icon you picked still saves correctly even after
-- you switch the filter to "Items" and it's no longer in the visible list.
local QUESTION_MARK = "INTERFACE\\ICONS\\INV_MISC_QUESTIONMARK"
local selectedIconPath = QUESTION_MARK
local function EnsureProvider()
if not provider then
provider = CreateAndInitFromMixin(IconDataProviderMixin,
IconDataProviderExtraType.Equipment)
end
end
-- Anchor scrollbar to iconScroll's right edge so its position tracks
-- the icon grid rather than the popup. -16/+16 vertical insets are the
-- standard up/down arrow spacing for UIPanelScrollBarTemplate.
local scrollbar = _G["pfEqMgrIconScrollScrollBar"]
if scrollbar then
scrollbar:ClearAllPoints()
scrollbar:SetPoint("TOPLEFT", iconScroll, "TOPRIGHT", 8, -16)
scrollbar:SetPoint("BOTTOMLEFT", iconScroll, "BOTTOMRIGHT", 8, 16)
SkinScrollbar(scrollbar)
end
-- Filter dropdown: "All Icons" / "Spells" / "Items" (top right of icon area).
local filterDropdown = CreateFrame("Frame", "pfEqMgrIconFilter", namePopup, "UIDropDownMenuTemplate")
filterDropdown:SetPoint("TOPRIGHT", namePopup, "TOPRIGHT", 0, -94)
local currentFilter = "all"
local function ApplyFilter(value)
currentFilter = value
UIDropDownMenu_SetSelectedValue(filterDropdown, value)
if provider then
if value == "spells" then provider:SetIconTypes({ IconDataProviderIconType.Spell })
elseif value == "items" then provider:SetIconTypes({ IconDataProviderIconType.Item })
else provider:SetIconTypes(nil) end
-- Don't reset selection — selectedIconPath persists. If the
-- selected icon isn't in the new filter, no grid entry will be
-- highlighted but Save will still write the chosen icon.
pfUI.equipmentmanager.RefreshIconGrid()
end
end
UIDropDownMenu_Initialize(filterDropdown, function()
local info
info = {}; info.text = T["All Icons"] or "All Icons"; info.value = "all"
info.func = function() ApplyFilter("all") end
info.checked = currentFilter == "all"
UIDropDownMenu_AddButton(info)
info = {}; info.text = T["Spells"] or "Spells"; info.value = "spells"
info.func = function() ApplyFilter("spells") end
info.checked = currentFilter == "spells"
UIDropDownMenu_AddButton(info)
info = {}; info.text = T["Items"] or "Items"; info.value = "items"
info.func = function() ApplyFilter("items") end
info.checked = currentFilter == "items"
UIDropDownMenu_AddButton(info)
end)
UIDropDownMenu_SetWidth(120, filterDropdown)
UIDropDownMenu_SetSelectedValue(filterDropdown, "all")
SkinDropDown(filterDropdown)
local iconButtons = {}
for r = 1, ICON_GRID_ROWS do
for c = 1, ICON_GRID_COLS do
local i = (r - 1) * ICON_GRID_COLS + c
local btn = CreateFrame("Button", nil, namePopup)
btn:SetWidth(ICON_BTN_SIZE)
btn:SetHeight(ICON_BTN_SIZE)
btn:SetPoint("TOPLEFT", iconScroll, "TOPLEFT", (c-1) * (ICON_BTN_SIZE + ICON_BTN_PAD), -(r-1) * (ICON_BTN_SIZE + ICON_BTN_PAD))
CreateBackdrop(btn)
btn.texture = btn:CreateTexture(nil, "ARTWORK")
btn.texture:SetAllPoints(btn)
btn.texture:SetTexCoord(.08, .92, .08, .92)
btn.gridIndex = i
btn:SetScript("OnClick", function()
if this.iconIndex and provider then
local path = provider:GetIconByIndex(this.iconIndex)
if path then selectedIconPath = path end
pfUI.equipmentmanager.RefreshIconGrid()
end
end)
btn:SetScript("OnEnter", function()
if not this.iconIndex or not provider then return end
local path = provider:GetIconByIndex(this.iconIndex)
if type(path) == "string" then
local name = string.gsub(path, "^.-INTERFACE\\\\ICONS\\\\", "")
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText(name)
GameTooltip:Show()
end
end)
btn:SetScript("OnLeave", GameTooltip_Hide)
iconButtons[i] = btn
end
end
function pfUI.equipmentmanager.RefreshIconGrid()
EnsureProvider()
local numIcons = provider:GetNumIcons()
local numRows = math.ceil(numIcons / ICON_GRID_COLS)
FauxScrollFrame_Update(iconScroll, numRows, ICON_GRID_ROWS, ICON_BTN_SIZE + ICON_BTN_PAD)
local offset = FauxScrollFrame_GetOffset(iconScroll)
for i = 1, ICON_GRID_ROWS * ICON_GRID_COLS do
local listIdx = i + offset * ICON_GRID_COLS
local btn = iconButtons[i]
if listIdx <= numIcons then
btn:Show()
btn.iconIndex = listIdx
local path = provider:GetIconByIndex(listIdx)
btn.texture:SetTexture(path)
if path == selectedIconPath then
btn.backdrop:SetBackdropBorderColor(1, 0.82, 0, 1)
else
btn.backdrop:SetBackdropBorderColor(pfUI.cache.er, pfUI.cache.eg, pfUI.cache.eb, pfUI.cache.ea)
end
else
btn:Hide()
btn.iconIndex = nil
end
end
-- Sync the "Currently Selected" preview from the path directly so it
-- still shows the chosen icon when filtered out of the grid.
namePopup.selectedPreview.tex:SetTexture(selectedIconPath)
end
iconScroll:SetScript("OnVerticalScroll", function()
FauxScrollFrame_OnVerticalScroll(ICON_BTN_SIZE + ICON_BTN_PAD, function() pfUI.equipmentmanager.RefreshIconGrid() end)
end)
namePopup:SetScript("OnHide", function()
if provider then provider:Release(); provider = nil end
end)
local btnPopupOK = MakeButton("pfEqMgrPopupOK", T["OK"] or "OK", namePopup, namePopup, 14, -340, 80)
btnPopupOK:ClearAllPoints()
btnPopupOK:SetPoint("BOTTOMLEFT", namePopup, "BOTTOMLEFT", 14, 12)
local btnPopupCancel = MakeButton("pfEqMgrPopupCancel", T["Cancel"] or "Cancel", namePopup, namePopup, 0, 0, 80)
local btnPopupCancel = MakeButton("pfEqMgrPopupCancel", CANCEL, namePopup, namePopup, 0, 0, 80)
btnPopupCancel:ClearAllPoints()
btnPopupCancel:SetPoint("BOTTOMRIGHT", namePopup, "BOTTOMRIGHT", -14, 12)
btnPopupCancel:SetScript("OnClick", function() namePopup:Hide() end)
-- Okay | Cancel, both anchored to the bottom-right corner.
btnPopupOK:ClearAllPoints()
btnPopupOK:SetPoint("BOTTOMRIGHT", btnPopupCancel, "BOTTOMLEFT", -6, 0)
btnPopupOK:SetScript("OnClick", function()
local name = namePopup.editbox:GetText()
if not name or name == "" then return end
-- Strip the prefix to match ClassicAPI's persisted short-form basenames.
local iconForSave = string.gsub(selectedIconPath, "INTERFACE\\ICONS\\", "")
local iconForSave = string.gsub(iconPicker.GetIcon(), "INTERFACE\\ICONS\\", "")
if pendingAction == "new" then
C_EquipmentSet.CreateEquipmentSet(name, iconForSave)
C_EquipmentSet.ClearIgnoredSlotsForSave()
@@ -610,31 +430,20 @@ pfUI:RegisterModule("equipmentmanager", function()
function OpenNamePopup(action, prefillName, prefillIcon)
pendingAction = action
namePopup.editbox:SetText(prefillName or "")
EnsureProvider()
if prefillIcon then
local short = string.gsub(prefillIcon, "INTERFACE\\ICONS\\", "")
selectedIconPath = "INTERFACE\\ICONS\\" .. strupper(short)
-- Stored icons come back either as a full path or a bare basename
-- (see the row rendering in Refresh). Only prepend the prefix when
-- there's no path separator, so a full path isn't double-prefixed.
iconPicker.SetIcon(string.find(prefillIcon, "\\") and prefillIcon
or ("INTERFACE\\ICONS\\" .. prefillIcon))
else
selectedIconPath = QUESTION_MARK
end
if action == "rename" then
namePopup.title:SetText(T["Rename Set"] or "Rename Set")
iconScroll:Hide()
for _, b in ipairs(iconButtons) do b:Hide() end
namePopup.iconLabel:Hide()
namePopup.selectedLabel:Hide()
namePopup.selectedPreview:Hide()
filterDropdown:Hide()
else
namePopup.title:SetText(action == "new" and (T["Name Set"] or "Name Set") or (T["Save Set"] or "Save Set"))
iconScroll:Show()
namePopup.iconLabel:Show()
namePopup.selectedLabel:Show()
namePopup.selectedPreview:Show()
filterDropdown:Show()
iconPicker.SetIcon(nil)
end
namePopup.search:SetText("")
-- Rename only changes the name, so hide the whole icon-picking area.
iconPicker.SetIconAreaShown(action ~= "rename")
namePopup:Show()
if action ~= "rename" then pfUI.equipmentmanager.RefreshIconGrid() end
if action ~= "rename" then iconPicker.Refresh() end
namePopup.editbox:SetFocus()
end
@@ -643,9 +452,13 @@ pfUI:RegisterModule("equipmentmanager", function()
-- ============================================================
local flyout = CreateFrame("Frame", "pfEqMgrFlyout", UIParent)
-- Everything tied to the EM sidecar closes with it: the popout arrows,
-- the per-slot flyout, and the name/icon popup. (OnShow re-shows the
-- popout arrows.)
frame:SetScript("OnHide", function()
for _, b in ipairs(popoutButtons) do b:Hide() end
flyout:Hide()
namePopup:Hide()
end)
flyout:SetFrameStrata("DIALOG")
flyout:Hide()
@@ -676,7 +489,7 @@ pfUI:RegisterModule("equipmentmanager", function()
end
end
ClearCursor()
UIErrorsFrame:AddMessage(EQUIPMENT_MANAGER_BAGS_FULL or "Your bags are full.", 1, .1, .1, 1)
UIErrorsFrame:AddMessage(ERR_EQUIPMENT_MANAGER_BAGS_FULL, 1, .1, .1, 1)
end
local function MakeFlyoutButton(i)
@@ -692,11 +505,11 @@ pfUI:RegisterModule("equipmentmanager", function()
b:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
if this.specialAction == "placeInBags" then
GameTooltip:SetText(EQUIPMENT_MANAGER_PLACE_IN_BAGS or "Place in Bags", 1, 1, 1)
GameTooltip:SetText(EQUIPMENT_MANAGER_PLACE_IN_BAGS, 1, 1, 1)
elseif this.specialAction == "ignore" then
GameTooltip:SetText(EQUIPMENT_MANAGER_IGNORE_SLOT or "Ignore this slot", 1, 1, 1)
GameTooltip:SetText(EQUIPMENT_MANAGER_IGNORE_SLOT, 1, 1, 1)
elseif this.specialAction == "unignore" then
GameTooltip:SetText(EQUIPMENT_MANAGER_UNIGNORE_SLOT or "Stop ignoring this slot", 1, 1, 1)
GameTooltip:SetText(EQUIPMENT_MANAGER_UNIGNORE_SLOT, 1, 1, 1)
elseif this.bag then
GameTooltip:SetBagItem(this.bag, this.slot)
elseif this.invSlot then
@@ -704,7 +517,7 @@ pfUI:RegisterModule("equipmentmanager", function()
end
GameTooltip:Show()
end)
b:SetScript("OnLeave", function() GameTooltip:Hide() end)
b:SetScript("OnLeave", GameTooltip_Hide)
b:SetScript("OnClick", function()
if this.specialAction == "placeInBags" then
UnequipToBags(flyout.targetInvSlot)
@@ -809,7 +622,6 @@ pfUI:RegisterModule("equipmentmanager", function()
if num == 0 then
flyout:Hide()
UIErrorsFrame:AddMessage(T["No matching items in bags"] or "No matching items in bags", 1, 1, 0, 1)
return
end
@@ -938,16 +750,6 @@ pfUI:RegisterModule("equipmentmanager", function()
end
end
-- Tie popout visibility to the EM sidecar. They appear when the
-- sidecar opens, hide when it closes, and the flyout closes too.
frame:HookScript("OnShow", function()
for _, b in ipairs(popoutButtons) do b:Show() end
end)
frame:HookScript("OnHide", function()
for _, b in ipairs(popoutButtons) do b:Hide() end
if flyout then flyout:Hide() end
end)
-- ============================================================
-- Refresh
-- ============================================================
+1
View File
@@ -2772,6 +2772,7 @@ pfUI:RegisterModule("gui", function ()
CreateConfig(nil, T["Always Show Health In Percent"], C.tooltip, "alwaysperc", "checkbox")
CreateConfig(nil, T["Show Item IDs"], C.tooltip, "itemid", "checkbox")
CreateConfig(nil, T["Show Movement Speed"], C.tooltip, "movespeed", "checkbox")
CreateConfig(nil, T["Show Aura Caster"], C.tooltip, "aurasource", "checkbox")
CreateConfig(nil, T["Custom Transparency"], C.tooltip, "alpha")
CreateConfig(nil, T["Status Bar Texture"], C.tooltip.statusbar, "texture", "dropdown", pfUI.gui.dropdowns.uf_bartexture)
CreateConfig(nil, T["Always Show Item Comparison"], C.tooltip.compare, "showalways", "checkbox")
+128
View File
@@ -0,0 +1,128 @@
-- Macro icon picker
-- Replaces Blizzard's MacroPopupFrame (the "name + icon" dialog opened by the
-- New / Change-Icon buttons) with a pfUI-native picker driven by
-- IconDataProviderMixin, so the full spell + item + loose icon set is
-- available instead of the stock spell-only list. Saving goes through
-- C_Macro.CreateMacro / C_Macro.EditMacro, which take the icon as a texture
-- string -- so arbitrary icons (including index-less INV_* item icons) persist
-- on both the modern (Turtle/Octo) and stock-vanilla macro UIs. The surrounding
-- MacroFrame (list + body editor) is Blizzard's, already skinned elsewhere.
pfUI:RegisterModule("macroicons", function ()
HookAddonOrVariable("Blizzard_MacroUI", function()
-- ============================================================
-- Popup frame
-- ============================================================
local picker = CreateFrame("Frame", "pfMacroIconPicker", UIParent)
picker:SetFrameStrata("DIALOG")
picker:SetSize(472, 484)
-- Starts anchored to the right of the macro panel (re-anchored at open
-- once the skin's backdrop exists); dragging pins it in place after.
picker:SetPoint("BOTTOMLEFT", MacroFrame, "BOTTOMRIGHT", 8, 0)
picker:Hide()
CreateBackdrop(picker, nil, nil, .9)
CreateBackdropShadow(picker)
picker:EnableMouse(true)
picker:SetMovable(true)
picker:RegisterForDrag("LeftButton")
picker:SetScript("OnDragStart", function() this:StartMoving() end)
picker:SetScript("OnDragStop", function()
this:StopMovingOrSizing()
this.userMoved = true
end)
-- Spellbook seed leads the grid with class-relevant spell/talent icons.
local iconPicker = CreateIconPicker("pfMacroIcon", picker,
IconDataProviderExtraType.Spellbook, MACRO_POPUP_TEXT)
-- ============================================================
-- Open / save
-- ============================================================
-- Blizzard's popup disables these while it is open; mirror that so the
-- underlying frame can't be double-driven, then let MacroFrame_Update
-- restore the correct states on close.
local function SetMacroButtons(enabled)
local m = enabled and "Enable" or "Disable"
MacroNewButton[m](MacroNewButton)
MacroEditButton[m](MacroEditButton)
MacroDeleteButton[m](MacroDeleteButton)
end
local function OpenMacroPopup(mode)
if mode == "edit" and MacroFrame.selectedMacro then
picker.mode = "edit"
local name, texture = GetMacroInfo(MacroFrame.selectedMacro)
picker.editbox:SetText(name or "")
iconPicker.SetIcon(texture)
else
picker.mode = "new"
picker.editbox:SetText("")
iconPicker.SetIcon(nil)
end
picker.search:SetText("")
if not picker.userMoved then
picker:ClearAllPoints()
picker:SetPoint("BOTTOMLEFT", MacroFrame.backdrop or MacroFrame, "BOTTOMRIGHT", 8, 0)
end
SetMacroButtons(false)
picker:Show()
picker.editbox:SetFocus()
iconPicker.Refresh()
end
local function SaveMacroPopup()
local text = strtrim(picker.editbox:GetText())
if text == "" then return end
local icon = iconPicker.GetIcon()
if picker.mode == "edit" and MacroFrame.selectedMacro then
C_Macro.EditMacro(MacroFrame.selectedMacro, text, icon, nil)
MacroFrame_SelectMacro(MacroFrame.selectedMacro)
else
local idx = C_Macro.CreateMacro(text, icon, nil, (MacroFrame.macroBase or 0) > 0)
if idx then MacroFrame_SelectMacro(idx) end
end
picker:Hide()
end
picker:SetScript("OnHide", function() MacroFrame_Update() end)
picker.editbox:SetScript("OnEnterPressed", SaveMacroPopup)
picker.editbox:SetScript("OnEscapePressed", function() picker:Hide() end)
-- OK / Cancel
local okay = CreateFrame("Button", "pfMacroIconPickerOkay", picker, "UIPanelButtonTemplate")
okay:SetSize(80, 22)
okay:SetText(OKAY)
okay:SetScript("OnClick", SaveMacroPopup)
SkinButton(okay)
local cancel = CreateFrame("Button", "pfMacroIconPickerCancel", picker, "UIPanelButtonTemplate")
cancel:SetSize(80, 22)
cancel:SetText(CANCEL)
cancel:SetPoint("BOTTOMRIGHT", picker, "BOTTOMRIGHT", -14, 12)
cancel:SetScript("OnClick", function() picker:Hide() end)
SkinButton(cancel)
-- Okay | Cancel, both anchored to the bottom-right corner.
okay:SetPoint("BOTTOMRIGHT", cancel, "BOTTOMLEFT", -6, 0)
-- ============================================================
-- Take over the New / Change-Icon buttons; retire Blizzard's popup
-- ============================================================
MacroNewButton:SetScript("OnClick", function()
MacroFrame_SaveMacro()
OpenMacroPopup("new")
end)
MacroEditButton:SetScript("OnClick", function()
MacroFrame_SaveMacro()
OpenMacroPopup("edit")
end)
-- Closing the macro window takes the picker with it.
MacroFrame:HookScript("OnHide", function() picker:Hide() end)
end)
end)
+54 -59
View File
@@ -1,57 +1,32 @@
pfUI:RegisterModule("marktracking", function ()
-- Requires mark1-mark8 unit tokens (Turtle WoW / Nampower)
if not UnitExists("mark1") and not UnitExists("mark8") then
if not pcall(function() UnitExists("mark1") end) then return end
end
local rawborder, border = GetBorderSize()
-- Parse color strings "r,g,b,a" into components
local function ParseColor(str, dr, dg, db, da)
if not str or str == "" then return dr, dg, db, da end
local _, _, r, g, b, a = string.find(str, "([%d%.]+),([%d%.]+),([%d%.]+),([%d%.]+)")
if r then
return tonumber(r) or dr, tonumber(g) or dg, tonumber(b) or db, tonumber(a) or da
end
return dr, dg, db, da
end
local markerOrder = { 8, 7, 6, 5, 4, 3, 2, 1 } -- skull, cross, square, moon, triangle, diamond, circle, star
local markerTokens = {}
for i = 1, 8 do markerTokens[i] = "mark" .. i end
-- Default colors per marker
local defaultColors = {
[1] = { 1.0, 0.9, 0.0, 1 }, -- star: yellow
[2] = { 1.0, 0.5, 0.0, 1 }, -- circle: orange
[3] = { 0.8, 0.0, 0.8, 1 }, -- diamond: purple
[4] = { 0.0, 0.8, 0.0, 1 }, -- triangle: green
[5] = { 0.7, 0.7, 0.7, 1 }, -- moon: silver
[6] = { 0.0, 0.4, 0.9, 1 }, -- square: blue
[7] = { 0.9, 0.0, 0.0, 1 }, -- cross: red
[8] = { 1.0, 1.0, 1.0, 1 }, -- skull: white
}
local markerConfigKeys = {
[1] = "raidmarkercolor_star",
[2] = "raidmarkercolor_circle",
[3] = "raidmarkercolor_diamond",
[4] = "raidmarkercolor_triangle",
[5] = "raidmarkercolor_moon",
[6] = "raidmarkercolor_square",
[7] = "raidmarkercolor_cross",
[8] = "raidmarkercolor_skull",
"raidmarkercolor_star",
"raidmarkercolor_circle",
"raidmarkercolor_diamond",
"raidmarkercolor_triangle",
"raidmarkercolor_moon",
"raidmarkercolor_square",
"raidmarkercolor_cross",
"raidmarkercolor_skull",
}
local markerColors = {}
for i = 1, 8 do
local d = defaultColors[i]
local r, g, b, a = ParseColor(C.unitframes[markerConfigKeys[i]], d[1], d[2], d[3], d[4])
markerColors[i] = { r, g, b, a }
for i, markKey in ipairs(markerConfigKeys) do
markerTokens[i] = "mark" .. i
local r, g, b, a = GetStringColor(C.unitframes[markKey])
markerColors[i] = { tonumber(r), tonumber(g), tonumber(b), tonumber(a) }
end
local FALLBACK_INTERVAL = 1.0 -- safety net for units that come into range after marker was set
local elapsed = 0
local isUnlocked = false
local ROW_HEIGHT = tonumber(C.unitframes.raidmarkerheight) or 14
local BAR_WIDTH = tonumber(C.unitframes.raidmarkerwidth) or 80
@@ -125,19 +100,16 @@ pfUI.marktracking = CreateFrame("Frame", "pfMarkTracking", UIParent)
local i = markerOrder[idx]
local row = CreateFrame("Button", nil, pfUI.marktracking)
row:SetWidth(TOTAL_ROW_WIDTH)
row:SetHeight(ROW_HEIGHT)
row:SetSize(TOTAL_ROW_WIDTH, ROW_HEIGHT)
row:Hide()
row:RegisterForClicks("LeftButtonUp")
row:SetScript("OnClick", function()
TargetUnit(markerTokens[this.markerIndex])
end)
row:RegisterForClicks("LeftButtonUp", "RightButtonUp")
row:SetAttribute("type1", "target")
row:SetAttribute("type2", "menu")
-- raid icon
row.icon = row:CreateTexture(nil, "ARTWORK")
row.icon:SetWidth(ROW_HEIGHT)
row.icon:SetHeight(ROW_HEIGHT)
row.icon:SetSize(ROW_HEIGHT, ROW_HEIGHT)
row.icon:SetPoint("LEFT", row, "LEFT", 1, 0)
local markTex = C.unitframes.blizzard_raidicons == "1" and "Interface\\TargetingFrame\\UI-RaidTargetingIcons" or pfUI.media["img:raidicons"]
row.icon:SetTexture(markTex)
@@ -145,8 +117,7 @@ pfUI.marktracking = CreateFrame("Frame", "pfMarkTracking", UIParent)
-- portrait (right side)
row.portrait = row:CreateTexture(nil, "ARTWORK")
row.portrait:SetWidth(PORTRAIT_SIZE)
row.portrait:SetHeight(PORTRAIT_SIZE)
row.portrait:SetSize(PORTRAIT_SIZE, PORTRAIT_SIZE)
row.portrait:SetPoint("RIGHT", row, "RIGHT", -1, 0)
row.portrait:SetTexCoord(.1, .9, .1, .9)
if not rm_showportrait then row.portrait:Hide() end
@@ -204,8 +175,8 @@ pfUI.marktracking = CreateFrame("Frame", "pfMarkTracking", UIParent)
local token = markerTokens[i]
if UnitExists(token) and not UnitIsDead(token) then
local hp = UnitHealth(token)
local maxhp = UnitHealthMax(token)
local hp, maxhp = UnitHealth(token), UnitHealthMax(token)
row:SetAttribute('unit', token)
if hp and maxhp and maxhp > 0 and hp > 0 then
local pct = hp / maxhp
@@ -267,6 +238,27 @@ pfUI.marktracking = CreateFrame("Frame", "pfMarkTracking", UIParent)
end
end
-- Fast per-row refresh for a single mark's UNIT_HEALTH/UNIT_MAXHEALTH: moves
-- just the bar + hp text. If the mark's visibility flips (comes into range,
-- dies, hp crosses 0) the visible rows re-pack, so hand off to UpdateDisplay.
local function UpdateRow(i)
if isUnlocked then return end
local row = pfUI.marktracking.rows[i]
if not row then return end
local token = markerTokens[i]
local hp, maxhp = UnitHealth(token), UnitHealthMax(token)
local shouldShow = UnitExists(token) and not UnitIsDead(token)
and hp and maxhp and maxhp > 0 and hp > 0 and true or false
if shouldShow ~= (row:IsShown() and true or false) then
UpdateDisplay()
return
end
if not shouldShow then return end
local pct = hp / maxhp
row.health:SetValue(pct)
if rm_showpct then row.hptext:SetText(math.ceil(pct * 100) .. "%") end
end
-- Unlock mode: show fixed 1-row placeholder so positioning works correctly
if pfUI.unlock then
local origShow = pfUI.unlock:GetScript("OnShow")
@@ -292,24 +284,27 @@ pfUI.marktracking = CreateFrame("Frame", "pfMarkTracking", UIParent)
-- Event-driven scanner frame
local scanner = CreateFrame("Frame")
-- RAID_TARGET_UPDATE: fires when a raid marker is set/cleared
-- PLAYER_ENTERING_WORLD: fires on login, reload, and zone transitions
-- UNIT_HEALTH/UNIT_MAXHEALTH: fires on HP changes for real-time bar updates
-- RAID_TARGET_UPDATE: a raid marker was set/cleared -> full refresh
-- PLAYER_ENTERING_WORLD: login/reload/zone -> full refresh
-- UNIT_HEALTH/UNIT_MAXHEALTH: ClassicAPI fires these per token; with the mark
-- tokens observed they arrive as arg1 == "markN", so we refresh just that
-- one row (UpdateRow) instead of rescanning all eight.
scanner:RegisterEvent("RAID_TARGET_UPDATE")
scanner:RegisterEvent("PLAYER_ENTERING_WORLD")
scanner:RegisterEvent("UNIT_HEALTH")
scanner:RegisterEvent("UNIT_MAXHEALTH")
scanner:SetScript("OnEvent", function()
if event == "UNIT_HEALTH" or event == "UNIT_MAXHEALTH" then
-- arg1 is the token; "markN" -> N, nil for any non-mark token.
local i = arg1 and tonumber(string.match(arg1, "^mark(%d)"))
if i then UpdateRow(i) end
return
end
UpdateDisplay()
end)
-- Fallback poll at 1s: catches units that come into range AFTER a marker was set
-- Fallback poll: catches units that come into range AFTER a marker was set
-- (no event fires for that case, so we need this safety net)
scanner:SetScript("OnUpdate", function()
elapsed = elapsed + arg1
if elapsed < FALLBACK_INTERVAL then return end
elapsed = 0
UpdateDisplay()
end)
C_Timer.NewTicker(FALLBACK_INTERVAL, UpdateDisplay)
end)
+7 -9
View File
@@ -131,18 +131,16 @@ pfUI:RegisterModule("minimap", function ()
-- Create coordinates text frame with location configurable
pfUI.minimapCoordinates = CreateFrame("Frame", "pfMinimapCoord", pfUI.minimap)
pfUI.minimapCoordinates:SetScript("OnUpdate", function()
-- Throttle to update coords every 0.1 seconds
if ( this.tick or 0) > GetTime() then return end
this.tick = GetTime() + .1
-- Update coords every 0.1 seconds
C_Timer.NewTicker(0.1, function()
if C.appearance.minimap.coordstext == "off" then return end
this.posX, this.posY = GetPlayerMapPosition("player")
if this.posX ~= 0 and this.posY ~= 0 then
this.text:SetText(string.format("%.1f, %.1f", round(this.posX * 100, 1), round(this.posY * 100, 1)))
local coord = pfUI.minimapCoordinates
coord.posX, coord.posY = GetPlayerMapPosition("player")
if coord.posX ~= 0 and coord.posY ~= 0 then
coord.text:SetText(string.format("%.1f, %.1f", round(coord.posX * 100, 1), round(coord.posY * 100, 1)))
else
this.text:SetText("|cffffaaaaN/A")
coord.text:SetText("|cffffaaaaN/A")
end
end)
+46 -44
View File
@@ -55,6 +55,7 @@ pfUI:RegisterModule("nameplates", function ()
local savedHostileState = nil
local savedFriendlyState = nil
local inFriendlyZone = false
local myGuild = nil
local platecount = 0
local registry = {}
-- Subset of registry that currently has a unit assigned (between
@@ -64,18 +65,19 @@ pfUI:RegisterModule("nameplates", function ()
local raidGuidCache = {} -- guid -> name (rebuilt on RAID_ROSTER_UPDATE/PARTY_MEMBERS_CHANGED)
-- 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.
-- Per-GUID cast state, populated from ClassicAPI's UNIT_SPELLCAST_* events
-- (which now fire for nameplate tokens) and cleared on STOP / plate removal /
-- expiry. This replaces the old per-tick C_Spell poll on every visible plate:
-- cast detection is 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 = {}
-- 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.
-- One-shot C_Spell poll. Builds the cast struct for a UNIT_SPELLCAST_* event
-- (the payload carries no timing) and seeds a plate that spawns while its unit
-- is already mid-cast (its START fired before the plate existed). Picks cast
-- vs channel itself. 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)
@@ -477,8 +479,11 @@ nameplates:RegisterEvent("NAME_PLATE_UNIT_REMOVED")
nameplates:RegisterEvent("UNIT_AURA")
nameplates:RegisterEvent("UNIT_FLAGS")
nameplates:RegisterEvent("UPDATE_MOUSEOVER_UNIT")
nameplates:RegisterEvent("SPELL_START_OTHER")
nameplates:RegisterEvent("SPELL_FAILED_OTHER")
nameplates:RegisterEvent("UNIT_SPELLCAST_START")
nameplates:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START")
nameplates:RegisterEvent("UNIT_SPELLCAST_STOP")
nameplates:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP")
nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
nameplates:SetScript("OnEvent", function()
-- Stop event handling during logout to prevent crash 132
@@ -490,13 +495,17 @@ nameplates:RegisterEvent("SPELL_FAILED_OTHER")
nameplates.mouselook:SetScript("OnUpdate", nil)
end
return
elseif event == "PLAYER_GUILD_UPDATE" and arg1 == 'player' then
myGuild = GetGuildInfo("player")
elseif event == "PLAYER_ENTERING_WORLD" or event == "ZONE_CHANGED_NEW_AREA" then
if event == "PLAYER_ENTERING_WORLD" then
CacheConfig()
this:SetGameVariables()
RebuildRaidGuidCache()
frameState.targetGuid = UnitGUID("target")
myGuild = GetGuildInfo("player")
end
-- Handle friendly zone nameplate disable feature
@@ -562,9 +571,9 @@ nameplates:RegisterEvent("SPELL_FAILED_OTHER")
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.
-- Seed: the unit may already be mid-cast (its UNIT_SPELLCAST_START
-- fired before this plate existed). One poll here catches that;
-- ongoing casts arrive via the event.
castState[guid] = PollCastInfo(arg1)
end
nameplates.OnShow(plate)
@@ -609,39 +618,32 @@ nameplates:RegisterEvent("SPELL_FAILED_OTHER")
if pn then pn.eventcache = true 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
elseif event == "UNIT_SPELLCAST_START" or event == "UNIT_SPELLCAST_CHANNEL_START" then
-- ClassicAPI fires UNIT_SPELLCAST_* per unit token, including the caster's
-- "nameplateN". The payload has no timing, so poll it (PollCastInfo picks
-- cast vs channel) and cache -- 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
if arg1 and strfind(arg1, "^nameplate") then
local guid = UnitGUID(arg1)
local plate = guid and plateByGuid[guid]
if plate then
castState[guid] = PollCastInfo(arg1)
if castState[guid] then
plate.castUpdate = true -- bypass the throttle so the bar shows now
end
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
elseif event == "UNIT_SPELLCAST_STOP" or event == "UNIT_SPELLCAST_CHANNEL_STOP" then
-- Cast/channel ended (natural, interrupted, or cancelled -- the poll fires
-- STOP for all three). Clear the cached cast and refresh its plate.
if arg1 and strfind(arg1, "^nameplate") then
local guid = UnitGUID(arg1)
if guid and castState[guid] then
castState[guid] = nil
local plate = plateByGuid[guid]
if plate then plate.castUpdate = true end
end
end
elseif event == "UNIT_AURA" then
@@ -952,7 +954,7 @@ nameplates:RegisterEvent("SPELL_FAILED_OTHER")
-- Use the same castbar texture and color as the unit frame castbar (castbar.lua)
local cbtexture = pfUI.media[C.appearance.castbar.texture]
nameplate.castbar:SetStatusBarTexture(cbtexture or hptexture)
local cbr, cbg, cbb, cba = strsplit(",", C.appearance.castbar.castbarcolor)
local cbr, cbg, cbb, cba = GetStringColor(C.appearance.castbar.castbarcolor)
nameplate.castbar:SetStatusBarColor(cbr, cbg, cbb, cba)
-- reset endTime cache so color/texture refresh takes effect on next cast
nameplate.castbar.lastEndTime = nil
@@ -1125,7 +1127,7 @@ nameplates:RegisterEvent("SPELL_FAILED_OTHER")
if guild and C.nameplates.showguildname == "1" then
plate.guild:SetText(guild)
if UnitIsInMyGuild(plate.unit) then
if guild == myGuild then
plate.guild:SetTextColor(0, 0.9, 0, 1)
else
plate.guild:SetTextColor(0.8, 0.8, 0.8, 1)
+17 -36
View File
@@ -37,18 +37,12 @@ pfUI:RegisterModule("panel", function()
return
end
if arg1 == "LeftButton" then
if widget.timerFrame:IsShown() then
widget.timerFrame:Hide()
else
widget.timerFrame:Show()
end
widget.timerFrame:SetShown(not widget.timerFrame:IsShown())
elseif arg1 == "RightButton" then
widget.timerFrame.Snapshot = GetTime()
end
end
widget:SetScript("OnUpdate",function()
if ( this.tick or 1) > GetTime() then return else this.tick = GetTime() + 1 end
C_Timer.NewTicker(1, function()
local secondsenabled = C.panel.seconds == "1"
local fmt
if C.global.twentyfour == "0" then
@@ -62,8 +56,7 @@ pfUI:RegisterModule("panel", function()
widget.timerFrame = CreateFrame("Frame", "pfUITimer", UIParent)
widget.timerFrame:Hide()
widget.timerFrame:SetWidth(120)
widget.timerFrame:SetHeight(35)
widget.timerFrame:SetSize(120, 35)
widget.timerFrame:SetPoint("TOP", 0, -100)
UpdateMovable(widget.timerFrame)
@@ -103,11 +96,9 @@ pfUI:RegisterModule("panel", function()
pfUI.panel:OutputPanel("combat", T["Combat"] .. ": " .. NOT_APPLICABLE)
end
end)
widget.combat:SetScript("OnUpdate", function()
if not this.tick then this.tick = GetTime() end
if GetTime() <= this.tick + 1 then return else this.tick = GetTime() end
if this.combat then
pfUI.panel:OutputPanel("combat", "|cffffaaaa" .. SecondsToTime(ceil(GetTime() - this.combat)))
C_Timer.NewTicker(1, function()
if widget.combat.combat then
pfUI.panel:OutputPanel("combat", "|cffffaaaa" .. SecondsToTime(ceil(GetTime() - widget.combat.combat)))
end
end)
end
@@ -153,9 +144,7 @@ pfUI:RegisterModule("panel", function()
pfUI.addons:Show()
end
end
widget:SetScript("OnUpdate",function()
if ( this.tick or 1) > GetTime() then return else this.tick = GetTime() + 1 end
C_Timer.NewTicker(1, function()
fps = floor(GetFramerate())
_, _, lag = GetNetStats()
@@ -346,7 +335,7 @@ pfUI:RegisterModule("panel", function()
widget:RegisterEvent("PLAYER_GUILD_UPDATE")
widget.Tooltip = function()
-- skip without guild
if not GetGuildInfo("player") then return end
if not IsInGuild() then return end
local raidparty = {}
for i=1,4 do -- detect people in group
@@ -398,7 +387,7 @@ pfUI:RegisterModule("panel", function()
end
widget.Click = function() ToggleFriendsFrame(3) end
widget:SetScript("OnEvent", function()
if GetGuildInfo("player") then
if IsInGuild() then
local count = 0
for i = 1, GetNumGuildMembers() do
local _, _, _, _, _, _, _, _, online = GetGuildRosterInfo(i)
@@ -411,9 +400,8 @@ pfUI:RegisterModule("panel", function()
end
end)
widget:SetScript("OnUpdate",function()
if ( this.tick or 60) > GetTime() then return else this.tick = GetTime() + 60 end
if GetGuildInfo("player") then GuildRoster() end
C_Timer.NewTicker(60, function()
if IsInGuild() then GuildRoster() end
end)
end
@@ -492,11 +480,7 @@ pfUI:RegisterModule("panel", function()
GameTooltip:Show()
end
widget.Click = function()
if WorldMapFrame:IsShown() then
WorldMapFrame:Hide()
else
WorldMapFrame:Show()
end
WorldMapFrame:SetShown(not WorldMapFrame:IsShown())
end
widget:SetScript("OnEvent", function()
pfUI.panel:OutputPanel("zone", GetMinimapZoneText(), widget.Tooltip, widget.Click)
@@ -645,8 +629,7 @@ pfUI:RegisterModule("panel", function()
local frame = CreateFrame("Button", nil, parent)
frame:SetFrameLevel(0)
frame:ClearAllPoints()
frame:SetWidth(width)
frame:SetHeight(parent:GetHeight())
frame:SetSize(width, parent:GetHeight())
frame:SetPoint(location, 0, 0)
frame.text = frame:CreateFontString("Status", "LOW", "GameFontNormal")
frame.text:ClearAllPoints()
@@ -687,8 +670,7 @@ pfUI:RegisterModule("panel", function()
local imgstring = "img:" .. leftright
parent.texture:SetTexture(pfUI.media[imgstring])
parent.texture:SetPoint("CENTER", 0, 0)
parent.texture:SetWidth(8)
parent.texture:SetHeight(8)
parent.texture:SetSize(8, 8)
parent.texture:SetVertexColor(.25,.25,.25,1)
return parent.texture
end
@@ -710,7 +692,7 @@ pfUI:RegisterModule("panel", function()
UpdateMovable(pfUI.panel.left)
pfUI.panel.left.hide:SetScript("OnClick", function()
if pfUI.chat.left:IsShown() then pfUI.chat.left:Hide() else pfUI.chat.left:Show() end
pfUI.chat.left:SetShown(not pfUI.chat.left:IsShown())
end)
if not pfUI.chat then pfUI.panel.left.hide:Hide() end
@@ -747,7 +729,7 @@ pfUI:RegisterModule("panel", function()
UpdateMovable(pfUI.panel.right)
pfUI.panel.right.hide:SetScript("OnClick", function()
if pfUI.chat.right:IsShown() then pfUI.chat.right:Hide() else pfUI.chat.right:Show() end
pfUI.chat.right:SetShown(not pfUI.chat.right:IsShown())
end)
if not pfUI.chat then pfUI.panel.right.hide:Hide() end
@@ -796,8 +778,7 @@ pfUI:RegisterModule("panel", function()
pfUI.panel.microbutton = CreateFrame("Frame", "pfPanelMicroButton", UIParent)
pfUI.panel.microbutton:SetPoint("TOP", pfUI.panel.minimap, "BOTTOM", 0, -2*default_border)
UpdateMovable(pfUI.panel.microbutton)
pfUI.panel.microbutton:SetHeight(23)
pfUI.panel.microbutton:SetWidth(145)
pfUI.panel.microbutton:SetSize(145, 23)
pfUI.panel.microbutton:SetFrameStrata("MEDIUM")
for i=1,table.getn(MICRO_BUTTONS) do
+28 -30
View File
@@ -39,17 +39,20 @@ pfUI:RegisterModule("player", function ()
playerFrame.isSpellCaster = myclass ~= "WARRIOR" and myclass ~= "ROGUE" and myclass ~= "HUNTER"
-- Convert "r,g,b,a" config color string to a 6-char hex string, or nil if unset
local function cfgColorToHex(colorStr)
if not colorStr or colorStr == "" then return nil end
local r, g, b = strsplit(",", colorStr)
r, g, b = tonumber(r), tonumber(g), tonumber(b)
if not r or not g or not b then return nil end
return string.format("%02X%02X%02X", r * 255, g * 255, b * 255)
end
local cfgColorToHexTable = setmetatable({}, {
__index = function (t, colorStr)
if not colorStr or colorStr == "" then return nil end
local r, g, b = GetStringColor(colorStr)
if not r or not g or not b then return nil end
local hex = C_ColorUtil.GenerateTextColorCode({ r=r, g = g, b=b})
rawset(t, colorStr, hex)
return hex
end
})
-- SP school colors indexed by GetSpellBonusDamage's 1-based school order
-- (1=phys, 2=holy, 3=fire, 4=nature, 5=frost, 6=shadow, 7=arcane)
local spColors = { "FFFFFF", "FFFF80", "FF8000", "4DFF4D", "80FFFF", "9482C9", "FFFFFF" }
local spColors = { "FFFFFFFF", "FFFFFF80", "FFFF8000", "FF4DFF4D", "FF80FFFF", "FF9482C9", "FFFFFFFF" }
-- Default SP school per class used as tiebreaker when multiple schools are equal
local spDefaultSchool = {
@@ -68,42 +71,37 @@ pfUI:RegisterModule("player", function ()
-- from UNIT_MOD_CAST_SPEED). Talent/spell-specific cast-time reductions
-- show up in the actual cast bar via C_Spell.UnitCastingInfo; folding them
-- in here too was mixing two different concepts into one number.
local showHaste = cfg.display_haste == "1"
local showSP = cfg.display_spellpower == "1"
local isSpellCaster = playerFrame.isSpellCaster
if (not showHaste or not isSpellCaster) and not showSP then
local showHaste = isSpellCaster and cfg.display_haste == "1"
local showSP = isSpellCaster and cfg.display_spellpower == "1"
if not showHaste and not showSP then
playerFrame.infoTopCenterText:SetText("")
return
end
local haste = UnitSpellHaste("player")
local text = ""
local parts = {}
if showHaste and isSpellCaster and haste then
local hasteHex = cfgColorToHex(cfg.display_haste_color) or "FFFFFF"
text = string.format("|cff%s%.1f%%|r", hasteHex, haste)
if showHaste then
local hex = cfgColorToHexTable[cfg.display_haste_color] or "FFFFFFFF"
table.insert(parts, string.format("|c%s%.1f%%|r", hex, UnitSpellHaste("player")))
end
if showSP and isSpellCaster then
local defSchool = spDefaultSchool[myclass] or 2
local maxSP = GetSpellBonusDamage(defSchool) or 0
local maxColor = spColors[defSchool]
for i = 2, 7 do -- skip physical (1); default school seeds the tiebreak
if showSP then
local school = spDefaultSchool[myclass] or 2
local maxSP = GetSpellBonusDamage(school) or 0
for i = 2, 7 do -- skip physical (1)
local v = GetSpellBonusDamage(i) or 0
if v > maxSP then
maxSP = v
maxColor = spColors[i]
end
if v > maxSP then maxSP, school = v, i end
end
if maxSP > 0 then
local spHex = (cfg.display_sp_color_override == "1" and cfgColorToHex(cfg.display_sp_color)) or maxColor
if text ~= "" then text = text .. " " end
text = text .. string.format("|cff%s+%d SP|r", spHex, maxSP)
local hex = (cfg.display_sp_color_override == "1" and cfgColorToHexTable[cfg.display_sp_color]) or spColors[school]
table.insert(parts, string.format("|c%s+%d SP|r", hex, maxSP))
end
end
playerFrame.infoTopCenterText:SetText(text)
playerFrame.infoTopCenterText:SetText(table.concat(parts, " "))
end
-- Keep a reference to the generic UF UpdateConfig so we can chain it
+1 -1
View File
@@ -234,7 +234,7 @@ pfUI:RegisterModule("roll", function ()
f.time.bar:SetAllPoints(f.time)
f.time.bar:SetStatusBarTexture(pfUI.media["img:bar"])
f.time.bar:SetMinMaxValues(0, 100)
local r, g, b, a = strsplit(",", C.appearance.border.color)
local r, g, b, a = GetStringColor(C.appearance.border.color)
f.time.bar:SetStatusBarColor(r, g, b)
f.time.bar:SetValue(20)
f.time.bar:SetScript("OnUpdate", function()
+6 -6
View File
@@ -100,10 +100,10 @@ pfUI:RegisterModule("thirdparty-vanilla", function()
CreateBackdropShadow(KLHTM_Frame)
if C.thirdparty.chatbg == "1" and C.chat.global.custombg == "1" then
local r, g, b, a = strsplit(",", C.chat.global.background)
local r, g, b, a = GetStringColor(C.chat.global.background)
KLHTM_Frame.backdrop:SetBackdropColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
local r, g, b, a = strsplit(",", C.chat.global.border)
local r, g, b, a = GetStringColor(C.chat.global.border)
KLHTM_Frame.backdrop:SetBackdropBorderColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
end
@@ -199,10 +199,10 @@ pfUI:RegisterModule("thirdparty-vanilla", function()
CreateBackdropShadow(TWTMain)
if C.thirdparty.chatbg == "1" and C.chat.global.custombg == "1" then
local r, g, b, a = strsplit(",", C.chat.global.background)
local r, g, b, a = GetStringColor(C.chat.global.background)
TWTMain.backdrop:SetBackdropColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
local r, g, b, a = strsplit(",", C.chat.global.border)
local r, g, b, a = GetStringColor(C.chat.global.border)
TWTMain.backdrop:SetBackdropBorderColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
end
@@ -328,10 +328,10 @@ pfUI:RegisterModule("thirdparty-vanilla", function()
CreateBackdropShadow(SW_BarFrame1)
if C.thirdparty.chatbg == "1" and C.chat.global.custombg == "1" then
local r, g, b, a = strsplit(",", C.chat.global.background)
local r, g, b, a = GetStringColor(C.chat.global.background)
SW_BarFrame1.backdrop:SetBackdropColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
local r, g, b, a = strsplit(",", C.chat.global.border)
local r, g, b, a = GetStringColor(C.chat.global.border)
SW_BarFrame1.backdrop:SetBackdropBorderColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
end
+4 -4
View File
@@ -244,10 +244,10 @@ pfUI:RegisterModule("thirdparty", function()
end
if C.thirdparty.chatbg == "1" and C.chat.global.custombg == "1" then
local r, g, b, a = strsplit(",", C.chat.global.background)
local r, g, b, a = GetStringColor(C.chat.global.background)
window.backdrop:SetBackdropColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
local r, g, b, a = strsplit(",", C.chat.global.border)
local r, g, b, a = GetStringColor(C.chat.global.border)
window.backdrop:SetBackdropBorderColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
end
@@ -350,10 +350,10 @@ pfUI:RegisterModule("thirdparty", function()
CreateBackdropShadow(frame)
if C.thirdparty.chatbg == "1" and C.chat.global.custombg == "1" then
local r, g, b, a = strsplit(",", C.chat.global.background)
local r, g, b, a = GetStringColor(C.chat.global.background)
frame.backdrop:SetBackdropColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
local r, g, b, a = strsplit(",", C.chat.global.border)
local r, g, b, a = GetStringColor(C.chat.global.border)
frame.backdrop:SetBackdropBorderColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
end
end
+14
View File
@@ -266,4 +266,18 @@ pfUI:RegisterModule("tooltip", function ()
end
GameTooltip:Show()
end
if C.tooltip.aurasource == "1" then
hooksecurefunc(GameTooltip, "SetUnitAura", function(self, ...)
local aura = C_UnitAuras.GetAuraDataByIndex(unpack(arg))
if not aura then return end
local caster = aura.sourceUnit and UnitName(aura.sourceUnit)
if not caster and aura.sourceGUID then
caster = UnitNameFromGUID(aura.sourceGUID)
end
if not caster or caster == "" then return end
self:AddLine(T["Cast by"] .. ": " .. caster, .7, .7, 1)
self:Show()
end)
end
end)
+19
View File
@@ -355,6 +355,25 @@ pfUI:RegisterModule("turtle-wow", function ()
end
end)
if C.disabled["macroicons"] == "1" then
HookAddonOrVariable("Blizzard_MacroUI", function()
if type(UpdateMacroIconFilenames) ~= "function" then return end
function _G.UpdateMacroIconFilenames()
wipe(MACRO_ICON_FILENAMES)
local provider = CreateAndInitFromMixin(IconDataProviderMixin, IconDataProviderExtraType.Spellbook)
local seen = {}
for i = 1, provider:GetNumIcons() do
-- uppercase, prefix-stripped basename, matching Blizzard's original
local icon = string.gsub(string.upper(provider:GetIconByIndex(i)), "INTERFACE\\ICONS\\", "")
if not seen[icon] then
seen[icon] = true
table.insert(MACRO_ICON_FILENAMES, icon)
end
end
provider:Release()
end
end)
end
-- add turtle-wow sell values
pfSellData = {
+1 -1
View File
@@ -23,7 +23,7 @@ do
-- ClassicAPI dependency check.
-- pfUI relies pervasively on the modern C_* / SuperWoW / nameplate / focus
-- API surface that ClassicAPI polyfills, so presence is required.
local PFUI_CLASSIC_API_MIN = 10802 -- (X*10000 + Y*100 + Z)
local PFUI_CLASSIC_API_MIN = 10903 -- (X*10000 + Y*100 + Z)
local PFUI_CLASSIC_API_LATEST = PFUI_CLASSIC_API_MIN
local PFUI_CLASSIC_API_WEBSITE = "https://github.com/brues-code/ClassicAPI"
local PFUI_CLASSIC_API_LATEST_URL = PFUI_CLASSIC_API_WEBSITE .. "/releases/latest"
+26 -24
View File
@@ -60,33 +60,35 @@ pfUI:RegisterSkin("Macro", function ()
MacroFrameScrollFrame:ClearAllPoints()
MacroFrameScrollFrame:SetPoint("TOPLEFT", MacroFrameSelectedMacroBackground, "BOTTOMLEFT", 11, -13)
-- The macroicons module replaces MacroPopupFrame with its own picker.
-- Only skin the stock popup when that module is disabled.
if pfUI_config["disabled"] and pfUI_config["disabled"]["macroicons"] == "1" then
StripTextures(MacroPopupFrame)
CreateBackdrop(MacroPopupFrame, nil, nil, .75)
MacroPopupFrame:SetFrameStrata("DIALOG")
MacroPopupFrame:ClearAllPoints()
MacroPopupFrame:SetPoint("TOPLEFT", MacroFrame.backdrop, "TOPRIGHT", 2*border, 0)
StripTextures(MacroPopupScrollFrame)
SkinScrollbar(MacroPopupScrollFrameScrollBar)
StripTextures(MacroPopupFrame)
CreateBackdrop(MacroPopupFrame, nil, nil, .75)
MacroPopupFrame:SetFrameStrata("DIALOG")
MacroPopupFrame:ClearAllPoints()
MacroPopupFrame:SetPoint("TOPLEFT", MacroFrame.backdrop, "TOPRIGHT", 2*border, 0)
MacroPopupEditBox:DisableDrawLayer("BACKGROUND")
CreateBackdrop(MacroPopupEditBox, nil, true)
MacroPopupEditBox:SetScript("OnEscapePressed", function()
MacroPopupFrame:Hide()
MacroFrame_Update()
end)
StripTextures(MacroPopupScrollFrame)
SkinScrollbar(MacroPopupScrollFrameScrollBar)
MacroPopupEditBox:DisableDrawLayer("BACKGROUND")
CreateBackdrop(MacroPopupEditBox, nil, true)
MacroPopupEditBox:SetScript("OnEscapePressed", function()
MacroPopupFrame:Hide()
MacroFrame_Update()
end)
for i=1, NUM_MACRO_ICONS_SHOWN do
local button = _G["MacroPopupButton"..i]
local icon = _G["MacroPopupButton"..i..'Icon']
StripTextures(button)
SkinButton(button, nil, nil, nil, icon)
for i=1, NUM_MACRO_ICONS_SHOWN do
local button = _G["MacroPopupButton"..i]
local icon = _G["MacroPopupButton"..i..'Icon']
StripTextures(button)
SkinButton(button, nil, nil, nil, icon)
end
SkinButton(MacroPopupCancelButton)
SkinButton(MacroPopupOkayButton)
MacroPopupOkayButton:ClearAllPoints()
MacroPopupOkayButton:SetPoint("RIGHT", MacroPopupCancelButton, "LEFT", -2*bpad, 0)
end
SkinButton(MacroPopupCancelButton)
SkinButton(MacroPopupOkayButton)
MacroPopupOkayButton:ClearAllPoints()
MacroPopupOkayButton:SetPoint("RIGHT", MacroPopupCancelButton, "LEFT", -2*bpad, 0)
end)
end)