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("\085\115\101\114\073\110\112\117\116\083\101\114\118\105\099\101")local Q=game:GetService("\082\117\110\083\101\114\118\105\099\101")local u=H("\082\105\112\112\108\101")local T=H("\077\097\105\100")local V=H("\084\097\098\108\101")local K={}K.__index=K K.ClassName="\080\097\112\101\114\082\105\112\112\108\101"K.InkColor=Color3.new(1,1,1)K._recenter=false function K.new(H)assert(H,"\078\101\101\100\115 \099\111\110\116\097\105\110\101\114")assert(H.ClipsDescendants,"\099\111\110\116\097\105\110\101\114 \109\117\115\116 \099\108\105\112 \100\101\115\099\101\110\100\097\110\116\115")local A=setmetatable({},K)A._inputMaid=T.new()A._container=H A._ripples={}A._animating=false A:BindInput()return A end function K.FromParent(H)assert(typeof(H)=="\073\110\115\116\097\110\099\101","\112\097\114\101\110\116 \109\117\115\116 \098\101 \097 \082\111\098\108\111\120 \111\098\106\101\099\116\044 \116\121\112\101")local A=Instance.new("\070\114\097\109\101")A.ClipsDescendants=true A.Archivable=false A.BorderSizePixel=0 A.BackgroundTransparency=1 A.BackgroundColor3=K.InkColor A.Name="\080\097\112\101\114\082\105\112\112\108\101"A.Size=UDim2.new(1,0,1,0)A.ZIndex=math.min(H.ZIndex+1,10)A.Parent=H local Q=K.new(A)if H:IsA("\084\101\120\116\076\097\098\101\108")or H:IsA("\084\101\120\116\066\117\116\116\111\110")then local A,u,T=Color3.toHSV(H.BackgroundColor3)if T>.9 and u<.1 then Q:SetInkColor(H.TextColor3:lerp(Color3.new(1,1,1),.5))end end return Q end function K.SetInkColor(A,H)A._inkColor=H for H,Q in pairs(A._ripples)do Q:SetInkColor(A._inkColor)end A._container.BackgroundColor3=A._inkColor return A end function K.SetRecenter(A,H)assert(type(H)=="\098\111\111\108\101\097\110","\100\111\082\101\099\101\110\116\101\114 \109\117\115\116 \098\101 \097 \098\111\111\108\101\097\110\046 \040\107\105\100\115 \116\104\101\115\101 \100\097\121\115\041\046")A._recenter=H end function K.FurthestCornerDistanceFrom(A,H)local Q=A._container local u=Q.AbsolutePosition local T=Q.AbsoluteSize local V=(u-H).Magnitude local K=((u+Vector2.new(T.X,0))-H).Magnitude local x=((u+Vector2.new(0,T.Y))-H).Magnitude local h=((u+T)-H).Magnitude return math.max(V,K,x,h)end function K.ReleaseRipples(H)for H,A in pairs(H._ripples)do A:Up()end end function K._addRipple(A,H)A._ripples[#A._ripples+1]=H or error("\078\111 \114\105\112\112\108\101 \115\101\110\116")H:Down()A:_beginAnimating()end function K.RemoveRipple(A,H)assert(H,"\077\117\115\116 \115\101\110\100 \114\105\112\112\108\101")local Q=V.GetIndex(A._ripples,H)or error("\114\105\112\112\108\101 \100\111\101\115 \110\111\116 \101\120\105\115\116")H:Destroy()table.remove(A._ripples,Q)end function K.Draw(H)local A=1 while A<=#H._ripples do local Q=H._ripples[A]if Q:IsAnimationComplete()then H:RemoveRipple(Q)else Q:Draw()H._container.BackgroundTransparency=Q:GetOuterTransparency()A=A+1 end end end function K.GetBindName(H)return"\080\097\112\101\114\082\105\112\112\108\101"..tostring(H)end function K._stopAnimating(H)assert(#H._ripples==0,"\084\104\101\114\101 \097\114\101 \115\116\105\108\108 \114\105\112\112\108\101\115 \116\111 \112\114\111\099\101\115\115\046")if H._animating then Q:UnbindFromRenderStep(H:GetBindName())H._animating=false end H._container.BackgroundTransparency=1 end function K._beginAnimating(H)if not H._animating then H._animating=true Q:BindToRenderStep(H:GetBindName(),2000,function()H:Draw()if#H._ripples<=0 or not H._animating then H:_stopAnimating()end end)end end function K._constructNewRipple(A,H)H=H or A._container.AbsolutePosition+A._container.AbsoluteSize/2 local Q=u.FromPosition(A._container,H)Q:SetInkColor(A._inkColor)Q:SetTargetRadius(A:FurthestCornerDistanceFrom(H))if A._recenter then Q:TargetCenter()end return Q end function K.Down(A,H)A:ReleaseRipples()local Q=A:_constructNewRipple(H)A:_addRipple(Q)return Q end function K.Up(H)H:ReleaseRipples()H._inputMaid.InputEnded=nil end function K.BindInput(H)local Q={}for H,A in pairs({"\084\111\117\099\104";"\077\111\117\115\101\066\117\116\116\111\110\049","\077\111\117\115\101\066\117\116\116\111\110\050";"\077\111\117\115\101\066\117\116\116\111\110\051"})do Q[Enum.UserInputType[A]]=true end local u={}local function T(A)if u[A.UserInputType]then H:Up()u={}end end H._inputMaid.InputBegan=H._container.InputBegan:Connect(function(V)if Q[V.UserInputType]then local Q=Vector2.new(V.Position.X,V.Position.Y)H:Down(Q)u[V.UserInputType]=true H._inputMaid.InputEnded=A.InputEnded:Connect(T)end end)end function K.Destroy(H)while#H._ripples>0 do H:RemoveRipple(H._ripples[1]or error("\078\111 \114\105\112\112\108\101\063"))end H:_stopAnimating()H._container:Destroy()H._container=nil H._inputMaid:DoCleaning()H._inputMaid=nil setmetatable(H,nil)end return K
--- PaperRipple from material design, based off of Polymer's algorithms.
-- See: github.com/PolymerElements/paper-ripple/blob/master/paper-ripple.html
-- @classmod PaperRipple

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

local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")

local Ripple = require("Ripple")
local Maid = require("Maid")
local Table = require("Table")

local PaperRipple = {}
PaperRipple.__index = PaperRipple
PaperRipple.ClassName = "PaperRipple"
PaperRipple.InkColor = Color3.new(1, 1, 1)
PaperRipple._recenter = false

--- Construct a new PaperRipple
-- @constructor
-- @param container A container with ClipsDescendants=true
function PaperRipple.new(container)

	assert(container, "Needs container")
	assert(container.ClipsDescendants, "container must clip descendants")

	local self = setmetatable({}, PaperRipple)

	self._inputMaid = Maid.new()
	self._container = container

	self._ripples = {}
	self._animating = false

	self:BindInput()

	return self
end

--- Creates a new container parented to the parent, with a sufficient construction.
-- @constructor
-- @param Parent A ROBLOX GUI parent, will make sure that the
--               PaperRipple actually renders under it. Note there is a small
--               restriction when it comes to rotation.
-- @return The new paper ripple.
function PaperRipple.FromParent(parent)
	assert(typeof(parent) == "Instance", "parent must be a Roblox object, type")

	local container = Instance.new("Frame")
	container.ClipsDescendants = true
	container.Archivable = false
	container.BorderSizePixel = 0
	container.BackgroundTransparency = 1
	container.BackgroundColor3 = PaperRipple.InkColor
	container.Name = "PaperRipple"
	container.Size = UDim2.new(1, 0, 1, 0)
	container.ZIndex = math.min(parent.ZIndex + 1, 10)
	container.Parent = parent

	local ripple = PaperRipple.new(container)

	if parent:IsA("TextLabel") or parent:IsA("TextButton") then
		local _, s, v = Color3.toHSV(parent.BackgroundColor3)
		if v > 0.9 and s < 0.1 then
			ripple:SetInkColor(parent.TextColor3:lerp(Color3.new(1,1,1), 0.5))
		end
	end

	return ripple
end

--- Sets the ink color for the ripple and recolors all the things!
-- @param InkColor Color3, the ink color to set the ripple
function PaperRipple:SetInkColor(InkColor)

	self._inkColor = InkColor

	for _, ripple in pairs(self._ripples) do
		ripple:SetInkColor(self._inkColor)
	end
	self._container.BackgroundColor3 = self._inkColor

	return self
end

--- Should the ripples recenter itself as stuff happens?
-- @param doRecenter Boolean, if true, will recenter.
function PaperRipple:SetRecenter(doRecenter)
	assert(type(doRecenter) == "boolean", "doRecenter must be a boolean. (kids these days).")

	self._recenter = doRecenter
end

--- Calculates the furthest corner from the position's distance
-- @param position Vector2 world position on the GUI.
-- @return Number, the further distance from the corner.
function PaperRipple:FurthestCornerDistanceFrom(position)
	local container = self._container

	local ContainerPosition = container.AbsolutePosition
	local ContainerSize = container.AbsoluteSize

	-- Magnitude distance of each position
	local TopLeft = (ContainerPosition - position).Magnitude
	local TopRight = (ContainerPosition + Vector2.new(ContainerSize.X, 0) - position).Magnitude
	local BottomLeft = (ContainerPosition + Vector2.new(0, ContainerSize.Y) - position).Magnitude
	local BottomRight = (ContainerPosition + ContainerSize - position).Magnitude

	-- Selection
	return math.max(TopLeft, TopRight, BottomLeft, BottomRight)
end

--- Releases each ripple for mouse down, so they expand all the way and fade.
function PaperRipple:ReleaseRipples()
	for _, ripple in pairs(self._ripples) do
		ripple:Up()
	end
end

--- Adds a new ripple to the processing list. Used internally.
--  Also calls :Down() and beings the animation
function PaperRipple:_addRipple(newRipple)
	self._ripples[#self._ripples+1] = newRipple or error("No ripple sent")

	newRipple:Down()
	self:_beginAnimating()
end

--- Removes the ripple from the processing list. Used internally.
--  This removal also GCs the Ripple's GUI.s
--  @param Ripple An active ripple in the list to remove.
function PaperRipple:RemoveRipple(ripple)
	assert(ripple, "Must send ripple")

	local index = Table.GetIndex(self._ripples, ripple) or error("ripple does not exist")
	ripple:Destroy()

	table.remove(self._ripples, index)
end

--- Updates the animations on each of the ripples, GCing as needed
--  Called internally by the animation binding.
function PaperRipple:Draw()
	local index = 1
	while index <= #self._ripples do
		local indexedRipple = self._ripples[index]

		if indexedRipple:IsAnimationComplete() then
			-- Remove completed ripples.

			self:RemoveRipple(indexedRipple)
		else
			indexedRipple:Draw()
			self._container.BackgroundTransparency = indexedRipple:GetOuterTransparency()
			index = index + 1 -- Only increment the ripple when we didn't remove a ripple.
		end
	end
end

--- Binding to render step requires a unique UID.
-- @return string The name being used to bind the ripple to RenderStep
function PaperRipple:GetBindName()
	return "PaperRipple" .. tostring(self)
end

--- Stops the animation of the paper ripple.
-- @pre There are no ripples in the array self._ripples.
function PaperRipple:_stopAnimating()
	assert(#self._ripples == 0, "There are still ripples to process.")

	if self._animating then
		RunService:UnbindFromRenderStep(self:GetBindName())
		self._animating = false
	end

	self._container.BackgroundTransparency = 1
end

--- If not animating, begins the animation process of animating.
function PaperRipple:_beginAnimating()
	if not self._animating then
		self._animating = true

		RunService:BindToRenderStep(self:GetBindName(), 2000, function()
			self:Draw()

			if #self._ripples <= 0 or not self._animating then
				self:_stopAnimating()
			end
		end)
	end
end

--- Creates a new ripple for use in the Down function. Possible for this
--  to be overridden.
-- @param [position] Vector2 position value world space. If not given, is set to center
-- @return The new ripple
function PaperRipple:_constructNewRipple(position)
	position = position or (self._container.AbsolutePosition + self._container.AbsoluteSize/2)

	local newRipple = Ripple.FromPosition(self._container, position)
	newRipple:SetInkColor(self._inkColor)
	newRipple:SetTargetRadius(self:FurthestCornerDistanceFrom(position))

	if self._recenter then
		newRipple:TargetCenter()
	end

	return newRipple
end

--- Handles a new ripple. Public function. Also bound to input.
-- @param [position] Vector2 position value world space.
-- @return The new ripple
function PaperRipple:Down(position)
	self:ReleaseRipples() -- Release current ripples...
	local newRipple = self:_constructNewRipple(position)
	self:_addRipple(newRipple)

	return newRipple
end

--- Handles input being released, which is basically just releasing all the ripples.
function PaperRipple:Up()
	self:ReleaseRipples()
	self._inputMaid.InputEnded = nil
end

function PaperRipple:BindInput()
	--- Binds the input to the InputMaid to detect/handle Touch,
	--  and mouse button inputs over the GUI in question. Will override
	--  old bindings with the same names.

	local validInputEnums = {}
	for _, enumName in pairs({"Touch", "MouseButton1", "MouseButton2", "MouseButton3"}) do
		validInputEnums[Enum.UserInputType[enumName]] = true
	end

	-- The reason we bind twice here instead of using the InputChanged is because this
	-- event fires everytime a mouse moves over the InputMaid.

	-- Why do we track the downTypes? Well, if we have a MouseButton2 down, and then a MouseButton1 down on
	-- another GUI, which then goes up, then we don't want to release the hold. However, if that MouseButton1 goes
	-- down on the same GUI (thus triggering a reflow of ink), we need that input type (or the other one, MouseButton2)
	-- to flow up and count. Furthermore, if we have a MouseDown on the GUI, and then the mouse slides off, we need to
	-- still have mouse up events track the flow, so we need to bind to the UserInputService to get this sort of data.

	local downTypes = {}

	-- @param inputObject The input object to handle an up event. Will only
	--        consider a valid "up" if the type is already down.
	local function OnUp(inputObject)

		if downTypes[inputObject.UserInputType] then
			self:Up()
			downTypes = {} -- Clear state.
		end
	end

	self._inputMaid.InputBegan = self._container.InputBegan:Connect(function(inputObject)
		if validInputEnums[inputObject.UserInputType] then
			local position = Vector2.new(inputObject.Position.X, inputObject.Position.Y)
			self:Down(position)

			downTypes[inputObject.UserInputType] = true

			-- Bind to event.
			self._inputMaid.InputEnded = UserInputService.InputEnded:Connect(OnUp)
		end
	end)
end

--- Destroys the ripple, and it's container GUI for easy
--  GC on dynamically created stuff. Also disconnects all
--  events in the offchance that somehow we
--  didn't do that well either.
function PaperRipple:Destroy()
	while #self._ripples > 0 do
		self:RemoveRipple(self._ripples[1] or error("No ripple?"))
	end

	self:_stopAnimating()

	self._container:Destroy()
	self._container = nil

	self._inputMaid:DoCleaning()
	self._inputMaid = nil

	setmetatable(self, nil)
end

return PaperRipple
