5 Commits
Author SHA1 Message Date
Brues d149b60b6c 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.
2026-08-05 21:58:33 -05:00
Brues be8a8c26b5 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.
2026-08-05 21:43:51 -05:00
Brues 0778f9a7be 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.
2026-08-05 21:43:51 -05:00
Brues 0cc9cf4165 Gate the macro icon hook behind a config flag
Run the Blizzard_MacroUI hook only when C.disabled["macroicons"] is
"1".
2026-08-05 21:43:51 -05:00
Brues 76927c2c18 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.
2026-08-05 21:43:51 -05:00
5 changed files with 510 additions and 136 deletions
+1
View File
@@ -71,6 +71,7 @@
<Include file="..\modules\energytick.lua"/> <Include file="..\modules\energytick.lua"/>
<Include file="..\modules\totems.lua"/> <Include file="..\modules\totems.lua"/>
<Include file="..\modules\macrotweak.lua"/> <Include file="..\modules\macrotweak.lua"/>
<Include file="..\modules\macroicons.lua"/>
<Include file="..\modules\turtle-wow.lua"/> <Include file="..\modules\turtle-wow.lua"/>
<Include file="..\modules\superwow.lua"/> <Include file="..\modules\superwow.lua"/>
<Include file="..\modules\innervatecall.lua"/> <Include file="..\modules\innervatecall.lua"/>
+129 -96
View File
@@ -1,11 +1,10 @@
-- Equipment Manager module -- Equipment Manager module
-- Backport of the 4.3.4 GearManagerDialog UI on top of ClassicAPI's -- Backport of the 4.3.4 GearManagerDialog UI on top of ClassicAPI's C_EquipmentSet.* API.
-- C_EquipmentSet.* API. Adds a 6th tab to CharacterFrame.
pfUI:RegisterModule("equipmentmanager", function() pfUI:RegisterModule("equipmentmanager", function()
if not C_EquipmentSet or not C_EquipmentSet.CanUseEquipmentSets() then return end if not C_EquipmentSet or not C_EquipmentSet.CanUseEquipmentSets() then return end
pfUI.equipmentmanager = pfUI.equipmentmanager or {} pfUI.equipmentmanager = {}
local SET_ROW_HEIGHT = 36 local SET_ROW_HEIGHT = 36
@@ -55,7 +54,7 @@ pfUI:RegisterModule("equipmentmanager", function()
local function EquipSet(setID) local function EquipSet(setID)
if not setID then return end if not setID then return end
if C_EquipmentSet.EquipmentSetContainsLockedItems(setID) then 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 return
end end
ClearCursor() ClearCursor()
@@ -68,8 +67,7 @@ pfUI:RegisterModule("equipmentmanager", function()
-- ============================================================ -- ============================================================
local frame = CreateFrame("Frame", "pfEquipmentManagerFrame", CharacterFrame) local frame = CreateFrame("Frame", "pfEquipmentManagerFrame", CharacterFrame)
frame:SetWidth(220) frame:SetSize(220, 350)
frame:SetHeight(350)
frame:SetFrameStrata("HIGH") frame:SetFrameStrata("HIGH")
frame:SetScript("OnShow", function() frame:SetScript("OnShow", function()
this:ClearAllPoints() this:ClearAllPoints()
@@ -86,7 +84,7 @@ pfUI:RegisterModule("equipmentmanager", function()
frame.title = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal") frame.title = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
frame.title:SetPoint("TOP", frame, "TOP", 0, -10) 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") local closeBtn = CreateFrame("Button", nil, frame, "UIPanelCloseButton")
closeBtn:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -4, -4) closeBtn:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -4, -4)
@@ -98,15 +96,14 @@ pfUI:RegisterModule("equipmentmanager", function()
-- ============================================================ -- ============================================================
local toggleBtn = CreateFrame("Button", "pfEqMgrToggleButton", PaperDollFrame) local toggleBtn = CreateFrame("Button", "pfEqMgrToggleButton", PaperDollFrame)
toggleBtn:SetWidth(28) toggleBtn:SetSize(28, 28)
toggleBtn:SetHeight(28)
toggleBtn:SetPoint("BOTTOM", CharacterHandsSlot, "TOP", 0, 4) toggleBtn:SetPoint("BOTTOM", CharacterHandsSlot, "TOP", 0, 4)
toggleBtn:SetNormalTexture(pfUI.path.."\\img\\UI-GearManager-Button") toggleBtn:SetNormalTexture(pfUI.path.."\\img\\UI-GearManager-Button")
toggleBtn:SetPushedTexture(pfUI.path.."\\img\\UI-GearManager-Button-Pushed") toggleBtn:SetPushedTexture(pfUI.path.."\\img\\UI-GearManager-Button-Pushed")
toggleBtn:SetHighlightTexture("Interface\\Buttons\\ButtonHilight-Square", "ADD") toggleBtn:SetHighlightTexture("Interface\\Buttons\\ButtonHilight-Square", "ADD")
toggleBtn:SetScript("OnEnter", function() toggleBtn:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT") GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText(T["Equipment Manager"] or "Equipment Manager") GameTooltip:SetText(PAPERDOLL_EQUIPMENTMANAGER)
GameTooltip:Show() GameTooltip:Show()
end) end)
toggleBtn:SetScript("OnLeave", function() GameTooltip:Hide() end) toggleBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
@@ -126,13 +123,13 @@ pfUI:RegisterModule("equipmentmanager", function()
-- ============================================================ -- ============================================================
local btnEquip = CreateFrame("Button", "pfEqMgrEquip", frame, "UIPanelButtonTemplate") 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) btnEquip:SetPoint("TOPLEFT", frame, "TOPLEFT", 16, -30)
SkinButton(btnEquip) SkinButton(btnEquip)
btnEquip:SetScript("OnClick", function() EquipSet(selectedSetID) end) btnEquip:SetScript("OnClick", function() EquipSet(selectedSetID) end)
local btnSave = CreateFrame("Button", "pfEqMgrSave", frame, "UIPanelButtonTemplate") 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) btnSave:SetPoint("LEFT", btnEquip, "RIGHT", 6, 0)
SkinButton(btnSave) SkinButton(btnSave)
btnSave:SetScript("OnClick", function() btnSave:SetScript("OnClick", function()
@@ -141,7 +138,7 @@ pfUI:RegisterModule("equipmentmanager", function()
if not name then return end if not name then return end
local targetID = selectedSetID local targetID = selectedSetID
StaticPopupDialogs["PFUI_EQMGR_SAVE_CONFIRM"] = { 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, button1 = YES, button2 = NO,
OnAccept = function() OnAccept = function()
C_EquipmentSet.ClearIgnoredSlotsForSave() C_EquipmentSet.ClearIgnoredSlotsForSave()
@@ -166,7 +163,7 @@ pfUI:RegisterModule("equipmentmanager", function()
local rowMenu = CreateFrame("Frame", "pfEqMgrRowMenu", UIParent) local rowMenu = CreateFrame("Frame", "pfEqMgrRowMenu", UIParent)
rowMenu:SetFrameStrata("DIALOG") rowMenu:SetFrameStrata("DIALOG")
rowMenu:SetWidth(140); rowMenu:SetHeight(50) rowMenu:SetSize(140, 50);
rowMenu:Hide() rowMenu:Hide()
CreateBackdrop(rowMenu, nil, nil, .95) CreateBackdrop(rowMenu, nil, nil, .95)
CreateBackdropShadow(rowMenu) CreateBackdropShadow(rowMenu)
@@ -185,9 +182,9 @@ pfUI:RegisterModule("equipmentmanager", function()
tinsert(UISpecialFrames, "pfEqMgrRowMenu") -- Escape closes it tinsert(UISpecialFrames, "pfEqMgrRowMenu") -- Escape closes it
rowMenu.changeBtn = CreateFrame("Button", nil, rowMenu, "UIPanelButtonTemplate") 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: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) SkinButton(rowMenu.changeBtn)
rowMenu.changeBtn:SetScript("OnClick", function() rowMenu.changeBtn:SetScript("OnClick", function()
rowMenu:Hide() rowMenu:Hide()
@@ -198,9 +195,9 @@ pfUI:RegisterModule("equipmentmanager", function()
end) end)
rowMenu.deleteBtn = CreateFrame("Button", nil, rowMenu, "UIPanelButtonTemplate") 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:SetPoint("TOP", rowMenu.changeBtn, "BOTTOM", 0, -2)
rowMenu.deleteBtn:SetText(T["Delete"] or "Delete") rowMenu.deleteBtn:SetText(DELETE)
SkinButton(rowMenu.deleteBtn) SkinButton(rowMenu.deleteBtn)
rowMenu.deleteBtn:SetScript("OnClick", function() rowMenu.deleteBtn:SetScript("OnClick", function()
rowMenu:Hide() rowMenu:Hide()
@@ -208,7 +205,7 @@ pfUI:RegisterModule("equipmentmanager", function()
local targetID = rowMenu.targetSetID local targetID = rowMenu.targetSetID
local name = C_EquipmentSet.GetEquipmentSetInfo(targetID) local name = C_EquipmentSet.GetEquipmentSetInfo(targetID)
StaticPopupDialogs["PFUI_EQMGR_DELETE"] = { 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, button1 = YES, button2 = NO,
OnAccept = function() OnAccept = function()
pendingIgnoredToggles[targetID] = nil pendingIgnoredToggles[targetID] = nil
@@ -229,9 +226,8 @@ pfUI:RegisterModule("equipmentmanager", function()
local LIST_ROW_STRIDE = SET_ROW_HEIGHT + 2 local LIST_ROW_STRIDE = SET_ROW_HEIGHT + 2
local listFrame = CreateFrame("Frame", nil, frame) local listFrame = CreateFrame("Frame", nil, frame)
listFrame:SetPoint("TOPLEFT", frame, "TOPLEFT", 16, -58) listFrame:SetPoint("TOPLEFT", frame, "TOPLEFT", 16, -58)
listFrame:SetWidth(180)
-- Height fits N rows + (N-1) inter-row gaps + 3px top/bottom padding. -- 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) CreateBackdrop(listFrame, nil, nil, .75)
-- Mouse-wheel scroll: list of [sets..., newSetRow] is virtualized -- Mouse-wheel scroll: list of [sets..., newSetRow] is virtualized
@@ -247,15 +243,13 @@ pfUI:RegisterModule("equipmentmanager", function()
local setRows = {} local setRows = {}
local function CreateSetRow() local function CreateSetRow()
local row = CreateFrame("Button", nil, listFrame) local row = CreateFrame("Button", nil, listFrame)
row:SetWidth(170) row:SetSize(170, SET_ROW_HEIGHT)
row:SetHeight(SET_ROW_HEIGHT)
-- Position is set per-Refresh based on the row's visible slot; -- Position is set per-Refresh based on the row's visible slot;
-- start anchored to avoid uninitialized geometry before first Refresh. -- start anchored to avoid uninitialized geometry before first Refresh.
row:SetPoint("TOPLEFT", listFrame, "TOPLEFT", 5, -3) row:SetPoint("TOPLEFT", listFrame, "TOPLEFT", 5, -3)
row.icon = row:CreateTexture(nil, "ARTWORK") row.icon = row:CreateTexture(nil, "ARTWORK")
row.icon:SetWidth(30) row.icon:SetSize(30, 30)
row.icon:SetHeight(30)
row.icon:SetPoint("LEFT", row, "LEFT", 2, 0) row.icon:SetPoint("LEFT", row, "LEFT", 2, 0)
row.icon:SetTexCoord(.08, .92, .08, .92) row.icon:SetTexCoord(.08, .92, .08, .92)
@@ -270,7 +264,7 @@ pfUI:RegisterModule("equipmentmanager", function()
row.highlight:Hide() row.highlight:Hide()
row.gear = CreateFrame("Button", nil, row) 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:SetPoint("RIGHT", row, "RIGHT", -4, 0)
row.gear.tex = row.gear:CreateTexture(nil, "ARTWORK") row.gear.tex = row.gear:CreateTexture(nil, "ARTWORK")
row.gear.tex:SetAllPoints(row.gear) row.gear.tex:SetAllPoints(row.gear)
@@ -306,27 +300,27 @@ pfUI:RegisterModule("equipmentmanager", function()
pfUI.equipmentmanager.Refresh() pfUI.equipmentmanager.Refresh()
end) end)
-- Hover handling: OnLeave fires when the cursor moves onto a child -- Show the gear and tooltip while the cursor is over the row or its
-- (the gear button becomes the topmost mouse target). Use an -- gear child. The gear sits inside the row's rectangle, so a single
-- OnUpdate poll so the gear stays shown while the cursor is over -- MouseIsOver(row) check covers both. OnLeave on the row and the gear
-- the row OR the gear itself. -- 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() row:SetScript("OnEnter", function()
if not this.setID then return end if not row.setID then return end
local name = C_EquipmentSet.GetEquipmentSetInfo(this.setID) local name = C_EquipmentSet.GetEquipmentSetInfo(row.setID)
if name then if name then
GameTooltip:SetOwner(this, "ANCHOR_RIGHT") GameTooltip:SetOwner(row, "ANCHOR_RIGHT")
GameTooltip:SetEquipmentSet(name) GameTooltip:SetEquipmentSet(name)
GameTooltip:Show() GameTooltip:Show()
end end
row.gear:Show() 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) end)
row:SetScript("OnLeave", HideRowHover)
row.gear:SetScript("OnLeave", HideRowHover)
return row return row
end end
@@ -338,10 +332,10 @@ pfUI:RegisterModule("equipmentmanager", function()
-- specific set index (row[i] always shows ids[i]). -- specific set index (row[i] always shows ids[i]).
local newSetRow = CreateFrame("Button", nil, listFrame) 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 = 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:SetPoint("LEFT", newSetRow, "LEFT", 5, 0)
newSetRow.icon:SetTexture(pfUI.path.."\\img\\Character-Plus") 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("LEFT", newSetRow.icon, "RIGHT", 8, 0)
newSetRow.text:SetPoint("RIGHT", newSetRow, "RIGHT", -4, 0) newSetRow.text:SetPoint("RIGHT", newSetRow, "RIGHT", -4, 0)
newSetRow.text:SetJustifyH("LEFT") 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.text:SetTextColor(0.2, 1, 0.2)
newSetRow.highlight = newSetRow:CreateTexture(nil, "BACKGROUND") 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 function MakeButton(name, label, parent, anchor, ax, ay, width)
local b = CreateFrame("Button", name, parent, "UIPanelButtonTemplate") local b = CreateFrame("Button", name, parent, "UIPanelButtonTemplate")
b:SetWidth(width or 70) b:SetSize(width or 70, 22)
b:SetHeight(22)
b:SetText(label) b:SetText(label)
b:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", ax, ay) b:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", ax, ay)
SkinButton(b) SkinButton(b)
@@ -376,8 +369,7 @@ pfUI:RegisterModule("equipmentmanager", function()
local namePopup = CreateFrame("Frame", "pfEqMgrNamePopup", UIParent) local namePopup = CreateFrame("Frame", "pfEqMgrNamePopup", UIParent)
namePopup:SetFrameStrata("DIALOG") namePopup:SetFrameStrata("DIALOG")
namePopup:SetWidth(472) namePopup:SetSize(472, 484)
namePopup:SetHeight(498)
namePopup:SetPoint("CENTER", UIParent, "CENTER") namePopup:SetPoint("CENTER", UIParent, "CENTER")
namePopup:Hide() namePopup:Hide()
CreateBackdrop(namePopup, nil, nil, .9) CreateBackdrop(namePopup, nil, nil, .9)
@@ -388,39 +380,33 @@ pfUI:RegisterModule("equipmentmanager", function()
namePopup:SetScript("OnDragStart", function() this:StartMoving() end) namePopup:SetScript("OnDragStart", function() this:StartMoving() end)
namePopup:SetScript("OnDragStop", function() this:StopMovingOrSizing() 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")
namePopup.nameLabel = namePopup:CreateFontString(nil, "OVERLAY", "GameFontNormal") namePopup.nameLabel = namePopup:CreateFontString(nil, "OVERLAY", "GameFontNormal")
namePopup.nameLabel:SetPoint("TOPLEFT", namePopup, "TOPLEFT", 14, -34) namePopup.nameLabel:SetPoint("TOPLEFT", namePopup, "TOPLEFT", 14, -20)
namePopup.nameLabel:SetText(T["Enter Set Name (Max 16 Characters):"] or "Enter Set Name (Max 16 Characters):") namePopup.nameLabel:SetText(GEARSETS_POPUP_TEXT)
namePopup.editbox = CreateFrame("EditBox", "pfEqMgrNameEdit", namePopup, "InputBoxTemplate") namePopup.editbox = CreateFrame("EditBox", "pfEqMgrNameEdit", namePopup, "InputBoxTemplate")
namePopup.editbox:SetWidth(280) namePopup.editbox:SetSize(280, 20)
namePopup.editbox:SetHeight(20) namePopup.editbox:SetPoint("TOPLEFT", namePopup, "TOPLEFT", 14, -38)
namePopup.editbox:SetPoint("TOPLEFT", namePopup, "TOPLEFT", 14, -52)
namePopup.editbox:SetAutoFocus(false) namePopup.editbox:SetAutoFocus(false)
namePopup.editbox:SetMaxLetters(16) namePopup.editbox:SetMaxLetters(16)
CreateBackdrop(namePopup.editbox) CreateBackdrop(namePopup.editbox)
namePopup.selectedLabel = namePopup:CreateFontString(nil, "OVERLAY", "GameFontNormal") namePopup.selectedLabel = namePopup:CreateFontString(nil, "OVERLAY", "GameFontNormal")
namePopup.selectedLabel:SetPoint("TOPRIGHT", namePopup, "TOPRIGHT", -14, -28) namePopup.selectedLabel:SetPoint("TOPRIGHT", namePopup, "TOPRIGHT", -14, -14)
namePopup.selectedLabel:SetText(T["Currently Selected"] or "Currently Selected") namePopup.selectedLabel:SetText(ICON_SELECTION_TITLE_CURRENT)
namePopup.selectedLabel:SetTextColor(1, 0.82, 0) namePopup.selectedLabel:SetTextColor(1, 0.82, 0)
namePopup.selectedPreview = CreateFrame("Frame", nil, namePopup) namePopup.selectedPreview = CreateFrame("Frame", nil, namePopup)
namePopup.selectedPreview:SetWidth(42) namePopup.selectedPreview:SetSize(42, 42)
namePopup.selectedPreview:SetHeight(42) namePopup.selectedPreview:SetPoint("TOPRIGHT", namePopup, "TOPRIGHT", -14, -30)
namePopup.selectedPreview:SetPoint("TOPRIGHT", namePopup, "TOPRIGHT", -14, -44)
CreateBackdrop(namePopup.selectedPreview) CreateBackdrop(namePopup.selectedPreview)
namePopup.selectedPreview.tex = namePopup.selectedPreview:CreateTexture(nil, "ARTWORK") namePopup.selectedPreview.tex = namePopup.selectedPreview:CreateTexture(nil, "ARTWORK")
namePopup.selectedPreview.tex:SetAllPoints(namePopup.selectedPreview) namePopup.selectedPreview.tex:SetAllPoints(namePopup.selectedPreview)
namePopup.selectedPreview.tex:SetTexCoord(.08, .92, .08, .92) namePopup.selectedPreview.tex:SetTexCoord(.08, .92, .08, .92)
namePopup.iconLabel = namePopup:CreateFontString(nil, "OVERLAY", "GameFontNormal") namePopup.iconLabel = namePopup:CreateFontString(nil, "OVERLAY", "GameFontNormal")
namePopup.iconLabel:SetPoint("TOPLEFT", namePopup, "TOPLEFT", 14, -100) namePopup.iconLabel:SetPoint("TOPLEFT", namePopup, "TOPLEFT", 14, -86)
namePopup.iconLabel:SetText(T["Choose an Icon:"] or "Choose an Icon:") namePopup.iconLabel:SetText(MACRO_POPUP_CHOOSE_ICON)
-- Icon picker: 10×8 grid of buttons + scroll -- Icon picker: 10×8 grid of buttons + scroll
local ICON_GRID_COLS = 10 local ICON_GRID_COLS = 10
@@ -429,7 +415,7 @@ pfUI:RegisterModule("equipmentmanager", function()
local ICON_BTN_PAD = 6 local ICON_BTN_PAD = 6
local iconScroll = CreateFrame("ScrollFrame", "pfEqMgrIconScroll", namePopup, "FauxScrollFrameTemplate") local iconScroll = CreateFrame("ScrollFrame", "pfEqMgrIconScroll", namePopup, "FauxScrollFrameTemplate")
iconScroll:SetPoint("TOPLEFT", namePopup, "TOPLEFT", 14, -130) iconScroll:SetPoint("TOPLEFT", namePopup, "TOPLEFT", 14, -116)
iconScroll:SetWidth(ICON_GRID_COLS * (ICON_BTN_SIZE + ICON_BTN_PAD) - ICON_BTN_PAD) 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) iconScroll:SetHeight(ICON_GRID_ROWS * (ICON_BTN_SIZE + ICON_BTN_PAD) - ICON_BTN_PAD)
@@ -441,6 +427,9 @@ pfUI:RegisterModule("equipmentmanager", function()
-- you switch the filter to "Items" and it's no longer in the visible list. -- you switch the filter to "Items" and it's no longer in the visible list.
local QUESTION_MARK = "INTERFACE\\ICONS\\INV_MISC_QUESTIONMARK" local QUESTION_MARK = "INTERFACE\\ICONS\\INV_MISC_QUESTIONMARK"
local selectedIconPath = QUESTION_MARK local selectedIconPath = QUESTION_MARK
local searchText = ""
local filtered = nil -- provider indices matching the search, or nil when empty
local RebuildFilter -- forward-declared; assigned below
local function EnsureProvider() local function EnsureProvider()
if not provider then if not provider then
@@ -462,7 +451,7 @@ pfUI:RegisterModule("equipmentmanager", function()
-- Filter dropdown: "All Icons" / "Spells" / "Items" (top right of icon area). -- Filter dropdown: "All Icons" / "Spells" / "Items" (top right of icon area).
local filterDropdown = CreateFrame("Frame", "pfEqMgrIconFilter", namePopup, "UIDropDownMenuTemplate") local filterDropdown = CreateFrame("Frame", "pfEqMgrIconFilter", namePopup, "UIDropDownMenuTemplate")
filterDropdown:SetPoint("TOPRIGHT", namePopup, "TOPRIGHT", 0, -94) filterDropdown:SetPoint("TOPRIGHT", namePopup, "TOPRIGHT", 0, -80)
local currentFilter = "all" local currentFilter = "all"
local function ApplyFilter(value) local function ApplyFilter(value)
currentFilter = value currentFilter = value
@@ -474,20 +463,21 @@ pfUI:RegisterModule("equipmentmanager", function()
-- Don't reset selection — selectedIconPath persists. If the -- Don't reset selection — selectedIconPath persists. If the
-- selected icon isn't in the new filter, no grid entry will be -- selected icon isn't in the new filter, no grid entry will be
-- highlighted but Save will still write the chosen icon. -- highlighted but Save will still write the chosen icon.
RebuildFilter()
pfUI.equipmentmanager.RefreshIconGrid() pfUI.equipmentmanager.RefreshIconGrid()
end end
end end
UIDropDownMenu_Initialize(filterDropdown, function() UIDropDownMenu_Initialize(filterDropdown, function()
local info local info
info = {}; info.text = T["All Icons"] or "All Icons"; info.value = "all" info = {}; info.text = ICON_FILTER_ALL; info.value = "all"
info.func = function() ApplyFilter("all") end info.func = function() ApplyFilter("all") end
info.checked = currentFilter == "all" info.checked = currentFilter == "all"
UIDropDownMenu_AddButton(info) UIDropDownMenu_AddButton(info)
info = {}; info.text = T["Spells"] or "Spells"; info.value = "spells" info = {}; info.text = ICON_FILTER_SPELL; info.value = "spells"
info.func = function() ApplyFilter("spells") end info.func = function() ApplyFilter("spells") end
info.checked = currentFilter == "spells" info.checked = currentFilter == "spells"
UIDropDownMenu_AddButton(info) UIDropDownMenu_AddButton(info)
info = {}; info.text = T["Items"] or "Items"; info.value = "items" info = {}; info.text = ICON_FILTER_ITEM; info.value = "items"
info.func = function() ApplyFilter("items") end info.func = function() ApplyFilter("items") end
info.checked = currentFilter == "items" info.checked = currentFilter == "items"
UIDropDownMenu_AddButton(info) UIDropDownMenu_AddButton(info)
@@ -531,9 +521,32 @@ pfUI:RegisterModule("equipmentmanager", function()
end end
end end
-- Normalize a texture path to an uppercase basename for search matching.
local function IconKey(path)
if type(path) ~= "string" then return path end
return string.gsub(string.upper(path), "^.*[\\/]", "")
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 pfUI.equipmentmanager.RefreshIconGrid() function pfUI.equipmentmanager.RefreshIconGrid()
EnsureProvider() EnsureProvider()
local numIcons = provider:GetNumIcons() local numIcons = filtered and table.getn(filtered) or provider:GetNumIcons()
local numRows = math.ceil(numIcons / ICON_GRID_COLS) local numRows = math.ceil(numIcons / ICON_GRID_COLS)
FauxScrollFrame_Update(iconScroll, numRows, ICON_GRID_ROWS, ICON_BTN_SIZE + ICON_BTN_PAD) FauxScrollFrame_Update(iconScroll, numRows, ICON_GRID_ROWS, ICON_BTN_SIZE + ICON_BTN_PAD)
local offset = FauxScrollFrame_GetOffset(iconScroll) local offset = FauxScrollFrame_GetOffset(iconScroll)
@@ -542,10 +555,11 @@ pfUI:RegisterModule("equipmentmanager", function()
local btn = iconButtons[i] local btn = iconButtons[i]
if listIdx <= numIcons then if listIdx <= numIcons then
btn:Show() btn:Show()
btn.iconIndex = listIdx local providerIdx = filtered and filtered[listIdx] or listIdx
local path = provider:GetIconByIndex(listIdx) btn.iconIndex = providerIdx
local path = provider:GetIconByIndex(providerIdx)
btn.texture:SetTexture(path) btn.texture:SetTexture(path)
if path == selectedIconPath then if IconKey(path) == IconKey(selectedIconPath) then
btn.backdrop:SetBackdropBorderColor(1, 0.82, 0, 1) btn.backdrop:SetBackdropBorderColor(1, 0.82, 0, 1)
else else
btn.backdrop:SetBackdropBorderColor(pfUI.cache.er, pfUI.cache.eg, pfUI.cache.eb, pfUI.cache.ea) btn.backdrop:SetBackdropBorderColor(pfUI.cache.er, pfUI.cache.eg, pfUI.cache.eb, pfUI.cache.ea)
@@ -568,15 +582,17 @@ pfUI:RegisterModule("equipmentmanager", function()
if provider then provider:Release(); provider = nil end if provider then provider:Release(); provider = nil end
end) end)
local btnPopupOK = MakeButton("pfEqMgrPopupOK", T["OK"] or "OK", namePopup, namePopup, 14, -340, 80) local btnPopupOK = MakeButton("pfEqMgrPopupOK", OKAY, 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:ClearAllPoints()
btnPopupCancel:SetPoint("BOTTOMRIGHT", namePopup, "BOTTOMRIGHT", -14, 12) btnPopupCancel:SetPoint("BOTTOMRIGHT", namePopup, "BOTTOMRIGHT", -14, 12)
btnPopupCancel:SetScript("OnClick", function() namePopup:Hide() end) 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() btnPopupOK:SetScript("OnClick", function()
local name = namePopup.editbox:GetText() local name = namePopup.editbox:GetText()
if not name or name == "" then return end if not name or name == "" then return end
@@ -604,6 +620,24 @@ pfUI:RegisterModule("equipmentmanager", function()
pfUI.equipmentmanager.Refresh() pfUI.equipmentmanager.Refresh()
end) end)
-- Search box (bottom-left) filters the icon grid by name.
namePopup.searchLabel = namePopup:CreateFontString(nil, "OVERLAY", "GameFontNormal")
namePopup.searchLabel:SetPoint("BOTTOMLEFT", namePopup, "BOTTOMLEFT", 16, 18)
namePopup.searchLabel:SetText(SEARCH)
namePopup.search = CreateFrame("EditBox", "pfEqMgrIconSearch", namePopup, "InputBoxTemplate")
namePopup.search:SetSize(180, 20)
namePopup.search:SetPoint("LEFT", namePopup.searchLabel, "RIGHT", 10, 0)
namePopup.search:SetAutoFocus(false)
CreateBackdrop(namePopup.search)
namePopup.search:SetScript("OnEscapePressed", function() this:ClearFocus() end)
namePopup.search:SetScript("OnTextChanged", function()
searchText = strupper(strtrim(this:GetText()))
RebuildFilter()
if scrollbar then scrollbar:SetValue(0) end
pfUI.equipmentmanager.RefreshIconGrid()
end)
-- Assignment (not `local function`) so this fills in the forward -- Assignment (not `local function`) so this fills in the forward
-- declaration at the top of the module — closures created earlier -- declaration at the top of the module — closures created earlier
-- (row gear menu, "+ New Set" row, etc.) capture the same upvalue. -- (row gear menu, "+ New Set" row, etc.) capture the same upvalue.
@@ -612,26 +646,32 @@ pfUI:RegisterModule("equipmentmanager", function()
namePopup.editbox:SetText(prefillName or "") namePopup.editbox:SetText(prefillName or "")
EnsureProvider() EnsureProvider()
if prefillIcon then if prefillIcon then
local short = string.gsub(prefillIcon, "INTERFACE\\ICONS\\", "") -- Stored icons come back either as a full path or a bare basename
selectedIconPath = "INTERFACE\\ICONS\\" .. strupper(short) -- (see the row rendering in Refresh). Only prepend the prefix when
-- there's no path separator, so a full path isn't double-prefixed.
selectedIconPath = string.find(prefillIcon, "\\") and prefillIcon
or ("INTERFACE\\ICONS\\" .. prefillIcon)
else else
selectedIconPath = QUESTION_MARK selectedIconPath = QUESTION_MARK
end end
namePopup.search:SetText("")
if action == "rename" then if action == "rename" then
namePopup.title:SetText(T["Rename Set"] or "Rename Set")
iconScroll:Hide() iconScroll:Hide()
for _, b in ipairs(iconButtons) do b:Hide() end for _, b in ipairs(iconButtons) do b:Hide() end
namePopup.iconLabel:Hide() namePopup.iconLabel:Hide()
namePopup.selectedLabel:Hide() namePopup.selectedLabel:Hide()
namePopup.selectedPreview:Hide() namePopup.selectedPreview:Hide()
filterDropdown:Hide() filterDropdown:Hide()
namePopup.searchLabel:Hide()
namePopup.search:Hide()
else else
namePopup.title:SetText(action == "new" and (T["Name Set"] or "Name Set") or (T["Save Set"] or "Save Set"))
iconScroll:Show() iconScroll:Show()
namePopup.iconLabel:Show() namePopup.iconLabel:Show()
namePopup.selectedLabel:Show() namePopup.selectedLabel:Show()
namePopup.selectedPreview:Show() namePopup.selectedPreview:Show()
filterDropdown:Show() filterDropdown:Show()
namePopup.searchLabel:Show()
namePopup.search:Show()
end end
namePopup:Show() namePopup:Show()
if action ~= "rename" then pfUI.equipmentmanager.RefreshIconGrid() end if action ~= "rename" then pfUI.equipmentmanager.RefreshIconGrid() end
@@ -643,9 +683,13 @@ pfUI:RegisterModule("equipmentmanager", function()
-- ============================================================ -- ============================================================
local flyout = CreateFrame("Frame", "pfEqMgrFlyout", UIParent) 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() frame:SetScript("OnHide", function()
for _, b in ipairs(popoutButtons) do b:Hide() end for _, b in ipairs(popoutButtons) do b:Hide() end
flyout:Hide() flyout:Hide()
namePopup:Hide()
end) end)
flyout:SetFrameStrata("DIALOG") flyout:SetFrameStrata("DIALOG")
flyout:Hide() flyout:Hide()
@@ -676,7 +720,7 @@ pfUI:RegisterModule("equipmentmanager", function()
end end
end end
ClearCursor() 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 end
local function MakeFlyoutButton(i) local function MakeFlyoutButton(i)
@@ -692,11 +736,11 @@ pfUI:RegisterModule("equipmentmanager", function()
b:SetScript("OnEnter", function() b:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT") GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
if this.specialAction == "placeInBags" then 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 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 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 elseif this.bag then
GameTooltip:SetBagItem(this.bag, this.slot) GameTooltip:SetBagItem(this.bag, this.slot)
elseif this.invSlot then elseif this.invSlot then
@@ -704,7 +748,7 @@ pfUI:RegisterModule("equipmentmanager", function()
end end
GameTooltip:Show() GameTooltip:Show()
end) end)
b:SetScript("OnLeave", function() GameTooltip:Hide() end) b:SetScript("OnLeave", GameTooltip_Hide)
b:SetScript("OnClick", function() b:SetScript("OnClick", function()
if this.specialAction == "placeInBags" then if this.specialAction == "placeInBags" then
UnequipToBags(flyout.targetInvSlot) UnequipToBags(flyout.targetInvSlot)
@@ -809,7 +853,6 @@ pfUI:RegisterModule("equipmentmanager", function()
if num == 0 then if num == 0 then
flyout:Hide() flyout:Hide()
UIErrorsFrame:AddMessage(T["No matching items in bags"] or "No matching items in bags", 1, 1, 0, 1)
return return
end end
@@ -938,16 +981,6 @@ pfUI:RegisterModule("equipmentmanager", function()
end end
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 -- Refresh
-- ============================================================ -- ============================================================
+337
View File
@@ -0,0 +1,337 @@
-- 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.
if not C_Macro or not C_Macro.CreateMacro then return end
pfUI:RegisterModule("macroicons", function ()
HookAddonOrVariable("Blizzard_MacroUI", function()
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
-- Shared picker state (forward-declared so the grid/filter closures below
-- can capture them before RefreshIconGrid is assigned).
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
-- Spellbook extra type leads with class-relevant spell/talent icons.
provider = CreateAndInitFromMixin(IconDataProviderMixin, IconDataProviderExtraType.Spellbook)
end
end
-- ============================================================
-- Popup frame
-- ============================================================
local picker = CreateFrame("Frame", "pfMacroIconPicker", UIParent)
picker:SetFrameStrata("DIALOG")
picker:SetSize(472, 498)
-- 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)
picker.title = picker:CreateFontString(nil, "OVERLAY", "GameFontNormal")
picker.title:SetPoint("TOP", picker, "TOP", 0, -10)
picker.nameLabel = picker:CreateFontString(nil, "OVERLAY", "GameFontNormal")
picker.nameLabel:SetPoint("TOPLEFT", picker, "TOPLEFT", 14, -34)
picker.nameLabel:SetText(MACRO_POPUP_TEXT)
picker.editbox = CreateFrame("EditBox", "pfMacroIconPickerName", picker, "InputBoxTemplate")
picker.editbox:SetSize(280, 20)
picker.editbox:SetPoint("TOPLEFT", picker, "TOPLEFT", 14, -52)
picker.editbox:SetAutoFocus(false)
picker.editbox:SetMaxLetters(16)
CreateBackdrop(picker.editbox)
picker.selectedPreview = CreateFrame("Frame", nil, picker)
picker.selectedPreview:SetSize(42, 42)
picker.selectedPreview:SetPoint("TOPRIGHT", picker, "TOPRIGHT", -14, -44)
CreateBackdrop(picker.selectedPreview)
picker.selectedPreview.tex = picker.selectedPreview:CreateTexture(nil, "ARTWORK")
picker.selectedPreview.tex:SetAllPoints(picker.selectedPreview)
picker.selectedPreview.tex:SetTexCoord(.08, .92, .08, .92)
picker.selectedLabel = picker:CreateFontString(nil, "OVERLAY", "GameFontNormal")
picker.selectedLabel:SetPoint("TOPRIGHT", picker, "TOPRIGHT", -14, -28)
picker.selectedLabel:SetText(ICON_SELECTION_TITLE_CURRENT)
picker.selectedLabel:SetTextColor(1, 0.82, 0)
picker.iconLabel = picker:CreateFontString(nil, "OVERLAY", "GameFontNormal")
picker.iconLabel:SetPoint("TOPLEFT", picker, "TOPLEFT", 14, -100)
picker.iconLabel:SetText(MACRO_POPUP_CHOOSE_ICON)
-- Icon grid + scroll
local iconScroll = CreateFrame("ScrollFrame", "pfMacroIconScroll", picker, "FauxScrollFrameTemplate")
iconScroll:SetPoint("TOPLEFT", picker, "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)
local scrollbar = _G["pfMacroIconScrollScrollBar"]
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", "pfMacroIconFilter", picker, "UIDropDownMenuTemplate")
filterDropdown:SetPoint("TOPRIGHT", picker, "TOPRIGHT", 0, -94)
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, picker)
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
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
-- Normalize a texture to an uppercase basename so selection matching
-- works across sources: GetMacroInfo returns "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
-- 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
picker.selectedPreview.tex:SetTexture(selectedIconPath)
end
iconScroll:SetScript("OnVerticalScroll", function()
FauxScrollFrame_OnVerticalScroll(ICON_BTN_SIZE + ICON_BTN_PAD, function() RefreshIconGrid() end)
end)
-- ============================================================
-- 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 "")
selectedIconPath = texture or QUESTION_MARK
picker.title:SetText(T["Edit Macro"] or "Edit Macro")
else
picker.mode = "new"
picker.editbox:SetText("")
selectedIconPath = QUESTION_MARK
picker.title:SetText(T["New Macro"] or "New Macro")
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()
RefreshIconGrid()
end
local function SaveMacroPopup()
local text = strtrim(picker.editbox:GetText())
if text == "" then return end
if picker.mode == "edit" and MacroFrame.selectedMacro then
C_Macro.EditMacro(MacroFrame.selectedMacro, text, selectedIconPath, nil)
MacroFrame_SelectMacro(MacroFrame.selectedMacro)
else
local idx = C_Macro.CreateMacro(text, selectedIconPath, nil, (MacroFrame.macroBase or 0) > 0)
if idx then MacroFrame_SelectMacro(idx) end
end
picker:Hide()
end
picker:SetScript("OnHide", function()
if provider then provider:Release(); provider = nil end
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)
-- Search box (bottom-left) filters the grid by icon name.
picker.searchLabel = picker:CreateFontString(nil, "OVERLAY", "GameFontNormal")
picker.searchLabel:SetPoint("BOTTOMLEFT", picker, "BOTTOMLEFT", 16, 18)
picker.searchLabel:SetText(SEARCH)
picker.search = CreateFrame("EditBox", "pfMacroIconSearch", picker, "InputBoxTemplate")
picker.search:SetSize(180, 20)
picker.search:SetPoint("LEFT", picker.searchLabel, "RIGHT", 10, 0)
picker.search:SetAutoFocus(false)
CreateBackdrop(picker.search)
picker.search:SetScript("OnEscapePressed", function() this:ClearFocus() end)
picker.search:SetScript("OnTextChanged", function()
searchText = strupper(strtrim(this:GetText()))
RebuildFilter()
scrollbar:SetValue(0)
RefreshIconGrid()
end)
-- ============================================================
-- 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)
+17 -16
View File
@@ -355,24 +355,25 @@ pfUI:RegisterModule("turtle-wow", function ()
end end
end) end)
HookAddonOrVariable("Blizzard_MacroUI", function() if C.disabled["macroicons"] == "1" or not C_Macro or not C_Macro.CreateMacro then
if type(UpdateMacroIconFilenames) ~= "function" then return end HookAddonOrVariable("Blizzard_MacroUI", function()
function _G.UpdateMacroIconFilenames() if type(UpdateMacroIconFilenames) ~= "function" then return end
wipe(MACRO_ICON_FILENAMES) function _G.UpdateMacroIconFilenames()
local provider = CreateAndInitFromMixin(IconDataProviderMixin, IconDataProviderExtraType.Spellbook) wipe(MACRO_ICON_FILENAMES)
local seen = {} local provider = CreateAndInitFromMixin(IconDataProviderMixin, IconDataProviderExtraType.Spellbook)
for i = 1, provider:GetNumIcons() do local seen = {}
-- uppercase, prefix-stripped basename, matching Blizzard's original for i = 1, provider:GetNumIcons() do
local icon = string.gsub(string.upper(provider:GetIconByIndex(i)), "INTERFACE\\ICONS\\", "") -- uppercase, prefix-stripped basename, matching Blizzard's original
if not seen[icon] then local icon = string.gsub(string.upper(provider:GetIconByIndex(i)), "INTERFACE\\ICONS\\", "")
seen[icon] = true if not seen[icon] then
table.insert(MACRO_ICON_FILENAMES, icon) seen[icon] = true
table.insert(MACRO_ICON_FILENAMES, icon)
end
end end
provider:Release()
end end
provider:Release() end)
end end
end)
-- add turtle-wow sell values -- add turtle-wow sell values
pfSellData = { pfSellData = {
+26 -24
View File
@@ -60,33 +60,35 @@ pfUI:RegisterSkin("Macro", function ()
MacroFrameScrollFrame:ClearAllPoints() MacroFrameScrollFrame:ClearAllPoints()
MacroFrameScrollFrame:SetPoint("TOPLEFT", MacroFrameSelectedMacroBackground, "BOTTOMLEFT", 11, -13) 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) MacroPopupEditBox:DisableDrawLayer("BACKGROUND")
CreateBackdrop(MacroPopupFrame, nil, nil, .75) CreateBackdrop(MacroPopupEditBox, nil, true)
MacroPopupFrame:SetFrameStrata("DIALOG") MacroPopupEditBox:SetScript("OnEscapePressed", function()
MacroPopupFrame:ClearAllPoints() MacroPopupFrame:Hide()
MacroPopupFrame:SetPoint("TOPLEFT", MacroFrame.backdrop, "TOPRIGHT", 2*border, 0) MacroFrame_Update()
end)
StripTextures(MacroPopupScrollFrame) for i=1, NUM_MACRO_ICONS_SHOWN do
SkinScrollbar(MacroPopupScrollFrameScrollBar) local button = _G["MacroPopupButton"..i]
local icon = _G["MacroPopupButton"..i..'Icon']
MacroPopupEditBox:DisableDrawLayer("BACKGROUND") StripTextures(button)
CreateBackdrop(MacroPopupEditBox, nil, true) SkinButton(button, nil, nil, nil, icon)
MacroPopupEditBox:SetScript("OnEscapePressed", function() end
MacroPopupFrame:Hide() SkinButton(MacroPopupCancelButton)
MacroFrame_Update() SkinButton(MacroPopupOkayButton)
end) MacroPopupOkayButton:ClearAllPoints()
MacroPopupOkayButton:SetPoint("RIGHT", MacroPopupCancelButton, "LEFT", -2*bpad, 0)
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 end
SkinButton(MacroPopupCancelButton)
SkinButton(MacroPopupOkayButton)
MacroPopupOkayButton:ClearAllPoints()
MacroPopupOkayButton:SetPoint("RIGHT", MacroPopupCancelButton, "LEFT", -2*bpad, 0)
end) end)
end) end)