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=game:GetService("\085\115\101\114\073\110\112\117\116\083\101\114\118\105\099\101")local u=H("\077\097\105\100")local T=H("\083\099\114\111\108\108\077\111\100\101\108")local V=H("\083\067\082\079\076\076\095\084\089\080\069")local K=H("\084\097\098\108\101")local x={}x.ClassName="\083\099\114\111\108\108\105\110\103\070\114\097\109\101"x.__index=x function x.new(H)local A=setmetatable({},x)A._maid=u.new()A.Gui=H or error("\078\111 \071\117\105")A._container=A.Gui.Parent or error("\078\111 \099\111\110\116\097\105\110\101\114")A._scrollType=V.Vertical A._scrollbars={}A._model=T.new()A._maid:GiveTask(A:BindInput(H))A._maid:GiveTask(A:BindInput(A._container))A._maid:GiveTask((A._container:GetPropertyChangedSignal("\065\098\115\111\108\117\116\101\083\105\122\101")):Connect(function()A:_updateScrollerSize()A:_freeScroll(true)end))A._maid:GiveTask((A.Gui:GetPropertyChangedSignal("\065\098\115\111\108\117\116\101\083\105\122\101")):Connect(function()A:_updateScrollerSize()A:_freeScroll(true)end))A:_updateScrollerSize()A:_updateRender()return A end function x.SetScrollType(A,H)assert(K.Contains(V,H))A._scrollType=H end function x.AddScrollbar(A,H)assert(H)H:SetScrollingFrame(A)table.insert(A._scrollbars,H)A._maid[H]=H end function x.RemoveScrollbar(A,H)local Q=K.GetIndex(A._scrollbars,H)if Q then table.remove(A._scrollbars,Q)A._maid[H]=nil end end function x.ScrollTo(Q,H,A)Q._model.Target=H if A then Q._model.position=Q._model.Target Q._model.Velocity=0 end end function x.ScrollToTop(A,H)A:ScrollTo(A._model.Min,H)end function x.ScrollToBottom(A,H)A:ScrollTo(A._model.Max,H)end function x.GetModel(H)return H._model end function x._updateScrollerSize(H)H._model.TotalContentLength=H.Gui.AbsoluteSize[H._scrollType.Direction]H._model.ViewSize=H._container.AbsoluteSize[H._scrollType.Direction]end function x._updateRender(H)if H._scrollType==V.Vertical then H.Gui.Position=UDim2.new(H.Gui.Position.X,UDim.new(0,H._model.BoundedRenderPosition))elseif H._scrollType==V.Horizontal then H.Gui.Position=UDim2.new(UDim.new(0,H._model.BoundedRenderPosition),H.Gui.Position.Y)else error("\091\083\099\114\111\108\108\105\110\103\070\114\097\109\101\093 - \066\097\100 \083\099\114\111\108\108\084\121\112\101")end for H,A in pairs(H._scrollbars)do if A.Destroy then A:UpdateRender()else warn("\091\083\099\114\111\108\108\105\110\103\070\114\097\109\101\093 - \083\099\114\111\108\108\098\097\114 \105\115 \100\101\115\116\114\111\121\101\100")end end end function x.StopDrag(H)local A=H._model.Position if H._model:GetDisplacementPastBounds(A)==0 then if H._model.Velocity>0 then H._model.Target=math.max(H._model.Target,A+H._model.Velocity*.5)else H._model.Target=math.min(H._model.Target,A+H._model.Velocity*.5)end end H:_freeScroll()end function x._freeScroll(Q,H)if H and Q._maid._updateMaid then return end local T=u.new()Q:_updateRender()T:GiveTask(A.Heartbeat:Connect(function()Q:_updateRender()if Q._model.AtRest then Q._maid._updateMaid=nil end end))Q._maid._updateMaid=T end function x._getVelocityTracker(A,H)H=H or 1 A._model.Velocity=0 local Q=tick()local u=A._model.Position return function()local T=A._model.Position local V=tick()-Q local K=u-T if V==0 then V=.03 end A._model.Velocity=A._model.Velocity-(K/V)*H u=T Q=tick()end end function x._getInputProcessor(A,H)local Q=A._model.Position local u=A:_getVelocityTracker()local T=H.Position return function(H)local V=(H.Position-T)[A._scrollType.Direction]local K=Q-V A._model.Position=K A._model.Target=K A:_updateRender()u()return V end end function x.BindInput(Q,H,A)local T=u.new()T:GiveTask(H.InputBegan:Connect(function(H)Q:StartScrolling(H,A)end))T:GiveTask(H.InputChanged:Connect(function(A)if H.Active and A.UserInputType==Enum.UserInputType.MouseWheel then Q._model.Target=Q._model.Target-A.Position.z*80 Q:_freeScroll()end end))Q._maid:GiveTask(T)return T end function x.StartScrolling(T,H,A)if H.UserInputState~=Enum.UserInputState.Begin then return end if H.UserInputType==Enum.UserInputType.MouseButton1 or H.UserInputType==Enum.UserInputType.Touch then local V=u.new()local K=tick()local x=0 local h=T:_getInputProcessor(H)if H.UserInputType==Enum.UserInputType.MouseButton1 then V:GiveTask(Q.InputChanged:Connect(function(H,A)if H.UserInputType==Enum.UserInputType.MouseMovement then x=x+math.abs(h(H))end end))elseif H.UserInputType==Enum.UserInputType.Touch then V:GiveTask(Q.InputChanged:Connect(function(H,A)if H.UserInputType==Enum.UserInputType.Touch then x=x+math.abs(h(H))end end))end V:GiveTask(function()T:_updateRender()if A and A.OnClick then local Q=tick()-K local u=Q<=.05 or x<1 if u then A.OnClick(H)end end end)V:GiveTask(Q.InputEnded:Connect(function(A,Q)if A==H then T:StopDrag()end end))V:GiveTask(Q.WindowFocusReleased:Connect(function()T:StopDrag()end))T._maid._updateMaid=V end end function x.StartScrollbarScrolling(T,H,A)assert(H)assert(A)local V=u.new()local K=A.Position local x=T._model.ContentScrollPercent local h=T:_getVelocityTracker(.25)V:GiveTask(Q.InputChanged:Connect(function(A)if A.UserInputType==Enum.UserInputType.MouseMovement then local Q=T._scrollType.Direction local u=(A.Position-K)[Q]local V=u/(H.AbsoluteSize[Q]*(1-T._model.ContentScrollPercentSize))T._model.ContentScrollPercent=x+V T._model.TargetContentScrollPercent=T._model.ContentScrollPercent T:_updateRender()h()end end))V:GiveTask(Q.InputEnded:Connect(function(H)if H==A then T:StopDrag()end end))T._maid._updateMaid=V return V end function x.Destroy(H)H._maid:DoCleaning()H._maid=nil setmetatable(H,nil)end return x
--- Creates an inertia based scrolling frame that is animated and has inertia frames
-- Alternative to a Roblox ScrollingFrame with inertia scrolling and complete control over behavior and style
-- @classmod ScrollingFrame

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

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

