local H=require((game:GetService("\082\101\112\108\105\099\097\116\101\100\083\116\111\114\097\103\101")):WaitForChild("\078\101\118\101\114\109\111\114\101"))local A=game:GetService("\082\117\110\083\101\114\118\105\099\101")local Q=H("\082\111\116\097\116\105\110\103\067\104\097\114\097\099\116\101\114\066\117\105\108\100\101\114")local u={}u.ClassName="\082\111\116\097\116\105\110\103\076\097\098\101\108"u._text=""u._speed=15 u._damper=.85 u._transparency=0 u._width=.5 u._textXAlignment="\076\101\102\116"function u.new()local H=setmetatable({},u)H._labels=setmetatable({},{__index=function(A,u)if u=="\082\101\109\111\118\101"then return function(H,Q)assert(rawget(A,Q),"\084\104\101\114\101 \105\115 \110\111 \108\097\098\101\108 \097\116 \105\110\100\101\120"..Q);(rawget(A,Q)):Destroy()rawset(A,Q,nil)end elseif u=="\071\101\116"then return function(u,T)if rawget(A,T)then return rawget(A,T)else local u=((((Q.new()):WithTemplate(H._template)):Generate(H._container)):WithCharacter(" ")):Create()u.Gui.Position=H:_getLabelPosition(T)for A,Q in pairs({"\084\114\097\110\115\112\097\114\101\110\099\121","\068\097\109\112\101\114";"\083\112\101\101\100"})do if u[Q]~=H[Q]then u[Q]=H[Q]end end rawset(A,T,u)return u end end else return rawget(A,u)end end})H._bindKey="\082\111\116\097\116\105\110\103\076\097\098\101\108"..tostring(H)return H end function u._getLabelPosition(A,H)if A.TextXAlignment=="\076\101\102\116"then return UDim2.new((H-1)*A.Width,0,0,0)else return UDim2.new(-A.TotalWidth+(H-1)*A.Width,0,0,0)end end function u.SetGui(A,H)A.Gui=H or error("\078\111 \071\085\073")A._container=A.Gui.Container end function u.SetTemplate(A,H)A._template=H or error("\078\111 \071\085\073")end function u.__index(A,H)if H=="\084\101\120\116"then return A._text elseif H=="\084\111\116\097\108\087\105\100\116\104"then return#A.Text*A.Width elseif H=="\087\105\100\116\104"then return A._width elseif H=="\084\114\097\110\115\112\097\114\101\110\099\121"or H=="\068\097\109\112\101\114"or H=="\083\112\101\101\100"then return A["\095"..H:lower()]elseif H=="\084\101\120\116\088\065\108\105\103\110\109\101\110\116"then return A._textXAlignment else return u[H]end end function u.__newindex(Q,H,A)if H=="\084\101\120\116"then if type(A)=="\110\117\109\098\101\114"then A=tostring(A)end assert(type(A)=="\115\116\114\105\110\103","\084\101\120\116 \109\117\115\116 \098\101 \097 \115\116\114\105\110\103\044 \103\111\116 "..type(A))if Q.TextXAlignment=="\082\105\103\104\116"then local H=#A-#Q.Text local u={}for T,V in pairs(Q._labels)do local K=T+H u[K]=V if K<1 or K>#A then V.TargetCharacter=" "end Q._labels[T]=nil end for H,A in pairs(u)do Q._labels[H]=A end else for H=#A+1,#Q.Text,1 do if Q._labels[H]then Q._labels[H].TargetCharacter=" "end end end Q._text=A for H=1,#Q.Text,1 do(Q._labels:Get(H)).TargetCharacter=Q.Text:sub(H,H)end for H,A in pairs(Q._labels)do A.Gui.Position=Q:_getLabelPosition(H)end Q:_beginUpdate()elseif H=="\087\105\100\116\104"then Q._width=A for H,A in pairs(Q._labels)do A.Gui.Position=Q:_getLabelPosition(H)end elseif H=="\084\114\097\110\115\112\097\114\101\110\099\121"or H=="\068\097\109\112\101\114"or H=="\083\112\101\101\100"then Q["\095"..H:lower()]=A for A,u in pairs(Q._labels)do u[H]=Q[H]end elseif H=="\084\101\120\116\088\065\108\105\103\110\109\101\110\116"then assert(A=="\076\101\102\116"or A=="\082\105\103\104\116","\118\097\108\117\101 \109\117\115\116 \098\101 \"\076\101\102\116\" \111\114 \"\082\105\103\104\116\"")if A=="\076\101\102\116"then Q._container.Position=UDim2.new(0,0,0,0)else Q._container.Position=UDim2.new(1,0,0,0)end Q._textXAlignment=A for H,A in pairs(Q._labels)do A.Gui.Position=Q:_getLabelPosition(H)end else rawset(Q,H,A)end end function u.UpdateRender(H)local A=true for Q,u in pairs(H._labels)do if u:UpdateRender()then if u.TargetCharacter==" "then H._labels:Remove(Q)else u.Value=u.Target u:UpdateRender()end else A=false end end return A end function u._stopUpdate(H)A:UnbindFromRenderStep(H._bindKey)H._bound=false end function u._beginUpdate(H)if not H._bound then H._bound=true A:BindToRenderStep(H._bindKey,2000,function()if H:UpdateRender()then H:_stopUpdate()end end)end end function u.Destroy(H)H:_stopUpdate()H._bindKey=nil for A,Q in pairs(H._labels)do H._labels:Remove(A)end H._labels=nil H.Gui:Destroy()H._template:Destroy()setmetatable(H,nil)end return u
--- A text label with most general properties of a textlabel, except when text is set,
-- it rotates uniformly like an old clock, animating in a satisfying way
-- @usage Construct with RotatingLabelBuilder.new(Template):Create()

