How to make TextButton work like iPod scroll wheel

How would i make a text button work like a iPod scroll wheel. I’m not good with math. It has 10 points, and functions for “Counterclockwise” and “Clockwise”. it is gonna be used to go up and down a list of items. Since there’s 10 points, if for example, I scroll around the wheel 1 and a half times going clockwise, that would be positive 15 points, and that would fire the "Clockwise" function 15 times, only problem is, I don’t know how to figure this part out. Any help is very appreciated. Thank you

local player = game.Players.LocalPlayer
local mouse = player:GetMouse()
local wheel = script.Parent:WaitForChild("Wheel")
local points = 10

local function Counterclockwise() -- aka go up 1
	print("Up")
end

local function Clockwise() -- aka go down 1
	print("Down")
end

local scrolling = false
local startPoint = nil -- the point (1-10) at which the player begins scrolling on the wheel

wheel.MouseButton1Down:Connect(function()
	scrolling = true
	repeat
		task.wait()
		-- code here
	until scrolling == false
end)

wheel.MouseButton1Up:Connect(function()
	scrolling = false
	startPoint = 0
end)
wheel.MouseLeave:Connect(function()
	scrolling = false
	startPoint = 0
end)

Screen Shot 2024-01-13 at 12.16.03 AM

9 Likes

I assume you mean that you want to be able to press down (Left mouse button) on a TextButton, and move your mouse around in a circle inside the button to emulate scrolling like on the old iPod you showed.

For starters, you can check what the mouse’s position is while pressing down.
And on each frame, you look at how the mouse moves.

So you’d get a direction vector telling you which way the mouse is moving.
Then, you have to check if the direction of mouse movement is tangent to the circle inside your button.

I quick drew this to try to illustrate what I mean.
image

Solving this requires a basic understanding of Vectors. Which isn’t that difficult of a thing to learn. It’s just a group of numbers (though, there is more to learn about it than just that).

Also, you will need to know a bit about math.sin and math.cos.
Also known as “sine” and “cosine”. Both of those are used to get a point on a circle, given an angle.

You will also need to know about a function called “dot product” which can be used to compare the direction of 2 vectors. This is needed for your circular scrolling because you need to compare the movement of your mouse is tangent to the wheel itself. And you can get a vector which is tangent to the circle, and you would use that to compare against your mouse movement vector. To determine if the mouse movement is moving around the circle properly.

I will also give you these things to read as well. As they are important.

2 Likes

You don’t need to use :Cross() or :Dot(), you can just use math.atan2 iirc

2 Likes

Try this

local rs = game:GetService('RunService')

local mouse = game.Players.LocalPlayer:GetMouse()
local btn = script.Parent
local connection

local interval = 10
local currentAngle = 0
local lastAngle
local lastCurrentAngle = 0
local scrollIndex = 0
local lastScrollCount
local intervalDone = false

local center = btn.AbsolutePosition + btn.AbsoluteSize / 2

local inputEnums = {
	MouseButton1 = true,
	Touch = true
}

btn.InputBegan:Connect(function(input)
	if not inputEnums[input.UserInputType.Name] then return end

	local initialAngle = 0
	local lastInterval = 0

	lastAngle = math.atan2(mouse.Y - center.Y, mouse.X - center.X)

	connection = rs.RenderStepped:Connect(function()
		local angle = math.atan2(mouse.Y - center.Y, mouse.X - center.X)
		
		if not angle or not lastAngle then return end
		
		local deltaAngle = angle - lastAngle

		deltaAngle = math.deg(deltaAngle - (deltaAngle > math.pi and 2 or -2) * math.pi)

		currentAngle = (currentAngle + deltaAngle + 360) % 360
		initialAngle = (initialAngle + deltaAngle + 360) % 360

		local currentInterval = math.floor(currentAngle / (360/interval))

		if lastInterval ~= currentInterval then
			if currentAngle == lastCurrentAngle then return end

			local angleDiff = (currentAngle - lastCurrentAngle + 360) % 360
			if angleDiff > 180 then
				scrollIndex -= 1
				print(scrollIndex)
			else
				scrollIndex += 1
				print(scrollIndex)
			end
		end

		lastInterval = currentInterval
		lastAngle = angle
		lastCurrentAngle = currentAngle
	end)
end)

btn.InputEnded:Connect(function(input)
	if not inputEnums[input.UserInputType.Name] then return end

	if connection then
		connection:Disconnect()
		connection = nil
	end
	lastAngle = nil
end)
2 Likes

Basically first of all you get the centerpos of the gui and mouse pos and given:

local GUI = -- path to spinny boi gui
local CenterPosition = GUI.AbsolutePosition+GUI.AbsoluteSize/2
local MousePosition = UserInputService:GetMouseLocation()
local function GetRotation()
	local difference = CenterPosition-MousePosition
	return math.deg(math.atan2(difference.Y,difference.X))
end
local function CalculateRotationDirection()
	local difference = GUI.Rotation-GetRotation()
	if difference > 0 then
		return "Clockwise"
	else
		return "Anti-Clockwise"
	end
end