local Maid = require("Maid")
local ScrollModel = require("ScrollModel")
local SCROLL_TYPE = require("SCROLL_TYPE")
local Table = require("Table")

local ScrollingFrame = {}
ScrollingFrame.ClassName = "ScrollingFrame"
ScrollingFrame.__index = ScrollingFrame

--- Creates a new ScrollingFrame which can be used. Prefer Container.Active = true so scroll wheel works.
-- Container should be in a Frame with ClipsDescendants = true
function ScrollingFrame.new(gui)
	local self = setmetatable({}, ScrollingFrame)

	self._maid = Maid.new()
	self.Gui = gui or error("No Gui")
	self._container = self.Gui.Parent or error("No container")
	self._scrollType = SCROLL_TYPE.Vertical;

	self._scrollbars = {}
	self._model = ScrollModel.new()

	self._maid:GiveTask(self:BindInput(gui))
	self._maid:GiveTask(self:BindInput(self._container))

	self._maid:GiveTask(self._container:GetPropertyChangedSignal("AbsoluteSize"):Connect(function()
		self:_updateScrollerSize()
		self:_freeScroll(true)
	end))

	self._maid:GiveTask(self.Gui:GetPropertyChangedSignal("AbsoluteSize"):Connect(function()
		self:_updateScrollerSize()
		self:_freeScroll(true)
	end))

	self:_updateScrollerSize()
	self:_updateRender()

	return self