local require = require(game:GetService("ReplicatedStorage"):WaitForChild("Nevermore"))

local RunService = game:GetService("RunService")

local RotatingCharacterBuilder = require("RotatingCharacterBuilder")

local RotatingLabel = {}
RotatingLabel.ClassName = "RotatingLabel"
RotatingLabel._text = ""
RotatingLabel._speed = 15
RotatingLabel._damper = 0.85
RotatingLabel._transparency = 0
RotatingLabel._width = 0.5 -- A scaler of UDim2.
RotatingLabel._textXAlignment = "Left"

function RotatingLabel.new()
	local self = setmetatable({}, RotatingLabel)

	self._labels = setmetatable({}, {
		__index = function(labels, index)
			if index == "Remove" then
				return function(_, index)
					assert(rawget(labels, index), "There is no label at index" .. index)
					rawget(labels, index):Destroy()
					rawset(labels, index, nil)
				end
			elseif index == "Get" then
				return function(_, index)
					-- @return The current label, or a newly constructed one

					if rawget(labels, index) then
						return rawget(labels, index)
					else
						local newLabel = RotatingCharacterBuilder.new()
							:WithTemplate(self._template)
							:Generate(self._container)
							:WithCharacter(" ")
							:Create()

						newLabel.Gui.Position = self:_getLabelPosition(index)

						for _, propertyName in pairs({"Transparency", "Damper", "Speed"}) do
							if newLabel[propertyName] ~= self[propertyName] then
								newLabel[propertyName] = self[propertyName]
							end
						end

						rawset(labels, index, newLabel)
						return newLabel
					end
				end
			else
				return rawget(labels, index)
				-- error(index .. " is not a valid member")
			end
		end;
	});

	self._bindKey = "RotatingLabel" .. tostring(self)

	return self
end

function RotatingLabel:_getLabelPosition(index)
	if self.TextXAlignment == "Left" then
		return UDim2.new((index-1)*self.Width, 0, 0, 0)
	else
		return UDim2.new(-self.TotalWidth + (index-1)*self.Width, 0, 0, 0)
	end
end

function RotatingLabel:SetGui(gui)
	self.Gui = gui or error("No GUI")
	self._container = self.Gui.Container
end

function RotatingLabel:SetTemplate(template)
	self._template = template or error("No GUI")
