Implementing dragging in a ui slider

Currently I’m using this tutorial as a reference for implementing ui sliders in a project of mine.

The problem is, I don’t find the code used in the tutorial to be very efficient.
Is there a better way of implementing dragging? As in my current code, I would have to go through a list of conditional statements to check if any of the button down variables are true (therefore held down) and then call a method from a module script from there.

Current code

HeightBar.MouseButton1Down:Connect(function()
	local BarSlider = SliderManager.new(HeightBar:FindFirstChildWhichIsA("Frame"))	-- Construct slider class
	BarSlider:ClickToSetBar()
	SliderHeldDown = true
end)

UserInputService.InputChanged:Connect(function(input, GameProcess)
	if not GameProcess then
		if input.UserInputType == Enum.UserInputType.MouseMovement and SliderHeldDown then	-- Button held down and mouse is moving position
			print("a")  -- Call sliding method here
		end
	end
end)
2 Likes

There are posts that already implemented this and should help, I recommend these and I’m assuming these are more efficient than that implementation you’re following.
(Also next time try searching before posting so it’ll be faster for you to answer next time)

1 Like

Your first link is doing exactly what is being done in the tutorial I had referred to, specifically this part.
image
I would still have to slog through a list of booleans checking if the button is being held down.

Your second link, my eyes just glaze over trying to understand the module code.

I would very much prefer for suggestions on how to implement this rather than direct links to other resources. Thank you for the response anyway.

1 Like

Just trying to help, I certainly never done this before.

I’m going to head off and go through this since I also want to implement this. You can wait for another response or just wait for my implementation.

1 Like

Alright, I’m back from implementing dragging. How I would implement this is by running a loop, preventing it from sliding from the x-axis, above and below the bar, and calculating the percentage by the slider’s y position offset, dividing with bar’s y size’s position offset subtracting the slider y size’s offset.

LocalScript (Note that my method uses offset so If you want it by scale, you need to rescript it)
local maxPercent = 100
local percent = 0

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

local slider = script.Parent
local bar = slider.Parent

local x = {slider.Position.X.Scale, slider.Position.X.Offset}
local y = {slider.Position.Y.Scale, slider.Position.Y.Offset}

run.RenderStepped:Connect(function()
	slider.Position = UDim2.new(x[1], x[2],slider.Position.Y.Scale, slider.Position.Y.Offset)
	-- prevents it from sliding the x axis 

	if slider.Position.Y.Offset >= bar.Size.Y.Offset - slider.Size.Y.Offset then 
		-- only runs if the button is below the bar
		slider.Position = UDim2.new(x[1], x[2] , y[1], y[2])
	elseif slider.Position.Y.Offset <= 0 then 
		-- only runs if the button's position is above the bar
		slider.Position = UDim2.new(x[1], x[2],slider.Position.Y.Scale, 0)
	end

	local value = math.floor(
		(slider.Position.Y.Offset / (bar.Size.Y.Offset - slider.Size.Y.Offset))
			* maxPercent
	)
	
	-- Apparently the value is in reverse so we're reverse it back
	value = maxPercent-value
	percent = value
	
	bar.PercentText.Text = percent
end)

rbxm file if u want to see the preview:
slider.rbxm (7.0 KB)

I think I still prefer my method using OOP. It allows for only one module script which can be required by a single local script which controls the UI sliders, it’s a lot more efficient, readable and cleaner.
I’m still gonna have to slog through a bunch of conditional statements to check if a button is being held down however. :pensive:
https://gyazo.com/ba2e49c4e9fb39f2fd2aff22904ff3b3

local script

HeightBar.MouseButton1Down:Connect(function()	-- Player pressed button
	HeightSlider_Down = true
end)
HeightBar.MouseButton1Up:Connect(function()	-- Player released button
	HeightSlider_Down = false
end)

UserInputService.InputChanged:Connect(function(input, GameProcess)
	if input.UserInputType == Enum.UserInputType.MouseMovement then -- Mouse moving
		
			if HeightSlider_Down then	-- Height slider dragging
				local BarSlider = SliderManager.new(HeightBar:FindFirstChildWhichIsA("Frame"))	-- Construct slider class
				local size = BarSlider:DragBar()	-- Call dragging method
				print(size)
			end
		
		end
end)

Module Class

local UserInputService = game:GetService("UserInputService")

local SliderManager = {}

SliderManager.__index = SliderManager

function SliderManager.new(BarFrame)	-- Construct slider class
	local BarSlider = {}
	setmetatable(BarSlider, SliderManager)
	
	BarSlider.Bar = BarFrame	-- The frame which is the actual sliding bar
	BarSlider.BarButton = BarFrame.Parent -- The text button which the frame is parented to
	return BarSlider
