Simple Module For Creating Draggable GUI Elements

you have to download the fixed code by @homermafia1:

That partially fixes it, but there’s still an issue. Here’s a video.

Fixed it by adding a currentlyDragging variable.
DraggableObject.lua (3.1 KB)

1 Like

It’s because you’re disconnecting the draggable object before you’re enabling it.
self.InputBegan and the other variables haven’t been assigned a connection yet!

How would you make it so that when something is reparented (which then causes the position to be displaced), it doesn’t become offset from the mouse?
This is what’s happening with me:

This really solves the problem . Please let’s update marketplace model with this bugfix cause market model is outdated.

1 Like

I’m having an issue where it seems like DragEnded doesn’t fire when I let go of the mouse. For some reason that also means that the next segment of code doesn’t fire and the UI doesn’t return to its original position.

local function inventorySlotSelected(index, slotFrame, dragObject)
	local item = inventory.items[index]
	
	-- Check if double click
	if tick() - lastClickTick <= 0.35 then
		dragObject:Disable()
		if item then
			if character:FindFirstChild(item.Name) then
				InventoryController:UnequipTools()
				InventoryController:ToggleUI(false)
			else
				InventoryController:EquipTool(item)
				InventoryController:ToggleUI(false)
			end
		else
			InventoryController:UnequipTools()
			InventoryController:ToggleUI(false)
		end
	else
		dragObject.DragEnded = function()
			print("Drag Ended")
			if not collidesWith(slotFrame.TextButton, INVENTORY_UI:FindFirstChild("Slots")) then
				-- DROP
				print("Drop")
				return
			else
				-- Check If Collides With Other
				print("Swap")
				return
			end
		end
	end
	dragObject:Disable()
	lastClickTick = tick()
	slotFrame.TextButton.Position = UDim2.new(0,0,0,0)
	print("Move Back")
	dragObject:Enable()
end

Can you share the code please? I can’t access the place.

--[[
	@Author: Spynaz
	@Description: Enables dragging on GuiObjects. Supports both mouse and touch.
	
	For instructions on how to use this module, go to this link:
	https://devforum.roblox.com/t/simple-module-for-creating-draggable-gui-elements/230678
--]]

local UDim2_new = UDim2.new

local UserInputService = game:GetService("UserInputService")

local currentlyDragging = false

local DraggableObject 		= {}
DraggableObject.__index 	= DraggableObject

-- Sets up a new draggable object
function DraggableObject.new(Object)
	local self 			= {}
	self.Object			= Object
	self.DragStarted	= nil
	self.DragEnded		= nil
	self.Dragged		= nil
	self.Dragging		= false
	
	setmetatable(self, DraggableObject)
	
	return self
end

-- Enables dragging
function DraggableObject:Enable()
	local object			= self.Object
	local dragInput			= nil
	local dragStart			= nil
	local startPos			= nil
	local preparingToDrag	= false
	
	-- Updates the element
	local function update(input)
		local delta 		= input.Position - dragStart
		local newPosition	= UDim2_new(startPos.X.Scale, startPos.X.Offset + delta.X, startPos.Y.Scale, startPos.Y.Offset + delta.Y)
		object.Position 	= newPosition
	
		return newPosition
	end
	
	self.InputBegan = object.InputBegan:Connect(function(input)
		if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch and not currentlyDragging then
			currentlyDragging = true
			preparingToDrag = true
			--[[if self.DragStarted then
				self.DragStarted()
			end
			
			dragging	 	= true
			dragStart 		= input.Position
			startPos 		= Element.Position
			--]]
			
			local connection 
			connection = input.Changed:Connect(function()
				if input.UserInputState == Enum.UserInputState.End and (self.Dragging or preparingToDrag) then
					self.Dragging = false
					connection:Disconnect()
					
					if self.DragEnded and not preparingToDrag then
						self.DragEnded()
					end
					
					currentlyDragging = false
					preparingToDrag = false
				end
			end)
		end
	end)
	
	self.InputChanged = object.InputChanged:Connect(function(input)
		if input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch then
			dragInput = input
		end
	end)
	
	self.InputChanged2 = UserInputService.InputChanged:Connect(function(input)
		if object.Parent == nil then
			self:Disable()
			return
		end
		
		if preparingToDrag then
			preparingToDrag = false
			
			if self.DragStarted then
				self.DragStarted()
			end
			
			self.Dragging	= true
			dragStart 		= input.Position
			startPos 		= object.Position
		end
		
		if input == dragInput and self.Dragging then
			local newPosition = update(input)
			
			if self.Dragged then
				self.Dragged(newPosition)
			end
		end
	end)
