You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

707 lines
20 KiB

5 years ago
-- ========================================================================= --
-- EskaTracker 2 --
-- https://www.curseforge.com/wow/addons/eskatracker-2 --
-- --
-- Repository: --
-- https://github.com/Skamer/EskaTracker2 --
-- --
-- ========================================================================= --
Syling "SylingTracker.Quests.QuestView" ""
-- ========================================================================= --
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
-- Check if the player is on the Shadowlands environment
IsOnShadowlands = Utils.IsOnShadowlands
-- ========================================================================= --
ResetStyles = Utils.ResetStyles
ShowContextMenu = API.ShowContextMenu
ValidateFlags = System.Toolset.validateflags
GameTooltip = GameTooltip
-- ========================================================================= --
__Recyclable__ "SylingTracker_QuestView%d"
class "QuestView" (function(_ENV)
inherit "Button" extend "IView"
enum "Type" {
Common = 1,
Dungeon = 2,
Raid = 3,
Legendary = 4,
}
__Flags__()
enum "Flags" {
NONE = 0,
HAS_OBJECTIVES = 1,
HAS_ITEM = 2
}
-----------------------------------------------------------------------------
-- Methods --
-----------------------------------------------------------------------------
function OnViewUpdate(self, data)
local header = self:GetChild("Header")
local name = header:GetChild("Name")
local levelBadge = header:GetChild("Level")
local levelTextBadge = levelBadge:GetChild("Label")
local tagIcon = header:GetChild("Tag")
-- Set the quest title
name:SetText(data.name)
-- Set the level
if data.level then
levelTextBadge:SetText(data.level)
local difficultyColor = GetQuestDifficultyColor(data.level)
if difficultyColor then
Style[levelTextBadge].textColor = Color(difficultyColor)
Style[levelBadge].backdropColor = {
r = difficultyColor.r,
g = difficultyColor.g,
b = difficultyColor.b,
a = 0.5
}
end
end
-- Update the context menu
if data.questID then
self.OnClick = function(_, mouseButton)
if mouseButton == "RightButton" then
ShowContextMenu("quest", self, data.questID)
else
if data.isAutoComplete and data.isComplete then
AutoQuestPopupTracker_RemovePopUp(data.questID)
if IsOnShadowlands() then
ShowQuestComplete(data.questID)
else
ShowQuestComplete(data.questLogIndex)
end
else
QuestMapFrame_OpenToQuestDetails(data.questID)
end
end
end
end
-- Determines the flags
local flags = Flags.NONE
if data.objectives then
flags = flags + Flags.HAS_OBJECTIVES
end
if data.item then
flags = flags + Flags.HAS_ITEM
end
if flags ~= self.Flags then
ResetStyles(self)
ResetStyles(name)
ResetStyles(header)
ResetStyles(tagIcon)
-- Is the quest has objectives
if ValidateFlags(Flags.HAS_OBJECTIVES, flags) then
self:AcquireObjectives()
else
self:ReleaseObjectives()
end
-- Is the quest has an item quest
if ValidateFlags(Flags.HAS_ITEM, flags) then
self:AcquireItemBadge()
else
self:ReleaseItemBadge()
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
-- Tag
local tag = data.tag
if tag then
local coords = QUEST_TAG_TCOORDS[tag.tagID]
if coords then
tagIcon:SetTexCoord(unpack(coords))
tagIcon:Show()
else
tagIcon:Hide()
end
else
tagIcon:Hide()
end
-- Update the conditionnal children if exists
local objectivesView = self.__objectivesView
if objectivesView then
if data.isAutoComplete and data.isComplete then
objectivesView:UpdateView({
[1] = { isCompleted = true, text = QUEST_WATCH_QUEST_COMPLETE},
[2] = { isCompleted = false, text = QUEST_WATCH_CLICK_TO_COMPLETE}
})
else
objectivesView:UpdateView(data.objectives)
end
end
local itemBadge = self.__itemBadge
if itemBadge then
Style[itemBadge].Icon.fileID = data.item.texture
if data.item.link then
itemBadge.OnLeave = function() GameTooltip:Hide() end
itemBadge.OnEnter = function()
GameTooltip:SetOwner(itemBadge, "ANCHOR_LEFT")
GameTooltip:SetHyperlink(data.item.link)
GameTooltip:Show()
end
end
end
self.Flags = flags
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 AcquireItemBadge(self)
local itemBadge = self:GetChild("Item")
if not itemBadge then
itemBadge = IconBadge.Acquire()
self.__previousItemBadgeName = itemBadge:GetName()
itemBadge:SetParent(self)
itemBadge:SetName("Item")
-- REVIEW: Is Inst
-- itemBadge:InstantApplyStyle()
self:AdjustHeight()
self.__itemBadge = itemBadge
end
return itemBadge
end
function ReleaseItemBadge(self)
local itemBadge = self:GetChild("Item")
if itemBadge then
itemBadge:SetName(self.__previousItemBadgeName)
self.__previousItemBadgeName = nil
itemBadge.OnLeave = nil
itemBadge.OnEnter = nil
itemBadge:Release()
self:AdjustHeight()
self.__itemBadge = nil
end
end
function AcquireObjectives(self)
local objectives = self:GetChild("Objectives")
if not objectives then
objectives = self.ObjectivesClass.Acquire()
-- We need to keep the old name when we'll release it
self.__previousObjectivesName = objectives:GetName()
objectives:SetParent(self)
objectives:SetName("Objectives")
-- It's important to only style it once we have set its parent and its new
-- name
-- if self.Objectives then
-- Style[objectives] = self.Objectives
-- end
-- Register the events
objectives.OnSizeChanged = objectives.OnSizeChanged + self.OnObjectivesSizeChanged
self:AdjustHeight()
self.__objectivesView = objectives
end
return objectives
end
function ReleaseObjectives(self)
local objectives = self:GetChild("Objectives")
if objectives then
-- Give its old name (generated by the recycle system)
objectives:SetName(self.__previousObjectivesName)
self.__previousObjectivesName = nil
-- Unregister the events
objectives.OnSizeChanged = objectives.OnSizeChanged - self.OnObjectivesSizeChanged
-- It's better to release after events have been unregistered for avoiding
-- useless class
objectives:Release()
self:AdjustHeight()
self.__objectivesView = nil
end
end
--- Recycle System
function OnRelease(self)
-- Release first the children
self:ReleaseItemBadge()
self:ReleaseObjectives()
self:ClearAllPoints()
self:SetParent()
self:Hide()
-- "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.Type = nil
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)
-- -- REVIEW: Probably find a better way ?
-- local objectives = self:GetChild("Objectives")
-- objectives:ReleaseUnusedObjectives(0)
-- self.Type = nil
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 "Type" {
type = QuestView.Type,
default = QuestView.Type.Common
}
property "Flags" {
type = QuestView.Flags,
default = QuestView.Flags.NONE
}
property "ObjectivesClass" {
type = ClassType,
default = ObjectiveListView
}
property "PaddingBottom" {
type = Number,
default = 5
}
-- property "Objectives" {
-- type = Table,
-- }
-- property "NewObjectives" {
-- type = Table
-- }
-----------------------------------------------------------------------------
-- Constructors --
-----------------------------------------------------------------------------
__Template__{
-- Objectives = ObjectiveListView,
Header = Frame,
{
Header = {
Tag = Texture,
Name = SLTFontString,
Level = TextBadge
}
}
}
function __ctor(self)
-- 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)
-- local objectives = self:GetChild("Objectives")
-- objectives.OnSizeChanged = objectives.OnSizeChanged + function(f, ...)
-- self:AdjustHeight()
-- end
self.OnObjectivesSizeChanged = function() self:AdjustHeight() end
-- self:AdjustHeight()
-- Experimental
self:SetClipsChildren(true)
end
end)
__Recyclable__ "SylingTracker_LegendaryQuestView%d"
class "LegendaryQuestView" { QuestView }
__Recyclable__ "SylingTracker_RaidQuestView%d"
class "RaidQuestView" { QuestView }
__Recyclable__ "SylingTracker_DungeonQuestView%d"
class "DungeonQuestView" { QuestView }
--- Manages the quests, if your view may have various quests, this is advised
-- using this class
__Recyclable__ "SylingTracker_QuestListView%d"
class "QuestListView" (function(_ENV)
inherit "Frame" extend "IView"
-----------------------------------------------------------------------------
-- Methods --
-----------------------------------------------------------------------------
function OnViewUpdate(self, data, updater)
local questIndex = 0
wipe(self.questsID)
wipe(self.questsOrder)
for _, questData in pairs(data) do
tinsert(self.questsOrder, questData)
end
table.sort(self.questsOrder, function(a, b)
local aDistance, bDistance = a.distance, b.distance
if aDistance and bDistance then
return aDistance < bDistance
end
return a.questID < b.questID
end)
local previousQuest
for _, questData in ipairs(self.questsOrder) do
questIndex = questIndex + 1
local questID = questData.questID
local isLegendary = questData.isLegendary
local isDungeon = questData.isDungeon
local isRaid = questData.isRaid
local type = QuestView.Type.Common
if isLegendary then
type = QuestView.Type.Legendary
elseif isRaid then
type = QuestView.Type.Raid
elseif isDungeon then
type = QuestView.Type.Dungeon
end
local quest = self:AcquireQuest(questID, type)
if questIndex > 1 then
quest:SetPoint("TOP", previousQuest, "BOTTOM", 0, -5) -- TODO: Add Spacing
quest:SetPoint("LEFT", 0, 0) -- 4
quest:SetPoint("RIGHT", 0, 0) -- -4
elseif questIndex == 1 then
quest:SetPoint("TOP")
quest:SetPoint("LEFT", 0, 0) -- 4
quest:SetPoint("RIGHT", 0, 0) -- -4
end
quest:UpdateView(questData, updater)
previousQuest = quest
self.questsID[questID] = true
end
self:ReleaseUnusedQuests()
end
__Static__() function IsCorrectObjectForType(obj, type)
if type == QuestView.Type.Common and Class.IsObjectType(obj, QuestView) then
return true
elseif type == QuestView.Type.Dungeon and Class.IsObjectType(obj, DungeonQuestView) then
return true
elseif type == QuestView.Type.Raid and Class.IsObjectType(obj, RaidQuestView) then
return true
elseif type == QuestView.Type.Legendary and Class.IsObjectType(obj, LegendaryQuestView) then
return true
end
return false
end
function AcquireQuest(self, id, type)
local quest = self.questsCache[id]
local new = false
if quest and not IsCorrectObjectForType(quest, type) then
quest:Release()
self.questsCache[id] = nil
new = true
end
if not quest or new then
if type == QuestView.Type.Legendary then
quest = LegendaryQuestView.Acquire()
elseif type == QuestView.Type.Raid then
quest = RaidQuestView.Acquire()
elseif type == QuestView.Type.Dungeon then
quest = DungeonQuestView.Acquire()
else
quest = QuestView.Acquire()
end
quest:Show()
quest:SetParent(self)
quest.OnSizeChanged = quest.OnSizeChanged + self.OnQuestSizeChanged
self:AdjustHeight()
self.questsCache[id] = quest
end
return quest
end
function ReleaseUnusedQuests(self)
for questID, quest in pairs(self.questsCache) do
if not self.questsID[questID] then
self.questsCache[questID] = nil
quest.OnSizeChanged = quest.OnSizeChanged - self.OnQuestSizeChanged
quest:Release()
self:AdjustHeight()
end
end
end
function OnAdjustHeight(self)
local height = 0
local count = 0
for _, child in IterateFrameChildren(self) do
height = height + child:GetHeight()
count = count + 1
end
height = height + 5 * math.max(0, count-1)
-- self:SetHeight(height)
PixelUtil.SetHeight(self, height)
end
function OnRelease(self)
wipe(self.questsID)
wipe(self.questsOrder)
self:ReleaseUnusedQuests()
self:ClearAllPoints()
self:SetParent()
self:Hide()
self:CancelAdjustHeight()
self:CancelAnimatingHeight()
self:SetHeight(1)
ResetStyles(self)
end
function OnAcquire(self)
self:Show()
end
-----------------------------------------------------------------------------
-- 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 quest, to be reused.
-- use: self.questsCache[questID] = questObject
self.questsCache = setmetatable({}, { __mode = "v"})
-- Get the current quest id's list. Used internally to release the
-- unused quests
-- use: self.questsID[questID] = true or nil
self.questsID = {}
self.questsOrder = {}
self.OnQuestSizeChanged = function() self:AdjustHeight() end
self:SetClipsChildren(true)
end
end)
-------------------------------------------------------------------------------
-- Styles --
-------------------------------------------------------------------------------
Style.UpdateSkin("Default", {
[QuestView] = {
width = 300,
backdrop = {
bgFile = [[Interface\AddOns\SylingTracker\Media\Textures\LinearGradient]],
},
backdropColor = { r = 35/255, g = 40/255, b = 46/255, a = 0.73},
registerForClicks = { "LeftButtonDown", "RightButtonDown" },
-- NormalTexture = {
-- file = [[Interface\AddOns\SylingTracker\Media\Textures\LinearGradient]],
-- vertexColor = { r = 35/255, g = 40/255, b = 46/255, a = 0.73},
-- setAllPoints = true,
-- },
-- HighlightTexture = {
-- file = [[Interface\AddOns\SylingTracker\Media\Textures\LinearGradient]],
-- vertexColor = { r = 35/255, g = 40/255, b = 46/255, a = 0.1},
-- setAllPoints = true,
-- },
-- Header Child
Header = {
height = 24,
location = {
Anchor("TOPLEFT"),
Anchor("TOPRIGHT")
},
Tag = {
height = 18,
width = 18,
file = QUEST_ICONS_FILE,
location = {
Anchor("LEFT", 3, 0)
}
},
-- Header/Name child
Name = {
location = {
Anchor("TOP"),
Anchor("LEFT", 0, 0, "Tag", "RIGHT"),
Anchor("RIGHT", 0, 0, "Level", "LEFT"),
Anchor("BOTTOM")
},
sharedMediaFont = FontType("DejaVuSansCondensed Bold", 10)
},
-- Header/Level child
Level = {
height = 16,
width = 30,
backdropColor = { r = 160/255, g = 160/255, b = 160/255, a = 0.5},
location = {
Anchor("RIGHT", -5, 0)
},
Label = {
justifyH = "RIGHT"
}
},
},
FlagsStyles = {
[QuestView.Flags.HAS_OBJECTIVES] = {
Objectives = {
spacing = 5,
location = {
Anchor("TOP", 0, -4, "Header", "BOTTOM"),
Anchor("LEFT"),
Anchor("RIGHT")
}
}
},
[QuestView.Flags.HAS_OBJECTIVES + QuestView.Flags.HAS_ITEM] = {
Item = {
height = 28,
width = 28,
location = {
Anchor("TOPLEFT", 4, -4, "Header", "BOTTOMLEFT")
},
Icon = {
texCoords = RectType(0.07, 0.93, 0.07, 0.93)
}
},
Objectives = {
spacing = 5,
location = {
Anchor("TOP", 2, -4, "Header", "BOTTOM"),
Anchor("LEFT", 2, 0, "Item", "RIGHT"),
Anchor("RIGHT")
}
}
}
}
},
-- [LegendaryQuestView] = {
-- backdropColor = { r = 35/255, g = 40/255, b = 46/255, a = 0.73},
-- },
[RaidQuestView] = {
backdropColor = { r = 0, g = 84/255, b = 2/255, a = 0.73}
},
[DungeonQuestView] = {
backdropColor = { r = 0, g = 72/255, b = 124/255, a = 0.73 }
}
})