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 LibSharedMedia = LibStub("LibSharedMedia-3.0")

local function abbrev(n)
	local v = abs(n)
	if v > 1000000000 then
		return format("%.0fb", n / 1000000000)
	elseif v > 10000000 then
		return format("%.0fm", n / 1000000)
	elseif v > 1000000 then
		return format("%.1fm", n / 1000000)
	elseif v > 10000 then
		return format("%.0fk", n / 1000)
	elseif v > 1000 then
		return format("%.1fk", n / 1000)
	else
		return n
	end
end

---------------------------
-- Frames
---------------------------

function Stardust.OnEnter(self)
	self.isMouseOver = true
	UnitFrame_OnEnter(self)
	self:UpdateAllElements("OnEnter")
end

function Stardust.OnLeave(self)
	self.isMouseOver = false
	UnitFrame_OnLeave(self)
	self:UpdateAllElements("OnLeave")
end

---------------------------
-- Health
---------------------------

function Stardust.PostUpdateHealth(element, unit, cur, max)
	element:SetValue(max - cur)
	element.value:SetText(element.__owner.isMouseOver and abbrev(cur) or cur < max and abbrev(-max + cur) or "")
end

---------------------------
-- Power / DruidMana / DemonicFury
---------------------------

local UnitIsDead = UnitIsDead

function Stardust.PostUpdatePower(element, unit, cur, max)
	if UnitIsDead(unit) then
		local t = oUF.colors.disconnected
		local r, g, b = t[1], t[2], t[3]
		local mu = element.bg.multiplier or 1
		element:SetStatusBarColor(r, g, b)
		element.bg:SetVertexColor(r * mu, g * mu, b * mu)
		element.value:SetText("")
		element:SetValue(0)
	else
		element.value:SetText(max > 0 and element.__owner.isMouseOver and abbrev(cur) or "")
	end
end

---------------------------
-- Stagger
---------------------------

function Stardust.PostUpdateStagger(element, maxHealth, stagger, staggerPercent, r, g, b)
	element.value:SetFormattedText("%d%%", staggerPercent)
end

---------------------------
-- ClassIcons
---------------------------

function Stardust.ResizeClassIcons(element)
	local COUNT = element.__max
	local widthTotal = ((Stardust.config.width - 6) * 0.7) - (COUNT * 3)
	local widthEach = floor(widthTotal / COUNT + 0.5)
	for i = 1, COUNT do
		element[i]:SetWidth(widthEach)
	end
end

function Stardust.PostUpdateClassIcons(element, cur, max, maxChanged, event)
	if maxChanged then
		Stardust.ResizeClassIcons(element)
		if element.__owner.Stagger then
			element.__owner.Stagger:SetPoint("TOPLEFT", element[max], 3, 0)
		end
	end
end

---------------------------
-- CPoints / AuraStack / BurningEmbers
---------------------------

function Stardust.PreUpdateCPoints(element)
	element.PreUpdate = nil
	Stardust.ResizeClassIcons(element)
end

function Stardust.PostUpdateCPoints(element, cur)
	if cur == 0 or element.__disabled then return end

	for i = 1, element.__max or #element do
		local bar = element[i]
		if i > cur then
			bar:SetValue(0)
			bar:Show()
		else
			bar:SetValue(1)
		end
	end
end

---------------------------
-- Runes
---------------------------

function Stardust.PostUpdateRuneType(element, rune, index, alt)
	local r, g, b = rune:GetStatusBarColor()
	rune.bg:SetVertexColor(r * 0.35, g * 0.35, b * 0.35)
end

function Stardust.PostUpdateRune(element, rune, index, start, duration, ready)
	-- The number of runes isn't dynamic, so they only need to be resized once per frame.
	element.PostUpdateRune = nil
	Stardust.ResizeClassIcons(element)
end

---------------------------
-- Totems
-- Partly adapted from oUF_Phanx
---------------------------

