-- Equipment manager page of the character sheet's right pane (camelot EquipmentManagerPane). -- The client's GearManagerDialog is moved into the pane and its set buttons are reskinned as -- cards; the client keeps the list, the selection, the tooltips and the clicks. -- Sizes and anchors: camelot PaperDollFrame.xml and GearSetButtonTemplate. ForeverUI = ForeverUI or {} local L = ForeverUI.L -- Card layout differs from camelot's GearSetButtonTemplate (card 169 x 44, background 152 x 49 -- at x = 42): list at x = 9, card 40 high, background 174 x 45 (the art keeps its 5 px overhang). -- Width 174 makes the gap from the pane separator to the icon (8 px) equal the gap from the card -- to the pane's right edge: 233 - 8 - LIST_X - 42 = 174. The button covers the whole card. local CARD_BACKGROUND_W, CARD_BACKGROUND_H = 174, 45 local CARD_BACKGROUND_X = 42 local CARD_W, CARD_H = CARD_BACKGROUND_X + CARD_BACKGROUND_W, 40 local CARD_ICON, CARD_ICON_X = 36, 4 local CARD_TEXT_X = 55 local CARD_TEXT_W, CARD_TEXT_H = 98, 38 -- camelot anchors the checkmark at RIGHT -23 of a button that ends 25 px before the card edge. -- Here the button covers the whole card, so the checkmark sits 12 px from the card's right edge. local CHECKMARK_X = -12 local LIST_X, LIST_Y = 9, -8 local LIST_Y2 = 105 local BUTTON_W, BUTTON_H = 99, 28 local BUTTON_Y, BUTTON_GAP = 20, 50 local NEW_BUTTON_W, NEW_BUTTON_H = 180, 34 local NEW_BUTTON_Y, NEW_BUTTON_ICON_X = 50, 13 -- common-insideframe (107 x 107) has a pattern in each corner: nine-sliced, not stretched. -- Corner 20: the corner pattern ends at 19 px. -- Margins from camelot's TOPLEFT (1, 1) / BOTTOMRIGHT (-4, 2), shifted 3 px to the right. local BORDER_CORNER = 20 local BORDER_MARGINS = { -4, 1, -1, -2 } local ATLAS_BACKGROUND = "ui-character-info-outfitcard" local ATLAS_HOVER = "ui-character-info-outfitcard-hover" local ATLAS_SELECTED = "ui-character-info-outfitcard-selected" local ATLAS_CHECKMARK = "ui-character-info-icon-tick" local ATLAS_CIRCLE = "ui-character-info-outfiticon-frame" local ATLAS_PLUS = "ui-character-info-icon-add" local ATLAS_BORDER = "common-insideframe" local ATLAS_LINE = "ui-character-info-scrollline" -- camelot GearSetButtonTemplate hover buttons: Delete 14 x 14 at BOTTOMRIGHT (-21, 2), Edit -- 16 x 16 left of it (-1); alpha 0.5 idle, 1 on hover; the texture shifts (1, -1) when pressed. -- camelot's gear assigns a specialization, which 3.3.5 lacks: here it reopens the save popup on -- the set to rename it or change its icon. SETTINGS replaces the missing EQUIPMENT_SET_SETTINGS. local DELETE_SIZE = 14 local DELETE_X, DELETE_Y = -21, 2 local EDIT_SIZE = 16 local EDIT_X = -1 local DELETE_ICON = "Interface\\Buttons\\UI-GroupLoot-Pass-Up" local EDIT_ICON = "Interface\\WorldMap\\GEAR_64GREY" local IDLE, HOVER = 0.5, 1.0 local panel, offset = nil, 0 -- Editing a set. 3.3.5 has no ModifyEquipmentSet: SaveEquipmentSet(name, icon) saves the worn -- gear. So an edit equips the old set, saves it under the new name and icon, deletes the old one -- and gives the new name the old one's rank. The swap is asynchronous (EQUIPMENT_SWAP_FINISHED). -- 3.3.5 lists sets in creation order; the display order is kept per character (characterSets). local editSession local intendedClose -- set while the addon hides the popup itself local EDIT_TIMEOUT = 10 -- seconds before an edit is abandoned -- Sets belong to a character and ForeverUIDB to the account: the display order and the last set -- worn are kept per character (one character's order was pruned to another's sets). The first -- use takes over the account-wide values of earlier versions. local function characterSets() ForeverUIDB = ForeverUIDB or {} ForeverUIDB.sets = ForeverUIDB.sets or {} local key = tostring(UnitName("player")) .. "-" .. tostring(GetRealmName()) local sets = ForeverUIDB.sets[key] if not sets then sets = { order = {}, equipped = ForeverUIDB.equippedSet } for rank, name in ipairs(ForeverUIDB.setOrder or {}) do sets.order[rank] = name end ForeverUIDB.sets[key] = sets end return sets end local function savedOrder() return characterSets().order end -- Client set indices in the saved order; sets not yet known go last. local function orderedSets() local total = (GetNumEquipmentSets and GetNumEquipmentSets()) or 0 local byName, inside = {}, {} for index = 1, total do local name = GetEquipmentSetInfo(index) if name then byName[name] = index end end -- Each name is kept once: duplicates take several ranks and push the card past the last rank. -- The saved order is pruned only when the client lists at least one set: between a save and -- its event the client lists none, and pruning would empty it. local order = savedOrder() local list, kept = {}, {} for _, name in ipairs(order) do if byName[name] and not inside[name] then list[#list + 1] = byName[name] inside[name] = true kept[#kept + 1] = name end end for index = 1, total do local name = GetEquipmentSetInfo(index) if name and not inside[name] then list[#list + 1] = index inside[name] = true kept[#kept + 1] = name end end if total > 0 then for rank = #order, 1, -1 do order[rank] = nil end for rank, name in ipairs(kept) do order[rank] = name end end return list end ForeverUI.EquipmentSetsOrder = orderedSets -- A set is worn when each piece is in its own slot (the unpacked slot must match the key). -- 3.3.5 has no active set and two sets can hold the same pieces, so the checkmark goes to the -- last set equipped (UseEquipmentSet hook), only while it is still worn. local function piecesInPlace(name) if not name or not GetEquipmentSetLocations or not EquipmentManager_UnpackLocation then return false end local locations = GetEquipmentSetLocations(name) if not locations then return false end local found = false for slotId, location in pairs(locations) do if type(location) == "number" and location > 1 then local player, _, bags, slot = EquipmentManager_UnpackLocation(location) if not player or bags or slot ~= slotId then return false end found = true end end return found end -- Name of the last set equipped. local function activeSet() return characterSets().equipped end local function isSetWorn(name) return name ~= nil and name == activeSet() and piecesInPlace(name) end -- Remember the last set equipped, across sessions. if hooksecurefunc and type(UseEquipmentSet) == "function" then hooksecurefunc("UseEquipmentSet", function(name) characterSets().equipped = name if ForeverUI.EquipmentSetsLayout then ForeverUI.EquipmentSetsLayout() end end) end -- Height of the card list, and how many cards fit in it. local function listHeight() if not panel then return 0 end return panel:GetHeight() - (-LIST_Y) - LIST_Y2 end local function visibleCards() local location = math.floor(listHeight() / CARD_H) if location < 1 then location = 1 end return location end -- Reskins a client set button as a card, once. local function skinCard(button) if button.foreverCard then return end button:SetWidth(CARD_W) button:SetHeight(CARD_H) -- Hide the 3.3.5 empty-slot background; the icon is the button's NormalTexture and stays. local icon = button:GetNormalTexture() local regions = { button:GetRegions() } for _, region in ipairs(regions) do if region ~= icon and region.GetObjectType and region:GetObjectType() == "Texture" then local path = region.GetTexture and region:GetTexture() if type(path) == "string" and string.find(string.lower(path), "emptyslot") then region:SetAlpha(0) end end end if button:GetHighlightTexture() then button:GetHighlightTexture():SetAlpha(0) end if button.GetCheckedTexture and button:GetCheckedTexture() then button:GetCheckedTexture():SetAlpha(0) end local function map(atlas, layer) local t = button:CreateTexture(nil, layer) ForeverUI.SetAtlas(t, atlas, true) t:SetWidth(CARD_BACKGROUND_W) t:SetHeight(CARD_BACKGROUND_H) t:SetPoint("TOPLEFT", button, "TOPLEFT", CARD_BACKGROUND_X, 0) return t end -- Selected (ARTWORK) draws above hover (BORDER), whatever the creation order. button.foreverBackground = map(ATLAS_BACKGROUND, "BACKGROUND") button.foreverHover = map(ATLAS_HOVER, "BORDER") button.foreverHover:Hide() button.foreverSelected = map(ATLAS_SELECTED, "ARTWORK") button.foreverSelected:Hide() if icon then icon:ClearAllPoints() icon:SetWidth(CARD_ICON) icon:SetHeight(CARD_ICON) icon:SetPoint("LEFT", button, "LEFT", CARD_ICON_X, 0) icon:SetDrawLayer("ARTWORK") end local circle = button:CreateTexture(nil, "OVERLAY") ForeverUI.SetAtlas(circle, ATLAS_CIRCLE) circle:SetPoint("CENTER", icon or button, "CENTER", 0, 0) local checkMark = button:CreateTexture(nil, "OVERLAY") ForeverUI.SetAtlas(checkMark, ATLAS_CHECKMARK) checkMark:SetPoint("RIGHT", button, "RIGHT", CHECKMARK_X, 0) checkMark:Hide() button.foreverCheck = checkMark local text = _G[button:GetName() .. "Name"] if text then text:ClearAllPoints() text:SetFontObject(GameFontNormalLeft or GameFontNormal) text:SetWidth(CARD_TEXT_W) text:SetHeight(CARD_TEXT_H) text:SetJustifyH("LEFT") text:SetPoint("LEFT", button, "LEFT", CARD_TEXT_X, 0) end -- Delete and edit buttons, shown on the hovered card. -- name: frame name suffix; side: size; icon: texture path; tooltip: tooltip text local function smallButton(name, side, icon, tooltip, onClick) local b = CreateFrame("Button", button:GetName() .. name, button) b:SetWidth(side) b:SetHeight(side) b:SetFrameLevel(button:GetFrameLevel() + 2) local t = b:CreateTexture(nil, "ARTWORK") t:SetTexture(icon) t:SetAllPoints(b) t:SetAlpha(IDLE) b.texture = t b:SetScript("OnEnter", function(self) self.texture:SetAlpha(HOVER) GameTooltip:SetOwner(self, "ANCHOR_RIGHT") GameTooltip:SetText(tooltip) GameTooltip:Show() end) b:SetScript("OnLeave", function(self) self.texture:SetAlpha(IDLE) GameTooltip:Hide() end) b:SetScript("OnMouseDown", function(self) self.texture:ClearAllPoints() self.texture:SetPoint("TOPLEFT", self, "TOPLEFT", 1, -1) self.texture:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", 1, -1) end) b:SetScript("OnMouseUp", function(self) self.texture:ClearAllPoints() self.texture:SetAllPoints(self) end) b:SetScript("OnClick", onClick) b:Hide() return b end button.foreverDelete = smallButton("ForeverUIDelete", DELETE_SIZE, DELETE_ICON, DELETE, function(self) local map = self:GetParent() if not map.name or map.name == "" then return end local window = StaticPopup_Show("CONFIRM_DELETE_EQUIPMENT_SET", map.name) if window then window.data = map.name elseif UIErrorsFrame then UIErrorsFrame:AddMessage(ERR_CLIENT_LOCKED_OUT, 1.0, 0.1, 0.1, 1.0) end end) button.foreverDelete:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", DELETE_X, DELETE_Y) button.foreverEdit = smallButton("ForeverUIEdit", EDIT_SIZE, EDIT_ICON, SETTINGS, function(self) local map = self:GetParent() if not map.name or map.name == "" then return end if ForeverUI.EquipmentSetEdit then ForeverUI.EquipmentSetEdit(map.name) end end) button.foreverEdit:SetPoint("RIGHT", button.foreverDelete, "LEFT", EDIT_X, 0) button.foreverCard = true end -- The set list is polled: sets live on the server, and no return, event or frame delay tells -- when the client list is ready. Every 0.2 s while the pane is shown, the count, names and icons -- are compared with the last layout, and the cards are laid out again when they differ. local WATCH_INTERVAL = 0.2 local lastState, sinceCheck = nil, 0 local function listState() local total = (GetNumEquipmentSets and GetNumEquipmentSets()) or 0 local parts = { tostring(total) } for index = 1, total do local name, icon = GetEquipmentSetInfo(index) parts[#parts + 1] = tostring(name) .. "=" .. tostring(icon) end return table.concat(parts, "|") end local function watchList(elapsed) sinceCheck = sinceCheck + (elapsed or 0) if sinceCheck < WATCH_INTERVAL then return end sinceCheck = 0 local state = listState() if state ~= lastState then lastState = state if GearManagerDialog_Update then GearManagerDialog_Update() end -- layoutCards is defined below: call it through the published function. ForeverUI.EquipmentSetsLayout() end end -- Makes the next poll lay out the list again. function ForeverUI.EquipmentSetsForget() lastState = nil sinceCheck = WATCH_INTERVAL end -- Hover is polled each frame, as in camelot's PaperDollEquipmentManagerPane_OnUpdate: the -- delete and edit buttons are children of the card, so entering them fires the card's OnLeave. local function trackHover(self, elapsed) watchList(elapsed) local dialog = _G["GearManagerDialog"] if not dialog or not dialog.buttons then return end for _, button in ipairs(dialog.buttons) do if button.foreverCard and button:IsShown() then local hovered = button:IsMouseOver() and button.name and button.name ~= "" if hovered then button.foreverHover:Show() button.foreverDelete:Show() button.foreverEdit:Show() else button.foreverHover:Hide() button.foreverDelete:Hide() button.foreverEdit:Hide() end end end end ForeverUI.EquipmentSetsHover = trackHover -- Lays out the existing sets in a column, from the scroll offset. local function layoutCards() local dialog = _G["GearManagerDialog"] if not dialog or not dialog.buttons or not panel then return end local total = (GetNumEquipmentSets and GetNumEquipmentSets()) or 0 local location = visibleCards() if offset > total - location then offset = math.max(0, total - location) end -- Button i holds client set i; the saved order decides where it goes. local order = orderedSets() local buttonRank = {} for position, index in ipairs(order) do buttonRank[index] = position end local previous local byRank = {} for index, button in ipairs(dialog.buttons) do skinCard(button) if buttonRank[index] then byRank[buttonRank[index]] = button end end for position = 1, #dialog.buttons do local button = byRank[position] -- The client index is order[position], not the position itself. local index = order[position] local rank = position - offset if button and index and index <= total and rank >= 1 and rank <= location then button:ClearAllPoints() if previous then button:SetPoint("TOPLEFT", previous, "BOTTOMLEFT", 0, 0) else button:SetPoint("TOPLEFT", panel, "TOPLEFT", LIST_X, LIST_Y) end previous = button button:Show() if button.foreverSelected then if button:GetChecked() then button.foreverSelected:Show() else button.foreverSelected:Hide() end end if button.foreverCheck then if isSetWorn(button.name) then button.foreverCheck:Show() else button.foreverCheck:Hide() end end elseif button then button:Hide() end end -- Buttons without a set stay hidden. for index, button in ipairs(dialog.buttons) do if not buttonRank[index] then button:Hide() end end end ForeverUI.EquipmentSetsLayout = layoutCards local function build(pane) panel = CreateFrame("Frame", "ForeverUIEquipmentPane", pane) -- Hidden until ForeverUI.Panes shows it as the equipment page: GearManagerDialog_Update also -- runs on EQUIPMENT_SWAP_FINISHED (a set worn from a bar, /equipset) and can build the panel -- before that page was ever opened, over the stats page panel:Hide() panel:SetPoint("TOPLEFT", pane.stone, "BOTTOMLEFT", 0, 0) panel:SetPoint("BOTTOMRIGHT", pane, "BOTTOMRIGHT", 0, 0) panel:SetFrameLevel(pane:GetFrameLevel() + 3) panel.border = ForeverUI.CreateNineSlice(panel, ATLAS_BORDER, BORDER_CORNER, BORDER_MARGINS, "BORDER") local line = panel:CreateTexture(nil, "BORDER") ForeverUI.SetAtlas(line, ATLAS_LINE) line:SetPoint("TOP", panel, "BOTTOM", 0, LIST_Y2) -- Mouse wheel scrolling: camelot's scroll bar is not reproduced. panel:SetScript("OnUpdate", trackHover) panel:EnableMouseWheel(true) panel:SetScript("OnMouseWheel", function(self, direction) local total = (GetNumEquipmentSets and GetNumEquipmentSets()) or 0 offset = math.max(0, math.min(offset - direction, total - visibleCards())) layoutCards() end) return panel end -- Equip and Save stay the client's. New Set clears the selection then runs Save (3.3.5 has no -- New Set button). The client's Delete is hidden: each card has its own. local function placeButtons() local equipButton = _G["GearManagerDialogEquipSet"] local saveButton = _G["GearManagerDialogSaveSet"] local clear = _G["GearManagerDialogDeleteSet"] if equipButton then equipButton:SetWidth(BUTTON_W) equipButton:SetHeight(BUTTON_H) equipButton:ClearAllPoints() equipButton:SetPoint("BOTTOM", panel, "BOTTOM", -BUTTON_GAP, BUTTON_Y) end if saveButton then saveButton:SetWidth(BUTTON_W) saveButton:SetHeight(BUTTON_H) saveButton:ClearAllPoints() saveButton:SetPoint("BOTTOM", panel, "BOTTOM", BUTTON_GAP, BUTTON_Y) end if clear then clear:SetWidth(BUTTON_W) clear:SetHeight(BUTTON_H) clear:ClearAllPoints() clear:SetPoint("BOTTOM", saveButton or panel, "TOP", 0, 0) clear:Hide() end if not panel.new then local new = CreateFrame("Button", "ForeverUIEquipmentNewSet", panel, "UIPanelButtonTemplate") new:SetWidth(NEW_BUTTON_W) new:SetHeight(NEW_BUTTON_H) new:SetPoint("BOTTOM", panel, "BOTTOM", 0, NEW_BUTTON_Y) -- No client string matches camelot's PAPERDOLL_NEWEQUIPMENTSET. new:SetText(L.EQUIPMENTMANAGER_NEW_SET) -- Same look as the stat selectors: tertiary button, pressed while held. ForeverUI.SkinTertiaryButton(new, true) local plus = new:CreateTexture(nil, "OVERLAY") ForeverUI.SetAtlas(plus, ATLAS_PLUS) plus:SetPoint("LEFT", new, "LEFT", NEW_BUTTON_ICON_X, 0) new:SetScript("OnClick", function() local dialog = _G["GearManagerDialog"] if dialog then dialog.selectedSetName = nil dialog.selectedSet = nil end if GearManagerDialogSaveSet_OnClick then GearManagerDialogSaveSet_OnClick(_G["GearManagerDialogSaveSet"]) end end) panel.new = new end -- New Set must stay above the client dialog, which covers the pane and takes the mouse. -- The dialog raises itself on each OnShow and this runs after it, so the level is recomputed -- from the dialog's current level every time. local dialog = _G["GearManagerDialog"] if panel.new and dialog then panel.new:SetFrameLevel(dialog:GetFrameLevel() + 5) end end -- Moves the client dialog into the pane: its window art is hidden and it covers the pane, -- holding only its cards and buttons. local function hostDialog() local dialog = _G["GearManagerDialog"] if not dialog or dialog.foreverHosted then return end dialog:SetParent(panel) dialog:ClearAllPoints() dialog:SetAllPoints(panel) dialog:SetFrameLevel(panel:GetFrameLevel() + 1) -- GearManagerDialog is toplevel and its OnShow calls Raise(), which would put it above -- everything in the pane on each opening. if dialog.SetToplevel then dialog:SetToplevel(false) end local regions = { dialog:GetRegions() } for _, region in ipairs(regions) do if region.GetObjectType and region:GetObjectType() == "Texture" then region:SetAlpha(0) end end if dialog.title then dialog.title:Hide() end dialog.foreverHosted = true end -- Hides the red close button, every time: the client shows it again when it reopens. -- UIPanelDialogTemplate names it $parentClose (GearManagerDialogClose), not $parentCloseButton. local function hideCloseButton() for _, name in ipairs({ "GearManagerDialogClose", "GearManagerDialogCloseButton" }) do local button = _G[name] if button then button:Hide() end end end local function applySkin() local panes = ForeverUI.CharacterPanes local pane = panes and panes.right if not pane or not pane.stone or InCombatLockdown() then return end if not panel then build(pane) end -- The pane is a page of the right host: ForeverUI.Panes shows and hides it with the client -- dialog, now its child, so it does not survive a side tab change. hostDialog() hideCloseButton() placeButtons() layoutCards() end -- GearManagerDialog is one of the client's UIChildWindows: CloseChildWindows hides it whenever a -- panel opens beside the sheet, and the page loses its Equip and Save buttons. Shown again while -- the page is on screen; out of combat only, its OnShow writes an attribute of the protected -- CharacterFrame and calls UpdateUIPanelPositions. hooksecurefunc("CloseChildWindows", function() local dialog = _G["GearManagerDialog"] if dialog and dialog.foreverHosted and panel and panel:IsVisible() and not dialog:IsShown() and not InCombatLockdown() then dialog:Show() end end) ForeverUI.EquipmentPane = { Apply = applySkin, -- The page records its root once: the panel (our own frame) is built even in combat, where -- applySkin waits, or a page first opened in combat would stay empty for the session Frame = function() local panes = ForeverUI.CharacterPanes if not panel and panes and panes.right and panes.right.stone then build(panes.right) end return panel end, } if hooksecurefunc then for _, name in ipairs({ "GearManagerDialog_Update", "GearManagerDialog_OnShow" }) do if type(_G[name]) == "function" then hooksecurefunc(name, function() applySkin() end) end end end -- ========== Edit a set local pendingEdit = CreateFrame("Frame", "ForeverUIEquipmentEdit") pendingEdit:Hide() -- Gives new the rank of old in the saved order (appended if old is not there). local function replaceInOrder(old, new) local order = savedOrder() local location for rank, known in ipairs(order) do if known == old then order[rank] = new location = rank break end end if not location then location = #order + 1 order[location] = new end -- The new name may already be elsewhere in the order: keep only this copy. for rank = #order, 1, -1 do if rank ~= location and order[rank] == new then table.remove(order, rank) if rank < location then location = location - 1 end end end end -- Saves the worn gear under the new name and icon, then deletes the old set. local function finishEdit() local e = editSession editSession = nil pendingEdit:Hide() if not e then return end -- SaveEquipmentSet refuses an empty name. if not e.name or e.name == "" or not SaveEquipmentSet then return end -- GearManagerDialogPopup_Update sets selectedIcon only for icons on the visible page: when the -- set's icon is on another page it stays nil and SaveEquipmentSet fails, so the edit stops -- before anything is deleted. if type(e.icon) ~= "number" then if UIErrorsFrame then UIErrorsFrame:AddMessage(ERR_CLIENT_LOCKED_OUT, 1.0, 0.1, 0.1, 1.0) end return end local renamed = e.name ~= e.old -- At MAX_EQUIPMENT_SETS_PER_PLAYER a rename has no free slot to save before deleting, so the -- old set is deleted first. Its gear is worn at this point, so nothing is lost. local cap = MAX_EQUIPMENT_SETS_PER_PLAYER or 10 local before = (GetNumEquipmentSets and GetNumEquipmentSets()) or 0 if renamed and before >= cap and DeleteEquipmentSet then DeleteEquipmentSet(e.old) replaceInOrder(e.old, e.name) renamed = false end SaveEquipmentSet(e.name, e.icon) -- Delete the old set only if the new one exists, so a failed save loses nothing. if renamed and DeleteEquipmentSet then if GetEquipmentSetInfoByName and GetEquipmentSetInfoByName(e.name) then DeleteEquipmentSet(e.old) replaceInOrder(e.old, e.name) elseif UIErrorsFrame then UIErrorsFrame:AddMessage(ERR_CLIENT_LOCKED_OUT, 1.0, 0.1, 0.1, 1.0) end end local sets = characterSets() if sets.equipped == e.old then sets.equipped = e.name end local dialog = _G["GearManagerDialog"] if dialog then dialog.selectedSetName = e.name end if ForeverUI.EquipmentSetsRefresh then ForeverUI.EquipmentSetsRefresh() end end -- SaveEquipmentSet and DeleteEquipmentSet return before the client list changes; the list is -- refreshed on EQUIPMENT_SETS_CHANGED, registered here because the client listens to it only -- while its dialog is shown. pendingEdit:RegisterEvent("EQUIPMENT_SETS_CHANGED") pendingEdit:RegisterEvent("EQUIPMENT_SWAP_FINISHED") pendingEdit:SetScript("OnEvent", function(self, event, completed, name) if event == "EQUIPMENT_SETS_CHANGED" then -- On the next frame: the client may not have rebuilt its list yet. if ForeverUI.EquipmentSetsRefresh then ForeverUI.EquipmentSetsRefresh() end return end if editSession and completed and name == editSession.old then finishEdit() end end) -- Abandon an edit whose gear swap does not finish in time (combat, locked item). pendingEdit:SetScript("OnUpdate", function(self, elapsed) if not editSession then self:Hide() return end editSession.rest = (editSession.rest or EDIT_TIMEOUT) - (elapsed or 0) if editSession.rest <= 0 then editSession = nil self:Hide() if UIErrorsFrame then UIErrorsFrame:AddMessage(ERR_CLIENT_LOCKED_OUT, 1.0, 0.1, 0.1, 1.0) end end end) -- Starts an edit: equips the old set; finishEdit runs on EQUIPMENT_SWAP_FINISHED. -- name: new set name; icon: icon index from the popup local function startEdit(name, icon) if not editSession or not name or name == "" then editSession = nil return end editSession.name = name editSession.icon = icon -- Already worn: nothing to equip. if piecesInPlace(editSession.old) then finishEdit() return end if UseEquipmentSet then UseEquipmentSet(editSession.old) end editSession.rest = EDIT_TIMEOUT pendingEdit:Show() end -- The popup's Okay is replaced, not hooked: a hook would run after the client has already saved -- the worn gear under that name. Outside an edit the client's handler runs unchanged. local function reclaimOkay() local okay = _G["GearManagerDialogPopupOkay"] if not okay or okay.foreverOkay then return end okay:SetScript("OnClick", function(self, button, pressed) local popup = _G["GearManagerDialogPopup"] if editSession and popup and popup.name and popup.name ~= "" then -- Read the name before hiding: GearManagerDialogPopup_OnHide clears popup.name and abandons -- the edit, hence the intendedClose flag. local name = popup.name -- No icon chosen, no index: the client's GetEquipmentSetIconInfo(nil) raises an error. local iconIndex if popup.selectedIcon then iconIndex = select(2, GetEquipmentSetIconInfo(popup.selectedIcon)) end local inProgress = editSession intendedClose = true popup:Hide() intendedClose = nil editSession = inProgress startEdit(name, iconIndex) return end if GearManagerDialogPopupOkay_OnClick then GearManagerDialogPopupOkay_OnClick(self, button, pressed) end end) okay.foreverOkay = true end -- Opens the save popup on an existing set (called by the gear button). function ForeverUI.EquipmentSetEdit(name) if not name or name == "" then return end local dialog = _G["GearManagerDialog"] if dialog then dialog.selectedSetName = name if GearManagerDialog_Update then GearManagerDialog_Update() end end editSession = { old = name } reclaimOkay() if GearManagerDialogSaveSet_OnClick then GearManagerDialogSaveSet_OnClick(_G["GearManagerDialogSaveSet"]) end -- Set the name here: the client fills the field only in RecalculateGearManagerDialogPopup, -- which only OnShow calls. local field = _G["GearManagerDialogPopupEditBox"] if field then field:SetText(name) end end -- Popup closed another way (Cancel, Escape, the sheet closing): the edit is abandoned. The XML -- binds OnHide to GearManagerDialogPopup_OnHide itself (function=), so a hook on the global -- never runs (see Taxi.lua): the frame's script is hooked. Otherwise the stale session made -- the next ordinary save equip the old set, save it under the typed name and delete it. local editPopup = _G["GearManagerDialogPopup"] if editPopup and editPopup.HookScript then editPopup:HookScript("OnHide", function() if editSession and not intendedClose and not pendingEdit:IsShown() then editSession = nil end end) end -- Lays the list out again on the next frame: right after a save or delete, -- GetNumEquipmentSets and GetEquipmentSetInfo still return the old state. -- Requested by events and by the end of an edit; it hides itself after one run. local listRecheck = CreateFrame("Frame", "ForeverUIEquipmentRecheck") listRecheck:Hide() listRecheck:SetScript("OnUpdate", function(self) self:Hide() if GearManagerDialog_Update then GearManagerDialog_Update() end ForeverUI.EquipmentSetsLayout() end) function ForeverUI.EquipmentSetsRefresh() -- Forget the known state so the next poll lays out the list, whenever the client is done. ForeverUI.EquipmentSetsForget() listRecheck:Show() end