mirror of
https://github.com/Blaststar/Questie-Octo-Vanilla.git
synced 2026-10-19 20:00:49 +00:00
1152 lines
39 KiB
Lua
1152 lines
39 KiB
Lua
-- Questie-Octo visual quest/objective tracker.
|
|
--
|
|
-- Backport lineage:
|
|
-- Questie 5.2.3/6.0.0: simple tracker presentation and quest title interactions.
|
|
-- Questie 3.3.5: TrackerBaseFrame/Header/QuestFrame separation, movable/resizable
|
|
-- shell, zone grouping and saved geometry. This is the primary implementation bridge.
|
|
-- Questie 7/8: renderer consumes cached quest state and rebuilds presentation only.
|
|
--
|
|
-- Vanilla/Turtle adaptation: native 1.12 frames/scrollframe only; no modern mixins.
|
|
|
|
QuestieOcto.TrackerFrame = QuestieOcto.TrackerFrame or {}
|
|
local T=QuestieOcto.TrackerFrame
|
|
|
|
T.started=false
|
|
T.frame=nil
|
|
T.header=nil
|
|
T.scroll=nil
|
|
T.content=nil
|
|
T.rows={}
|
|
T.rowCount=0
|
|
T.topRow=1
|
|
T.timerRows={}
|
|
T.timerElapsed=0
|
|
T.defaultWidth=280
|
|
T.defaultHeight=420
|
|
T.defaultVisibleRows=18
|
|
T.minVisibleRows=6
|
|
T.maxVisibleRows=60
|
|
T.frameVerticalChrome=38
|
|
T.minWidth=100
|
|
T.minHeight=100
|
|
T.maxWidth=2000
|
|
T.maxHeight=2000
|
|
T.padding=8
|
|
T.autoFitMinWidth=100
|
|
T.autoFitMaxWidth=280
|
|
|
|
local function Settings()
|
|
return QuestieOcto.MinimapSettings
|
|
end
|
|
|
|
local function CharacterDB()
|
|
QuestieOctoDB=QuestieOctoDB or {}
|
|
QuestieOctoDB.tracker=QuestieOctoDB.tracker or {}
|
|
local db=QuestieOctoDB.tracker
|
|
db.window=db.window or {}
|
|
if db.expanded==nil then db.expanded=true end
|
|
return db
|
|
end
|
|
|
|
local function Clamp(v,minv,maxv)
|
|
v=tonumber(v) or minv
|
|
if v<minv then return minv end
|
|
if v>maxv then return maxv end
|
|
return v
|
|
end
|
|
|
|
local function CollapsedZones()
|
|
local db=CharacterDB()
|
|
db.collapsedZones=db.collapsedZones or {}
|
|
return db.collapsedZones
|
|
end
|
|
|
|
function T:IsZoneCollapsed(zone)
|
|
return CollapsedZones()[tostring(zone)] and true or false
|
|
end
|
|
|
|
function T:ToggleZone(zone)
|
|
local z=CollapsedZones()
|
|
local key=tostring(zone)
|
|
z[key]=(not z[key]) or nil
|
|
self:Render()
|
|
end
|
|
|
|
local function GetScreenBounds()
|
|
local width=(UIParent and UIParent.GetWidth and UIParent:GetWidth()) or 1024
|
|
local height=(UIParent and UIParent.GetHeight and UIParent:GetHeight()) or 768
|
|
return math.max(T.minWidth,width-20),math.max(T.minHeight,height-20)
|
|
end
|
|
|
|
function T:AnchorTopRightAtCurrentPosition()
|
|
if not self.frame or not UIParent then return end
|
|
local right=self.frame.GetRight and self.frame:GetRight()
|
|
local top=self.frame.GetTop and self.frame:GetTop()
|
|
local parentRight=UIParent.GetRight and UIParent:GetRight()
|
|
local parentTop=UIParent.GetTop and UIParent:GetTop()
|
|
if not right or not top or not parentRight or not parentTop then return end
|
|
|
|
local x=right-parentRight
|
|
local y=top-parentTop
|
|
self.frame:ClearAllPoints()
|
|
self.frame:SetPoint("TOPRIGHT",UIParent,"TOPRIGHT",x,y)
|
|
end
|
|
|
|
function T:SaveGeometry()
|
|
if not self.frame then return end
|
|
-- The tracker is intentionally normalized to TOPRIGHT after user movement.
|
|
-- This keeps its right/top edge fixed when auto-fit changes width later.
|
|
self:AnchorTopRightAtCurrentPosition()
|
|
|
|
local db=CharacterDB()
|
|
local point,relativeTo,relativePoint,x,y=self.frame:GetPoint(1)
|
|
db.window.point=point or "TOPRIGHT"
|
|
db.window.relativePoint=relativePoint or point or "TOPRIGHT"
|
|
db.window.x=tonumber(x) or -30
|
|
db.window.y=tonumber(y) or -180
|
|
-- Width and height are content/settings-driven; placement stores only anchor offsets.
|
|
end
|
|
|
|
function T:RestoreGeometry()
|
|
if not self.frame then return end
|
|
local db=CharacterDB()
|
|
local window=db.window
|
|
self.frame:SetWidth(self.defaultWidth)
|
|
self.frame:SetHeight(self.defaultHeight)
|
|
self.frame:ClearAllPoints()
|
|
|
|
-- New tracker placement is always TOPRIGHT so content-driven width changes
|
|
-- expand to the left and never move the player's chosen right/top edge.
|
|
-- Existing TOPRIGHT saves carry over directly; other legacy anchors fall
|
|
-- back to the normal Questie placement once and are then saved canonically.
|
|
local x=tonumber(window.x)
|
|
local y=tonumber(window.y)
|
|
if window.point~="TOPRIGHT" or window.relativePoint~="TOPRIGHT" then
|
|
x=-30
|
|
y=-180
|
|
end
|
|
if x==nil then x=-30 end
|
|
if y==nil then y=-180 end
|
|
self.frame:SetPoint("TOPRIGHT",UIParent,"TOPRIGHT",x,y)
|
|
end
|
|
|
|
local function SetFont(fs,size,r,g,b)
|
|
if not fs then return end
|
|
local font=STANDARD_TEXT_FONT or "Fonts\\FRIZQT__.TTF"
|
|
local flags=""
|
|
if Settings():Get("trackerThickOutlineText") then
|
|
flags="THICKOUTLINE"
|
|
elseif Settings():Get("trackerOutlineText") then
|
|
flags="OUTLINE"
|
|
end
|
|
fs:SetFont(font,size or 10,flags)
|
|
if r then fs:SetTextColor(r,g,b) end
|
|
end
|
|
|
|
function T:AutoFitWidth()
|
|
if not self.frame then return self.defaultWidth,self.defaultWidth-16 end
|
|
|
|
-- pfQuest's proven Vanilla technique: measure the actual FontStrings that
|
|
-- are already being rendered at their natural one-line width. Questie-Octo
|
|
-- then applies the player-selected practical tracker cap (280px by default).
|
|
-- Long quest/objective text is wrapped during the second layout pass instead
|
|
-- of being allowed to stretch the entire tracker across the screen.
|
|
local longest=0
|
|
for i=1,self.rowCount do
|
|
local row=self.rows[i]
|
|
if row and row:IsShown() and row.text and row.text.GetStringWidth then
|
|
local indent=tonumber(row.textIndent) or 0
|
|
local width=tonumber(row.text:GetStringWidth()) or 0
|
|
width=width+indent
|
|
if width>longest then longest=width end
|
|
end
|
|
end
|
|
|
|
local screenW=GetScreenBounds()
|
|
-- Scroll area begins 8px inside the frame. Add another 8px of breathing
|
|
-- room after the longest rendered line. The fixed TOPRIGHT anchor means
|
|
-- this width change grows/shrinks leftward without moving the right edge.
|
|
local frameWidth=longest+16
|
|
local configuredMax=tonumber(Settings():Get("trackerMaxWidth")) or self.autoFitMaxWidth or 280
|
|
configuredMax=Clamp(configuredMax,200,500)
|
|
local maxWidth=math.min(configuredMax,screenW)
|
|
frameWidth=Clamp(frameWidth,self.autoFitMinWidth,maxWidth)
|
|
local scrollWidth=math.max(1,frameWidth-16)
|
|
local contentWidth=scrollWidth
|
|
|
|
self.frame:SetWidth(frameWidth)
|
|
if self.scroll and self.scroll.SetWidth then self.scroll:SetWidth(scrollWidth) end
|
|
if self.content and self.content.SetWidth then self.content:SetWidth(contentWidth) end
|
|
return frameWidth,contentWidth
|
|
end
|
|
|
|
function T:GetVisibleRowsSetting()
|
|
local value=tonumber(Settings():Get("trackerVisibleRows")) or self.defaultVisibleRows
|
|
return math.floor(Clamp(value,self.minVisibleRows,self.maxVisibleRows)+0.5)
|
|
end
|
|
|
|
function T:ApplyVisibleRowsHeight()
|
|
if not self.frame or not self.scroll then return end
|
|
|
|
local wanted=self:GetVisibleRowsSetting()
|
|
local available=math.min(wanted,self.rowCount)
|
|
if available<1 then available=1 end
|
|
|
|
-- Use the tallest actual consecutive page of up to the requested row count.
|
|
-- The frame therefore has one stable, safe pixel height for every scroll page
|
|
-- without exposing raw pixel resizing to the player.
|
|
local maxPageHeight=0
|
|
if self.rowCount>0 then
|
|
for first=1,self.rowCount do
|
|
local pageHeight=0
|
|
local last=math.min(self.rowCount,first+available-1)
|
|
for i=first,last do
|
|
local row=self.rows[i]
|
|
local h=(row and row.GetHeight and row:GetHeight()) or 1
|
|
if h<1 then h=1 end
|
|
pageHeight=pageHeight+h
|
|
end
|
|
if pageHeight>maxPageHeight then maxPageHeight=pageHeight end
|
|
end
|
|
end
|
|
if maxPageHeight<1 then
|
|
local size=tonumber(Settings():Get("trackerFontSize")) or 10
|
|
maxPageHeight=(size+5)*available
|
|
end
|
|
|
|
local _,maxH=GetScreenBounds()
|
|
|
|
-- The tracker grows downward from a fixed top anchor, so the real ceiling is
|
|
-- the space from its top edge to the bottom of the screen. Using the whole
|
|
-- screen height let the frame run off the bottom, which made high Visible Rows
|
|
-- values render rows below the screen where they could never be seen.
|
|
if self.frame.GetTop and UIParent and UIParent.GetBottom then
|
|
local top=self.frame:GetTop()
|
|
local bottom=UIParent:GetBottom()
|
|
if top and bottom then
|
|
local available=top-bottom-6
|
|
if available>=self.minHeight then maxH=math.min(maxH,available) end
|
|
end
|
|
end
|
|
|
|
local frameHeight=Clamp(maxPageHeight+self.frameVerticalChrome,self.minHeight,math.min(self.maxHeight,maxH))
|
|
self.frame:SetHeight(frameHeight)
|
|
|
|
-- Vanilla does not always resolve a TOP/BOTTOM anchored child to its new
|
|
-- height synchronously after the parent changes size. Whole-row paging must
|
|
-- use the new viewport during this same render pass, so make it explicit.
|
|
local scrollHeight=math.max(1,frameHeight-self.frameVerticalChrome)
|
|
if self.scroll.SetHeight then self.scroll:SetHeight(scrollHeight) end
|
|
end
|
|
|
|
function T:GetVisibleRowRange()
|
|
if self.rowCount<=0 then return 1,0,0 end
|
|
|
|
local first=tonumber(self.topRow) or 1
|
|
if first<1 then first=1 end
|
|
if first>self.rowCount then first=self.rowCount end
|
|
|
|
local viewHeight=(self.scroll and self.scroll.GetHeight and self.scroll:GetHeight()) or 0
|
|
local maxRows=self:GetVisibleRowsSetting()
|
|
local used=0
|
|
local count=0
|
|
local last=first-1
|
|
for i=first,self.rowCount do
|
|
if count>=maxRows then break end
|
|
local row=self.rows[i]
|
|
local h=(row and row.GetHeight and row:GetHeight()) or 0
|
|
if h<=0 then h=1 end
|
|
-- 1px tolerance: the viewport height is set to the exact summed row heights,
|
|
-- but SetHeight->GetHeight can round down a fraction, which would otherwise
|
|
-- clip the final row that actually fits (worst on the last zone after a
|
|
-- collapse changes the row set).
|
|
if used+h>viewHeight+1 then break end
|
|
used=used+h
|
|
count=count+1
|
|
last=i
|
|
end
|
|
return first,last,used
|
|
end
|
|
|
|
function T:ClampTopRow()
|
|
if self.rowCount<=0 then
|
|
self.topRow=1
|
|
return
|
|
end
|
|
|
|
local top=tonumber(self.topRow) or 1
|
|
if top<1 then top=1 end
|
|
if top>self.rowCount then top=self.rowCount end
|
|
self.topRow=top
|
|
|
|
-- On the final page, walk upward while the entire remainder still fits.
|
|
-- This prevents an unnecessary blank area at the bottom while preserving
|
|
-- the rule that every visible tracker row is shown completely.
|
|
local first,last=self:GetVisibleRowRange()
|
|
if last==self.rowCount then
|
|
while self.topRow>1 do
|
|
local old=self.topRow
|
|
self.topRow=old-1
|
|
local prevFirst,prevLast=self:GetVisibleRowRange()
|
|
if prevLast~=self.rowCount then
|
|
self.topRow=old
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function T:LayoutVisibleRows()
|
|
if not self.scroll or not self.content then return end
|
|
|
|
self:ClampTopRow()
|
|
local first,last=self:GetVisibleRowRange()
|
|
local y=0
|
|
local contentWidth=math.max(1,self.scroll:GetWidth())
|
|
|
|
for i=1,self.rowCount do
|
|
local row=self.rows[i]
|
|
if i>=first and i<=last then
|
|
row:Show()
|
|
row:SetWidth(contentWidth)
|
|
row:ClearAllPoints()
|
|
row:SetPoint("TOPLEFT",self.content,"TOPLEFT",0,-y)
|
|
y=y+row:GetHeight()
|
|
else
|
|
row:Hide()
|
|
end
|
|
end
|
|
|
|
self.content:SetWidth(contentWidth)
|
|
self.content:SetHeight(math.max(1,y))
|
|
if self.scroll.SetVerticalScroll then self.scroll:SetVerticalScroll(0) end
|
|
end
|
|
|
|
function T:ScrollBy(delta)
|
|
if not self.scroll or not self.scroll:IsShown() then return end
|
|
local wheel=tonumber(delta) or 0
|
|
if wheel==0 or self.rowCount<=0 then return end
|
|
|
|
if wheel<0 then
|
|
self.topRow=(tonumber(self.topRow) or 1)+1
|
|
else
|
|
self.topRow=(tonumber(self.topRow) or 1)-1
|
|
end
|
|
self:ClampTopRow()
|
|
self:LayoutVisibleRows()
|
|
end
|
|
|
|
-- Put a clickable quest hyperlink into the chat edit box so the player can send
|
|
-- it, falling back to a direct SAY message when no chat box is available.
|
|
local function LinkQuestToChat(questID)
|
|
questID=tonumber(questID)
|
|
if not questID then return end
|
|
local q=QuestieOcto.QuestModel and QuestieOcto.QuestModel:Get(questID) or nil
|
|
local state=QuestieOcto.QuestLog and QuestieOcto.QuestLog.active and QuestieOcto.QuestLog.active[questID] or nil
|
|
local name=(q and q.title) or (state and state.title) or ("Quest "..tostring(questID))
|
|
local level=(q and tonumber(q.level)) or (state and tonumber(state.level)) or 0
|
|
local link="|cffffff00|Hquest:"..tostring(questID)..":"..tostring(level).."|h["..tostring(name).."]|h|r"
|
|
|
|
local editBox=getglobal and (getglobal("ChatFrame1EditBox") or getglobal("ChatFrameEditBox")) or nil
|
|
|
|
-- If the player already has text typed, keep it and append the link
|
|
if editBox and editBox:IsVisible() and editBox.GetText and editBox.SetText then
|
|
local current=editBox:GetText() or ""
|
|
if current~="" then
|
|
editBox:SetText(current..link)
|
|
else
|
|
editBox:SetText(link)
|
|
end
|
|
if editBox.SetFocus then editBox:SetFocus() end
|
|
return
|
|
end
|
|
|
|
if ChatFrame_OpenChat then
|
|
ChatFrame_OpenChat(link)
|
|
elseif SendChatMessage then
|
|
SendChatMessage(link,"SAY")
|
|
end
|
|
end
|
|
|
|
local function EnsureRow(index,parent)
|
|
local row=T.rows[index]
|
|
if row then
|
|
row:Show()
|
|
return row
|
|
end
|
|
|
|
row=CreateFrame("Button",nil,parent)
|
|
row:SetHeight(14)
|
|
row:EnableMouse(true)
|
|
if row.RegisterForClicks then row:RegisterForClicks("LeftButtonUp","RightButtonUp") end
|
|
|
|
local text=row:CreateFontString(nil,"ARTWORK","GameFontNormal")
|
|
text:SetPoint("TOPLEFT",row,"TOPLEFT",0,0)
|
|
text:SetJustifyH("LEFT")
|
|
text:SetJustifyV("TOP")
|
|
row.text=text
|
|
|
|
-- Native quest-log style +/- button, shown only on zone header rows.
|
|
local collapse=CreateFrame("Button",nil,row)
|
|
collapse:SetWidth(14)
|
|
collapse:SetHeight(14)
|
|
collapse:SetPoint("LEFT",row,"LEFT",0,0)
|
|
local nt=collapse:CreateTexture(nil,"BACKGROUND"); nt:SetAllPoints(collapse); collapse:SetNormalTexture(nt)
|
|
local pt=collapse:CreateTexture(nil,"BACKGROUND"); pt:SetAllPoints(collapse); collapse:SetPushedTexture(pt)
|
|
local ht=collapse:CreateTexture(nil,"HIGHLIGHT"); ht:SetTexture("Interface\\Buttons\\UI-PlusButton-Hilight"); ht:SetAllPoints(collapse); collapse:SetHighlightTexture(ht)
|
|
collapse:SetScript("OnClick",function() if this.zoneName then T:ToggleZone(this.zoneName) end end)
|
|
collapse:Hide()
|
|
row.collapse=collapse
|
|
|
|
row:SetScript("OnEnter",function()
|
|
if this.questID then
|
|
if this.kind=="quest" and QuestieOcto.MapHighlight then QuestieOcto.MapHighlight:Set(this.questID) end
|
|
GameTooltip:SetOwner(this,"ANCHOR_LEFT")
|
|
GameTooltip:SetText(this.questTitle or "Quest",1,0.82,0)
|
|
GameTooltip:AddLine("Click to open this quest in the Quest Log.",1,1,1)
|
|
GameTooltip:AddLine("Ctrl + Click to stop tracking it.",0.7,0.7,0.7)
|
|
GameTooltip:AddLine("Shift + Click to link it to chat.",0.7,0.7,0.7)
|
|
GameTooltip:Show()
|
|
end
|
|
end)
|
|
row:SetScript("OnLeave",function()
|
|
if QuestieOcto.MapHighlight then QuestieOcto.MapHighlight:Clear() end
|
|
if GameTooltip then GameTooltip:Hide() end
|
|
end)
|
|
row:SetScript("OnClick",function()
|
|
if this.kind=="zone" then
|
|
if this.zoneName then T:ToggleZone(this.zoneName) end
|
|
return
|
|
end
|
|
if not this.questID then return end
|
|
if arg1=="RightButton" then
|
|
if this.kind=="quest" and QuestieOcto.QuestMenu then QuestieOcto.QuestMenu:Open(this.questID,this) end
|
|
return
|
|
end
|
|
if IsControlKeyDown and IsControlKeyDown() then
|
|
if QuestieOcto.TrackerDriver then QuestieOcto.TrackerDriver:Toggle(this.questID) end
|
|
return
|
|
end
|
|
if IsShiftKeyDown and IsShiftKeyDown() then
|
|
LinkQuestToChat(this.questID)
|
|
return
|
|
end
|
|
T:OpenQuest(this.questID)
|
|
end)
|
|
if row.EnableMouseWheel then row:EnableMouseWheel(true) end
|
|
row:SetScript("OnMouseWheel",function() T:ScrollBy(arg1) end)
|
|
|
|
T.rows[index]=row
|
|
return row
|
|
end
|
|
|
|
function T:ClearRows()
|
|
for i=1,table.getn(self.rows) do
|
|
local row=self.rows[i]
|
|
row:Hide()
|
|
row.questID=nil
|
|
row.questTitle=nil
|
|
row.kind=nil
|
|
row.displayText=nil
|
|
row.timerQuestID=nil
|
|
row.timerLogIndex=nil
|
|
row.failed=nil
|
|
row.textIndent=nil
|
|
row.zoneName=nil
|
|
if row.collapse then row.collapse:Hide(); row.collapse.zoneName=nil end
|
|
end
|
|
self.rowCount=0
|
|
self.topRow=1
|
|
self.timerRows={}
|
|
end
|
|
|
|
local function WrapTextToWidth(fs,text,maxWidth)
|
|
text=tostring(text or "")
|
|
maxWidth=tonumber(maxWidth) or 0
|
|
if text=="" or maxWidth<=0 or not fs or not fs.SetText or not fs.GetStringWidth then
|
|
return text,1
|
|
end
|
|
|
|
-- Do not rely on Vanilla/Turtle FontString auto-wrapping. Some client/UI
|
|
-- combinations keep drawing a constrained FontString at its natural one-line
|
|
-- width. Build the line breaks explicitly from the exact rendered font width
|
|
-- so long event/exploration objectives are deterministic on every client.
|
|
local lines={}
|
|
local line=""
|
|
local pos=1
|
|
while true do
|
|
local s,e=string.find(text,"%S+",pos)
|
|
if not s then break end
|
|
local word=string.sub(text,s,e)
|
|
local candidate=(line=="") and word or (line.." "..word)
|
|
fs:SetText(candidate)
|
|
local candidateWidth=tonumber(fs:GetStringWidth()) or 0
|
|
if line~="" and candidateWidth>maxWidth then
|
|
table.insert(lines,line)
|
|
line=word
|
|
else
|
|
line=candidate
|
|
end
|
|
pos=e+1
|
|
end
|
|
|
|
if line~="" then table.insert(lines,line) end
|
|
local count=table.getn(lines)
|
|
if count<1 then return text,1 end
|
|
return table.concat(lines,"\n"),count
|
|
end
|
|
|
|
local function ZoneSpacerHeight(fontSize)
|
|
-- Keep zone groups visually separated without consuming a full text row.
|
|
-- The spacer scales with the configured tracker font and adds a small amount
|
|
-- for OUTLINE/THICKOUTLINE so heavier glyph edges do not feel crowded.
|
|
local height=(tonumber(fontSize) or 10)-4
|
|
if height<4 then height=4 end
|
|
if height>13 then height=13 end
|
|
|
|
if Settings():Get("trackerThickOutlineText") then
|
|
height=height+2
|
|
elseif Settings():Get("trackerOutlineText") then
|
|
height=height+1
|
|
end
|
|
|
|
if height>15 then height=15 end
|
|
return height
|
|
end
|
|
|
|
local function ApplyRowStyle(row, contentWidth, constrainWidth)
|
|
local size=tonumber(Settings():Get("trackerFontSize")) or 10
|
|
local left=0
|
|
local r,g,b=1,1,1
|
|
local kind=row.kind
|
|
|
|
if kind=="zone" then
|
|
size=size+1
|
|
r,g,b=0.88,0.69,0.24
|
|
left=16
|
|
elseif kind=="quest" then
|
|
-- Match the classic Questie/Quest Log presentation: completion is shown
|
|
-- by appending "(Complete)" to the title, not by replacing difficulty color.
|
|
if row.failed then
|
|
r,g,b=1,0.35,0.35
|
|
elseif QuestieOcto.GetNativeQuestDifficultyColor then
|
|
local nr,ng,nb=QuestieOcto:GetNativeQuestDifficultyColor(row.questLevel,row.questID)
|
|
if nr then r,g,b=nr,ng,nb else r,g,b=1,0.82,0 end
|
|
else
|
|
r,g,b=1,0.82,0
|
|
end
|
|
left=6
|
|
elseif kind=="objective" then
|
|
r,g,b=row.complete and 0.35 or 0.82, row.complete and 0.80 or 0.82, row.complete and 0.35 or 0.82
|
|
left=18
|
|
elseif kind=="timer" then
|
|
r,g,b=0.35,0.75,1
|
|
left=18
|
|
end
|
|
|
|
if kind=="zone" and row.collapse then
|
|
row.collapse.zoneName=row.zoneName
|
|
local collapsed=T:IsZoneCollapsed(row.zoneName)
|
|
local nt=row.collapse.GetNormalTexture and row.collapse:GetNormalTexture()
|
|
local pt=row.collapse.GetPushedTexture and row.collapse:GetPushedTexture()
|
|
if nt then nt:SetTexture(collapsed and "Interface\\Buttons\\UI-PlusButton-Up" or "Interface\\Buttons\\UI-MinusButton-Up") end
|
|
if pt then pt:SetTexture(collapsed and "Interface\\Buttons\\UI-PlusButton-Down" or "Interface\\Buttons\\UI-MinusButton-Down") end
|
|
row.collapse:ClearAllPoints()
|
|
row.collapse:SetPoint("LEFT",row,"LEFT",0,0)
|
|
row.collapse:Show()
|
|
elseif row.collapse then
|
|
row.collapse:Hide()
|
|
end
|
|
|
|
row:SetWidth(math.max(1,contentWidth or 1))
|
|
row.text:ClearAllPoints()
|
|
row.text:SetPoint("TOPLEFT",row,"TOPLEFT",left,0)
|
|
|
|
-- Measure before adding a right-side constraint. GetStringWidth() must see
|
|
-- each candidate at its natural width so explicit wrapping is deterministic.
|
|
SetFont(row.text,size,r,g,b)
|
|
if kind=="zone" and row.text.SetFont then
|
|
local font=STANDARD_TEXT_FONT or "Fonts\\FRIZQT__.TTF"
|
|
local flags=""
|
|
if Settings():Get("trackerThickOutlineText") then
|
|
flags="THICKOUTLINE"
|
|
elseif Settings():Get("trackerOutlineText") then
|
|
flags="OUTLINE"
|
|
end
|
|
row.text:SetFont(font,size,flags)
|
|
row.text:SetTextColor(r,g,b)
|
|
end
|
|
|
|
local displayText=row.displayText or ""
|
|
local lineCount=1
|
|
if constrainWidth and kind=="objective" then
|
|
local available=math.max(1,(tonumber(contentWidth) or 1)-left)
|
|
displayText,lineCount=WrapTextToWidth(row.text,displayText,available)
|
|
end
|
|
|
|
-- Preserve the normal right boundary too, but explicit newlines above are
|
|
-- what guarantee the visual wrap on old clients where anchors alone do not.
|
|
if constrainWidth then
|
|
row.text:SetPoint("TOPRIGHT",row,"TOPRIGHT",0,0)
|
|
end
|
|
row.text:SetText(displayText)
|
|
row.textIndent=left
|
|
|
|
local baseHeight=size+4
|
|
local height
|
|
if kind=="spacer" then
|
|
height=ZoneSpacerHeight(size)
|
|
else
|
|
height=baseHeight*math.max(1,lineCount)
|
|
end
|
|
row:SetHeight(height)
|
|
end
|
|
|
|
function T:AddRow(text,kind,questID,complete,questLevel,failed)
|
|
self.rowCount=self.rowCount+1
|
|
local row=EnsureRow(self.rowCount,self.content)
|
|
row.questID=questID
|
|
row.questTitle=(kind=="quest") and text or nil
|
|
row.kind=kind
|
|
row.complete=complete and true or false
|
|
row.failed=failed and true or false
|
|
row.questLevel=questLevel
|
|
row.displayText=text or ""
|
|
row:EnableMouse(questID and true or false)
|
|
-- First render pass: create the exact visible text at natural width. The
|
|
-- subsequent AutoFitWidth() measures this same FontString via GetStringWidth().
|
|
ApplyRowStyle(row, math.max(1,(self.scroll and self.scroll:GetWidth()) or self.defaultWidth))
|
|
return row
|
|
end
|
|
|
|
local function FormatTimer(seconds)
|
|
seconds=tonumber(seconds)
|
|
if not seconds then return nil end
|
|
if seconds<0 then seconds=0 end
|
|
if SecondsToTime then
|
|
local ok,value=pcall(SecondsToTime,seconds)
|
|
if ok and value and value~="" then return value end
|
|
end
|
|
local total=math.floor(seconds+0.5)
|
|
local minutes=math.floor(total/60)
|
|
local secs=math.mod(total,60)
|
|
if minutes>=60 then
|
|
local hours=math.floor(minutes/60)
|
|
minutes=math.mod(minutes,60)
|
|
return tostring(hours).."h "..tostring(minutes).."m"
|
|
end
|
|
if minutes>0 then return tostring(minutes).."m "..tostring(secs).."s" end
|
|
return tostring(secs).."s"
|
|
end
|
|
|
|
function T:GetQuestTimerSeconds(quest)
|
|
if not quest then return nil end
|
|
local index=tonumber(quest.logIndex)
|
|
if not index or type(GetQuestLogTimeLeft)~="function" then return nil end
|
|
|
|
-- Questie 3.3.5 compatibility path: GetQuestLogTimeLeft is tied to the
|
|
-- selected quest on old clients. Temporarily select this quest, read the
|
|
-- timer, then restore the player's previous selection immediately.
|
|
if type(GetQuestLogSelection)=="function" and type(SelectQuestLogEntry)=="function" then
|
|
local selected=GetQuestLogSelection()
|
|
SelectQuestLogEntry(index)
|
|
local ok,value=pcall(GetQuestLogTimeLeft,index)
|
|
if selected and selected>0 then SelectQuestLogEntry(selected) end
|
|
if ok and tonumber(value) then return tonumber(value) end
|
|
return nil
|
|
end
|
|
|
|
-- Compatibility fallback for ClassicAPI implementations that accept the
|
|
-- quest-log index directly.
|
|
local ok,value=pcall(GetQuestLogTimeLeft,index)
|
|
if ok and tonumber(value) then return tonumber(value) end
|
|
return nil
|
|
end
|
|
|
|
function T:AddTimerRow(quest)
|
|
local seconds=self:GetQuestTimerSeconds(quest)
|
|
if not seconds then return end
|
|
local text=FormatTimer(seconds)
|
|
if not text then return end
|
|
local row=self:AddRow(text,"timer",nil,false)
|
|
row.timerQuestID=quest.id
|
|
row.timerLogIndex=quest.logIndex
|
|
self.timerRows[quest.id]=row
|
|
end
|
|
|
|
function T:UpdateTimerRows()
|
|
if not self.frame or not self.frame:IsShown() or not CharacterDB().expanded then return end
|
|
local expired=false
|
|
for questID,row in pairs(self.timerRows or {}) do
|
|
if row then
|
|
local seconds=nil
|
|
local quest=QuestieOcto.TrackerDriver and QuestieOcto.TrackerDriver.tracked and QuestieOcto.TrackerDriver.tracked[questID]
|
|
if quest then seconds=self:GetQuestTimerSeconds(quest) end
|
|
if seconds then
|
|
local value=FormatTimer(seconds)
|
|
if value and row.displayText~=value then
|
|
row.displayText=value
|
|
row.text:SetText(value)
|
|
end
|
|
else
|
|
expired=true
|
|
end
|
|
end
|
|
end
|
|
if expired then self:Render() end
|
|
end
|
|
|
|
local function ObjectiveDisplayText(objective)
|
|
-- Turtle can return leaderboard text without its entity name prefer the DB-localized text
|
|
local raw=objective and objective.rawText or nil
|
|
local loc=objective and objective.text or nil
|
|
local text
|
|
if raw and string.find(raw,"%a") then
|
|
text=raw
|
|
elseif loc and string.find(loc,"%a") then
|
|
text=loc
|
|
else
|
|
text=raw or loc or ""
|
|
end
|
|
if not text then text="" end
|
|
|
|
-- Only synthesize amounts when the compatibility API supplied numeric progress
|
|
local current=objective and tonumber(objective.current)
|
|
local required=objective and tonumber(objective.required)
|
|
if current and required and required>0 and not string.find(text,"%d+%s*/%s*%d+") then
|
|
if text~="" then
|
|
text=tostring(current).."/"..tostring(required).." "..text
|
|
else
|
|
text=tostring(current).."/"..tostring(required)
|
|
end
|
|
end
|
|
if text~="" then
|
|
text="- "..text
|
|
end
|
|
return text
|
|
end
|
|
|
|
function T:Render()
|
|
if not self.frame or not self.content then return end
|
|
|
|
if not Settings():Get("trackerEnabled") or (Settings():Get("trackerHideInCombat") and self.inCombat) then
|
|
self.frame:Hide()
|
|
return
|
|
end
|
|
|
|
self.frame:Show()
|
|
self:ApplyLockState()
|
|
|
|
local db=CharacterDB()
|
|
local expanded=db.expanded and true or false
|
|
self.minimize:SetText(expanded and "-" or "+")
|
|
|
|
self.headerText:SetText("")
|
|
|
|
if not expanded then
|
|
self.scroll:Hide()
|
|
if self.resizeGrip then self.resizeGrip:Hide() end
|
|
self.frame:SetHeight(30)
|
|
return
|
|
end
|
|
|
|
self.scroll:Show()
|
|
|
|
self:ClearRows()
|
|
local ordered=(QuestieOcto.TrackerDriver and QuestieOcto.TrackerDriver.ordered) or {}
|
|
local sortMode=Settings():Get("trackerSort")
|
|
local lastZone=nil
|
|
local collapsedZone=false
|
|
|
|
for i=1,table.getn(ordered) do
|
|
local quest=ordered[i]
|
|
if sortMode=="zone" then
|
|
local zone=quest.zoneGroup or "Other"
|
|
if zone~=lastZone then
|
|
if self.rowCount>0 then self:AddRow("","spacer",nil,false) end
|
|
local zoneRow=self:AddRow(zone,"zone",nil,false)
|
|
if zoneRow then zoneRow.zoneName=zone end
|
|
lastZone=zone
|
|
collapsedZone=self:IsZoneCollapsed(zone)
|
|
end
|
|
end
|
|
|
|
if not collapsedZone then
|
|
|
|
local prefix=""
|
|
if quest.level and tonumber(quest.level) and tonumber(quest.level)>0 then
|
|
-- Match pfQuest's Vanilla tracker convention: the native quest-log tag
|
|
-- adds a '+' inside the level brackets, e.g. [40+].
|
|
prefix="["..tostring(quest.level)..(quest.tag and "+" or "").."] "
|
|
end
|
|
local title=tostring(quest.title or "Quest")
|
|
if quest.failed then
|
|
title=title.." (Failed)"
|
|
elseif quest.complete then
|
|
title=title.." ("..tostring(_G["COMPLETE"] or "Complete")..")"
|
|
end
|
|
self:AddRow(prefix..title,"quest",quest.id,quest.complete,quest.level,quest.failed)
|
|
self:AddTimerRow(quest)
|
|
|
|
-- A completed quest is represented by "(Complete)" in its title, matching
|
|
-- the classic Questie/Quest Log presentation. Do not keep redundant 8/8,
|
|
-- 10/10, etc. objective rows beneath an already-complete quest.
|
|
if not quest.complete then
|
|
for j=1,table.getn(quest.objectives or {}) do
|
|
local objective=quest.objectives[j]
|
|
self:AddRow(ObjectiveDisplayText(objective),"objective",nil,objective.complete)
|
|
end
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
if self.rowCount==0 then
|
|
self:AddRow("No tracked quests.","objective",nil,false)
|
|
end
|
|
|
|
-- First pass has already created the visible FontStrings at natural width.
|
|
-- Measure those exact strings and choose a tracker width capped like pfQuest.
|
|
-- Then perform one constrained pass so only text that exceeds the available
|
|
-- width wraps, and row heights expand to keep every wrapped line visible.
|
|
local frameWidth,contentWidth=self:AutoFitWidth()
|
|
|
|
for i=1,self.rowCount do
|
|
ApplyRowStyle(self.rows[i],contentWidth,true)
|
|
end
|
|
self:ApplyVisibleRowsHeight()
|
|
self:LayoutVisibleRows()
|
|
end
|
|
|
|
function T:OpenQuest(questID)
|
|
local state=QuestieOcto.QuestLog and QuestieOcto.QuestLog.active and QuestieOcto.QuestLog.active[tonumber(questID)]
|
|
if not state then return end
|
|
|
|
if QuestLogFrame and ShowUIPanel then
|
|
ShowUIPanel(QuestLogFrame)
|
|
elseif ToggleQuestLog and (not QuestLogFrame or not QuestLogFrame:IsShown()) then
|
|
ToggleQuestLog()
|
|
end
|
|
|
|
if QuestLog_SetSelection and state.logIndex then
|
|
QuestLog_SetSelection(state.logIndex)
|
|
end
|
|
if QuestLog_Update then QuestLog_Update() end
|
|
if QuestLog_UpdateQuestDetails then QuestLog_UpdateQuestDetails() end
|
|
end
|
|
|
|
function T:ApplyLockState()
|
|
if not self.frame then return end
|
|
local locked=Settings():Get("trackerLocked") and true or false
|
|
self.frame:SetMovable(not locked)
|
|
-- Height is controlled exclusively by the discrete Visible Rows option.
|
|
-- Do not expose pixel resizing, which can reintroduce partial-row clipping.
|
|
self.frame:SetResizable(false)
|
|
if self.resizeGrip then self.resizeGrip:Hide() end
|
|
end
|
|
|
|
function T:ToggleExpanded()
|
|
local db=CharacterDB()
|
|
if db.expanded then
|
|
self:SaveGeometry()
|
|
db.expanded=false
|
|
else
|
|
db.expanded=true
|
|
end
|
|
self:Render()
|
|
end
|
|
|
|
function T:ResetLocation()
|
|
if not self.frame then return end
|
|
local db=CharacterDB()
|
|
db.window.point="TOPRIGHT"
|
|
db.window.relativePoint="TOPRIGHT"
|
|
db.window.x=-30
|
|
db.window.y=-180
|
|
db.expanded=true
|
|
self:RestoreGeometry()
|
|
self:Render()
|
|
self:SaveGeometry()
|
|
end
|
|
|
|
function T:HideNativeQuestTimer()
|
|
if not QuestTimerFrame then return end
|
|
|
|
local clear=QuestTimerFrame.questieOctoOriginalClearAllPoints or QuestTimerFrame.ClearAllPoints
|
|
local setpoint=QuestTimerFrame.questieOctoOriginalSetPoint or QuestTimerFrame.SetPoint
|
|
|
|
if clear then clear(QuestTimerFrame) end
|
|
if setpoint then setpoint(QuestTimerFrame,"TOP",UIParent,"TOP",-10000,-10000) end
|
|
end
|
|
|
|
function T:LockNativeQuestTimerPosition()
|
|
if not QuestTimerFrame or self.nativeTimerMovementLocked then return end
|
|
|
|
QuestTimerFrame.questieOctoOriginalSetPoint=QuestTimerFrame.SetPoint
|
|
QuestTimerFrame.questieOctoOriginalClearAllPoints=QuestTimerFrame.ClearAllPoints
|
|
QuestTimerFrame.questieOctoOriginalSetAllPoints=QuestTimerFrame.SetAllPoints
|
|
|
|
QuestTimerFrame.SetPoint=function() end
|
|
QuestTimerFrame.ClearAllPoints=function() end
|
|
if QuestTimerFrame.SetAllPoints then QuestTimerFrame.SetAllPoints=function() end end
|
|
|
|
self.nativeTimerMovementLocked=true
|
|
end
|
|
|
|
function T:UnlockNativeQuestTimerPosition()
|
|
if not QuestTimerFrame or not self.nativeTimerMovementLocked then return end
|
|
|
|
if QuestTimerFrame.questieOctoOriginalSetPoint then
|
|
QuestTimerFrame.SetPoint=QuestTimerFrame.questieOctoOriginalSetPoint
|
|
end
|
|
if QuestTimerFrame.questieOctoOriginalClearAllPoints then
|
|
QuestTimerFrame.ClearAllPoints=QuestTimerFrame.questieOctoOriginalClearAllPoints
|
|
end
|
|
if QuestTimerFrame.questieOctoOriginalSetAllPoints then
|
|
QuestTimerFrame.SetAllPoints=QuestTimerFrame.questieOctoOriginalSetAllPoints
|
|
end
|
|
|
|
QuestTimerFrame.questieOctoOriginalSetPoint=nil
|
|
QuestTimerFrame.questieOctoOriginalClearAllPoints=nil
|
|
QuestTimerFrame.questieOctoOriginalSetAllPoints=nil
|
|
self.nativeTimerMovementLocked=false
|
|
end
|
|
|
|
function T:InstallNativeTimerSuppression()
|
|
if self.nativeTimerSuppressionInstalled then return end
|
|
self.nativeTimerSuppressionInstalled=true
|
|
|
|
if type(QuestTimerFrame_Update)=="function" then
|
|
local secureHook=hooksecurefunc
|
|
if type(secureHook)~="function" and LibStub then
|
|
local aceCore=LibStub("QuestieOcto_AceCore-3.0",true)
|
|
secureHook=aceCore and aceCore.hooksecurefunc
|
|
end
|
|
|
|
if type(secureHook)=="function" then
|
|
secureHook("QuestTimerFrame_Update",function()
|
|
if Settings():Get("trackerEnabled") then T:HideNativeQuestTimer() end
|
|
end)
|
|
else
|
|
-- Last-resort Vanilla compatibility only if neither the client nor Ace3v
|
|
-- provides Questie's normal post-hook primitive.
|
|
local original=QuestTimerFrame_Update
|
|
QuestTimerFrame_Update=function()
|
|
local result=original()
|
|
if Settings():Get("trackerEnabled") then T:HideNativeQuestTimer() end
|
|
return result
|
|
end
|
|
end
|
|
end
|
|
|
|
if QuestTimerFrame and QuestTimerFrame.SetScript then
|
|
local previous=QuestTimerFrame:GetScript("OnShow")
|
|
QuestTimerFrame:SetScript("OnShow",function()
|
|
if previous then previous() end
|
|
if Settings():Get("trackerEnabled") then T:HideNativeQuestTimer() end
|
|
end)
|
|
end
|
|
end
|
|
|
|
function T:ApplyNativeTrackerVisibility()
|
|
local enabled=Settings():Get("trackerEnabled") and true or false
|
|
self:InstallNativeTimerSuppression()
|
|
|
|
-- Questie 5.2.3/6.0.0 replace Vanilla's QuestWatchFrame while the Questie
|
|
-- tracker is enabled. Turtle may expose this frame depending on its UI build.
|
|
if QuestWatchFrame then
|
|
if enabled then QuestWatchFrame:Hide() else QuestWatchFrame:Show() end
|
|
end
|
|
|
|
-- Old Questie embeds quest timers in its tracker and moves Blizzard's timer
|
|
-- frame offscreen instead of destroying it. Preserve its original point so
|
|
-- disabling Questie Tracker can restore the native timer cleanly.
|
|
if QuestTimerFrame then
|
|
self.nativeTimerPoint=self.nativeTimerPoint or {QuestTimerFrame:GetPoint()}
|
|
if enabled then
|
|
self:HideNativeQuestTimer()
|
|
self:LockNativeQuestTimerPosition()
|
|
else
|
|
self:UnlockNativeQuestTimerPosition()
|
|
if self.nativeTimerPoint and self.nativeTimerPoint[1] then
|
|
QuestTimerFrame:ClearAllPoints()
|
|
QuestTimerFrame:SetPoint(unpack(self.nativeTimerPoint))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function T:ApplyBackgroundOpacity()
|
|
if not self.frame or not self.frame.SetBackdropColor then return end
|
|
local alpha=tonumber(Settings():Get("trackerBackgroundOpacity")) or 0
|
|
alpha=Clamp(alpha,0,1)
|
|
self.frame:SetBackdropColor(0.03,0.03,0.03,alpha)
|
|
-- Old Questie's transparent tracker is genuinely frameless. Tie the border
|
|
-- to the optional background so the default 0 opacity has no chrome.
|
|
if self.frame.SetBackdropBorderColor then
|
|
self.frame:SetBackdropBorderColor(0.45,0.45,0.45,0.75*alpha)
|
|
end
|
|
end
|
|
|
|
function T:CreateFrame()
|
|
if self.frame then return end
|
|
local frame=CreateFrame("Frame","QuestieOctoTrackerFrame",UIParent)
|
|
self.frame=frame
|
|
frame:SetFrameStrata("MEDIUM")
|
|
if frame.SetClampedToScreen then frame:SetClampedToScreen(false) end
|
|
frame:SetMovable(true)
|
|
frame:SetResizable(false)
|
|
if frame.EnableMouseWheel then frame:EnableMouseWheel(true) end
|
|
frame:SetScript("OnMouseWheel",function() T:ScrollBy(arg1) end)
|
|
frame:SetScript("OnUpdate",function()
|
|
T.timerElapsed=(T.timerElapsed or 0)+(arg1 or 0)
|
|
if T.timerElapsed>=1 then
|
|
T.timerElapsed=0
|
|
T:UpdateTimerRows()
|
|
end
|
|
end)
|
|
frame:EnableMouse(true)
|
|
frame:SetBackdrop({
|
|
bgFile="Interface\\ChatFrame\\ChatFrameBackground",
|
|
edgeFile="Interface\\Tooltips\\UI-Tooltip-Border",
|
|
tile=true,tileSize=16,edgeSize=12,
|
|
insets={left=3,right=3,top=3,bottom=3}
|
|
})
|
|
frame:SetBackdropColor(0.03,0.03,0.03,0)
|
|
frame:SetBackdropBorderColor(0.45,0.45,0.45,0)
|
|
|
|
|
|
local header=CreateFrame("Frame",nil,frame)
|
|
self.header=header
|
|
header:SetPoint("TOPLEFT",frame,"TOPLEFT",5,-5)
|
|
header:SetPoint("TOPRIGHT",frame,"TOPRIGHT",-5,-5)
|
|
header:SetHeight(20)
|
|
header:EnableMouse(true)
|
|
if header.EnableMouseWheel then header:EnableMouseWheel(true) end
|
|
header:SetScript("OnMouseWheel",function() T:ScrollBy(arg1) end)
|
|
header:SetScript("OnMouseDown",function()
|
|
if arg1=="LeftButton" and not Settings():Get("trackerLocked") then
|
|
frame:StartMoving()
|
|
end
|
|
end)
|
|
header:SetScript("OnMouseUp",function()
|
|
frame:StopMovingOrSizing()
|
|
T:SaveGeometry()
|
|
end)
|
|
|
|
local title=header:CreateFontString(nil,"ARTWORK","GameFontNormal")
|
|
self.headerText=title
|
|
title:SetJustifyH("RIGHT")
|
|
SetFont(title,12,1,0.82,0)
|
|
title:SetText("(0)")
|
|
|
|
-- Old Questie/pfQuest-style lightweight expand control: keep the hit area,
|
|
-- but render only a +/- glyph with no button chrome.
|
|
local minimize=CreateFrame("Button",nil,header)
|
|
self.minimize=minimize
|
|
minimize:SetWidth(16)
|
|
minimize:SetHeight(16)
|
|
minimize:SetPoint("RIGHT",header,"RIGHT",0,0)
|
|
local minimizeText=minimize:CreateFontString(nil,"OVERLAY","GameFontNormal")
|
|
minimize.text=minimizeText
|
|
minimizeText:SetAllPoints(minimize)
|
|
minimizeText:SetJustifyH("CENTER")
|
|
minimizeText:SetJustifyV("MIDDLE")
|
|
SetFont(minimizeText,13,1,0.82,0)
|
|
minimizeText:SetText("-")
|
|
minimize.SetText=function(self,value) self.text:SetText(value or "") end
|
|
title:SetPoint("RIGHT",minimize,"LEFT",-2,0)
|
|
minimize:SetScript("OnClick",function() T:ToggleExpanded() end)
|
|
|
|
-- Questie 3.3.5-style tracker scrolling does not need visible scrollbar
|
|
-- chrome on Vanilla. Keep a plain ScrollFrame and use the mouse wheel.
|
|
local scroll=CreateFrame("ScrollFrame","QuestieOctoTrackerScrollFrame",frame)
|
|
self.scroll=scroll
|
|
-- Keep vertical anchors, but make width explicit. A TOPLEFT+BOTTOMRIGHT
|
|
-- horizontal anchor pair would override SetWidth() on Vanilla and leave the
|
|
-- scroll/content region one render behind the auto-fit frame width.
|
|
scroll:SetPoint("TOPLEFT",frame,"TOPLEFT",8,-29)
|
|
-- Height is explicit rather than derived from a second vertical anchor.
|
|
-- This prevents Vanilla from keeping the previous viewport height until the
|
|
-- next input event after a row-count driven frame resize.
|
|
scroll:SetWidth(math.max(1,self.defaultWidth-16))
|
|
scroll:SetHeight(math.max(1,self.defaultHeight-self.frameVerticalChrome))
|
|
if scroll.EnableMouseWheel then scroll:EnableMouseWheel(true) end
|
|
scroll:SetScript("OnMouseWheel",function() T:ScrollBy(arg1) end)
|
|
|
|
local content=CreateFrame("Frame","QuestieOctoTrackerScrollChild",scroll)
|
|
self.content=content
|
|
content:SetWidth(220)
|
|
content:SetHeight(1)
|
|
if content.EnableMouseWheel then content:EnableMouseWheel(true) end
|
|
content:SetScript("OnMouseWheel",function() T:ScrollBy(arg1) end)
|
|
scroll:SetScrollChild(content)
|
|
|
|
-- No mouse resize grip: tracker height is row-count driven.
|
|
self.resizeGrip=nil
|
|
|
|
-- Keep Blizzard's native watch/timer UI from duplicating Questie while
|
|
-- enabled. Re-apply when those native frames try to show later.
|
|
if QuestWatchFrame and QuestWatchFrame.HookScript then
|
|
QuestWatchFrame:HookScript("OnShow",function()
|
|
if Settings():Get("trackerEnabled") then this:Hide() end
|
|
end)
|
|
end
|
|
if QuestTimerFrame and QuestTimerFrame.HookScript then
|
|
QuestTimerFrame:HookScript("OnShow",function()
|
|
if Settings():Get("trackerEnabled") then T:ApplyNativeTrackerVisibility() end
|
|
end)
|
|
end
|
|
|
|
self:RestoreGeometry()
|
|
self:ApplyLockState()
|
|
self:ApplyBackgroundOpacity()
|
|
self:ApplyNativeTrackerVisibility()
|
|
frame:Hide()
|
|
end
|
|
|
|
function T:OnTrackerStateChanged()
|
|
self:Render()
|
|
end
|
|
|
|
function T:OnTrackerSettingChanged(key,value)
|
|
if key=="trackerEnabled" then
|
|
self:ApplyNativeTrackerVisibility()
|
|
self:Render()
|
|
elseif key=="trackerLocked" then
|
|
self:ApplyLockState()
|
|
self:Render()
|
|
elseif key=="trackerBackgroundOpacity" then
|
|
self:ApplyBackgroundOpacity()
|
|
elseif key=="trackerFontSize" or key=="trackerMaxWidth" or key=="trackerVisibleRows" or key=="trackerSort" or
|
|
key=="trackerOutlineText" or key=="trackerThickOutlineText" or
|
|
key=="trackerShowCompleted" or key=="trackerHideCompletedObjectives" or
|
|
key=="trackerAutoTrack" or key=="trackerHideInCombat" then
|
|
self:Render()
|
|
end
|
|
end
|
|
|
|
function T:OnCombatEvent(eventName)
|
|
if eventName=="PLAYER_REGEN_DISABLED" then self.inCombat=true else self.inCombat=false end
|
|
self:Render()
|
|
end
|
|
|
|
function T:Start()
|
|
if self.started then return end
|
|
self.started=true
|
|
self:CreateFrame()
|
|
if self.frame and self.frame.RegisterEvent then
|
|
self.frame:RegisterEvent("PLAYER_REGEN_DISABLED")
|
|
self.frame:RegisterEvent("PLAYER_REGEN_ENABLED")
|
|
local previous=self.frame:GetScript("OnEvent")
|
|
self.frame:SetScript("OnEvent",function()
|
|
if previous then previous() end
|
|
if event=="PLAYER_REGEN_DISABLED" or event=="PLAYER_REGEN_ENABLED" then T:OnCombatEvent(event) end
|
|
end)
|
|
end
|
|
QuestieOcto:RegisterMessage("TRACKER_STATE_CHANGED",self,"OnTrackerStateChanged")
|
|
QuestieOcto:RegisterMessage("TRACKER_SETTING_CHANGED",self,"OnTrackerSettingChanged")
|
|
self:Render()
|
|
end
|
|
|
|
QuestieOcto:RegisterMessage("FOUNDATION_READY",T,"Start") |