end

function SliderManager:DragBar()	-- Drags slider and returns slider value(s)
	-- Dragging bar visual effect
	local absolutePos = Vector2.new(self.BarButton.AbsolutePosition.X, self.BarButton.AbsolutePosition.Y)
	local mouseLoc = UserInputService:GetMouseLocation()
	local max = self.BarButton.AbsoluteSize.X
	self.Bar.Size = UDim2.new(0, math.clamp(mouseLoc.X - absolutePos.X, 0, max), 1, 0) -- bar size changes position to mouse x position, clamp it to the size of the bar
	
	-- Calculate bar value out of 100
	local MaxSize = Vector2.new(self.BarButton.AbsoluteSize.X, self.BarButton.AbsoluteSize.Y)
	local size = self.Bar.AbsoluteSize
	local value = math.floor(100 * (size.X / MaxSize.X) + 0.5) -- "+0.5" , any value above 0.5 rounds up
	
	return value
end

return SliderManager

You can certainly use ContextActionService to bind and unbind actions whenever you want, it would lead to cleaner code for sure.

local function dragSlider(_, _, input)
	-- this doesn't need any conditionals!
	BarSlider:DragBar(input.Position) -- passing in the mouse location
end

HeightBar.MouseButton1Down:Connect(function()
	ContextActionService:BindAction(
		"DragSlider", -- action name
		dragSlider, -- function to bind
		false, -- whether this should create a mobile button
		Enum.UserInputType.MouseMovement -- inputs to bind to
	)
end)

HeightBar.MouseButton1Up:Connect(function()
	ContextActionService:UnbindAction("DragSlider")
end)

Well it’s just how I would implement, you could’ve try my implementation (excluding the loop) since I find it more efficient, clean, and readable and more simple than that imo.

(If you did try, I’m assuming that didn’t work because I tried it without a loop and with functions and it doesn’t work somehow)

This doesn’t seem to work whenever the mouse is actually over the button gui. I’m assuming this is because of game processed event being true when the mouse is over the button?
https://gyazo.com/afd86da86409e35f5fb4baf31a572d13

Post bumping

That’s because the button’s mouse events only fire when the mouse is hovering over the button, so that’s my mistake.

You can add logic for when to unbind the action in the dragSlider function.

local function isDragging(input)
	return input.UserInputType == Enum.UserInputType.MouseMovement
end

local function isReleasing(input)
	return input.UserInputType == Enum.UserInputType.MouseButton1
		and input.UserInputState == Enum.UserInputState.End
end

local function dragSlider(_, _, input)
	if isDragging(input) then
		-- dragging code
		BarSlider:DragBar(input.Position)
	elseif isReleasing(input) then
		ContextActionService:UnbindAction("DragSlider")
	end
end

HeightBar.MouseButton1Down:Connect(function()
	ContextActionService:BindAction(
		"DragSlider", dragSlider, false,
		Enum.UserInputType.MouseMovement,
		Enum.UserInputType.MouseButton1
	)
end

You could also try binding two separate actions.

local function dragSlider(_, _, input)
	-- dragging code
	BarSlider:DragBar(input.Position)
end

local function releaseSlider(_, state)
	if state == Enum.UserInputState.End then
		ContextActionService:UnbindAction("DragSlider")
		ContextActionService:UnbindAction("ReleaseSlider")
	end
end

HeightBar.MouseButton1Down:Connect(function()
	ContextActionService:BindAction(
		"DragSlider", dragSlider, false,
		Enum.UserInputType.MouseMovement
	)
	
	ContextActionService:BindAction(
		"ReleaseSlider", releaseSlider, false,
		Enum.UserInputType.MouseButton1
	)
end)

Sorry for answering late!

1 Like

No what I mean is dragging won’t work when the mouse is in the button gui
https://gyazo.com/41f1f2552a46e59aa1b8180b942b797f

1 Like

It seems that TextButtons always sink certain inputs even with the .Active property set to false. Two solutions:

  1. With .Active = false, you can connect to the button’s MouseButton1Up event and unbind the action there, and you should be good.
-- the rest of your code

HeightBar.MouseButton1Up:Connect(function()
	ContextActionService:UnbindAction("DragSlider")
end)
  1. Using a TextLabel or a Frame allows the context action to do its job, but you would have to connect to its InputBegan event instead.
HeightBar.InputBegan:Connect(function(input)
	if input.UserInputType == Enum.UserInputType.MouseButton1 then
		ContextActionService:BindAction(
			"DragSlider", dragSlider, false,
			Enum.UserInputType.MouseMovement,
			Enum.UserInputType.MouseButton1
		)
	end
end)

This time it’s tested, so it can’t be wrong :+1: unless it is

1 Like