end

function RotatingLabel:__index(index)
	if index == "Text" then
		return self._text
	elseif index == "TotalWidth" then
		return #self.Text * self.Width
	elseif index == "Width" then
		return self._width
	elseif index == "Transparency" or index == "Damper" or index == "Speed" then
		return self["_" .. index:lower()]
	elseif index == "TextXAlignment" then
		return self._textXAlignment
	else
		return RotatingLabel[index]
	end
end

---
-- @usage
--[[
	RotatingLabel.Text = string
		Sets the text label, which it will automatically update
	RotatingLabel.Width = number
		Sets the general width of each character
	RotatingLabel.Transparency
		Sets the transparency
	RotatingLabel.Damper
		Sets the damper of the underlying spring model
	RotatingLabel.Speed
		Sets the speed of the underlying spring model
	RotatingLabel.TextXAlignment
		Sets the alignment on the X axis. Cannot be Center.
]]
function RotatingLabel:__newindex(index, value)
	if index == "Text" then
		if type(value) == "number" then
			value = tostring(value)
		end

		assert(type(value) == "string", "Text must be a string, got " .. type(value))

		if self.TextXAlignment == "Right" then
			-- Shifts existing labels over in the stack so when we add more they
			local Delta = #value - #self.Text

			local labels = {}

			for index, label in pairs(self._labels) do
				local NewIndex = index+Delta
				labels[NewIndex] = label

				-- Clean up
				if NewIndex < 1 or NewIndex > #value then
					label.TargetCharacter = " "
				end

				self._labels[index] = nil
			end

			for index, label in pairs(labels) do
				self._labels[index] = label
			end
		else
			-- Clean up past characters

			for index = #value+1, #self.Text do
				if self._labels[index] then
					self._labels[index].TargetCharacter = " "
				end
			end
		end

		self._text = value

		for index = 1, #self.Text do
			self._labels:Get(index).TargetCharacter = self.Text:sub(index, index)
		end

		for index, label in pairs(self._labels) do
			label.Gui.Position = self:_getLabelPosition(index)
		end

		self:_beginUpdate()
	elseif index == "Width" then
		self._width = value

		for index, label in pairs(self._labels) do
			label.Gui.Position = self:_getLabelPosition(index)
		end
	elseif index == "Transparency" or index == "Damper" or index == "Speed" then
		self["_" .. index:lower()] = value
		for _, label in pairs(self._labels) do
			label[index] = self[index]
		end
	elseif index == "TextXAlignment" then
		assert(value == "Left" or value == "Right", "value must be \"Left\" or \"Right\"")

		if value == "Left" then
			self._container.Position = UDim2.new(0, 0, 0, 0)
		else
			self._container.Position = UDim2.new(1, 0, 0, 0)
		end

		self._textXAlignment = value
		for index, label in pairs(self._labels) do
			label.Gui.Position = self:_getLabelPosition(index)
		end
	else
		rawset(self, index, value)
	end
end

-- @return IsDoneUpdating
function RotatingLabel:UpdateRender()
	local isDone = true

	for index, label in pairs(self._labels) do
		if label:UpdateRender() then
			if label.TargetCharacter == " " then
				self._labels:Remove(index)
			else
				label.Value = label.Target
				label:UpdateRender()
			end
		else -- label is sitll animating
			isDone = false
		end
	end

	return isDone
end

function RotatingLabel:_stopUpdate()
	RunService:UnbindFromRenderStep(self._bindKey)
	self._bound = false
end

function RotatingLabel:_beginUpdate()
	if not self._bound then
		self._bound = true

		RunService:BindToRenderStep(self._bindKey, 2000, function()
			if self:UpdateRender() then
				self:_stopUpdate()
			end
		end)
	end
end

function RotatingLabel:Destroy()
	self:_stopUpdate()
	self._bindKey = nil

	for index, _ in pairs(self._labels) do
		self._labels:Remove(index)
	end
	self._labels = nil

	self.Gui:Destroy()
	self._template:Destroy()

	setmetatable(self, nil)
end

return RotatingLabel