local W=script.Parent.Parent local U=require(W:WaitForChild("\076\097\122\121\076\111\097\100\101\114"))local V=W:WaitForChild("\068\101\102\097\117\108\116\115")local H=game:GetService("\085\115\101\114\073\110\112\117\116\083\101\114\118\105\099\101")local M=game:GetService("\082\117\110\083\101\114\118\105\099\101")local x=U.Classes.Children.RadialMenu.CONSTANTS local S=math.pi local v=x.TAU local j=x.EX_OFFSET local c={[Enum.KeyCode.ButtonA]=true}local d={[Enum.UserInputType.MouseButton1]=true,[Enum.UserInputType.MouseMovement]=true,[Enum.UserInputType.Touch]=true}local G={[Enum.UserInputType.Gamepad1]=true,[Enum.UserInputType.Gamepad2]=true;[Enum.UserInputType.Gamepad3]=true;[Enum.UserInputType.Gamepad4]=true;[Enum.UserInputType.Gamepad5]=true,[Enum.UserInputType.Gamepad6]=true;[Enum.UserInputType.Gamepad7]=true;[Enum.UserInputType.Gamepad8]=true}local a=U.Classes.Children.RadialMenu.CreateRadial local s={}s.__index=s s.__type="\082\097\100\105\097\108\077\101\110\117"function s.__tostring(W)return s.__type end function s.new(W,V,H)local M=setmetatable({},s)M._Maid=U.Utilities.Maid.new()M._ClickedBind=Instance.new("\066\105\110\100\097\098\108\101\069\118\101\110\116")M._HoverBind=Instance.new("\066\105\110\100\097\098\108\101\069\118\101\110\116")M._LastHoverIndex=nil M.Frame=a(W,V,H)M.Rotation=H M.SubN=W M.Enabled=true M.DeadZoneIn=.5 M.DeadZoneOut=math.huge M.Clicked=M._ClickedBind.Event M.Hover=M._HoverBind.Event init(M)M:SetDialProps({ImageColor3=Color3.new(0,0,0)})M:SetRadialProps({ImageColor3=Color3.new(0,0,0),ImageTransparency=.7})return M end local function b(W,U)local V=(U-W)%v local H=S-math.abs(math.abs(V)-S)if(V+v)%v<S then return H else return-H end end function init(W)local U=W.SubN local V=W.Frame.RadialDial local x=Enum.UserInputType.MouseMovement W._Maid:Mark(W._ClickedBind)W._Maid:Mark(W._HoverBind)W._Maid:Mark(H.LastInputTypeChanged:Connect(function(W)if d[W]or G[W]then x=W end end))local S=0 W._Maid:Mark(H.InputBegan:Connect(function(U)if not W.Enabled then return end if G[U.UserInputType]then if not c[U.KeyCode]then return else W._ClickedBind:Fire(W:PickIndex(S))end end local V=W:GetTheta(U.UserInputType)if V then W._ClickedBind:Fire(W:PickIndex(V))end end))W._Maid:Mark(M.RenderStepped:Connect(function(U)if not W.Enabled then return end local H=W:GetTheta(x)if H and W:IsVisible()then S=H local U=math.rad(W.Frame.Rotation)local M=math.deg((((H-W.Rotation)+U)+j)+2*v)%360 local x=M/(360/W.SubN)+.5 V.Rotation=math.deg(W:GetRotation(x))local c=W:PickIndex(H)if c~=W._LastHoverIndex then W._HoverBind:Fire(W._LastHoverIndex,c)W._LastHoverIndex=c end end end))end function s.SetRadialProps(U,W)for U,V in next,U.Frame.Radial:GetChildren()do for W,U in next,W do V[W]=U end end end function s.SetDialProps(U,W)local V=U.Frame.RadialDial for W,U in next,W do V[W]=U end end function s.GetTheta(U,W)local V=nil if d[W]then local W=U.Frame local M=W.AbsoluteSize.y/2 local x=W.AbsolutePosition+W.AbsoluteSize/2 local S=H:GetMouseLocation()+Vector2.new(0,-36)V=(S-x)/M elseif G[W]then local U=H:GetGamepadState(W)for W,V in next,U do U[V.KeyCode]=V end V=U[Enum.KeyCode.Thumbstick2].Position*Vector3.new(1,-1,1)end if V then local W=V.Magnitude if W>=U.DeadZoneIn and W<=U.DeadZoneOut then return math.atan2(V.y,-V.x)end end end function s.PickIndex(U,W)local V=math.rad(U.Frame.Rotation)local H=math.deg((((W-U.Rotation)+V)+j)+2*v)%360 local M=math.floor(H/(360/U.SubN))return M+1 end function s.GetRotation(U,W)return-v*((W-1)/U.SubN)end function s.GetRadial(U,W)return U.Frame.Radial[W]end function s.GetAttachment(U,W)return U.Frame.Attach[W]end function s.IsVisible(W)local U=W.Frame while U and(U:IsA("\071\117\105\079\098\106\101\099\116")and U.Visible)do U=U.Parent if U and(U:IsA("\083\099\114\101\101\110\071\117\105")and U.Enabled)then return true end end return false end function s.Destroy(W)W._Maid:Sweep()W.Frame:Destroy()W.Clicked=nil W.Hover=nil W.Frame=nil end return s
--[[
Classes.RadialMenu

This class creates a radial menu. It is by far the most "raw" module in this library as so much of how you interact with it is developer defined.

Constructors:
	new(subN [integer], tPercent [float], rotation [float])
		> Creates a radial menu divided into subN sections where the ring is tPercent width of the frame radius
		> and the ring is rotationally offset by rotation radians

Properties:
	Frame [instance]
		> The container frame for the radial menu. Can be used for positioning and resizing.
		> Note that this frame should always be square, by default it's set to YY size constraining
	Rotation [float]
 		> The rotation offset that the developer entered as an argument when creating the radial menu.
	SubN [integer]
		> The number of subsections that the developer entered as an argument when creating the radial menu.
	Enabled [boolean]
		> Whether or not the radial menu is actively tracking input.
		> Defaults to true
	DeadZoneIn [float]
		> Number represents a percentage from the radius that will be ignored in regards to input
		> By default this is 0.5 meaning the center 50% of the radial frame ignores input
	DeadZoneOut [float]
		> Number represents a percentage from the radius that once passed will be ignored in regards to input.
		> By default this is math.huge meaning that as long as your outside of DeadZoneIn your input will not be ignored.

Methods:
	:SetRadialProps(props [dictionary]) [void]
		> Sets the properties of all the radial background UI
	:SetDialProps(props [dictionary]) [void]
		> Sets the properties of the radial dial UI
	:GetTheta(userInputType [Enum.UserInputType]) [float]
		> Depending on if MouseMovement, Touch, or a Gamepad returns the directional angle that the user is inputting on their device
		> If input is not in deadzone range then this method returns nil
		> Returns the angle in radians
	:PickIndex(theta) [integer]
		> Given a directional angle returns the closest element on the radial wheel as an index.
	:GetRadial(index) [instance]
		> Returns the radial background UI for that index.
	:GetAttachment(index) [instance]
		> Returns the radial attachment UI for that index.
		> This frame is useful for putting text or images in.
	:IsVisible() [boolean]
		> Returns whether or not the radial menu is visible to the user or not.
	:Destroy() [void]
		> Destroys the RadioButtonGroup and all the events, etc that were running it.

Events:
	.Clicked:Connect(function(index [integer])
		> Fired when the user selects an element on the radial menu.
	.Hover:Connect(function(oldIndex [integer], newIndex [integer])
		> Fired when the user hovers in the direction of a new element on the radial menu
--]]


-- CONSTANTS

local GuiLib = script.Parent.Parent
local Lazy = require(GuiLib:WaitForChild("LazyLoader"))
local Defaults = GuiLib:WaitForChild("Defaults")

local UIS = game:GetService("UserInputService")
local RUNSERVICE = game:GetService("RunService")
local CONSTANTS = Lazy.Classes.Children.RadialMenu.CONSTANTS

local PI = math.pi
local TAU = CONSTANTS.TAU
local EX_OFFSET = CONSTANTS.EX_OFFSET

local GAMEPAD_CLICK = {
	[Enum.KeyCode.ButtonA] = true
}

local MOUSE_GROUP = {
	[Enum.UserInputType.MouseButton1] = true,
	[Enum.UserInputType.MouseMovement] = true,
	[Enum.UserInputType.Touch] = true
}

local GAMEPAD_GROUP = {
	[Enum.UserInputType.Gamepad1] = true,
	[Enum.UserInputType.Gamepad2] = true,
	[Enum.UserInputType.Gamepad3] = true,
	[Enum.UserInputType.Gamepad4] = true,
	[Enum.UserInputType.Gamepad5] = true,
	[Enum.UserInputType.Gamepad6] = true,
	[Enum.UserInputType.Gamepad7] = true,
	[Enum.UserInputType.Gamepad8] = true
}

local CreateRadial = Lazy.Classes.Children.RadialMenu.CreateRadial

-- Class

local RadialMenuClass = {}
RadialMenuClass.__index = RadialMenuClass
RadialMenuClass.__type = "RadialMenu"

function RadialMenuClass:__tostring()
	return RadialMenuClass.__type
end

-- Public Constructors

function RadialMenuClass.new(subN, tPercent, rotation)
	local self = setmetatable({}, RadialMenuClass)
	
	self._Maid = Lazy.Utilities.Maid.new()
	self._ClickedBind = Instance.new("BindableEvent")
	self._HoverBind = Instance.new("BindableEvent")
	self._LastHoverIndex = nil
	
	self.Frame = CreateRadial(subN, tPercent, rotation)
	self.Rotation = rotation
	self.SubN = subN
	
	self.Enabled = true
	self.DeadZoneIn = 0.5
	self.DeadZoneOut = math.huge
	
	self.Clicked = self._ClickedBind.Event
	self.Hover = self._HoverBind.Event
	
	init(self)
	
	self:SetDialProps{ImageColor3 = Color3.new(0, 0, 0)}
	self:SetRadialProps{
		ImageColor3 = Color3.new(0, 0, 0),
		ImageTransparency = 0.7
	}
	
	return self
end

-- Private Methods
	
local function shortestDist(start, stop)
	local modDiff = (stop - start) % TAU
	local sDist = PI - math.abs(math.abs(modDiff) - PI)
	if ((modDiff + TAU) % TAU < PI) then
		return sDist
	else
		return -sDist
	end
end

function init(self)
	local subN = self.SubN
	local dial = self.Frame.RadialDial
	
	local inputType = Enum.UserInputType.MouseMovement
	
	self._Maid:Mark(self._ClickedBind)
	self._Maid:Mark(self._HoverBind)
	
	self._Maid:Mark(UIS.LastInputTypeChanged:Connect(function(iType)
		if (MOUSE_GROUP[iType] or GAMEPAD_GROUP[iType]) then
			inputType = iType
		end
	end))
	
	local lTheta = 0
	
	self._Maid:Mark(UIS.InputBegan:Connect(function(input)
		if (not self.Enabled) then
			return
		end
		
		
		if (GAMEPAD_GROUP[input.UserInputType]) then
			if (not GAMEPAD_CLICK[input.KeyCode]) then
				return
			else
				self._ClickedBind:Fire(self:PickIndex(lTheta))
			end
		end
		
		local theta = self:GetTheta(input.UserInputType)
		if (theta) then
			self._ClickedBind:Fire(self:PickIndex(theta))
		end
	end))

	self._Maid:Mark(RUNSERVICE.RenderStepped:Connect(function(dt)
		if (not self.Enabled) then
			return
		end
		
		local theta = self:GetTheta(inputType)
		if (theta and self:IsVisible()) then
			lTheta = theta
			
			local frameRot = math.rad(self.Frame.Rotation)
			local toDeg = math.deg(theta - self.Rotation + frameRot + EX_OFFSET + 2*TAU) % 360
			local closest = toDeg / (360 / self.SubN) + 0.5
			
			dial.Rotation = math.deg(self:GetRotation(closest))
			
			local index = self:PickIndex(theta)
			if (index ~= self._LastHoverIndex) then
				self._HoverBind:Fire(self._LastHoverIndex, index)
				self._LastHoverIndex = index
			end
		end
	end))
end

-- Public Methods

function RadialMenuClass:SetRadialProps(props)
	for _, child in next, self.Frame.Radial:GetChildren() do
		for prop, value in next, props do
			child[prop] = value
		end
	end
end

function RadialMenuClass:SetDialProps(props)
	local dial = self.Frame.RadialDial
	for prop, value in next, props do
		dial[prop] = value
	end
end

function RadialMenuClass:GetTheta(userInputType)
	local delta = nil
	
	if (MOUSE_GROUP[userInputType]) then
		local frame = self.Frame
		local radius = frame.AbsoluteSize.y/2
		local center = frame.AbsolutePosition + frame.AbsoluteSize/2
		local mousePos = UIS:GetMouseLocation() + Vector2.new(0, -36)
		delta = (mousePos - center) / radius
	elseif (GAMEPAD_GROUP[userInputType]) then
		local states = UIS:GetGamepadState(userInputType)
		for _, state in next, states do
			states[state.KeyCode] = state
		end
		delta = states[Enum.KeyCode.Thumbstick2].Position * Vector3.new(1, -1, 1)
	end
	
	if (delta) then
		local m = delta.Magnitude
		if (m >= self.DeadZoneIn and m <= self.DeadZoneOut) then
			return math.atan2(delta.y, -delta.x)
		end
	end
end

function RadialMenuClass:PickIndex(theta)
	local frameRot = math.rad(self.Frame.Rotation)
	local toDeg = math.deg(theta - self.Rotation + frameRot + EX_OFFSET + 2*TAU) % 360
	local closest = math.floor(toDeg / (360 / self.SubN))
	return closest + 1
end

function RadialMenuClass:GetRotation(index)
	return -TAU * ((index - 1) / self.SubN)
end

function RadialMenuClass:GetRadial(index)
	return self.Frame.Radial[index]
end

function RadialMenuClass:GetAttachment(index)
	return self.Frame.Attach[index]
end

function RadialMenuClass:IsVisible()
	local frame = self.Frame
	while (frame and frame:IsA("GuiObject") and frame.Visible) do
		frame = frame.Parent
		if (frame and frame:IsA("ScreenGui") and frame.Enabled) then
			return true
		end
	end
	return false
end

function RadialMenuClass:Destroy()
	self._Maid:Sweep()
	self.Frame:Destroy()
	self.Clicked = nil
	self.Hover = nil
	self.Frame = nil
end

--

return RadialMenuClass