end

--- Sets the scroll type for the frame
function ScrollingFrame:SetScrollType(scrollType)
	assert(Table.Contains(SCROLL_TYPE, scrollType))
	self._scrollType = scrollType
end

function ScrollingFrame:AddScrollbar(scrollbar)
	assert(scrollbar)
	scrollbar:SetScrollingFrame(self)

	table.insert(self._scrollbars, scrollbar)
	self._maid[scrollbar] = scrollbar
end

function ScrollingFrame:RemoveScrollbar(scrollbar)
	local index = Table.GetIndex(self._scrollbars, scrollbar)
	if index then
		table.remove(self._scrollbars, index)
		self._maid[scrollbar] = nil
	end
end

--- Scrolls to the position in pixels offset
function ScrollingFrame:ScrollTo(position, doNotAnimate)
	self._model.Target = position
	if doNotAnimate then
		self._model.position = self._model.Target
		self._model.Velocity = 0
	end
end

--- Scrolls to the top
function ScrollingFrame:ScrollToTop(doNotAnimate)
	self:ScrollTo(self._model.Min, doNotAnimate)
end

--- Scrolls to the bottom
function ScrollingFrame:ScrollToBottom(doNotAnimate)
	self:ScrollTo(self._model.Max, doNotAnimate)
end

function ScrollingFrame:GetModel()
	return self._model
end

function ScrollingFrame:_updateScrollerSize()
	self._model.TotalContentLength = self.Gui.AbsoluteSize[self._scrollType.Direction]
	self._model.ViewSize = self._container.AbsoluteSize[self._scrollType.Direction]
end

function ScrollingFrame:_updateRender()
	if self._scrollType == SCROLL_TYPE.Vertical then
		self.Gui.Position = UDim2.new(self.Gui.Position.X, UDim.new(0, self._model.BoundedRenderPosition))
	elseif self._scrollType == SCROLL_TYPE.Horizontal then
		self.Gui.Position = UDim2.new(UDim.new(0, self._model.BoundedRenderPosition), self.Gui.Position.Y)
	else
		error("[ScrollingFrame] - Bad ScrollType")
	end

	for _, scrollbar in pairs(self._scrollbars) do
		if scrollbar.Destroy then
			scrollbar:UpdateRender()
		else
			warn("[ScrollingFrame] - Scrollbar is destroyed")
		end
	end
end

function ScrollingFrame:StopDrag()
	local position = self._model.Position

	if self._model:GetDisplacementPastBounds(position) == 0 then
		if self._model.Velocity > 0 then
			self._model.Target = math.max(self._model.Target, position + self._model.Velocity * 0.5)
		else
			self._model.Target = math.min(self._model.Target, position + self._model.Velocity * 0.5)
		end
	end

	self:_freeScroll()
end

--- Scrolls until model is at rest
function ScrollingFrame:_freeScroll(lowPriority)
	if lowPriority and self._maid._updateMaid then
		return
	end

	local maid = Maid.new()

	self:_updateRender()
	maid:GiveTask(RunService.Heartbeat:Connect(function()
		self:_updateRender()
		if self._model.AtRest then
			self._maid._updateMaid = nil
		end
	end))

	self._maid._updateMaid = maid
end

---
-- @param[opt=1] strength
function ScrollingFrame:_getVelocityTracker(strength)
	strength = strength or 1
	self._model.Velocity = 0

	local lastUpdate = tick()
	local lastPos = self._model.Position

	return function()
		local pos = self._model.Position
		local elapsed = tick() - lastUpdate
		local delta = lastPos - pos

		if elapsed == 0 then
			elapsed = 0.03
		end

		self._model.Velocity = self._model.Velocity - (delta / elapsed) * strength
		lastPos = pos
		lastUpdate = tick()
	end
end