end

-- Disables dragging
function DraggableObject:Disable()
	self.InputBegan:Disconnect()
	self.InputChanged:Disconnect()
	self.InputChanged2:Disconnect()
	
	if self.Dragging then
		self.Dragging = false
		
		if self.DragEnded then
			self.DragEnded()
		end
	end
end

return DraggableObject
1 Like

I know this is somewhat old, but this is probably the most simple and reliable dragging module out there.

Nice and easy to set up, very lightweight, has function based events which is far more reliable than most signal modules I can find, and overall, a very nice little module.

2 Likes

I know this is probably old by now but how would I take into account rotation while its being dragged? Currently when rotated whilst dragging the position is way off to the mouse (which is understandable since yk its dragging)

But how do i fix this?

btw this is my implementation of turning the position from offset → scale
for any one of you wondering

local function toScale(pos:UDim2,ancestor:GuiObject)
	local scaleXPos = pos.X.Offset/ancestor.AbsoluteSize.X + pos.X.Scale
	local scaleYPos = pos.Y.Offset/ancestor.AbsoluteSize.Y  + pos.Y.Scale
	return UDim2.fromScale(scaleXPos,scaleYPos)
end

local function update(input)
		local delta 		= input.Position - dragStart
		
		local newPosition	= UDim2_new(startPos.X.Scale, startPos.X.Offset + delta.X, startPos.Y.Scale, startPos.Y.Offset + delta.Y)
		newPosition         = toScale(newPosition,object:FindFirstAncestorOfClass("Frame"))
		
		object.Position 	= newPosition
		
		return newPosition
	end

I wanted to add a draggable option to my recently made UI (they’re very poorly made) and had no idea what I was getting myself into.

Thanks to your resource, I was able to easily implement UI drag feature into my game fairly easily.

One thing that I even appreciate, is the fact that the UI do not reset position when the player close/re open.

Altho it could be considered as bad practice (mostly on my end for not reseting it properly) I do see it as a nice touch of personal flavor for the user’s end.

I would convert it directly to scale and not first to offset thats how I did it:

	local function update(input)
		local delta 		= input.Position - dragStart
		local scaleX, scaleY 	= startPos.X.Scale + (delta.X / object.Parent.AbsoluteSize.X), startPos.Y.Scale + (delta.Y / object.Parent.AbsoluteSize.Y)
		local newPosition	= UDim2_new(scaleX, 0, scaleY, 0)
		object.Position 	= newPosition
	
		return newPosition
	end
	

Is this still working for others? I’m getting an error: " ReplicatedStorage.Modules.DraggableObject: attempt to index nil with ‘Object’" when doing :Enable()

Edit: Nevermind. Using Mercutio’s (Simple Module For Creating Draggable GUI Elements - #47 by CipherFunctions) worked.

Edit2: New Issue: If you drag two GUI’s on top of each other, then drag it again, the two become glued together, even if the Z-Index’s are different.

Edit3: Temporary Solution: GetGuiObjectsAtPosition at InputBegan and :Disable() all Draggables at that position except for the maximum Z-Index then reenable at .DragEnded

Edit4: Ran with my GetGuiObjectsAtPosition idea and wrote my own version for my use :slight_smile: so bye forum page.

Edit5: If you wanna replicate but wanna avoid using a billion connects and disconnects using this module, just rewrite the module as a single localscript and filter the guis you want by some property, name, or a draggable bool value. Then use GetGuiObjectsAtPosition on InputBegan to find the top Z-Index at the position. Everything else is essentially the same, just written as one script instead of module form. So no self’s. Just don’t forget to disconnect your stuffs.

There is going to be a drag detector for ui in a bit just letting y’all know but this is still a great module