diff --git a/Localization/Translations/Options/Tracker.lua b/Localization/Translations/Options/Tracker.lua index ad39abf..b58d56f 100644 --- a/Localization/Translations/Options/Tracker.lua +++ b/Localization/Translations/Options/Tracker.lua @@ -116,10 +116,6 @@ local trackerOptionsLocales = { ["enUS"] = true, }, --------------------------------------------------------- - ["Point the objective marker at this quest"] = { - ["enUS"] = true, - }, - --------------------------------------------------------- ["Auto Minimize Completed Quests"] = { ["ptBR"] = "Minimizar missões concluídas", ["ruRU"] = "Свернуть выполненные", diff --git a/Modules/Options/TrackerTab/QuestieOptionsTracker.lua b/Modules/Options/TrackerTab/QuestieOptionsTracker.lua index 3febc66..ec8790d 100644 --- a/Modules/Options/TrackerTab/QuestieOptionsTracker.lua +++ b/Modules/Options/TrackerTab/QuestieOptionsTracker.lua @@ -200,7 +200,7 @@ function QuestieOptions.tabs.tracker:Initialize() hidden = function() return not TrackerUtils:IsSuperTrackAvailable() end, disabled = function() return (not Questie.db.profile.trackerEnabled) or (not Questie.db.profile.trackerShowSuperTrackButton) end, min = 8, - max = 32, + max = 70, step = 1, get = function() return Questie.db.profile.trackerSuperTrackButtonSize end, set = function(_, value) diff --git a/Modules/Tracker/QuestieTracker.lua b/Modules/Tracker/QuestieTracker.lua index 16885e9..a6a322d 100644 --- a/Modules/Tracker/QuestieTracker.lua +++ b/Modules/Tracker/QuestieTracker.lua @@ -845,7 +845,11 @@ function QuestieTracker:Update() -- Setup local QuestieTracker:Update vars local trackerFontSizeZone = Questie.db.profile.trackerFontSizeZone local trackerFontSizeQuest = Questie.db.profile.trackerFontSizeQuest - local questMarginLeft = (trackerMarginLeft + trackerMarginRight) - (18 - trackerFontSizeQuest) + -- The supertrack button sits at the very left of a quest line, so the whole quest list is + -- indented past it: otherwise it is clipped by the tracker's left edge and collides with the + -- quest item buttons, which share that gutter. + local superTrackMarginLeft = TrackerLinePool.GetSuperTrackMarginReserve() + local questMarginLeft = (trackerMarginLeft + trackerMarginRight) - (18 - trackerFontSizeQuest) + superTrackMarginLeft local objectiveMarginLeft = questMarginLeft + trackerFontSizeQuest local questItemButtonSize = 12 + trackerFontSizeQuest local objectiveColor = Questie.db.profile.trackerColorObjectives @@ -895,9 +899,11 @@ function QuestieTracker:Update() line.criteriaMark:Hide() line.playButton:Hide() - -- Setup Zone Label + -- Setup Zone Label. Indented like the quest lines below it: the supertrack + -- buttons overflow their own line at larger sizes, and the zone label is the + -- only text that would otherwise share that gutter with them. line.label:ClearAllPoints() - line.label:SetPoint("TOPLEFT", line, "TOPLEFT", 0, 0) + line.label:SetPoint("TOPLEFT", line, "TOPLEFT", superTrackMarginLeft, 0) -- Set Zone Title and default Min/Max states if Questie.db.char.collapsedZones[zoneName] then @@ -924,19 +930,19 @@ function QuestieTracker:Update() -- Check and measure Zone Label text width and update tracker width QuestieTracker:UpdateWidth(line.label:GetStringWidth() + trackerMarginLeft + - trackerMarginRight) + superTrackMarginLeft + trackerMarginRight) -- Set Zone Label and Line widths - line.label:SetWidth(trackerBaseFrame:GetWidth() - trackerMarginLeft - trackerMarginRight) - line:SetWidth(line.label:GetWidth()) + line.label:SetWidth(trackerBaseFrame:GetWidth() - trackerMarginLeft - superTrackMarginLeft - trackerMarginRight) + line:SetWidth(line.label:GetWidth() + superTrackMarginLeft) -- Compare largest text Label in the tracker with current Label, then save widest width trackerLineWidth = math.max(trackerLineWidth, - line.label:GetStringWidth() + trackerMarginLeft) + line.label:GetStringWidth() + trackerMarginLeft + superTrackMarginLeft) -- Setup Min/Max Button line.expandZone:ClearAllPoints() - line.expandZone:SetPoint("TOPLEFT", line, "TOPLEFT", 0, 0) + line.expandZone:SetPoint("TOPLEFT", line, "TOPLEFT", superTrackMarginLeft, 0) line.expandZone:SetWidth(line.label:GetWidth()) line.expandZone:SetHeight(line.label:GetHeight()) line.expandZone:Show() @@ -1175,7 +1181,7 @@ function QuestieTracker:Update() end -- Attach button to Quest Title linePool - button:SetPoint("TOPLEFT", button.line, "TOPLEFT", 0, 0) + button:SetPoint("TOPLEFT", button.line, "TOPLEFT", superTrackMarginLeft, 0) button:SetParent(button.line) button:Show() @@ -1283,7 +1289,7 @@ function QuestieTracker:Update() -- Attach button to Quest Title linePool altButton:SetPoint("TOPLEFT", altButton.line, "TOPLEFT", - 2 + questItemButtonSize, 0) + superTrackMarginLeft + 2 + questItemButtonSize, 0) altButton:SetParent(altButton.line) altButton:Show() @@ -1642,9 +1648,9 @@ function QuestieTracker:Update() line.criteriaMark:Hide() line.playButton:Hide() - -- Setup Zone Label + -- Setup Zone Label (indented past the supertrack button gutter, as above) line.label:ClearAllPoints() - line.label:SetPoint("TOPLEFT", line, "TOPLEFT", 0, 0) + line.label:SetPoint("TOPLEFT", line, "TOPLEFT", superTrackMarginLeft, 0) -- Set Zone Title and Min/Max states if Questie.db.char.collapsedZones[zoneName] then @@ -1673,15 +1679,15 @@ function QuestieTracker:Update() -- Check and measure Zone Label text width and update tracker width QuestieTracker:UpdateWidth(line.label:GetStringWidth() + trackerMarginLeft + - trackerMarginRight) + superTrackMarginLeft + trackerMarginRight) -- Set Zone Label and Line widths - line.label:SetWidth(trackerBaseFrame:GetWidth() - trackerMarginLeft - trackerMarginRight) - line:SetWidth(line.label:GetWidth()) + line.label:SetWidth(trackerBaseFrame:GetWidth() - trackerMarginLeft - superTrackMarginLeft - trackerMarginRight) + line:SetWidth(line.label:GetWidth() + superTrackMarginLeft) -- Compare largest text Label in the tracker with current Label, then save widest width trackerLineWidth = math.max(trackerLineWidth, - line.label:GetStringWidth() + trackerMarginLeft) + line.label:GetStringWidth() + trackerMarginLeft + superTrackMarginLeft) -- Setup Min/Max Button line.expandZone:ClearAllPoints() diff --git a/Modules/Tracker/TrackerLinePool.lua b/Modules/Tracker/TrackerLinePool.lua index 080dec8..66ac360 100644 --- a/Modules/Tracker/TrackerLinePool.lua +++ b/Modules/Tracker/TrackerLinePool.lua @@ -62,6 +62,29 @@ local function MirrorPinRegion(destination, source, size, pinSize) destination:Show() end +-- The pin atlases stack the selected (yellow circle, black digits) variant of every cell exactly +-- half a texture above the normal one. The client only restyles its pins during the world map's own +-- selection pass, which has not run yet right after a login or a reload, so the variant is forced +-- here rather than taken on trust from the pin. +local function ApplySelectedVariant(texture, selected) + local topLeftX, topLeftY, bottomLeftX, bottomLeftY, topRightX, topRightY, bottomRightX, bottomRightY = texture:GetTexCoord() + if (not topLeftY) or (not bottomLeftY) or (bottomLeftY - topLeftY) > 0.5 then + return + end + + local offset + if selected and topLeftY >= 0.5 then + offset = -0.5 + elseif (not selected) and bottomLeftY <= 0.5 then + offset = 0.5 + else + return + end + + texture:SetTexCoord(topLeftX, topLeftY + offset, bottomLeftX, bottomLeftY + offset, + topRightX, topRightY + offset, bottomRightX, bottomRightY + offset) +end + local function GetNumLines(label) if label.GetNumLines then return label:GetNumLines() @@ -81,6 +104,20 @@ local linePool = {} local buttonPool = {} local lineMarginLeft = 10 +-- Gutter given to the supertrack button, which sits flush with the left edge of a quest line. The +-- collapse button, the quest item buttons and the labels are all shifted right by this much, so +-- nothing lands on top of it and nothing hangs over the tracker's left edge. Fed into +-- questMarginLeft, which every width calculation already builds on, so the tracker widens to match. +function TrackerLinePool.GetSuperTrackMarginReserve() + if (not Questie.db.profile.trackerShowSuperTrackButton) or (not TrackerUtils:IsSuperTrackAvailable()) then + return 0 + end + + -- Reserved even for quests whose button is hidden, so the list does not shift around as quests + -- come in and out of the supertrackable set. + return (Questie.db.profile.trackerSuperTrackButtonSize or 25) + 4 +end + ---@param questFrame Frame function TrackerLinePool.Initialize(questFrame) local trackerQuestFrame = questFrame @@ -489,31 +526,33 @@ function TrackerLinePool.Initialize(questFrame) -- texture file has to be copied along with the coordinates: completed quests swap in a -- different file for these slots, and coordinates from one file applied to another -- sample nonsense. + local isSuperTracked = TrackerUtils:GetSuperTrackedQuestId() == self.questId + local pinTexture = pin.GetNormalTexture and pin:GetNormalTexture() if pinTexture then local ownTexture = self:GetNormalTexture() ownTexture:SetTexture(pinTexture:GetTexture()) ownTexture:SetTexCoord(pinTexture:GetTexCoord()) + ApplySelectedVariant(ownTexture, isSuperTracked) end local pinSize = pin:GetWidth() MirrorPinRegion(self.number, pin.number, buttonSize, pinSize) MirrorPinRegion(self.turnin, pin.turnin, buttonSize, pinSize) - - -- Sit to the left of whatever already occupies the gutter: the AI_VoiceOver play button - -- when that addon is loaded, otherwise the quest collapse button. - self:ClearAllPoints() - if playButton:IsShown() then - self:SetPoint("RIGHT", playButton, "LEFT", -2, 0) - elseif line.expandQuest then - self:SetPoint("RIGHT", line.expandQuest, "LEFT", -2, 0) - else - self:SetPoint("RIGHT", line.label, "LEFT", -4, 0) + if self.number:IsShown() then + ApplySelectedVariant(self.number, isSuperTracked) end - -- The icon itself already shows the selected variant, copied from the pin above; the - -- glow is the one part the pin draws as a separate texture. - if TrackerUtils:GetSuperTrackedQuestId() == self.questId then + -- Flush with the left edge of the line, in the gutter GetSuperTrackMarginReserve keeps + -- clear. Anchored to the top rather than centred on the line so the button stays level + -- with the first row of a quest title that wraps, matching the collapse button. + local fontSizeQuest = Questie.db.profile.trackerFontSizeQuest + self:ClearAllPoints() + self:SetPoint("TOPLEFT", line, "TOPLEFT", 0, (buttonSize - fontSizeQuest) / 2 + 1) + + -- The icon carries the selected variant itself; the glow is the one part the pin draws + -- as a separate texture. + if isSuperTracked then self.glow:Show() else self.glow:Hide() @@ -533,16 +572,6 @@ function TrackerLinePool.Initialize(questFrame) end end) - superTrackButton:SetScript("OnEnter", function(self) - GameTooltip:SetOwner(self, "ANCHOR_RIGHT") - GameTooltip:SetText(l10n("Point the objective marker at this quest"), 1, 1, 1) - GameTooltip:Show() - end) - - superTrackButton:SetScript("OnLeave", function() - GameTooltip:Hide() - end) - superTrackButton:Hide() line.superTrackButton = superTrackButton diff --git a/Modules/Tracker/TrackerUtils.lua b/Modules/Tracker/TrackerUtils.lua index 866f9cd..0c6115d 100644 --- a/Modules/Tracker/TrackerUtils.lua +++ b/Modules/Tracker/TrackerUtils.lua @@ -1272,16 +1272,40 @@ end -- stomps it, so we click the same POI frames the client itself clicks. local superTrackedQuestId local superTrackHooked +local superTrackRefreshing +local superTrackRefreshPending +local superTrackEventFrame + +-- Refreshing rebuilds the map's POI frames, which can land back in the very hooks that asked for the +-- refresh, so this is the only way the buttons are ever repainted. Requests are also coalesced: +-- callers like TrackerQuestTimers select a quest log entry and immediately restore the previous one, +-- so reading the selection on the first of those calls would catch a state the client is about to +-- undo. Waiting a tick means the burst has settled. +local function RefreshSuperTrackButtons() + if superTrackRefreshing or superTrackRefreshPending then + return + end + + superTrackRefreshPending = true + C_Timer.After(0.05, function() + superTrackRefreshPending = false + superTrackRefreshing = true + TrackerLinePool.UpdateSuperTrackButtons() + superTrackRefreshing = false + end) +end function TrackerUtils:IsSuperTrackAvailable() return (C_SuperTrack ~= nil) and ((WorldMapFrame_SelectQuestFrame ~= nil) or (WatchFrameQuestPOI_OnClick ~= nil)) end --- Every path that changes the supertracked quest ends up in SetSuperTrackedQuestID -- our own --- button, world map pins, the map quest list, the Blizzard tracker, and the automatic re-pick that --- happens when the map switches zone. Hooking it is the only way to know what is supertracked, --- since this client dropped the GetSuperTrackedQuestID getter. Caching what we last set would go --- stale the moment the player changed it by any other means. +-- Most paths that change the supertracked quest end up in SetSuperTrackedQuestID -- our own button, +-- world map pins, the map quest list, the Blizzard tracker, and the automatic re-pick that happens +-- when the map switches zone -- so hooking it stands in for the GetSuperTrackedQuestID getter this +-- client dropped. It goes quiet while the player is a ghost, though: the corpse arrow takes the +-- marker over, so no quest is ever handed to it even though the map keeps selecting one. The quest +-- selection itself is therefore hooked as well, and that is what keeps the buttons honest while +-- dead. Caching what we last set would go stale the moment the player changed it by other means. function TrackerUtils:InitSuperTrackHook() if superTrackHooked or (not C_SuperTrack) then return @@ -1291,19 +1315,106 @@ function TrackerUtils:InitSuperTrackHook() hooksecurefunc(C_SuperTrack, "SetSuperTrackedQuestID", function(questId) superTrackedQuestId = questId - TrackerLinePool.UpdateSuperTrackButtons() + RefreshSuperTrackButtons() end) if C_SuperTrack.ClearSuperTracker then hooksecurefunc(C_SuperTrack, "ClearSuperTracker", function() superTrackedQuestId = nil - TrackerLinePool.UpdateSuperTrackButtons() + RefreshSuperTrackButtons() end) end + + if WorldMapFrame_SelectQuestFrame then + hooksecurefunc("WorldMapFrame_SelectQuestFrame", RefreshSuperTrackButtons) + end + + if WatchFrameQuestPOI_OnClick then + hooksecurefunc("WatchFrameQuestPOI_OnClick", RefreshSuperTrackButtons) + end + + -- Everything that supertracks a quest goes through the quest log selection, map or no map, alive + -- or dead -- including opening a quest in the quest log window, which no other hook here sees. + if SelectQuestLogEntry then + hooksecurefunc("SelectQuestLogEntry", RefreshSuperTrackButtons) + end + + -- Nothing at all fires on a login or a reload: the map has not been touched, so the hooks above + -- stay silent and the tracker draws before the client has styled its pins. These events are the + -- only prompt to go back and look. + superTrackEventFrame = CreateFrame("Frame") + superTrackEventFrame:RegisterEvent("PLAYER_ENTERING_WORLD") + superTrackEventFrame:RegisterEvent("PLAYER_UNGHOST") + superTrackEventFrame:RegisterEvent("PLAYER_ALIVE") + superTrackEventFrame:RegisterEvent("PLAYER_DEAD") + superTrackEventFrame:SetScript("OnEvent", function() + RefreshSuperTrackButtons() + -- The client fills in its POI frames a moment after entering the world, so the immediate + -- pass above can still come up empty. + C_Timer.After(2, RefreshSuperTrackButtons) + end) end +-- The hook only hears about the changes the client itself makes, and while the player is a ghost the +-- floating marker is disabled: selecting a quest then never reaches SetSuperTrackedQuestID, so the +-- hook reports nothing after a login or a reload in that state and goes stale after any click. The +-- client's own frames still know. Watch frame POI buttons say so outright, and the world map's quest +-- frames say it through their art: the atlases stack the selected (yellow) variant of a cell half a +-- texture above the normal one, so a pin drawn from the upper half is the supertracked one. +local function FindSelectedQuestId() + for i = 1, 30 do + local firstInRow = _G["poiWatchFrameLines" .. i .. "_1"] + if not firstInRow then + break + end + for j = 1, 5 do + local poiButton = (j == 1) and firstInRow or _G["poiWatchFrameLines" .. i .. "_" .. j] + if not poiButton then + break + end + if poiButton.isSelected and poiButton.questId then + return poiButton.questId + end + end + end + + for i = 1, 25 do + local questFrame = _G["WorldMapQuestFrame" .. i] + if not questFrame then + break + end + + local pin = questFrame.ownPOI or questFrame.poiIcon + local pinTexture = pin and pin.GetNormalTexture and pin:GetNormalTexture() + if pinTexture and questFrame.questId then + local _, topY = pinTexture:GetTexCoord() + if topY and topY < 0.5 then + return questFrame.questId + end + end + end + + -- Right after a login or a reload no pin is styled at all: the client marks them the first time + -- the world map is opened. The quest log selection is what it reads when it gets there, so it + -- answers for the gap in between. + local selection = GetQuestLogSelection and GetQuestLogSelection() + if selection and selection > 0 then + -- GetQuestIDFromLogIndex, not the raw API: GetQuestLogTitle is the compat wrapper here, + -- which normalises the client's 9 return values down to 8. + local questId = QuestieCompat.GetQuestIDFromLogIndex(selection) + if questId and questId ~= 0 then + return questId + end + end + + return nil +end + +-- The client is asked before the hook: the hook cannot see a ghost's selection changes at all, so +-- its value is the fallback for when no POI frame exists to read (another zone, or frames not built +-- yet), not the source of truth. function TrackerUtils:GetSuperTrackedQuestId() - return superTrackedQuestId + return FindSelectedQuestId() or superTrackedQuestId end -- The world map builds its quest POI frames lazily, so right after login -- or after a zone change @@ -1414,6 +1525,10 @@ function TrackerUtils:SetSuperTrackedQuest(questId) end clickHandler(frame) + + -- Refresh here rather than leaning on the SetSuperTrackedQuestID hook: it stays silent while the + -- player is a ghost, which would leave the button we just clicked looking untouched. + RefreshSuperTrackButtons() return true end