local OnUpdateTotem
do
	local ceil = ceil
	local unpack = unpack
	local SMOOTH_COLORS = oUF.colors.smooth
	local ColorGradient = oUF.ColorGradient

	function OnUpdateTotem(bar, elapsed)
		local t = bar.remaining - elapsed
		if t > 0 then
			bar.remaining = t
			bar:SetValue(t)
			if bar.__owner.isMouseOver then
				bar.value:SetText(ceil(t))
				bar.value:SetTextColor(ColorGradient(t, bar.max, unpack(SMOOTH_COLORS)))
			else
				bar.value:SetText("")
			end
		else
			bar:SetValue(0)
			bar.value:SetText("")
		end
	end
end

function Stardust.PostUpdateTotem(element, index, _, name, start, duration)
	local bar = element[index]
	bar.remaining, bar.max = start + duration - GetTime(), duration
	if duration > 0 then
		bar:SetMinMaxValues(0, duration)
		bar:SetScript("OnUpdate", OnUpdateTotem)
	else
		bar:SetScript("OnUpdate", nil)
		bar:SetValue(0)
		bar.value:SetText("")
	end
end

function Stardust.PreUpdateTotems(element, index)
	-- The number of totems isn't dynamic, so they only need to be resized once per frame.
	element.PreUpdate = nil
	Stardust.ResizeClassIcons(element)
end

---------------------------
-- Status icons
---------------------------

function Stardust.PostUpdateStatusIcon(element)
	if not element:IsShown() then return end
	local icons, x = element.__owner.StatusIcons, 2
	for i = 1, #icons do
		local icon = icons[i]
		if icon == element then
			break
		elseif icon:IsShown() and icon:GetTexture() then
			x = x + icon:GetWidth()
		end
	end
	element:SetPoint("LEFT", element.__owner.Health, "TOPLEFT", x + element.offsetX, 0 + element.offsetY)
end

local updatingCombatOrResting

function Stardust.PostUpdateCombat(element)
	if not updatingCombatOrResting then
		updatingCombatOrResting = true
		element.__owner.Resting:ForceUpdate()
		updatingCombatOrResting = nil
	end
end

function Stardust.PostUpdateResting(element)
	if not updatingCombatOrResting then
		updatingCombatOrResting = true
		element.__owner.Combat:ForceUpdate()
		updatingCombatOrResting = nil
	end
end

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

function Stardust.PostUpdateAFK(element, isAFK)
	local Power = element.__owner.Power
	if Power and Power.value then
		Power.value:SetShown(not isAFK)
	end
end

---------------------------
-- DispelIcon (redirected to frame.Shadows)
---------------------------

Stardust.DispelIconStub = {
	SetTexture = nop,
	SetVertexColor = nop,
	Show = nop,
	Hide = nop,
}

function Stardust.PostUpdateDispelIcon(element, dispelType, r, g, b)
	local frame = element.__owner
	if not frame.__shadowSize then
		frame.__shadowSize = frame.Shadows[1]:GetWidth()
	end
	if dispelType then
		frame:SetShadowColor(r, g, b)
		frame:SetShadowSize(frame.__shadowSize * 2)
	else
		frame:SetShadowColor()
		frame:SetShadowSize(frame.__shadowSize)
	end
end

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

function Stardust.PostCreateAuraIcon(element, button)
	button.cd:SetDrawBling(false)
	button.cd:SetDrawEdge(false)
	button.cd:SetReverse(true)

	button.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)

	button.count:SetFont(LibSharedMedia:Fetch("font", config.numberFont), config.numberSmall, "OUTLINE")
	button.count:SetShadowOffset(0, 0)
	button.count:SetTextColor(1, 1, 1)

	button.backdrop = button:CreateTexture(nil, "BACKGROUND")
	button.backdrop:SetPoint("BOTTOMLEFT", -1, -1)
	button.backdrop:SetPoint("TOPRIGHT", 1, 1)
	button.backdrop:SetTexture("Interface\\BUTTONS\\WHITE8X8")

	Stardust.AddShadow(button, 6)
end

function Stardust.PostUpdateAuraIcon(element, unit, button, index, offset)
	local _, _, _, _, dispelType, duration, timeLeft, _, isStealable, _, spellID, canApplyAura, isBossDebuff = UnitAura(unit, index, button.filter)

	if timeLeft > 600 then
		button.cd:Hide()
	end

	local color = button.isDebuff and dispelType and DebuffTypeColor[dispelType]
	if color then
		button.backdrop:SetVertexColor(color.r, color.g, color.b)
	else
		button.backdrop:SetVertexColor(0, 0, 0)
	end
