-- Who objects to the ObjectiveTracker...? KT_OBJECTIVE_TRACKER_ITEM_WIDTH = 33; -- 1 KT_OBJECTIVE_TRACKER_HEADER_HEIGHT = 25; KT_OBJECTIVE_TRACKER_LINE_WIDTH = 248; KT_OBJECTIVE_TRACKER_HEADER_OFFSET_X = -10; -- calculated values KT_OBJECTIVE_TRACKER_DASH_WIDTH = 0; KT_OBJECTIVE_TRACKER_TEXT_WIDTH = 0; KT_OBJECTIVE_TRACKER_COLOR = { ["Normal"] = { r = 0.8, g = 0.8, b = 0.8 }, ["NormalHighlight"] = { r = HIGHLIGHT_FONT_COLOR.r, g = HIGHLIGHT_FONT_COLOR.g, b = HIGHLIGHT_FONT_COLOR.b }, ["Failed"] = { r = DIM_RED_FONT_COLOR.r, g = DIM_RED_FONT_COLOR.g, b = DIM_RED_FONT_COLOR.b }, ["FailedHighlight"] = { r = RED_FONT_COLOR.r, g = RED_FONT_COLOR.g, b = RED_FONT_COLOR.b }, ["Header"] = { r = 0.75, g = 0.61, b = 0 }, ["HeaderHighlight"] = { r = NORMAL_FONT_COLOR.r, g = NORMAL_FONT_COLOR.g, b = NORMAL_FONT_COLOR.b }, ["Complete"] = { r = 0.6, g = 0.6, b = 0.6 }, ["TimeLeft"] = { r = DIM_RED_FONT_COLOR.r, g = DIM_RED_FONT_COLOR.g, b = DIM_RED_FONT_COLOR.b }, ["TimeLeftHighlight"] = { r = RED_FONT_COLOR.r, g = RED_FONT_COLOR.g, b = RED_FONT_COLOR.b }, }; KT_OBJECTIVE_TRACKER_COLOR["Normal"].reverse = KT_OBJECTIVE_TRACKER_COLOR["NormalHighlight"]; KT_OBJECTIVE_TRACKER_COLOR["NormalHighlight"].reverse = KT_OBJECTIVE_TRACKER_COLOR["Normal"]; KT_OBJECTIVE_TRACKER_COLOR["Failed"].reverse = KT_OBJECTIVE_TRACKER_COLOR["FailedHighlight"]; KT_OBJECTIVE_TRACKER_COLOR["FailedHighlight"].reverse = KT_OBJECTIVE_TRACKER_COLOR["Failed"]; KT_OBJECTIVE_TRACKER_COLOR["Header"].reverse = KT_OBJECTIVE_TRACKER_COLOR["HeaderHighlight"]; KT_OBJECTIVE_TRACKER_COLOR["HeaderHighlight"].reverse = KT_OBJECTIVE_TRACKER_COLOR["Header"]; KT_OBJECTIVE_TRACKER_COLOR["TimeLeft"].reverse = KT_OBJECTIVE_TRACKER_COLOR["TimeLeftHighlight"]; KT_OBJECTIVE_TRACKER_COLOR["TimeLeftHighlight"].reverse = KT_OBJECTIVE_TRACKER_COLOR["TimeLeft"]; -- these are generally from events KT_OBJECTIVE_TRACKER_UPDATE_QUEST = 0x00001; KT_OBJECTIVE_TRACKER_UPDATE_QUEST_ADDED = 0x00002; KT_OBJECTIVE_TRACKER_UPDATE_TASK_ADDED = 0x00004; KT_OBJECTIVE_TRACKER_UPDATE_WORLD_QUEST_ADDED = 0x00008; KT_OBJECTIVE_TRACKER_UPDATE_SCENARIO = 0x00010; KT_OBJECTIVE_TRACKER_UPDATE_SCENARIO_NEW_STAGE = 0x00020; KT_OBJECTIVE_TRACKER_UPDATE_ACHIEVEMENT = 0x00040; KT_OBJECTIVE_TRACKER_UPDATE_ACHIEVEMENT_ADDED = 0x00080; KT_OBJECTIVE_TRACKER_UPDATE_SCENARIO_BONUS_DELAYED = 0x00100; KT_OBJECTIVE_TRACKER_UPDATE_SUPER_TRACK_CHANGED = 0x00200; KT_OBJECTIVE_TRACKER_UPDATE_MOVED = 0x80000; -- these are for the specific module ONLY! KT_OBJECTIVE_TRACKER_UPDATE_MODULE_QUEST = 0x00400; KT_OBJECTIVE_TRACKER_UPDATE_MODULE_AUTO_QUEST_POPUP = 0x00800; KT_OBJECTIVE_TRACKER_UPDATE_MODULE_BONUS_OBJECTIVE = 0x01000; KT_OBJECTIVE_TRACKER_UPDATE_MODULE_WORLD_QUEST = 0x02000; KT_OBJECTIVE_TRACKER_UPDATE_MODULE_SCENARIO = 0x04000; KT_OBJECTIVE_TRACKER_UPDATE_MODULE_ACHIEVEMENT = 0x08000; KT_OBJECTIVE_TRACKER_UPDATE_SCENARIO_SPELLS = 0x10000; KT_OBJECTIVE_TRACKER_UPDATE_MODULE_UI_WIDGETS = 0x20000; KT_OBJECTIVE_TRACKER_UPDATE_MODULE_PROFESSION_RECIPE = 0x40000; KT_OBJECTIVE_TRACKER_UPDATE_MODULE_MONTHLY_ACTIVITIES = 0x100000; -- fix Blizz bug (0x80000) -- special updates KT_OBJECTIVE_TRACKER_UPDATE_STATIC = 0x0000; KT_OBJECTIVE_TRACKER_UPDATE_ALL = 0xFFFFFFFF; KT_OBJECTIVE_TRACKER_UPDATE_REASON = KT_OBJECTIVE_TRACKER_UPDATE_ALL; -- default KT_OBJECTIVE_TRACKER_UPDATE_ID = 0; -- speed optimizations local floor = math.floor; local min = min; local band = bit.band; -- ***************************************************************************************************** -- ***** MODULE STUFF -- ***************************************************************************************************** --[[ blockTemplate: template for the blocks - a quest would be a single block blockType: type of object lineTemplate: template for the lines - a quest objective would be a single line (even if it wordwraps); only FRAME supported lineSpacing: spacing between lines; for the first line it'll be the distance from the top of its block blockOffsetX: offset from the left edge of the blocksframe \__These are both added to a table indexed by blockTemplate. blockOffsetY: offset from the block above / fromHeaderOffsetY: offset from the header for the first block, if there's a header; used instead of blockOffsetY fromModuleOffsetY: offset from the previous module poolCollection: pool of (potentially) multiple frame types for use in a module usedBlocks: table of used blocks; a module should always have its own, this table uses template type so that GetBlock(id) ALWAYS returns the correct frame that already exists (for animation purposes) freelines: table of free lines; a module needs it own if not using default line template there's no table of used lines, that's per block updateReasonModule: the update for this module alone updateReasonEvents: the events which should update the module === modules do not need to change these, they're keyed by block & line === usedTimerBars: table of used timer bars freeTimerBars: table of free timer bars === modules should NOT change these === contentsHeight: the current combined height of all the blocks in the module contentsAnimHeight: the current combined animation height of all the blocks in the module oldContentsHeight: the previous height on the last update hasSkippedBlocks: if the module couldn't display all its blocks because of not enough space --]] KT_DEFAULT_OBJECTIVE_TRACKER_MODULE = {}; function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:OnLoad(friendlyName, defaultTemplate) self.friendlyName = friendlyName or "UnnamedTrackerModule"; self.blockTemplate = defaultTemplate or "KT_ObjectiveTrackerBlockTemplate"; self.blockType = "Frame"; self.lineTemplate = "KT_ObjectiveTrackerLineTemplate"; self.lineSpacing = 2; self.lineWidth = KT_OBJECTIVE_TRACKER_TEXT_WIDTH; self.poolCollection = CreateFramePoolCollection(); self.usedBlocks = { }; self.freeLines = { }; self.fromHeaderOffsetY = -10; self.fromModuleOffsetY = -10; self.contentsHeight = 0; self.contentsAnimHeight = 0; self.oldContentsHeight = 0; self.hasSkippedBlocks = false; self.usedTimerBars = { }; self.freeTimerBars = { }; self.usedProgressBars = { }; self.freeProgressBars = { }; self.updateReasonModule = 0; self.updateReasonEvents = 0; self.BlocksFrame = KT_ObjectiveTrackerFrame.BlocksFrame; KT_DEFAULT_OBJECTIVE_TRACKER_MODULE.AddBlockOffset(self, self.blockTemplate, 0, -6); end function KT_ObjectiveTracker_GetModuleInfoTable(friendlyName, baseModule, defaultTemplate) local info = CreateFromMixins(baseModule or KT_DEFAULT_OBJECTIVE_TRACKER_MODULE); info:OnLoad(friendlyName, defaultTemplate); return info; end function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:BeginLayout(isStaticReanchor) self.topBlock = nil; -- this is the header or the first block for header-less modules self.firstBlock = nil; -- this is the first non-header block self.lastBlock = nil; self.oldContentsHeight = self.contentsHeight; self.contentsHeight = 0; self.contentsAnimHeight = 0; self.potentialBlocksAddedThisLayout = 0; -- this isn't a ref count, this is the total number of blocks that the module tried to add. -- if it's not a static reanchor, reset whether we've skipped blocks if ( not isStaticReanchor ) then self.hasSkippedBlocks = false; if not self:UsesSharedHeader() and self.Header then self.Header:Hide(); end end self:MarkBlocksUnused(); end function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:EndLayout(isStaticReanchor) -- isStaticReanchor not used yet self.lastBlock = self.BlocksFrame.currentBlock; self:FreeUnusedBlocks(); end -- ***** BLOCKS function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:SetHeader(block, text, animateReason) block.module = self; block.isHeader = true; block.Text:SetText(text); block.animateReason = animateReason or 0; self.Header = block; end function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:SetSharedHeader(block) self.Header = block; self.usesSharedHeader = true; end function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:UsesSharedHeader() return self.usesSharedHeader; end function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:GetBlock(id, overrideType, overrideTemplate) local blockType = overrideType or self.blockType; local blockTemplate = overrideTemplate or self.blockTemplate; if not self.usedBlocks[blockTemplate] then self.usedBlocks[blockTemplate] = {}; end -- first try to return existing block local block = self.usedBlocks[blockTemplate][id]; if not block then local pool = self.poolCollection:GetOrCreatePool(blockType, self.BlocksFrame or KT_ObjectiveTrackerFrame.BlocksFrame, blockTemplate); local isNewBlock = nil; block, isNewBlock = pool:Acquire(blockTemplate); if isNewBlock then block.blockTemplate = blockTemplate; -- stored so we can use it to free from the lookup later block.lines = {}; end self.usedBlocks[blockTemplate][id] = block; block.id = id; block.module = self; end block.used = true; block.height = 0; block.currentLine = nil; -- prep lines if block.lines then for objectiveKey, line in pairs(block.lines) do line.used = nil; end end return block; end function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:GetExistingBlock(id, overrideTemplate) local template = overrideTemplate or self.blockTemplate; assert(template); assert(self.usedBlocks) local blocks = self.usedBlocks[template]; if blocks then return blocks[id]; end return nil; end function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:MarkBlocksUnused() for blockTemplate, blockTable in pairs(self.usedBlocks) do for blockID, block in pairs(blockTable) do block.used = nil; end end end function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:FreeBlock(block) -- free all the lines for _, line in pairs(block.lines) do self:FreeLine(block, line); end block.lines = { }; -- free the block self.usedBlocks[block.blockTemplate][block.id] = nil; self.poolCollection:Release(block); -- callback if ( self.OnFreeBlock ) then self:OnFreeBlock(block); end end function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:FreeUnusedBlocks() for blockTemplate, blockTable in pairs(self.usedBlocks) do for blockID, block in pairs(blockTable) do if not block.used then self:FreeBlock(block); end end end end function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:GetBlockCount() local count = 0; local modules = self:GetRelatedModules(); for index, module in ipairs(modules) do count = count + (module.potentialBlocksAddedThisLayout or 0); end return count; end function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:GetActiveBlocks(blockTemplate) -- By default use the module's preferred block type. blockTemplate = blockTemplate or self.blockTemplate; if not self.usedBlocks[blockTemplate] then self.usedBlocks[blockTemplate] = {}; end return self.usedBlocks[blockTemplate]; end -- ***** LINES function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:FreeLine(block, line) block.lines[line.objectiveKey] = nil; -- if the line has a type, the freeLines will be the cache for that type of line, otherwise use the module's default local freeLines = (line.type and line.type.freeLines) or self.freeLines; tinsert(freeLines, line); -- remove timer bar if ( line.TimerBar ) then self:FreeTimerBar(block, line); end if ( line.ProgressBar ) then self:FreeProgressBar(block, line); end if ( line.type and self.OnFreeTypedLine ) then self:OnFreeTypedLine(line); elseif ( self.OnFreeLine ) then self:OnFreeLine(line); end line:Hide(); end function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:FreeUnusedLines(block) for objectiveKey, line in pairs(block.lines) do if ( not line.used ) then self:FreeLine(block, line); end end end function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:GetLine(block, objectiveKey, lineType) -- first look for existing line local line = block.lines[objectiveKey]; -- if existing line is not of the same type, discard it if ( line and line.type ~= lineType ) then self:FreeLine(block, line); line = nil; end if ( line ) then line.used = true; return line; end local freeLines = (lineType and lineType.freeLines) or self.freeLines; local numFreeLines = #freeLines; local parent = block.ScrollContents or block; if ( numFreeLines > 0 ) then -- get a free line line = freeLines[numFreeLines]; tremove(freeLines, numFreeLines); line:SetParent(parent); line:Show(); else -- create a new line line = CreateFrame("Frame", nil, parent, (lineType and lineType.template) or self.lineTemplate); line.type = lineType; end block.lines[objectiveKey] = line; line.objectiveKey = objectiveKey; line.used = true; return line; end -- ***** OBJECTIVES KT_OBJECTIVE_DASH_STYLE_SHOW = 1; KT_OBJECTIVE_DASH_STYLE_HIDE = 2; KT_OBJECTIVE_DASH_STYLE_HIDE_AND_COLLAPSE = 3; function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:AddObjective(block, objectiveKey, text, lineType, useFullHeight, dashStyle, colorStyle, adjustForNoText, overrideHeight) local line = self:GetLine(block, objectiveKey, lineType); -- width if ( block.lineWidth ~= line.width ) then line.Text:SetWidth(block.lineWidth or self.lineWidth); line.width = block.lineWidth; -- default should be nil end -- dash if ( line.Dash ) then if ( not dashStyle ) then dashStyle = KT_OBJECTIVE_DASH_STYLE_SHOW; end if ( line.dashStyle ~= dashStyle ) then if ( dashStyle == KT_OBJECTIVE_DASH_STYLE_SHOW ) then line.Dash:Show(); line.Dash:SetText(QUEST_DASH); elseif ( dashStyle == KT_OBJECTIVE_DASH_STYLE_HIDE ) then line.Dash:Hide(); line.Dash:SetText(QUEST_DASH); elseif ( dashStyle == KT_OBJECTIVE_DASH_STYLE_HIDE_AND_COLLAPSE ) then line.Dash:Hide(); line.Dash:SetText(nil); else error("Invalid dash style: " .. tostring(dashStyle)); end line.dashStyle = dashStyle; end end -- set the text local textHeight = self:SetStringText(line.Text, text, useFullHeight, colorStyle, block.isHighlighted); local height = overrideHeight or textHeight; line:SetHeight(height); local yOffset; if ( adjustForNoText and text == "" ) then -- don't change the height -- move the line up so the next object ends up in the same position as if there had been no line yOffset = height; else block.height = block.height + height + block.module.lineSpacing; yOffset = -block.module.lineSpacing; end -- anchor the line local anchor = block.currentLine or block.HeaderText; if ( anchor ) then line:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", 0, yOffset); else line:SetPoint("TOPLEFT", 0, yOffset); end block.currentLine = line; return line; end function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:SetStringText(fontString, text, useFullHeight, colorStyle, useHighlight) if useFullHeight then fontString:SetMaxLines(0); else fontString:SetMaxLines(2); end fontString:SetText(text); local stringHeight = fontString:GetHeight(); colorStyle = colorStyle or KT_OBJECTIVE_TRACKER_COLOR["Normal"]; if ( useHighlight and colorStyle.reverse ) then colorStyle = colorStyle.reverse; end if ( fontString.colorStyle ~= colorStyle ) then fontString:SetTextColor(colorStyle.r, colorStyle.g, colorStyle.b); fontString.colorStyle = colorStyle; end return stringHeight; end -- ***** BLOCK HEADER function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:SetBlockHeader(block, text) local height = self:SetStringText(block.HeaderText, text, nil, KT_OBJECTIVE_TRACKER_COLOR["Header"], block.isHighlighted); block.height = height; end function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:OnBlockHeaderClick(block, mouseButton) end function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:OnBlockHeaderEnter(block) --block.isHighlighted = true; -- MSA if not block.isHighlighted then -- MSA if ( block.HeaderText ) then local headerColorStyle = KT_OBJECTIVE_TRACKER_COLOR["HeaderHighlight"]; block.HeaderText:SetTextColor(headerColorStyle.r, headerColorStyle.g, headerColorStyle.b); block.HeaderText.colorStyle = headerColorStyle; end for objectiveKey, line in pairs(block.lines) do local colorStyle = line.Text.colorStyle.reverse; if ( colorStyle ) then line.Text:SetTextColor(colorStyle.r, colorStyle.g, colorStyle.b); line.Text.colorStyle = colorStyle; if ( line.Dash ) then line.Dash:SetTextColor(KT_OBJECTIVE_TRACKER_COLOR["NormalHighlight"].r, KT_OBJECTIVE_TRACKER_COLOR["NormalHighlight"].g, KT_OBJECTIVE_TRACKER_COLOR["NormalHighlight"].b); end end end block.isHighlighted = true; -- MSA end -- MSA end function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:OnBlockHeaderLeave(block) block.isHighlighted = nil; if ( block.HeaderText ) then local headerColorStyle = KT_OBJECTIVE_TRACKER_COLOR["Header"]; block.HeaderText:SetTextColor(headerColorStyle.r, headerColorStyle.g, headerColorStyle.b); block.HeaderText.colorStyle = headerColorStyle; end for objectiveKey, line in pairs(block.lines) do local colorStyle = line.Text.colorStyle.reverse; if ( colorStyle ) then line.Text:SetTextColor(colorStyle.r, colorStyle.g, colorStyle.b); line.Text.colorStyle = colorStyle; if ( line.Dash ) then line.Dash:SetTextColor(KT_OBJECTIVE_TRACKER_COLOR["Normal"].r, KT_OBJECTIVE_TRACKER_COLOR["Normal"].g, KT_OBJECTIVE_TRACKER_COLOR["Normal"].b); end end end end -- ***** TIMER BAR function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:AddTimerBar(block, line, duration, startTime) local timerBar = self.usedTimerBars[block] and self.usedTimerBars[block][line]; if ( not timerBar ) then local numFreeTimerBars = #self.freeTimerBars; local parent = block.ScrollContents or block; if ( numFreeTimerBars > 0 ) then timerBar = self.freeTimerBars[numFreeTimerBars]; tremove(self.freeTimerBars, numFreeTimerBars); timerBar:SetParent(parent); timerBar:Show(); else timerBar = CreateFrame("Frame", nil, parent, "KT_ObjectiveTrackerTimerBarTemplate"); timerBar.Label:SetPoint("LEFT", KT_OBJECTIVE_TRACKER_DASH_WIDTH, 0); timerBar.height = timerBar:GetHeight(); end if ( not self.usedTimerBars[block] ) then self.usedTimerBars[block] = { }; end self.usedTimerBars[block][line] = timerBar; timerBar:Show(); end -- anchor the status bar local anchor = block.currentLine or block.HeaderText; if ( anchor ) then timerBar:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", 0, -block.module.lineSpacing); else timerBar:SetPoint("TOPLEFT", 0, -block.module.lineSpacing); end timerBar.Bar:SetMinMaxValues(0, duration); timerBar.duration = duration; timerBar.startTime = startTime; timerBar.block = block; line.TimerBar = timerBar; block.height = block.height + timerBar.height + block.module.lineSpacing; block.currentLine = timerBar; return timerBar; end function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:FreeTimerBar(block, line) local timerBar = line.TimerBar; if ( timerBar ) then self.usedTimerBars[block][line] = nil; tinsert(self.freeTimerBars, timerBar); timerBar:Hide(); line.TimerBar = nil; end end -- ***************************************************************************************************** -- ***** PROGRESS BAR -- ***************************************************************************************************** function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:AddProgressBar(block, line, questID) local progressBar = self.usedProgressBars[block] and self.usedProgressBars[block][line]; if ( not progressBar ) then local numFreeProgressBars = #self.freeProgressBars; local parent = block.ScrollContents or block; if ( numFreeProgressBars > 0 ) then progressBar = self.freeProgressBars[numFreeProgressBars]; tremove(self.freeProgressBars, numFreeProgressBars); progressBar:SetParent(parent); progressBar:Show(); else progressBar = CreateFrame("Frame", nil, parent, "KT_ObjectiveTrackerProgressBarTemplate"); progressBar.height = progressBar:GetHeight(); end if ( not self.usedProgressBars[block] ) then self.usedProgressBars[block] = { }; end self.usedProgressBars[block][line] = progressBar; progressBar:RegisterEvent("QUEST_LOG_UPDATE"); progressBar:Show(); -- initialize to the right values progressBar.questID = questID; KT_ObjectiveTrackerProgressBar_SetValue(progressBar, GetQuestProgressBarPercent(questID)); end -- anchor the status bar local anchor = block.currentLine or block.HeaderText; if ( anchor ) then progressBar:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", 0, -block.module.lineSpacing); else progressBar:SetPoint("TOPLEFT", 0, -block.module.lineSpacing); end progressBar.block = block; progressBar.questID = questID; line.ProgressBar = progressBar; block.height = block.height + progressBar.height + block.module.lineSpacing; block.currentLine = progressBar; return progressBar; end function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:FreeProgressBar(block, line) local progressBar = line.ProgressBar; if ( progressBar ) then self.usedProgressBars[block][line] = nil; tinsert(self.freeProgressBars, progressBar); progressBar:Hide(); line.ProgressBar = nil; progressBar:UnregisterEvent("QUEST_LOG_UPDATE"); end end local function ObjectiveTracker_SetModulesCollapsed(collapsed, modules) for index, module in ipairs(modules) do module.collapsed = collapsed; end end function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:GetRelatedModules() -- Default implementation, most single/shared modules can be found this way -- NOTE: This actually inserts self as well, since the header matches, that's fine. local modules = {}; local header = self.Header; for index, module in ipairs(KT_ObjectiveTrackerFrame.MODULES) do if module.Header == header then table.insert(modules, module); end end return modules; end function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:SetCollapsed(collapsed) ObjectiveTracker_SetModulesCollapsed(collapsed, self:GetRelatedModules()); if self.Header and self.Header.MinimizeButton then self.Header.MinimizeButton:SetCollapsed(collapsed); end end function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:IsCollapsed() return self.collapsed; end -- ***************************************************************************************************** -- ***** MODULE/BLOCK CUSTOMIZATION -- ***************************************************************************************************** local function ObjectiveTracker_AddCustomizationData(module, customizationKey, template, data) if not module[customizationKey] then module[customizationKey] = {}; end module[customizationKey][template] = data; end function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:AddButtonOffsets(template, offsets) ObjectiveTracker_AddCustomizationData(self, "buttonOffsets", template, offsets); end function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:AddBlockOffset(template, x, y) ObjectiveTracker_AddCustomizationData(self, "blockOffset", template, { x or 0, y or 0 }); end function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:AddPaddingBetweenButtons(template, padding) ObjectiveTracker_AddCustomizationData(self, "paddingBetweenButtons", template, padding); end local function GetBlockTemplate(block) return block.blockTemplate or block.module.blockTemplate; end function KT_ObjectiveTracker_GetButtonOffsets(block, offsetTag) local offsets = block.module.buttonOffsets; if offsets then return unpack(offsets[GetBlockTemplate(block)][offsetTag]); end return 0, 0; end function KT_ObjectiveTracker_GetBlockOffset(block) local offset = block.module.blockOffset; if offset then return unpack(offset[GetBlockTemplate(block)]); end return 0, 0; end function KT_ObjectiveTracker_GetPaddingBetweenButtons(block) local padding = block.module.paddingBetweenButtons; if padding then return padding[GetBlockTemplate(block)]; end return 0; end -- ***************************************************************************************************** -- ***** BLOCK HEADER HANDLERS -- ***************************************************************************************************** function KT_ObjectiveTrackerBlockHeader_OnLoad(self) self:RegisterForClicks("LeftButtonUp", "RightButtonUp"); self.GetDebugReportInfo = KT_ObjectiveTrackerBlockHeader_GetDebugReportInfo; end function KT_ObjectiveTrackerBlockHeader_OnClick(self, mouseButton) local block = self:GetParent(); block.module:OnBlockHeaderClick(block, mouseButton); end function KT_ObjectiveTrackerBlockHeader_OnEnter(self) local block = self:GetParent(); block.module:OnBlockHeaderEnter(block); end function KT_ObjectiveTrackerBlockHeader_OnLeave(self) local block = self:GetParent(); block.module:OnBlockHeaderLeave(block); end function KT_ObjectiveTrackerBlockHeader_GetDebugReportInfo(self) local block = self:GetParent(); if block.module.GetDebugReportInfo then return block.module:GetDebugReportInfo(block); end return nil; end -- ***************************************************************************************************** -- ***** OBJECTIVE TRACKER LINES -- ***************************************************************************************************** function KT_ObjectiveTrackerCheckLine_OnHide(self) self.Glow.Anim:Stop(); self.Sheen.Anim:Stop(); end -- ***************************************************************************************************** -- ***** TIMER BARS -- ***************************************************************************************************** function KT_ObjectiveTrackerTimerBar_OnUpdate(self, elapsed) local timeNow = GetTime(); local timeRemaining = self.duration - (timeNow - self.startTime); self.Bar:SetValue(timeRemaining); if ( timeRemaining < 0 ) then -- hold at 0 for a moment if ( timeRemaining > -1 ) then timeRemaining = 0; else KT_ObjectiveTracker_Update(self.block.module.updateReasonModule); return; end end self.Label:SetText(SecondsToClock(timeRemaining)); self.Label:SetTextColor(KT_ObjectiveTrackerTimerBar_GetTextColor(self.duration, self.duration - timeRemaining)); end function KT_ObjectiveTrackerTimerBar_GetTextColor(duration, elapsed) local START_PERCENTAGE_YELLOW = .66 local START_PERCENTAGE_RED = .33 local percentageLeft = 1 - ( elapsed / duration ) if ( percentageLeft > START_PERCENTAGE_YELLOW ) then return 1, 1, 1; elseif ( percentageLeft > START_PERCENTAGE_RED ) then -- Start fading to yellow by eliminating blue local blueOffset = (percentageLeft - START_PERCENTAGE_RED) / (START_PERCENTAGE_YELLOW - START_PERCENTAGE_RED); return 1, 1, blueOffset; else local greenOffset = percentageLeft / START_PERCENTAGE_RED; -- Fade to red by eliminating green return 1, greenOffset, 0; end end -- ***************************************************************************************************** -- ***** PROGRESS BARS -- ***************************************************************************************************** function KT_ObjectiveTrackerProgressBar_SetValue(self, percent) self.Bar:SetValue(percent); self.Bar.Label:SetFormattedText(PERCENTAGE_STRING, percent); end function KT_ObjectiveTrackerProgressBar_OnEvent(self) KT_ObjectiveTrackerProgressBar_SetValue(self, GetQuestProgressBarPercent(self.questID)); end -- ***************************************************************************************************** -- ***** FRAME HANDLERS -- ***************************************************************************************************** function KT_ObjectiveTracker_OnLoad(self) KT_DEFAULT_OBJECTIVE_TRACKER_MODULE.OnLoad(self, "KT_DEFAULT_OBJECTIVE_TRACKER_MODULE"); -- create a line so we can get some measurements local line = CreateFrame("Frame", nil, self, self.lineTemplate); line.Text:SetText("Double line|ntest"); -- reuse it tinsert(self.freeLines, line); -- get measurements KT_OBJECTIVE_TRACKER_DASH_WIDTH = line.Dash:GetWidth(); KT_OBJECTIVE_TRACKER_TEXT_WIDTH = KT_OBJECTIVE_TRACKER_LINE_WIDTH - KT_OBJECTIVE_TRACKER_DASH_WIDTH - 12; line.Text:SetWidth(KT_OBJECTIVE_TRACKER_TEXT_WIDTH); local frameLevel = self.BlocksFrame:GetFrameLevel(); self.HeaderMenu:SetFrameLevel(frameLevel + 2); self:RegisterEvent("PLAYER_ENTERING_WORLD"); --UIDropDownMenu_Initialize(self.BlockDropDown, nil, "MENU"); QuestPOI_Initialize(self.BlocksFrame, function(self) self:SetScale(0.9); self:RegisterForClicks("LeftButtonUp", "RightButtonUp"); end ); end function KT_ObjectiveTracker_OnShow(self) -- UIParentManagedFrameMixin.OnShow(self); -- KT_ObjectiveTracker_UpdateHeight(); end function KT_ObjectiveTracker_Initialize(self) self.MODULES = { KT_SCENARIO_CONTENT_TRACKER_MODULE, KT_UI_WIDGET_TRACKER_MODULE, KT_BONUS_OBJECTIVE_TRACKER_MODULE, KT_WORLD_QUEST_TRACKER_MODULE, KT_CAMPAIGN_QUEST_TRACKER_MODULE, KT_QUEST_TRACKER_MODULE, KT_ACHIEVEMENT_TRACKER_MODULE, KT_PROFESSION_RECIPE_TRACKER_MODULE, KT_MONTHLY_ACTIVITIES_TRACKER_MODULE, }; self.MODULES_UI_ORDER = { KT_SCENARIO_CONTENT_TRACKER_MODULE, KT_UI_WIDGET_TRACKER_MODULE, KT_CAMPAIGN_QUEST_TRACKER_MODULE, KT_QUEST_TRACKER_MODULE, KT_BONUS_OBJECTIVE_TRACKER_MODULE, KT_WORLD_QUEST_TRACKER_MODULE, KT_ACHIEVEMENT_TRACKER_MODULE, KT_PROFESSION_RECIPE_TRACKER_MODULE, KT_MONTHLY_ACTIVITIES_TRACKER_MODULE, }; self:RegisterEvent("QUEST_LOG_UPDATE"); self:RegisterEvent("TRACKED_ACHIEVEMENT_LIST_CHANGED"); self:RegisterEvent("PERKS_ACTIVITIES_TRACKED_UPDATED"); self:RegisterEvent("PERKS_ACTIVITY_COMPLETED"); self:RegisterEvent("QUEST_WATCH_LIST_CHANGED"); self:RegisterEvent("QUEST_AUTOCOMPLETE"); self:RegisterEvent("QUEST_ACCEPTED"); self:RegisterEvent("SUPER_TRACKING_CHANGED"); self:RegisterEvent("SCENARIO_UPDATE"); self:RegisterEvent("SCENARIO_CRITERIA_UPDATE"); self:RegisterEvent("SCENARIO_SPELL_UPDATE"); self:RegisterEvent("SCENARIO_BONUS_VISIBILITY_UPDATE"); self:RegisterEvent("TRACKED_ACHIEVEMENT_UPDATE"); self:RegisterEvent("ZONE_CHANGED_NEW_AREA"); self:RegisterEvent("ZONE_CHANGED"); self:RegisterEvent("QUEST_POI_UPDATE"); self:RegisterEvent("VARIABLES_LOADED"); self:RegisterEvent("QUEST_TURNED_IN"); self:RegisterEvent("PLAYER_MONEY"); self:RegisterEvent("CVAR_UPDATE"); self:RegisterEvent("WAYPOINT_UPDATE"); self.watchMoneyReasons = 0; WorldMapFrame:RegisterCallback("SetFocusedQuestID", KT_ObjectiveTracker_OnFocusedQuestChanged, self); WorldMapFrame:RegisterCallback("ClearFocusedQuestID", KT_ObjectiveTracker_OnFocusedQuestChanged, self); KT_ProfessionsRecipeTracking_Initialize(); self.initialized = true; end function KT_ObjectiveTracker_OnFocusedQuestChanged(self) KT_ObjectiveTracker_Update(KT_OBJECTIVE_TRACKER_UPDATE_MODULE_QUEST); end function KT_ObjectiveTracker_OnEvent(self, event, ...) if ( event == "QUEST_LOG_UPDATE" ) then KT_ObjectiveTracker_Update(KT_OBJECTIVE_TRACKER_UPDATE_QUEST); elseif ( event == "TRACKED_ACHIEVEMENT_UPDATE" ) then KT_AchievementObjectiveTracker_OnAchievementUpdate(...); elseif ( event == "PERKS_ACTIVITIES_TRACKED_UPDATED" ) then KT_ObjectiveTracker_Update(KT_OBJECTIVE_TRACKER_UPDATE_MODULE_MONTHLY_ACTIVITIES); elseif ( event == "PERKS_ACTIVITY_COMPLETED" ) then KT_MonthlyActivitiesObjectiveTracker_OnActivityCompleted(...); elseif ( event == "QUEST_ACCEPTED" ) then local questID = ...; if ( not C_QuestLog.IsQuestBounty(questID) ) then if ( C_QuestLog.IsQuestTask(questID) ) then if ( QuestUtils_IsQuestWorldQuest(questID) ) then KT_ObjectiveTracker_Update(KT_OBJECTIVE_TRACKER_UPDATE_WORLD_QUEST_ADDED, questID); else KT_ObjectiveTracker_Update(KT_OBJECTIVE_TRACKER_UPDATE_TASK_ADDED, questID); end else if ( GetCVarBool("autoQuestWatch") and C_QuestLog.GetNumQuestWatches() < Constants.QuestWatchConsts.MAX_QUEST_WATCHES ) then C_QuestLog.AddQuestWatch(questID, Enum.QuestWatchType.Automatic); KT_QuestSuperTracking_OnQuestTracked(questID); end end end elseif ( event == "TRACKED_ACHIEVEMENT_LIST_CHANGED" ) then local achievementID, added = ...; if ( added ) then KT_ObjectiveTracker_Update(KT_OBJECTIVE_TRACKER_UPDATE_ACHIEVEMENT_ADDED, achievementID); else KT_ObjectiveTracker_Update(KT_OBJECTIVE_TRACKER_UPDATE_ACHIEVEMENT); end elseif ( event == "QUEST_WATCH_LIST_CHANGED" ) then local questID, added = ...; if ( added ) then if ( not C_QuestLog.IsQuestBounty(questID) or C_QuestLog.IsComplete(questID) ) then KT_ObjectiveTracker_Update(KT_OBJECTIVE_TRACKER_UPDATE_QUEST_ADDED, questID); end else KT_ObjectiveTracker_Update(KT_OBJECTIVE_TRACKER_UPDATE_QUEST); end elseif ( event == "QUEST_POI_UPDATE" ) then QuestPOIUpdateIcons(); if ( GetCVar("trackQuestSorting") == "proximity" ) then C_QuestLog.SortQuestWatches(); end -- C_QuestLog.SortQuestWatches might not trigger a QUEST_WATCH_LIST_CHANGED due to unique signals, so force an update KT_ObjectiveTracker_Update(KT_OBJECTIVE_TRACKER_UPDATE_MODULE_QUEST); KT_QuestSuperTracking_OnPOIUpdate(); elseif ( event == "SCENARIO_CRITERIA_UPDATE" ) then KT_ObjectiveTracker_Update(KT_OBJECTIVE_TRACKER_UPDATE_SCENARIO); elseif ( event == "SCENARIO_SPELL_UPDATE" ) then KT_ObjectiveTracker_Update(KT_OBJECTIVE_TRACKER_UPDATE_SCENARIO_SPELLS); elseif ( event == "SCENARIO_BONUS_VISIBILITY_UPDATE") then KT_ObjectiveTracker_Update(KT_OBJECTIVE_TRACKER_UPDATE_MODULE_BONUS_OBJECTIVE); elseif ( event == "SUPER_TRACKING_CHANGED" ) then KT_ObjectiveTracker_UpdateSuperTrackedQuest(self); elseif ( event == "ZONE_CHANGED" ) then local lastMapID = C_Map.GetBestMapForUnit("player"); if ( lastMapID ~= self.lastMapID ) then C_QuestLog.SortQuestWatches(); self.lastMapID = lastMapID; end elseif ( event == "QUEST_AUTOCOMPLETE" ) then local questId = ...; KT_AutoQuestPopupTracker_AddPopUp(questId, "COMPLETE"); elseif ( event == "SCENARIO_UPDATE" ) then local newStage = ...; if ( newStage ) then KT_ObjectiveTracker_Update(KT_OBJECTIVE_TRACKER_UPDATE_SCENARIO_NEW_STAGE); else KT_ObjectiveTracker_Update(KT_OBJECTIVE_TRACKER_UPDATE_SCENARIO); end elseif ( event == "ZONE_CHANGED_NEW_AREA" ) then C_QuestLog.SortQuestWatches(); elseif ( event == "QUEST_TURNED_IN" ) then local questID, xp, money = ...; if ( C_QuestLog.IsQuestTask(questID) and not C_QuestLog.IsQuestBounty(questID) ) then KT_BonusObjectiveTracker_OnTaskCompleted(...); end elseif ( event == "PLAYER_MONEY" and self.watchMoneyReasons > 0 ) then KT_ObjectiveTracker_Update(self.watchMoneyReasons); elseif ( event == "PLAYER_ENTERING_WORLD" ) then if ( not self.initialized ) then KT_ObjectiveTracker_Initialize(self); end KT_ObjectiveTracker_Update(); if not KT_QuestSuperTracking_IsSuperTrackedQuestValid() then KT_QuestSuperTracking_ChooseClosestQuest(); end self.lastMapID = C_Map.GetBestMapForUnit("player"); elseif ( event == "CVAR_UPDATE" ) then local arg1 =...; if ( arg1 == "questPOI" ) then KT_ObjectiveTracker_Update(KT_OBJECTIVE_TRACKER_UPDATE_MODULE_QUEST); end elseif ( event == "VARIABLES_LOADED" ) then KT_ObjectiveTracker_Update(); elseif ( event == "WAYPOINT_UPDATE" ) then KT_ObjectiveTracker_Update(); end end function KT_ObjectiveTracker_OnSizeChanged(self) KT_ObjectiveTracker_Update(); end function KT_ObjectiveTracker_OnUpdate(self) if self.isUpdateDirty then KT_ObjectiveTracker_Update(); end end function KT_ObjectiveTrackerHeader_OnAnimFinished(self) local header = self:GetParent(); header.animating = nil; end KT_ObjectiveTrackerHeaderMixin = {}; function KT_ObjectiveTrackerHeaderMixin:OnLoad() self.height = KT_OBJECTIVE_TRACKER_HEADER_HEIGHT; end function KT_ObjectiveTrackerHeaderMixin:PlayAddAnimation() self.animating = true; self.HeaderOpenAnim:Restart(); end -- ***************************************************************************************************** -- ***** BUTTONS -- ***************************************************************************************************** KT_ObjectiveTrackerMinimizeButtonMixin = {}; function KT_ObjectiveTrackerMinimizeButtonMixin:OnLoad() local collapsed = false; self:SetAtlases(collapsed); end function KT_ObjectiveTrackerMinimizeButtonMixin:SetAtlases(collapsed) local normalTexture = self:GetNormalTexture(); local pushedTexture = self:GetPushedTexture(); if self.buttonType == "module" then if collapsed then normalTexture:SetAtlas("UI-QuestTrackerButton-Expand-Section", true); pushedTexture:SetAtlas("UI-QuestTrackerButton-Expand-Section-Pressed", true); else normalTexture:SetAtlas("UI-QuestTrackerButton-Collapse-Section", true); pushedTexture:SetAtlas("UI-QuestTrackerButton-Collapse-Section-Pressed", true); end else if collapsed then normalTexture:SetAtlas("UI-QuestTrackerButton-Expand-All", true); pushedTexture:SetAtlas("UI-QuestTrackerButton-Expand-All-Pressed", true); else normalTexture:SetAtlas("UI-QuestTrackerButton-Collapse-All", true); pushedTexture:SetAtlas("UI-QuestTrackerButton-Collapse-All-Pressed", true); end end end function KT_ObjectiveTrackerMinimizeButtonMixin:SetCollapsed(collapsed) self:SetAtlases(collapsed); end function KT_ObjectiveTracker_MinimizeButton_OnClick(self) PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON); if ( KT_ObjectiveTrackerFrame.collapsed ) then KT_ObjectiveTracker_Expand(); else KT_ObjectiveTracker_Collapse(); end KT_ObjectiveTracker_Update(); end function KT_ObjectiveTracker_MinimizeModuleButton_OnClick(self) PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON); local module = self:GetParent().module; module:SetCollapsed(not module:IsCollapsed()); KT_ObjectiveTracker_Update(0, nil, module); end function KT_ObjectiveTracker_Collapse() KT_ObjectiveTrackerFrame.collapsed = true; KT_ObjectiveTrackerFrame.BlocksFrame:Hide(); KT_ObjectiveTrackerFrame.HeaderMenu.MinimizeButton:SetCollapsed(true); KT_ObjectiveTrackerFrame.HeaderMenu.Title:Show(); end function KT_ObjectiveTracker_Expand() KT_ObjectiveTrackerFrame.collapsed = nil; KT_ObjectiveTrackerFrame.BlocksFrame:Show(); KT_ObjectiveTrackerFrame.HeaderMenu.MinimizeButton:SetCollapsed(false); KT_ObjectiveTrackerFrame.HeaderMenu.Title:Hide(); end function KT_ObjectiveTracker_ToggleDropDown(frame, handlerFunc) --[[local dropDown = ObjectiveTrackerBlockDropDown; if ( dropDown.activeFrame ~= frame ) then CloseDropDownMenus(); end dropDown.activeFrame = frame; dropDown.initialize = handlerFunc; ToggleDropDownMenu(1, nil, dropDown, "cursor", 3, -3); PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON);]] end -- ***************************************************************************************************** -- ***** BLOCK CONTROL -- ***************************************************************************************************** local function AnchorBlock(block, anchorBlock, checkFit) local module = block.module; local blocksFrame = module.BlocksFrame; local offsetX, offsetY = KT_ObjectiveTracker_GetBlockOffset(block); block:ClearAllPoints(); if ( anchorBlock ) then if ( anchorBlock.isHeader ) then offsetY = module.fromHeaderOffsetY; end -- check if the block can fit if ( checkFit and (blocksFrame.contentsHeight + block.height - offsetY > blocksFrame.maxHeight) ) then return; end if ( block.isHeader ) then offsetY = offsetY + anchorBlock.module.fromModuleOffsetY; block:SetPoint("LEFT", KT_OBJECTIVE_TRACKER_HEADER_OFFSET_X, 0); else block:SetPoint("LEFT", offsetX, 0); end block:SetPoint("TOP", anchorBlock, "BOTTOM", 0, offsetY); else offsetY = 0; -- check if the block can fit if ( checkFit and (blocksFrame.contentsHeight + block.height > blocksFrame.maxHeight) ) then return; end -- if the blocks frame is a scrollframe, attach to its scrollchild if ( block.isHeader ) then block:SetPoint("TOPLEFT", blocksFrame.ScrollContents or blocksFrame, "TOPLEFT", KT_OBJECTIVE_TRACKER_HEADER_OFFSET_X, offsetY); else block:SetPoint("TOPLEFT", blocksFrame.ScrollContents or blocksFrame, "TOPLEFT", offsetX, offsetY); end end return offsetY; end local function InternalAddBlock(block) local module = block.module or KT_DEFAULT_OBJECTIVE_TRACKER_MODULE; local blocksFrame = module.BlocksFrame; block.nextBlock = nil; -- This doesn't take fit into account, it just assumes that there's content to be added, so the potential count -- should increase (this is related to showing the collapse buttons on the headers, see Reorder) -- NOTE: Never count headers as added blocks if not block.isHeader then module.potentialBlocksAddedThisLayout = (module.potentialBlocksAddedThisLayout or 0) + 1; end -- Only allow headers to be added if the module is collapsed. if not block.isHeader and module:IsCollapsed() then return false; end local offsetY = AnchorBlock(block, blocksFrame.currentBlock, not module.ignoreFit); if ( not offsetY ) then return false; end if ( not module.topBlock ) then module.topBlock = block; end if ( not module.firstBlock and not block.isHeader ) then module.firstBlock = block; end if ( blocksFrame.currentBlock ) then blocksFrame.currentBlock.nextBlock = block; end blocksFrame.currentBlock = block; blocksFrame.contentsHeight = blocksFrame.contentsHeight + block.height - offsetY; module.contentsAnimHeight = module.contentsAnimHeight + block.height; module.contentsHeight = module.contentsHeight + block.height - offsetY; return true; end function KT_ObjectiveTracker_AddHeader(header, isStaticReanchor) if InternalAddBlock(header) then header.added = true; header:Show(); return true; end return false; end function KT_ObjectiveTracker_AddBlock(block) local header = block.module.Header; local blockAdded = false; -- if there's no header or it's been added, just add the block... if not header or header.added then blockAdded = InternalAddBlock(block); elseif KT_ObjectiveTracker_CanFitBlock(block, header) then -- try to add header and maybe block if KT_ObjectiveTracker_AddHeader(header) then blockAdded = InternalAddBlock(block); end end if not blockAdded then block.module.hasSkippedBlocks = true; end return blockAdded; end function KT_ObjectiveTracker_CanFitBlock(block, header) local module = block.module; local blocksFrame = module.BlocksFrame; local offsetY; if ( not blocksFrame.currentBlock ) then offsetY = 0; elseif ( blocksFrame.currentBlock.isHeader ) then offsetY = module.fromHeaderOffsetY; else offsetY = select(2, KT_ObjectiveTracker_GetBlockOffset(block)); end local totalHeight; if header then totalHeight = header.height - offsetY + block.height - module.fromHeaderOffsetY; else totalHeight = block.height - offsetY; end return (blocksFrame.contentsHeight + totalHeight) <= blocksFrame.maxHeight; end -- ***** SLIDING function KT_ObjectiveTracker_SlideBlock(block, slideData) block.slideData = slideData; if ( slideData.startDelay ) then block.slideTime = -slideData.startDelay; else block.slideTime = 0; end block.slideHeight = slideData.startHeight; block:SetHeight(slideData.startHeight); block:SetScript("OnUpdate", KT_ObjectiveTracker_OnSlideBlockUpdate); end function KT_ObjectiveTracker_EndSlideBlock(block) block:SetScript("OnUpdate", nil); if ( block.slideData ) then block:SetHeight(block.slideData.endHeight); if ( block.slideData.scroll ) then block:SetVerticalScroll(0); end end block.slidingAction = nil; end function KT_ObjectiveTracker_OnSlideBlockUpdate(block, elapsed) local slideData = block.slideData; block.slideTime = block.slideTime + elapsed; if ( block.slideTime <= 0 ) then return; end local height = floor(slideData.startHeight + (slideData.endHeight - slideData.startHeight) * (min(block.slideTime, slideData.duration) / slideData.duration)); if ( height ~= block.slideHeight ) then block.slideHeight = height; block:SetHeight(height); if ( slideData.scroll ) then block:UpdateScrollChildRect(); -- scrolling means the bottom of the content comes in first or leaves last if (slideData.expanding) then block:SetVerticalScroll(0); else block:SetVerticalScroll(max(slideData.endHeight, slideData.startHeight) - height); end end end if ( block.slideTime >= slideData.duration + (slideData.endDelay or 0) ) then block:SetScript("OnUpdate", nil); if ( slideData.onFinishFunc ) then slideData.onFinishFunc(block); end end end function KT_ObjectiveTracker_CancelSlideBlock(block) block:SetScript("OnUpdate", nil); local slideData = block.slideData; if( slideData ) then block:SetHeight(slideData.startHeight); if ( slideData.scroll ) then block:UpdateScrollChildRect(); -- scrolling means the bottom of the content comes in first or leaves last block:SetVerticalScroll(0); end end end -- ***** UPDATE function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:StaticReanchorCheckAddHeaderOnly() if self:IsCollapsed() and not self.Header.added and self:GetBlockCount() > 0 then KT_ObjectiveTracker_AddHeader(self.Header, true); -- the header was marked as not being added, make sure to add it again... return true; end return false; end function KT_DEFAULT_OBJECTIVE_TRACKER_MODULE:StaticReanchor() -- If this module is collapsed, don't process anything, it will result in the entire module being hidden, since just the header -- is showing, there's nothing to update. if self:StaticReanchorCheckAddHeaderOnly() then return; end local block = self.firstBlock; self:BeginLayout(true); while ( block ) do if ( block.module == self ) then local nextBlock = block.nextBlock; if ( KT_ObjectiveTracker_AddBlock(block) ) then block.used = true; block:Show(); block = nextBlock; else -- a prior module reduced the previously available space block.used = false; block:Hide(); break; end else break; end end self:EndLayout(true); end local function GetRelatedModulesForUpdate(module) if module then return tInvert(module:GetRelatedModules()) end return nil; end local function IsRelatedModuleForUpdate(module, moduleLookup) if moduleLookup then return moduleLookup[module] ~= nil; end return false; end local function ObjectiveTracker_GetVisibleHeaders() local headers = {}; for index, module in ipairs(KT_ObjectiveTrackerFrame.MODULES) do local header = module.Header; if header.added and header:IsVisible() then headers[header] = true; end end return headers; end local function ObjectiveTracker_AnimateHeaders(previouslyVisibleHeaders) local currentHeaders = ObjectiveTracker_GetVisibleHeaders(); for header, isVisible in pairs(currentHeaders) do if isVisible and not previouslyVisibleHeaders[header] then header:PlayAddAnimation(); end end end function KT_ObjectiveTracker_UpdateSuperTrackedQuest(self) local questID = C_SuperTrack.GetSuperTrackedQuestID(); KT_ObjectiveTracker_Update(KT_OBJECTIVE_TRACKER_UPDATE_SUPER_TRACK_CHANGED, questID); QuestPOI_SelectButtonByQuestID(self.BlocksFrame, questID); end function KT_ObjectiveTracker_Update(reason, id, moduleWhoseCollapseChanged) local tracker = KT_ObjectiveTrackerFrame; if tracker.isUpdating then -- Trying to update while we're already updating, try again next frame tracker.isUpdateDirty = true; return; end tracker.isUpdating = true; if ( not tracker.initialized ) then tracker.isUpdating = false; return; end tracker.BlocksFrame.maxHeight = KT_ObjectiveTrackerFrame.BlocksFrame:GetHeight(); if ( tracker.BlocksFrame.maxHeight == 0 ) then tracker.isUpdating = false; return; end tracker.isUpdateDirty = false; KT_OBJECTIVE_TRACKER_UPDATE_REASON = reason or KT_OBJECTIVE_TRACKER_UPDATE_ALL; KT_OBJECTIVE_TRACKER_UPDATE_ID = id; tracker.BlocksFrame.currentBlock = nil; tracker.BlocksFrame.contentsHeight = 0; -- Gather existing headers, only newly added ones will animate local currentHeaders = ObjectiveTracker_GetVisibleHeaders(); -- mark headers unused for index, module in ipairs(tracker.MODULES) do if module.Header then module.Header.added = nil; end end -- These can be nil, it's fine, trust the API. local relatedModules = GetRelatedModulesForUpdate(moduleWhoseCollapseChanged); -- run module updates local gotMoreRoomThisPass = false; for i = 1, #tracker.MODULES do local module = tracker.MODULES[i]; if IsRelatedModuleForUpdate(moduleWhoseCollapseChanged, relatedModules) or (band(KT_OBJECTIVE_TRACKER_UPDATE_REASON, module.updateReasonModule + module.updateReasonEvents) > 0) then -- run a full update on this module module:Update(); -- check if it's now taking up less space, using subtraction because of floats if ( module.oldContentsHeight - module.contentsHeight >= 1 ) then -- it is taking up less space, might have freed room for other modules gotMoreRoomThisPass = true; end else -- this module's contents have not have changed -- but if we got more room and this module has unshown content, do a full update -- also do a full update if the header is animating since the module does not technically have any blocks at that point if ( (module.hasSkippedBlocks and gotMoreRoomThisPass) or (module.Header and module.Header.animating) ) then module:Update(); else module:StaticReanchor(); end end end KT_ObjectiveTracker_ReorderModules(); KT_ObjectiveTracker_UpdatePOIs(); ObjectiveTracker_AnimateHeaders(currentHeaders); -- hide unused headers for i = 1, #tracker.MODULES do KT_ObjectiveTracker_CheckAndHideHeader(tracker.MODULES[i].Header); end if ( tracker.BlocksFrame.currentBlock ) then tracker.HeaderMenu:Show(); else tracker.HeaderMenu:Hide(); end tracker.BlocksFrame.currentBlock = nil; tracker.isUpdating = false; end function KT_ObjectiveTracker_CheckAndHideHeader(moduleHeader) if ( moduleHeader and not moduleHeader.added and moduleHeader:IsShown() ) then moduleHeader:Hide(); if ( moduleHeader.animating ) then moduleHeader.animating = nil; moduleHeader.HeaderOpenAnim:Stop(); end end end function KT_ObjectiveTracker_WatchMoney(watchMoney, reason) if ( watchMoney ) then if ( band(KT_ObjectiveTrackerFrame.watchMoneyReasons, reason) == 0 ) then KT_ObjectiveTrackerFrame.watchMoneyReasons = KT_ObjectiveTrackerFrame.watchMoneyReasons + reason; end else if ( band(KT_ObjectiveTrackerFrame.watchMoneyReasons, reason) > 0 ) then KT_ObjectiveTrackerFrame.watchMoneyReasons = KT_ObjectiveTrackerFrame.watchMoneyReasons - reason; end end end local function ObjectiveTracker_CountVisibleModules() local count = 0; local seen = {}; for index, module in ipairs(KT_ObjectiveTrackerFrame.MODULES) do local header = module.Header; if header and not seen[header] then seen[header] = true; if header:IsVisible() and module:GetBlockCount() > 0 then -- testing out the whole active block count concept.... count = count + 1; end end end return count; end function KT_ObjectiveTracker_ReorderModules() local visibleCount = ObjectiveTracker_CountVisibleModules(); local showAllModuleMinimizeButtons = visibleCount > 1; local detachIndex = nil; local anchorBlock = nil; local header = KT_ObjectiveTrackerFrame.HeaderMenu; header:ClearAllPoints(); for index, module in ipairs(KT_ObjectiveTrackerFrame.MODULES_UI_ORDER) do local topBlock = module.topBlock; if topBlock then if module:UsesSharedHeader() then AnchorBlock(topBlock, module.Header); local containingModule = module.Header.module; if containingModule and containingModule.firstBlock then containingModule.firstBlock:ClearAllPoints(); AnchorBlock(containingModule.firstBlock, module.lastBlock); end else AnchorBlock(topBlock, anchorBlock); anchorBlock = module.lastBlock; end local headerPoint = KT_ObjectiveTrackerFrame.isOnLeftSideOfScreen and "LEFT" or "RIGHT"; if header then header:ClearAllPoints(); header:SetPoint(headerPoint, module.Header, headerPoint, KT_ObjectiveTrackerFrame.isOnLeftSideOfScreen and -10 or 0, 0); header = nil; end module.Header.Text:ClearAllPoints(); module.Header.Text:SetPoint("LEFT", module.Header, "LEFT", KT_ObjectiveTrackerFrame.isOnLeftSideOfScreen and 30 or 4, -1); -- Side-step annoying "uncollapse" issue by allowing a collapsed module to continue showing its minimize button even if -- it's the only remaining visible module local shouldShowThisModuleMinimizeButton = showAllModuleMinimizeButtons or module:IsCollapsed(); module.Header.MinimizeButton:SetShown(shouldShowThisModuleMinimizeButton); if shouldShowThisModuleMinimizeButton then module.Header.MinimizeButton:ClearAllPoints(); module.Header.MinimizeButton:SetPoint(headerPoint, module.Header, headerPoint, KT_ObjectiveTrackerFrame.isOnLeftSideOfScreen and 9 or -20, 0); end end end end function KT_ObjectiveTracker_UpdatePOIs() if not KT_ObjectiveTrackerFrame.MODULES then return; end local blocksFrame = KT_ObjectiveTrackerFrame.BlocksFrame; QuestPOI_ResetUsage(blocksFrame); local showPOIs = GetCVarBool("questPOI"); if ( not showPOIs ) then QuestPOI_HideUnusedButtons(blocksFrame); return; end local numPOINumeric = 0; -- This is tied to the QuestPOI system, it must be maintained across tracker instances. for i, module in ipairs(KT_ObjectiveTrackerFrame.MODULES) do if module.UpdatePOIs then numPOINumeric = module:UpdatePOIs(numPOINumeric); end end QuestPOI_SelectButtonByQuestID(blocksFrame, C_SuperTrack.GetSuperTrackedQuestID()); QuestPOI_HideUnusedButtons(blocksFrame); end KT_QuestHeaderMixin = {}; function KT_QuestHeaderMixin:OnShow() self:RegisterEvent("QUEST_SESSION_JOINED"); self:RegisterEvent("QUEST_SESSION_LEFT"); self:UpdateHeader(); end function KT_QuestHeaderMixin:OnHide() self:UnregisterEvent("QUEST_SESSION_JOINED"); self:UnregisterEvent("QUEST_SESSION_LEFT"); end function KT_QuestHeaderMixin:OnEvent() self:UpdateHeader(); end function KT_QuestHeaderMixin:UpdateHeader() if C_QuestSession.HasJoined() then self.Text:SetText(TRACKER_HEADER_PARTY_QUESTS); else self.Text:SetText(TRACKER_HEADER_QUESTS); end end