Quantcast
--[[-----------------------------------------------------------------------
	oUF: Stardust - a layout for the oUF framework
	Copyright (c) 2016 Andrew Mordecai <armordecai@protonmail.ch>
	This code is released under the zlib license; see LICENSE for details.
-------------------------------------------------------------------------]]

local _, Stardust = ...
local config = Stardust.config
local colors = oUF.colors

local _, class = UnitClass("player")
local LibSharedMedia = LibStub("LibSharedMedia-3.0")

---------------------------
-- Widget creation
---------------------------

local function NewStatusBar(parent, textSize, noBG, noBorder, noShadow)
	local TEXTURE = LibSharedMedia:Fetch("statusbar", config.barTexture)

	local bar = CreateFrame("StatusBar", nil, parent)
	bar:SetStatusBarTexture(TEXTURE)

	if textSize then
		bar.value = bar:CreateFontString(nil, "OVERLAY")
		bar.value:SetFont(LibSharedMedia:Fetch("font", config.numberFont), textSize, "OUTLINE")
		bar.value:SetShadowOffset(0, 0)
		bar.value:SetTextColor(1, 1, 1)
		bar.value:SetPoint("CENTER")
	end

	if not noBG then
		bar.bg = bar:CreateTexture(nil, "BORDER")
		bar.bg:SetAllPoints()
		bar.bg:SetTexture(TEXTURE)
		bar.bg.multiplier = config.bgMultiplier
	end

	if not noBorder then
		bar.backdrop = bar:CreateTexture(nil, "BACKGROUND")
		bar.backdrop:SetPoint("BOTTOMLEFT", -1, -1)
		bar.backdrop:SetPoint("TOPRIGHT", 1, 1)
		bar.backdrop:SetTexture(0, 0, 0)
	end

	if not noShadow then
		Stardust.AddShadow(bar, 3)
	end

	return bar
end

local function NewBarGroup(parent, count, width, height, textSize)
	local group = {
		__max = count
	}

	for i = 1, count do
		local bar = NewStatusBar(parent, textSize)
		bar:SetSize(width, height)
		bar:SetMinMaxValues(0, 1)
		bar:SetValue(1)

		-- ClassIcons expects texture objects
		bar.SetVertexColor = bar.SetStatusBarColor

		if i > 1 then
			bar:SetPoint("LEFT", group[i-1], "RIGHT", 3, 0)
		end

		group[i] = bar
	end

	return group
end

local function AddStatusIcon(self, element, size, offsetX, offsetY)
	local icon = self.Health:CreateTexture(nil, "OVERLAY")
	icon:SetPoint("LEFT", self.Health, "TOPLEFT", 3, 0)
	icon:SetSize(size or 16, size or 16)

	self.StatusIcons = self.StatusIcons or {}
	tinsert(self.StatusIcons, icon)

	icon.offsetX = offsetX or 0
	icon.offsetY = offsetY or 0
	icon.PostUpdate = Stardust.PostUpdateStatusIcon
	self[element] = icon
	return icon
end

---------------------------
-- Frame setup
---------------------------