end

---------------------------
-- Shadow
---------------------------

local SHADOW_SIZE = 3
local SHADOW_COLOR = { 0, 0, 0 }

local SHADOW_SEGMENTS = {
	-- 1 point, 2 coordLeft, 3 coordRight, 4 coordTop, 5 coordBottom, 6 offsetMultiplierX, 7 offsetMultiplierY
	{ "TOPLEFT", 0, 1/3, 0, 1/3, -1, 1 },
	{ "TOPRIGHT", 2/3, 1, 0, 1/3, 1, 1 },
	{ "BOTTOMRIGHT", 2/3, 1, 2/3, 1, 1, -1 },
	{ "BOTTOMLEFT", 0, 1/3, 2/3, 1, -1, -1 },
	{ "TOP", 1/3, 2/3, 0, 1/3, 0, 1 },
	{ "RIGHT", 2/3, 1, 1/3, 2/3, 1, 0 },
	{ "BOTTOM", 1/3, 2/3, 2/3, 1, 0, -1 },
	{ "LEFT", 0, 1/3, 1/3, 2/3, -1, 0 },
}

local function SetShadowColor(self, r, g, b, a)
	if not r or not g or not b then
		r, g, b = unpack(SHADOW_COLOR)
	end
	for i = 1, #self.Shadows do
		self.Shadows[i]:SetVertexColor(r, g, b, a or 1)
	end
end

local function SetShadowSize(self, size, offset)
	if not size then size = SHADOW_SIZE end

	local Shadows = self.Shadows
	for i = 1, #Shadows do
		Shadows[i]:SetSize(size, size)
	end

	local d = offset or floor(size * 2 / 3 + 0.5)
	Shadows[1]:SetPoint("TOPLEFT", -d, d)
	Shadows[2]:SetPoint("TOPRIGHT", d, d)
	Shadows[3]:SetPoint("BOTTOMRIGHT", d, -d)
	Shadows[4]:SetPoint("BOTTOMLEFT", -d, -d)

	local showH = self:GetWidth() > (2 * (size - d))
	local showV = self:GetHeight() > (2 * (size - d))
	Shadows[5]:SetShown(showH)
	Shadows[6]:SetShown(showV)
	Shadows[7]:SetShown(showH)
	Shadows[8]:SetShown(showV)
end

function Stardust.AddShadow(self, size, r, g, b, a)
	local Shadows = {}
	for i = 1, #SHADOW_SEGMENTS do
		local seg = SHADOW_SEGMENTS[i]
		local tex = self:CreateTexture(nil, "BACKGROUND")
		tex:SetTexture("Interface\\AddOns\\oUF_Stardust\\Textures\\glow")
		tex:SetTexCoord(seg[2], seg[3], seg[4], seg[5])
		tex:SetVertexColor(0, 0, 0)
		Shadows[i] = tex
	end

	-- TOP
	Shadows[5]:SetPoint("LEFT", Shadows[1], "RIGHT")
	Shadows[5]:SetPoint("RIGHT", Shadows[2], "LEFT")

	-- RIGHT
	Shadows[6]:SetPoint("TOP", Shadows[2], "BOTTOM")
	Shadows[6]:SetPoint("BOTTOM", Shadows[3], "TOP")

	-- BOTTOM
	Shadows[7]:SetPoint("LEFT", Shadows[4], "RIGHT")
	Shadows[7]:SetPoint("RIGHT", Shadows[3], "LEFT")

	-- LEFT
	Shadows[8]:SetPoint("TOP", Shadows[1], "BOTTOM")
	Shadows[8]:SetPoint("BOTTOM", Shadows[4], "TOP")

	self.Shadows = Shadows
	self.SetBackdropBorderColor = SetShadowColor
	self.SetShadowColor = SetShadowColor
	self.SetShadowSize = SetShadowSize

	self:SetShadowColor(r, g, b, a)
	self:SetShadowSize(size)

	return Shadows
end