---@class QuestieFramePool local QuestieFramePool = QuestieLoader:CreateModule("QuestieFramePool") ------------------------- --Import modules. ------------------------- ---@type QuestieQuest local QuestieQuest = QuestieLoader:ImportModule("QuestieQuest") ---@type QuestieMap local QuestieMap = QuestieLoader:ImportModule("QuestieMap") ---@type MapIconTooltip local MapIconTooltip = QuestieLoader:ImportModule("MapIconTooltip") ---@type QuestieLib local QuestieLib = QuestieLoader:ImportModule("QuestieLib") ---@type l10n local l10n = QuestieLoader:ImportModule("l10n") --- COMPATIBILITY --- local C_Timer = QuestieCompat.C_Timer local WorldMapFrame = QuestieCompat.WorldMapFrame local HBDPins = QuestieCompat.HBDPins or LibStub("HereBeDragonsQuestie-Pins-2.0") -- set pins parent to QuestieFrameGroup for easier compatibility with other addons -- cant use this because it fucks with everything, but we gotta stick with HereBeDragonsQuestie anyway HBDPins.MinimapGroup = CreateFrame("Frame", "QuestieFrameGroup", Minimap) local WAYPOINT_COLOR = { 1, 0.72, 0, 0.5} local _QuestieFramePool = {} local numberOfFrames = 0 local unusedFrames = {} local usedFrames = {}; local allFrames = {} StaticPopupDialogs["QUESTIE_CONFIRMHIDE"] = { text = "", -- set before showing questID = 0, -- set before showing button1 = l10n("Yes"), button2 = l10n("No"), OnAccept = function() QuestieQuest:HideQuest(StaticPopupDialogs["QUESTIE_CONFIRMHIDE"].questID) end, SetQuest = function(self, id) self.questID = id self.text = l10n("Are you sure you want to hide the quest '%s'?\nIf this quest isn't actually available, please report it to us!", QuestieLib:GetColoredQuestName(id, Questie.db.profile.enableTooltipsQuestLevel, false, true)) -- locale might not be loaded when this is first created (this does happen almost always) self.button1 = l10n("Yes") self.button2 = l10n("No") end, OnShow = function(self) self:SetFrameStrata("TOOLTIP") end, timeout = 0, whileDead = true, hideOnEscape = true, preferredIndex = 3 } -- Global Functions -- ---@return IconFrame function QuestieFramePool:GetFrame() --Questie:Debug(Questie.DEBUG_SPAM, "[QuestieFramePool:GetFrame]") ---@type IconFrame local returnFrame = next(unusedFrames) returnFrame = returnFrame and unusedFrames[returnFrame] if returnFrame and returnFrame.frameId and usedFrames[returnFrame.frameId] then -- something went horribly wrong (desync bug?) don't use this frame since its already in use Questie:Debug(Questie.DEBUG_CRITICAL, "[QuestieFramePool:GetFrame] Tried to reuse frame, but that frame is already in use. frameId:", returnFrame.frameId) returnFrame = nil end if not returnFrame then returnFrame = _QuestieFramePool:QuestieCreateFrame() else --Questie:Debug(Questie.DEBUG_SPAM, "[QuestieFramePool:GetFrame] Reusing frame") unusedFrames[returnFrame.frameId] = nil end if returnFrame ~= nil and returnFrame.hidden and returnFrame._show ~= nil and returnFrame._hide ~= nil then -- restore state to normal (toggle questie) returnFrame.hidden = false returnFrame.Show = returnFrame._show; returnFrame.Hide = returnFrame._hide; returnFrame._show = nil returnFrame._hide = nil end returnFrame.FadeLogic = nil returnFrame.faded = nil returnFrame.miniMapIcon = nil returnFrame.data = nil returnFrame.x = nil; returnFrame.y = nil; returnFrame.AreaID = nil; returnFrame.UiMapID = nil if returnFrame.texture then returnFrame.texture:SetVertexColor(1, 1, 1, 1) end returnFrame.loaded = true returnFrame.shouldBeShowing = nil returnFrame.hidden = nil if returnFrame.BaseOnShow then returnFrame:SetScript("OnShow", returnFrame.BaseOnShow) end if returnFrame.BaseOnUpdate then returnFrame.glowLogicTimer = C_Timer.NewTicker(1, returnFrame.BaseOnUpdate); else returnFrame:SetScript("OnUpdate", nil) end if returnFrame.BaseOnHide then returnFrame:SetScript("OnHide", returnFrame.BaseOnHide) end usedFrames[returnFrame.frameId] = returnFrame return returnFrame end function QuestieFramePool:UpdateGlowConfig(mini, mode) if mode then for _, icon in pairs(usedFrames) do if (((mini and icon.miniMapIcon) or not mini) and icon.glow) and icon.IsShown and icon:IsShown() then icon:GetScript("OnShow")(icon) -- forces a glow update end end else for _, icon in pairs(usedFrames) do if ((mini and icon.miniMapIcon) or (not mini and not icon.miniMapIcon)) and icon.glow then icon.glow:Hide() end end end end function QuestieFramePool:UpdateColorConfig(mini, enable) if enable then for _, icon in pairs(usedFrames) do if (mini and icon.miniMapIcon) or (not mini and not icon.miniMapIcon) then local colors = {1, 1, 1} if icon.data.IconColor ~= nil then colors = icon.data.IconColor end icon.texture:SetVertexColor(colors[1], colors[2], colors[3], 1) end end else for _, icon in pairs(usedFrames) do if (mini and icon.miniMapIcon) or (not mini and not icon.miniMapIcon) then icon.texture:SetVertexColor(1, 1, 1, 1) end end end end function QuestieFramePool:RecycleFrame(frame) --Questie:Debug(Questie.DEBUG_SPAM, "[QuestieFramePool:RecycleFrame]") usedFrames[frame.frameId] = nil unusedFrames[frame.frameId] = frame end function _QuestieFramePool:QuestieCreateFrame() --Questie:Debug(Questie.DEBUG_SPAM, "[QuestieFramePool:QuestieCreateFrame]") numberOfFrames = numberOfFrames + 1 local newFrame = QuestieFramePool.Qframe:New(numberOfFrames, MapIconTooltip.Show) tinsert(allFrames, newFrame) return newFrame end ---@param iconFrame IconFrame @The parent frame for the current line. ---@param waypointTable table @A table containing waypoints {{X, Y}, ...} ---@param lineWidth number @Width of the line. ---@param color number[] @A table consisting of 4 variable {1, 1, 1, 1} RGB-Opacity ---@return LineFrame[] function QuestieFramePool:CreateWaypoints(iconFrame, waypointTable, lineWidth, color, areaId) local lineFrameList = {} local lastPos --Set defaults if needed. local lWidth = lineWidth or 1.5; local col = color or WAYPOINT_COLOR for _, waypointSubTable in pairs(waypointTable) do lastPos = nil for _, waypoint in pairs(waypointSubTable) do if lastPos then local lineFrame = QuestieFramePool:CreateLine(iconFrame, lastPos[1], lastPos[2], waypoint[1], waypoint[2], lWidth, col, areaId) tinsert(lineFrameList, lineFrame); end lastPos = waypoint end end return lineFrameList; end local lineFrameCount = 1 ---@param iconFrame IconFrame @The parent frame for the current line. ---@param startX number @A value between 0-100 ---@param startY number @A value between 0-100 ---@param endX number @A value between 0-100 ---@param endY number @A value between 0-100 ---@param lineWidth number @Width of the line. ---@param color number[] @A table consisting of 4 variable {1, 1, 1, 1} RGB-Opacity ---@return LineFrame ---@class LineFrame @A frame that contains the line used in waypoints. function QuestieFramePool:CreateLine(iconFrame, startX, startY, endX, endY, lineWidth, color, areaId) --Create the framepool for lines if it does not already exist. if not QuestieFramePool.Routes_Lines then QuestieFramePool.Routes_Lines={} end --Names are not stricktly needed, but it is nice for debugging. local frameName = "questieLineFrame".. lineFrameCount; --tremove default always picks the last element, however counting arrays is kinda bugged? So just get index 1 instead. local lineFrame = tremove(QuestieFramePool.Routes_Lines, 1) or CreateFrame("Button", frameName, iconFrame); if not lineFrame.frameId then lineFrame.frameId = lineFrameCount; end local canvas = WorldMapFrame:GetCanvas() local width = canvas:GetWidth(); local height = canvas:GetHeight(); --Setting the parent is required to get the correct frame levels. lineFrame:SetParent(canvas) --This fixes the pan and zoom for lines if QuestieCompat.Is335 then lineFrame.CreateLine = QuestieCompat.CreateLine else lineFrame:SetFrameLevel(2015) -- This needs to be high, because of the regular WorldMapFrame.ScrollContainer lineFrame:SetFrameStrata("FULLSCREEN") end --How to identify what the frame actually contains, this is not used atm could easily be changed. lineFrame.type = "line" --Include the line in the iconFrame. if not iconFrame.data.lineFrames then iconFrame.data.lineFrames = {}; end tinsert(iconFrame.data.lineFrames, lineFrame); lineFrame.iconFrame = iconFrame; lineFrame.data = iconFrame.data lineFrame.x = (startX + endX) / 2 lineFrame.y = (startY + endY) / 2 lineFrame.AreaID = areaId or iconFrame.AreaID lineFrame.texture = iconFrame.texture function lineFrame:Unload() if not self.iconFrame then return -- already unloaded end self:Hide(); self.iconFrame = nil; self.x = nil self.y = nil self.data = nil self.texture = nil self.AreaID = nil HBDPins:RemoveWorldMapIcon(Questie, self) tinsert(QuestieFramePool.Routes_Lines, self); end local line = lineFrame.line or lineFrame:CreateLine(); lineFrame.line = line; line.dR = color[1]; line.dG = color[2]; line.dB = color[3]; line.dA = color[4]; line:SetColorTexture(color[1],color[2],color[3],color[4]); local lineBorder = lineFrame.lineBorder or lineFrame:CreateLine(); lineFrame.lineBorder = lineBorder; lineBorder.dR = color[1]; lineBorder.dG = color[2]; lineBorder.dB = color[3]; lineBorder.dA = color[4]; lineBorder:SetColorTexture(0,0,0,color[4]/2); -- Set texture coordinates and anchors --line:ClearAllPoints(); startX = startX * width / 100 startY = startY * height / -100 -- We do by / -100 due to using the top left point endX = endX * width / 100 endY = endY * height / -100 width = abs(startX - endX) + lineWidth * 4 height = abs(startY - endY) + lineWidth * 4 local framePosX = max(startX, endX) - lineWidth * 2 - width / 2 local framePosY = min(startY, endY) + lineWidth * 2 + height / 2 lineFrame:SetHeight(height); lineFrame:SetWidth(width); lineFrame:SetPoint("TOPLEFT", canvas, "TOPLEFT", framePosX, framePosY) line:SetDrawLayer("OVERLAY", -5) line:SetStartPoint("TOPLEFT", startX - framePosX, startY - framePosY) line:SetEndPoint("TOPLEFT", endX - framePosX, endY - framePosY) line:SetThickness(lineWidth); lineBorder:SetDrawLayer("OVERLAY", -6) lineBorder:SetStartPoint("TOPLEFT", startX - framePosX, startY - framePosY) lineBorder:SetEndPoint("TOPLEFT", endX - framePosX, endY - framePosY) lineBorder:SetThickness(lineWidth+2); lineFrame:EnableMouse(true) --- This is needed because HBD will show the icons again after switching zones and stuff like that function lineFrame:FakeHide() if not self.hidden then self.shouldBeShowing = self:IsShown(); self._show = self.Show; self.Show = function() self.shouldBeShowing = true; end self:Hide(); self._hide = self.Hide; self.Hide = function() self.shouldBeShowing = false; end self.hidden = true end end --- This is needed because HBD will show the icons again after switching zones and stuff like that function lineFrame:FakeShow() if self.hidden then self.hidden = false self.Show = self._show; self.Hide = self._hide; self._show = nil self._hide = nil if self.shouldBeShowing then self:Show(); end end end --lineFrame:SetBackdrop({ -- mouseover debugging -- bgFile = "Interface/Tooltips/UI-Tooltip-Background", -- edgeFile = nil, -- edgeSize = 0, -- insets = { left = 0, right = 0, top = 0, bottom = 0 }, --}) --lineFrame:SetBackdropColor(1,0,1,1) lineFrame:SetScript("OnEnter", function(self) if self and self.iconFrame then local script = self.iconFrame:GetScript("OnEnter") if script then script(self.iconFrame) end end end) lineFrame:SetScript("OnLeave", function(self) if self and self.iconFrame then local script = self.iconFrame:GetScript("OnLeave") if script then script(self.iconFrame) end end end) lineFrame:RegisterForClicks("RightButtonUp", "LeftButtonUp") lineFrame:SetScript("OnClick", function(self, button) if self and self.iconFrame then local script = self.iconFrame:GetScript("OnClick") if script then script(self.iconFrame, button) end end end) lineFrame:Hide(); --Should we keep these frames in the questIdFrames? Currently it is also a child of the icon. --Maybe the unload of the parent should just unload the children. --For safety we check this here too. --if (QuestieMap.questIdFrames[lineFrame.iconFrame.data.Id] == nil) then -- QuestieMap.questIdFrames[lineFrame.iconFrame.data.Id] = {} --end --tinsert(QuestieMap.questIdFrames[lineFrame.iconFrame.data.Id], lineFrame:GetName()); --Keep a total lineFrame count for names. lineFrameCount = lineFrameCount + 1; return lineFrame end