-- ========================================================================= -- -- SylingTracker -- -- https://www.curseforge.com/wow/addons/sylingtracker -- -- -- -- Repository: -- -- https://github.com/Skamer/SylingTracker -- -- -- -- ========================================================================= -- Syling "SylingTracker.Quests.QuestCategoryView" "" -- ========================================================================= -- namespace "SLT" -- ========================================================================= -- -- Iterator helper for ignoring the children are used for backdrop, and avoiding -- they are taken as account for their parent height IterateFrameChildren = Utils.IterateFrameChildren ResetStyles = Utils.ResetStyles ValidateFlags = System.Toolset.validateflags -- ========================================================================= -- __Recyclable__ "SylingTracker_QuestCategoryView%d" class "QuestCategoryView" (function(_ENV) inherit "Frame" extend "IView" __Flags__() enum "Flags" { NONE = 0, HAS_QUESTS = 1 } ----------------------------------------------------------------------------- -- Methods -- ----------------------------------------------------------------------------- function OnViewUpdate(self, data) local nameFS = self:GetChild("Name") -- Determines the flags local flags = Flags.NONE if data then flags = Flags.HAS_QUESTS end if flags ~= self.Flags then ResetStyles(self) ResetStyles(nameFS) -- Is there quests if ValidateFlags(Flags.HAS_QUESTS, flags) then self:AcquireQuests() else self:ReleaseQuests() end -- Styling stuff if flags ~= Flags.NONE then local styles = self.FlagsStyles and self.FlagsStyles[flags] if styles then Style[self] = styles end end end if data then -- Update the category name local name for _, v in pairs(data) do name = v.category break end Style[nameFS].text = name local questsView = self:AcquireQuests() questsView:UpdateView(data) end self.Flags = flags end function AcquireQuests(self) local quests = self:GetChild("Quests") if not quests then quests = QuestListView.Acquire() -- We need to keep the old name when we'll release it self.__PreviousQuestsName = quests:GetName() quests:SetParent(self) quests:SetName("Quests") quests:InstantApplyStyle() -- Register the events quests.OnSizeChanged = quests.OnSizeChanged + self.OnQuestsSizeChanged self:AdjustHeight() end return quests end function ReleaseQuests(self) local quests = self:GetChild("Quests") if quests then -- Give its old name (generated by the recycle system) quests:SetName(self.__PreviousQuestsName) self.__PreviousQuestsName = nil -- Unregister the events quests.OnSizeChanged = quests.OnSizeChanged - self.OnQuestsSizeChanged -- It's better to release it after the event has been unregistered for avoiding -- useless call quests:Release() self:AdjustHeight() end end function OnAdjustHeight(self, useAnimation) local height = 0 local maxOuterBottom for childName, child in IterateFrameChildren(self) do local outerBottom = child:GetBottom() local outerTop = child:GetTop() if outerBottom then if not maxOuterBottom or maxOuterBottom > outerBottom then maxOuterBottom = outerBottom maxChild = child end end end if maxOuterBottom then local computeHeight = (self:GetTop() - maxOuterBottom) + self.PaddingBottom if useAnimation then self:SetAnimatedHeight(computeHeight) else self:SetHeight(computeHeight) end end end function OnRelease(self) -- Release first the children self:ReleaseQuests() self:Hide() self:ClearAllPoints() self:SetParent() -- "CancelAdjustHeight" and "CancelAnimatingHeight" wiil cancel the pending -- computing stuff for height, so they not prevent "SetHeight" here doing -- its stuff. self:CancelAdjustHeight() self:CancelAnimatingHeight() self:SetHeight(1) -- Reset the class properties self.Flags = nil -- Will Remove all custom styles properties, so the next time the object will -- be used, this one will be in a clean state ResetStyles(self) end function OnAcquire(self) -- Important ! We need the frame is instantly styled as this may affect -- its height. self:InstantApplyStyle() self:Show() self:AdjustHeight() end ----------------------------------------------------------------------------- -- Properties -- ----------------------------------------------------------------------------- property "FlagsStyles" { type = Table } property "Flags" { type = QuestCategoryView.Flags, default = QuestCategoryView.Flags.NONE } property "PaddingBottom" { type = Number, default = 0 } ----------------------------------------------------------------------------- -- Constructors -- ----------------------------------------------------------------------------- __Template__ { Name = SLTFontString } function __ctor(self) -- Important ! We need the frame is instantly styled as this may affect -- its height. self:InstantApplyStyle() -- Important! As the frame ajusts its height depending of its children height -- we need to set its height when contructed for the event "OnSizechanged" of -- its children is triggered. self:SetHeight(1) self.OnQuestsSizeChanged = function() self:AdjustHeight() end self:SetClipsChildren(true) end end) __Recyclable__ "SylingTracker_QuestCategoryListView%d" class "QuestCategoryListView" (function(_ENV) inherit "Frame" extend "IView" ----------------------------------------------------------------------------- -- Methods -- ----------------------------------------------------------------------------- function OnViewUpdate(self, data) -- Clear the current achievement id list wipe(self.categoriesID) wipe(self.categoriesRange) wipe(self.categoriesData) wipe(self.categoriesOrder) if data then -- 1. Split the quests data in their respective category for questID, questData in pairs(data) do local categoryName = questData.category local t = self.categoriesData[categoryName] if not t then t = setmetatable({}, { __mode = "v"}) self.categoriesData[categoryName] = t tinsert(self.categoriesOrder, categoryName) end local bestRange = self.categoriesRange[categoryName] if not bestRange or questData.distance < bestRange then self.categoriesRange[categoryName] = questData.distance or 99999 end t[questID] = questData end -- 2. Sort the category by their best range table.sort(self.categoriesOrder, function(a, b) return self.categoriesRange[a] < self.categoriesRange[b] end) -- 3. Update the category with their quests data local previousCategory for index, categoryName in ipairs(self.categoriesOrder) do local category = self:AcquireCategory(categoryName) if index > 1 then category:SetPoint("TOP", previousCategory, "BOTTOM", 0, -self.CategorySpacing) else category:SetPoint("TOP") end -- Update our category with data category:UpdateView(self.categoriesData[categoryName]) -- Build the current quest id list self.categoriesID[categoryName] = true previousCategory = category end end self:ReleaseUnusedCategories() end function AcquireCategory(self, id) local category = self.categoriesCache[id] if not category then category = QuestCategoryView.Acquire() category:SetParent(self) category:SetPoint("LEFT") category:SetPoint("RIGHT") category.OnSizeChanged = category.OnSizeChanged + self.OnCategorySizeChanged self.categoriesCache[id] = category self:AdjustHeight() end return category end function ReleaseUnusedCategories(self) for categoryName, category in pairs(self.categoriesCache) do if not self.categoriesID[categoryName] then self.categoriesCache[categoryName] = nil category.OnSizeChanged = category.OnSizeChanged - self.OnCategorySizeChanged category:Release() self:AdjustHeight() end end end function OnAdjustHeight(self, useAnimation) local height = 0 local count = 0 for _, child in IterateFrameChildren(self) do height = height + child:GetHeight() count = count + 1 end height = height + self.CategorySpacing * math.max(0, count-1) if useAnimation then self:SetAnimatedHeight(height) else self:SetHeight(height) end end function OnRelease(self) wipe(self.categoriesID) wipe(self.categoriesRange) wipe(self.categoriesData) wipe(self.categoriesOrder) self:ReleaseUnusedCategories() self:Hide() self:ClearAllPoints() self:SetParent() self:CancelAdjustHeight() self:CancelAnimatingHeight() self:SetHeight(1) ResetStyles(self) end function OnAcquire(self) -- Important ! We need the frame is instantly styled as this may affect -- its height. self:InstantApplyStyle() self:Show() self:AdjustHeight() end ----------------------------------------------------------------------------- -- Properties -- ----------------------------------------------------------------------------- property "CategorySpacing" { type = Number, default = 13 } ----------------------------------------------------------------------------- -- Constructors -- ----------------------------------------------------------------------------- function __ctor(self) -- Important ! We need the frame is instantly styled as this may affect -- its height. self:InstantApplyStyle() -- Important! As the frame ajusts its height depending of its children height -- we need to set its height when contructed for the event "OnSizechanged" of -- its children is triggered. self:SetHeight(1) -- !important -- Keep in the cache the categories, to be reused. -- use: self.categoriesCache[categoryName] = categoryObject self.categoriesCache = setmetatable({}, { __mode = "v"}) -- Get the current categories id's list. Used internally to release the -- unused categories -- use: self.achievementsID[categoryName] = true or nil self.categoriesID = {} self.categoriesRange = {} self.categoriesData = {} self.categoriesOrder = {} self.OnCategorySizeChanged = function() self:AdjustHeight() end self:SetClipsChildren(true) end end) ------------------------------------------------------------------------------- -- Styles -- ------------------------------------------------------------------------------- Style.UpdateSkin("Default", { [QuestCategoryView] = { Name = { sharedMediaFont = FontType("PT Sans Narrow Bold", 12), textColor = Color(1, 0.38, 0), textTransform = "UPPERCASE", justifyH = "LEFT", location = { Anchor("TOP"), Anchor("LEFT", 10, 0), Anchor("RIGHT") } }, FlagsStyles = { [QuestCategoryView.Flags.HAS_QUESTS] = { Quests = { location = { Anchor("TOP", 0, -13, "Name", "BOTTOM"), Anchor("LEFT"), Anchor("RIGHT") } } } } } })