local function ApplyStyle(self, unit, isSingle)
	unit = unit:gsub("%d+", "")

	local unitConfig = config.units[unit]
	local classColor = oUF.colors.class[class]
	local classR, classG, classB = classColor[1], classColor[2], classColor[3]

	---------------------------
	-- Frame
	---------------------------

	self:SetScript("OnEnter", Stardust.OnEnter)
	self:SetScript("OnLeave", Stardust.OnLeave)

	self:RegisterForClicks("AnyUp")

	if (isSingle) then
		self:SetSize(config.width * (unitConfig and unitConfig.width or 1), config.height * (unitConfig and unitConfig.height or 1))
	end

	Stardust.AddShadow(self, 6)

	---------------------------
	-- Health bar
	---------------------------

	local Health = NewStatusBar(self, config.numberLarge)
	Health:SetPoint("BOTTOMLEFT")
	Health:SetPoint("TOPRIGHT")
	Health:SetReverseFill(true)

	Health:SetStatusBarColor(0.3, 0.3, 0.3)
	Health.bg:SetVertexColor(0.15, 0.15, 0.15)

	Health.value:ClearAllPoints()
	Health.value:SetPoint("BOTTOMRIGHT", 0, 3 + config.powerHeight + 2)

	Health.frequentUpdates = true

	Health.PostUpdate = Stardust.PostUpdateHealth

	self:SmoothBar(Health)
	self.Health = Health

	---------------------------
	-- Name text
	---------------------------

	local Name = self.Health:CreateFontString(nil, "OVERLAY")
	Name:SetPoint("BOTTOMLEFT", 2, 3 + config.powerHeight + 2)
	Name:SetPoint("RIGHT", self.Health.value, "LEFT")
	Name:SetJustifyH("LEFT")
	Name:SetFont(LibSharedMedia:Fetch("font", config.textFont), config.textSize, "OUTLINE")
	Name:SetShadowOffset(0, 0)
	Name:SetTextColor(1, 1, 1)
	self:Tag(Name, "[unitcolor][name]")
	self.Name = Name

	---------------------------
	-- Power bar
	---------------------------

	local Power = NewStatusBar(Health, config.numberSmall)
	Power:SetPoint("BOTTOMLEFT", 3, 3)
	Power:SetPoint("BOTTOMRIGHT", -3, 3)
	Power:SetHeight(config.powerHeight)

	Power.colorClass = true
	Power.colorDisconnected = true
	Power.colorReaction = true
	Power.colorTapping = true
	Power.frequentUpdates = true

	Power.PostUpdate = Stardust.PostUpdatePower

	self:SmoothBar(Power)
	self.Power = Power

	Stardust.AddShadow(Power, 3)

	---------------------------
	-- AFK text
	---------------------------

	if unit == "player" or unit == "party" then
		local AFK = self:CreateFontString(nil, "OVERLAY")
		AFK:SetPoint("CENTER", self.Power)
		AFK:SetFont(LibSharedMedia:Fetch("font", config.numberFont), config.numberSmall, "OUTLINE")
		AFK:SetShadowOffset(0, 0)
		AFK:SetTextColor(1, 1, 1)
		AFK.PostUpdate = Stardust.PostUpdateAFK
		self.AFK = AFK
	end

	---------------------------
	-- Secondary resources
	---------------------------

	if unit == "player" then

		---------------------------
		-- Runes @ TOPLEFT
		---------------------------

		if class == "DEATHKNIGHT" then
			local Runes = NewBarGroup(self.Health, 6, 30, config.powerHeight)
			Runes[1]:SetPoint("TOPLEFT", self.Health, 3, -3)
			Runes.PostUpdateRune = Stardust.PostUpdateRune
			Runes.PostUpdateRuneType = Stardust.PostUpdateRuneType
			self.Runes = Runes
		end

		---------------------------
		-- Combo Points @ TOPLEFT
		---------------------------

		if class == "DRUID" or class == "ROGUE" then
			local CPoints = NewBarGroup(self.Health, 5, 30, config.powerHeight)
			CPoints[1]:SetPoint("TOPLEFT", self.Health, 3, -3)
			CPoints.PreUpdate = Stardust.PreUpdateCPoints
			self.CPoints = CPoints

			for i = 1, #CPoints do
				local bar = CPoints[i]
				local mu = bar.bg.multiplier
				bar:SetStatusBarColor(classR, classG, classB)
				bar.bg:SetVertexColor(classR * mu, classG * mu, classB * mu)
			end
		end

		---------------------------
		-- Totems @ TOPLEFT
		---------------------------

		if class == "SHAMAN" then
			local Totems = NewBarGroup(self.Health, 4, 30, config.powerHeight, config.numberSmall)
			Totems[1]:SetPoint("TOPLEFT", self.Health, 3, -3)
			Totems.PreUpdate = Stardust.PreUpdateTotems
			Totems.PostUpdate = Stardust.PostUpdateTotem
			self.Totems = Totems

			for i = 1, 4 do
				local bar = Totems[i]
				bar:EnableMouse(true)
				bar:SetHitRectInsets(0, 0, -6, -3)
				bar.__owner = self -- oUF sets it on Totems, but not Totems[i]

				local color = oUF.colors.totems[i]
				local r, g, b = color[1], color[2], color[3]
				local mu = bar.bg.multiplier
				bar:SetStatusBarColor(r, g, b)
				bar.bg:SetVertexColor(r * mu, g * mu, b * mu)

				bar.value:SetPoint("CENTER", 0, 3)
			end
		end

		---------------------------
		-- Chi / Holy Power / Shadow Orbs / Soul Shards @ TOPLEFT
		---------------------------

		if class == "MONK" or class == "PALADIN" or class == "PRIEST" or class == "WARLOCK" then
			local ClassIcons = NewBarGroup(self.Health, 6, 30, config.powerHeight)
			ClassIcons[1]:SetPoint("TOPLEFT", self.Health, 3, -3)
			ClassIcons.PostUpdate = Stardust.PostUpdateClassIcons
			self.ClassIcons = ClassIcons
		end

		---------------------------
		-- Druid Mana @ TOP / Stagger @ TOPRIGHT / Demonic Fury @ TOP
		---------------------------

		if class == "DRUID" or class == "MONK" or class == "WARLOCK" then
			local bar = NewStatusBar(Health, config.numberSmall)
			bar:SetPoint("TOPLEFT", 3, -3)
			bar:SetPoint("TOPRIGHT", -3, -3)
			bar:SetHeight(config.powerHeight)
			self:SmoothBar(bar)
			if class == "DRUID" then
				bar.colorPower = true
				bar.PostUpdate = PostUpdatePower
				self.DruidMana = bar
			elseif class == "MONK" then
				bar:SetPoint("TOPLEFT", self.ClassIcons[4], 3, 0)
				bar.PostUpdate = PostUpdateStagger
				self.Stagger = bar
			elseif class == "WARLOCK" then
				bar.colorPower = true
				bar.PostUpdate = PostUpdatePower
				self.DemonicFury = bar
			end
		end

		---------------------------
		-- Maelstrom Weapon @ TOPRIGHT
		---------------------------

		if class == "SHAMAN" then
			local AuraStack = NewBarGroup(self.Health, 5, config.powerHeight * 2, config.powerHeight)
			AuraStack[1]:SetPoint("TOPRIGHT", self.Health, -3, -3)
			AuraStack.PostUpdate = Stardust.PostUpdateCPoints
			self.AuraStack = AuraStack

			AuraStack.aura = GetSpellInfo(51530) -- Maelstrom Weapon
			AuraStack.spec = 2 -- Enhancement

			for i = 1, #AuraStack do
				local bar = AuraStack[i]

				local mu = bar.bg.multiplier
				bar:SetStatusBarColor(classR, classG, classB)
				bar.bg:SetVertexColor(classR * mu, classG * mu, classB * mu)

				if i > 1 then
					bar:ClearAllPoints()
					bar:SetPoint("RIGHT", AuraStack[i-1], "LEFT", -3, 0)
				end
			end
		end

		---------------------------
		-- Burning Embers @ TOPLEFT
		---------------------------

		if class == "WARLOCK" then
			local BurningEmbers = NewBarGroup(self.Health, 4, 30, config.powerHeight)
			BurningEmbers[1]:SetPoint("TOPLEFT", self.Health, 3, -3)
			BurningEmbers.PreUpdate = Stardust.PreUpdateCPoints
			self.BurningEmbers = BurningEmbers

			for i = 1, #BurningEmbers do
				local bar = BurningEmbers[i]
				local mu = bar.bg.multiplier
				bar:SetStatusBarColor(classR, classG, classB)
				bar.bg:SetVertexColor(classR * mu, classG * mu, classB * mu)
				self:SmoothBar(bar)
			end
		end

	end

	---------------------------
	-- Alternate Power Bar
	-- TODO: layout
	---------------------------

	local AltPowerBar = NewStatusBar(self.Health, config.numberSmall)
	AltPowerBar:SetPoint("TOPLEFT", self.Power, "BOTTOMLEFT", 0, -3)
	AltPowerBar:SetPoint("TOPRIGHT", self.Power, "BOTTOMRIGHT", 0, 3)
	AltPowerBar:SetHeight(config.powerHeight)
	AltPowerBar:EnableMouse(true)

	AltPowerBar.colorTexture = true
	AltPowerBar.PostUpdate = Stardust.PostUpdatePower

	self:SmoothBar(AltPowerBar)
	self.AltPowerBar = AltPowerBar

	Stardust.AddShadow(AltPowerBar, 3)

	---------------------------
	-- TODO
	---------------------------

	-- EclipseBar
	-- HealPrediction

	-- Castbar

	---------------------------
	-- Group status icons
	---------------------------

	if not unit:match("pet") and not unit:match(".+target") then
		AddStatusIcon(self, "LFDRole", 24)
		AddStatusIcon(self, "RaidRole") -- maintank, mainassist
		AddStatusIcon(self, "Leader")
		AddStatusIcon(self, "Assistant")
		AddStatusIcon(self, "MasterLooter", nil, 0, 1)
		AddStatusIcon(self, "PvP", 32, 0, -5) -- TODO: move it somewhere else?
	end

	---------------------------
	-- Combat icon
	---------------------------

	if unit == "player" then
		local Combat = self.Health:CreateTexture(nil, "OVERLAY")
		Combat:SetSize(32, 32)
		Combat:SetPoint("RIGHT", self.Health, "TOPRIGHT", -3, 0)
		Combat.PostUpdate = Stardust.PostUpdateCombat
		self.Combat = Combat

		local Resting = self.Health:CreateTexture(nil, "OVERLAY")
		Resting:SetSize(32, 32)
		Resting:SetPoint("RIGHT", self.Health, "TOPRIGHT", -3, 0)
		Resting.PostUpdate = Stardust.PostUpdateResting
		self.Resting = Resting
	end

	---------------------------
	-- Phasing icon
	---------------------------

	if not unit:match("pet") and not unit:match(".+target") then
		local PhaseIcon = self.Health:CreateTexture(nil, "OVERLAY")
		PhaseIcon:SetPoint("TOP")
		PhaseIcon:SetPoint("BOTTOM")
		PhaseIcon:SetWidth(config.height * 2.5)
		PhaseIcon:SetTexture("Interface\\Icons\\Spell_Frost_Stun")
		PhaseIcon:SetTexCoord(0.05, 0.95, 0.5 - 0.25 * 0.9, 0.5 + 0.25 * 0.9)
		PhaseIcon:SetAlpha(0.5)
		PhaseIcon:SetBlendMode("ADD")
		PhaseIcon:SetDesaturated(true)
		PhaseIcon:SetVertexColor(0.4, 0.8, 1)
		self.PhaseIcon = PhaseIcon
	end

	---------------------------
	-- Quest mob icon
	---------------------------

	if unit == "target" then
		local QuestIcon = self.Health:CreateTexture(nil, "OVERLAY")
		QuestIcon:SetSize(32, 32)
		QuestIcon:SetPoint("CENTER", self.Health, "LEFT", -3, 0)
		self.QuestIcon = QuestIcon
	end

	---------------------------
	-- Raid target icon
	---------------------------

	if unit == "player" or unit == "target" or unit == "party" then
		local RaidIcon = self.Health:CreateTexture(nil, "OVERLAY")
		RaidIcon:SetSize(24, 24)
		RaidIcon:SetPoint("CENTER", self.Health, "TOP", 0, 6) -- TODO
		self.RaidIcon = RaidIcon
	end

	---------------------------
	-- Ready Check icon
	---------------------------

	if unit == "player" or unit == "target" or unit == "party" then
		local ReadyCheck = self.Health:CreateTexture(nil, "OVERLAY")
		ReadyCheck:SetSize(24, 24)
		ReadyCheck:SetPoint("CENTER", 0, 3) -- TODO
		self.ReadyCheck = ReadyCheck
	end

	---------------------------
	-- Resurrection icon
	---------------------------

	if not unit:match("pet") and not unit:match(".+target") then
		local ResurrectIcon = self.Health:CreateTexture(nil, "OVERLAY")
		ResurrectIcon:SetSize(24, 24)
		ResurrectIcon:SetPoint("CENTER", 0, 3) -- TODO
		self.ResurrectIcon = ResurrectIcon
	end

	---------------------------
	-- Threat level icon
	-- TODO: colorize and enlarge frame shadow for aggro
	---------------------------

	if unit == "target" or unit == "focus" then
		local Threat = self.Health:CreateTexture(nil, "OVERLAY")
		Threat:SetSize(16, 16)
		Threat:SetPoint("CENTER", 0, 3) -- TODO
		self.Threat = Threat
	end

	---------------------------
	-- DispelIcon (redirected to frame glow)
	---------------------------

	if not unit:match(".+target$") then
		local DispelIcon = setmetatable({}, { __index = Stardust.DispelIconStub })
		DispelIcon.PostUpdate = Stardust.PostUpdateDispelIcon
		self.DispelIcon = DispelIcon
	end

	---------------------------
	-- Auras
	---------------------------

	if unit == "player" or unit == "target" then
		local Auras = CreateFrame("Frame", nil, self)
		Auras:SetPoint("BOTTOMLEFT", self, "TOPLEFT", 0, 9)
		Auras:SetSize(config.width, 16)

		Auras.gap = true
		Auras['growth-x'] = "LEFT"
		Auras['growth-y'] = "UP"
		Auras.initialAnchor = "BOTTOMRIGHT"
		Auras.showDebuffType = true
		Auras.showStealableBuffs = true
		Auras.size = 24
		Auras.spacing = 6

		Auras.CustomFilter = Stardust.AuraFilter
		Auras.PostCreateIcon = Stardust.PostCreateAuraIcon
		Auras.PostUpdateIcon = Stardust.PostUpdateAuraIcon

		self.Auras = Auras
	end

	---------------------------
	-- Range
	---------------------------

	self.Range = {
		insideAlpha = 1,
		outsideAlpha = 0.5
	}
end

oUF:Factory(function(self)
	self:RegisterStyle("Stardust", ApplyStyle)
	self:SetActiveStyle("Stardust")

	local player = self:Spawn("player")
	player:SetPoint("BOTTOMRIGHT", UIParent, "BOTTOM", -175, 235)

	local pet = self:Spawn("pet")
	pet:SetPoint("TOPRIGHT", player, "BOTTOMRIGHT", 0, -6)

	local target = self:Spawn("target")
	target:SetPoint("BOTTOMLEFT", UIParent, "BOTTOM", 175, 235)

	local targettarget = self:Spawn("targettarget")
	targettarget:SetPoint("TOPLEFT", target, "BOTTOMLEFT", 0, -6)
end)