Then you find the rotation of the gui to the mouse position and then you need to find the direction of motion which I gave above. Now you can accordingly do your thing. That being I suppose:

local GUI = -- path to spinny boi gui
local CenterPosition = GUI.AbsolutePosition+GUI.AbsoluteSize/2
local MousePosition = UserInputService:GetMouseLocation()
local function GetRotation()
	local difference = CenterPosition-MousePosition
	return math.deg(math.atan2(difference.Y,difference.X))
end
local function CalculateRotationDirection()
	local difference = GUI.Rotation-GetRotation()
	return difference > 0 and -1 or 1
end
local value = 0
local function SpinGui()
	local direction = CalculateRotationDirection()
	GUI.Rotation = GetRotation()
	value += direction
end

GUI.MouseButton1Down:Connect(function()
	-- bla bla bla scrolling thingy
end)
1 Like

He didn’t ask for how to rotate the button, he asked how to make a scrolling function similar to the iPod’s touch wheel (e.g clockwise +1 counter clockwise -1) when you move your mouse around it.

1 Like

Well then simply not rotate the gui?

local rotation = 0
local function CalculateRotationDirection()
	local difference = rotation-GetRotation()
	return difference > 0 and -1 or 1
end
local value = 0
local function SpinGui()
	local direction = CalculateRotationDirection()
	rotation = GetRotation()
	value += direction
end
1 Like

He’s asking for something that isn’t even in your solution. If you see his attempt he’s got variables of the startpoint and scrolling index including lines that say local function Counterclockwise() -- aka go up 1.

I uhh am confused. What do you mean?

You know how the iPod’s scroll wheel works right, you move your finger clockwise around the wheel, you scroll down and vice versa. He’s asking how to make something like this with a TextButton where if you move your mouse clockwise and counterclockwise around the gui it scrolls. He’s already started with a scroll index variable which he wants to increase or decrease based of the clockwise/counter-clockwise.

The gui shouldn’t rotate since it’s a ipod wheel (right?).

Ohhhh… : /. I suppose then:

local wheel = script.Parent.Wheel
local CenterPosition = GUI.AbsolutePosition+GUI.AbsoluteSize/2
local UIS = game:GetService("UserInputService")

local function GetRotation(Pos1,Pos2)
	local MousePosition = UIS:GetMouseLocation()
	local difference = Pos1-Pos2
	return math.deg(math.atan2(difference.Y,difference.X))
end
local function CalculateRotationDirection(StartPosition)
	local difference = GetRotation(CenterPosition,StartPosition)-GetRotation(CenterPosition,MousePosition)
	return difference > 0 and -1 or 1
end

local function Counterclockwise() -- aka go up 1
	print("Up")
end

local function Clockwise() -- aka go down 1
	print("Down")
end

local scrolling = false
local startPoint = nil

wheel.MouseButton1Down:Connect(function()
	scrolling = true
	startpoint = UIS:GetLocation()
	repeat
		task.wait()
		if CalculateRotationDirection(startpoint) > 1 then
			Counterclockwise()
		else
			Clockwise()
		end
	until scrolling == false
end)

wheel.MouseButton1Up:Connect(function()
	scrolling = false
	startPoint = 0
end)
wheel.MouseLeave:Connect(function()
	scrolling = false
	startPoint = 0
end)

I wrote this in the forum so pardon me if any mistakes are made : p.

1 Like

okay thank you it works really well except when i scroll past the top of the scroll wheel it subtracts by 1 when im scrolling clockwise and vice versa. do you know how to fix this?

I tried fixing it but no matter what I do it doesn’t help. I believe it’s a floating point error and you can’t really avoid that in this case since you have to divide regardless of rounding or not.

This is actually a fun project so I’ll see if I can fix it later.

1 Like

thanks so much for looking into it. it is good enough for now how it is, i’ll look into it a bit more as well

What I would do for now is remove the math.floor from the currentInterval that way it’s the raw numbers then print out the currentInterval inside the if currentAngle ~= lastCurrentAngle scope. See what the 9th number prints out.

There are two things I believe could be the problem, either:

  • floating point decimals
  • initialAngle fluctuating due to the % operator used beforehand.
1 Like

alright sorry for the late reply, i was away from my laptop so i couldn’t test it. this is what the currentInterval prints when it messes up:

Is this the 9th? I’m assuming these are negatives as they go down. do a few more tests see if the results are consistent. It’s strange how this same pattern happens EXACTLY for the first cycle regardless you’re going clockwise or counter clockwise. I noticed how close the 9th one is to 10, 9.98 is a really close number

ive noticed it usually triggers the opposite, in this case, counterclockwise (up), right when i scroll around the wheel once and scroll past where i started, so once ive gone around once
it prints out different every time

in this pic, ive gone around the wheel once clockwise and right as i go past where i started, the highlighted area, it prints “up”, aka counterclockwise

Yeah, the pattern only happens on the first cycle but then after that it’s more of random. Try rounding out the initialAngle before you do any of the currentinterval calcs

1 Like

okay i changed that line to this

initialAngle = math.floor((initialAngle + deltaAngle + 360) % 360)

it still prints up