function ScrollingFrame:_getInputProcessor(inputBeganObject)
	local startPos = self._model.Position
	local updateVelocity = self:_getVelocityTracker()
	local originalPos = inputBeganObject.Position

	return function(inputObject)
		local distance = (inputObject.Position - originalPos)[self._scrollType.Direction]
		local pos = startPos - distance
		self._model.Position = pos
		self._model.Target = pos

		self:_updateRender()
		updateVelocity()

		return distance
	end
end



--- Binds input to a specific GUI
-- @treturn Maid maid to cleanup inputs
function ScrollingFrame:BindInput(gui, options)
	local maid = Maid.new()

	maid:GiveTask(gui.InputBegan:Connect(function(inputObject)
		self:StartScrolling(inputObject, options)
	end))

	maid:GiveTask(gui.InputChanged:Connect(function(inputObject)
		if gui.Active and inputObject.UserInputType == Enum.UserInputType.MouseWheel then
			self._model.Target = self._model.Target - inputObject.Position.z * 80
			self:_freeScroll()
		end
	end))

	self._maid:GiveTask(maid)
	return maid
end

function ScrollingFrame:StartScrolling(inputBeganObject, options)
	if inputBeganObject.UserInputState ~= Enum.UserInputState.Begin then
		-- Touch events moving into GUIs occur sometimes
		return
	end

	if inputBeganObject.UserInputType == Enum.UserInputType.MouseButton1
		or inputBeganObject.UserInputType == Enum.UserInputType.Touch then

		local maid = Maid.new()

		local startTime = tick()
		local totalScrollDistance = 0
		local processInput = self:_getInputProcessor(inputBeganObject)

		if inputBeganObject.UserInputType == Enum.UserInputType.MouseButton1 then
			maid:GiveTask(UserInputService.InputChanged:Connect(function(inputObject, gameProcessed)
				if inputObject.UserInputType == Enum.UserInputType.MouseMovement then
					totalScrollDistance = totalScrollDistance + math.abs(processInput(inputObject))
				end
			end))
		elseif inputBeganObject.UserInputType == Enum.UserInputType.Touch then
			maid:GiveTask(UserInputService.InputChanged:Connect(function(inputObject, gameProcessed)
				if inputObject.UserInputType == Enum.UserInputType.Touch then
					totalScrollDistance = totalScrollDistance + math.abs(processInput(inputObject))
				end
			end))
		end

		maid:GiveTask(function()
			self:_updateRender()
			if options and options.OnClick then
				local elapsedTime = tick() - startTime
				local consideredClick = (elapsedTime <= 0.05) or (totalScrollDistance < 1)
				if consideredClick then
					options.OnClick(inputBeganObject)
				end
			end
		end)

		maid:GiveTask(UserInputService.InputEnded:Connect(function(inputObject, gameProcessed)
			if inputObject == inputBeganObject then
				self:StopDrag()
			end
		end))

		maid:GiveTask(UserInputService.WindowFocusReleased:Connect(function()
			self:StopDrag()
		end))

		self._maid._updateMaid = maid
	end
end

function ScrollingFrame:StartScrollbarScrolling(scrollbarContainer, inputBeganObject)
	assert(scrollbarContainer)
	assert(inputBeganObject)

	local maid = Maid.new()

	local startPosition = inputBeganObject.Position
	local startPercent = self._model.ContentScrollPercent
	local updateVelocity = self:_getVelocityTracker(0.25)

	maid:GiveTask(UserInputService.InputChanged:Connect(function(inputObject)
		if inputObject.UserInputType == Enum.UserInputType.MouseMovement then
			local direction = self._scrollType.Direction
			local offset = (inputObject.Position - startPosition)[direction]
			local percent = offset / (scrollbarContainer.AbsoluteSize[direction] * (1 - self._model.ContentScrollPercentSize))
			self._model.ContentScrollPercent = startPercent + percent
			self._model.TargetContentScrollPercent = self._model.ContentScrollPercent

			self:_updateRender()
			updateVelocity()
		end
	end))

	maid:GiveTask(UserInputService.InputEnded:Connect(function(inputObject)
		if inputObject == inputBeganObject then
			self:StopDrag()
		end
	end))

	self._maid._updateMaid = maid

	return maid
end

function ScrollingFrame:Destroy()
	self._maid:DoCleaning()
	self._maid = nil
	setmetatable(self, nil)
end

return ScrollingFrame