Draggable UI disappearing

I’m trying to create a draggable UI element (an icon) to be able to move it around my inventory, however, clicking on it immediately makes it ‘disappear’

Checking the position of it, it is moving as the mouse moves, but since it’s parented inside multiple frames, it’s being positioned relative to the frame and not a global position.

for _, v in pairs(Frame:GetDescendants()) do
	if v.Name == 'Icon' then		
		v.Parent.MouseButton1Down:Connect(function()
			MouseDown = true
			spawn(function()
				repeat
					v.Position = UDim2.new(0, mouse.X, 0, mouse.Y)
					wait()
				until MouseDown == false
			end)
		end)
		
		v.Parent.MouseButton1Up:Connect(function()
			MouseDown = false
		end)
	end
end

So how can I easily work around this? I don’t want to parent it to something else, etc.
robloxapp-20191227-1941441

2 Likes
  1. Make sure the parent of your inventory isn’t ClipDescendants
  2. You need to account for the parent’s position of the GUI Obj:
--[!] The following uses UserInputService:

--> Save the initial start position of both the input and the obj's position:
local startPos = Gui_Obj.Position
local initialPositionOfInput = input.Position

--> Now, everytime the mouse/touched event is changed:
local delta = input.Position - initialPositionOfInput

Gui_Object.Position = UDim2.new(startPos.X.Scale, startPos.X.Offset + delta.x, startPos.Y.Scale, startPos.Y.Offset + delta.y)
1 Like
for _, v in pairs(Frame:GetDescendants()) do
	if v.Name == 'Icon' then		
		local startPos = v.Position
		
		UserInputService.InputBegan:Connect(function(input)
			if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
				local initialPositionOfInput = input.Position
			
				local delta = input.Position - initialPositionOfInput

				v.Position = UDim2.new(startPos.X.Scale, startPos.X.Offset + delta.x, startPos.Y.Scale, startPos.Y.Offset + delta.y)
			end	
		end)

	end
end

Does nothing

That’s because you’ve not got a changed event:

local Dragger = {
	dragging = false;
	dragObj  = nil;
	initPos  = nil;
	startPos = nil;
}

for _, v in next, Frame:GetDescendants() do
	if v.Name == "Icon" then
		v.InputBegan:connect(function (input)
			if Dragger.dragging then
				return
			end
			if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
				Dragger.dragging = true
				Dragger.startPos = v.Position
				Dragger.initPos  = input.Position
				Dragger.dragObj  = v

				local con; con = input.Changed:Connect(function()
					if input.UserInputState == Enum.UserInputState.End then
						Dragger.dragging = false
						
						--> Do stuff
						
						return con:disconnect()
					end
				end)
			end
		end)
	end
end

UserInputService.InputChanged:Connect(function(input)
	if Dragger.dragging and Dragger.dragObj then
		local delta = input.Position - Dragger.initPos

		Dragger.dragObj.Position = UDim2.new(Dragger.startPos.X.Scale, Dragger.startPos.X.Offset + delta.x, Dragger.startPos.Y.Scale, Dragger.startPos.Y.Offset + delta.y)
	end
end)

NOT TESTED

1 Like

That seemed to work :smiley: Can get help with the final bit too. I tried using MouseEnter and MouseLeave to detect whether or not the player has actually moved the item to a new box.

for _, v in next, Frame:GetDescendants() do
	if v.Name == 'Holder' then
		v.MouseEnter:Connect(function()
			CurrentItemSelected = v
			print('Enter')
		end)
		
		v.MouseLeave:Connect(function()
			CurrentItemSelected = nil
			print('Leaving')
		end)
		
		v.InputBegan:Connect(function(input)
			if Dragger.Dragging then return end
			
			if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
				if v.Item.Value == '' then print('No item') return end
				
				Dragger.Dragging = true
				Dragger.StartPos = v.Position
				Dragger.InitPos  = input.Position
				Dragger.DragObj  = v

				local Connection; Connection = input.Changed:Connect(function()
					if input.UserInputState == Enum.UserInputState.End then
						Dragger.Dragging = false
						
						if CurrentItemSelected then
							print('Placing in new spot')
						else
							print('No')
						end
						
						return Connection:Disconnect()
					end
				end)
			end
		end)
	end
end

So my logic was, check if the mouse was currently inside another ‘box’, and then go from there, but it seems that MouseLeave does not fire if you click inside the frame, and leave while holding the mouse button down. Is there an easier way to go about this problem?

Good logic, but won’t work because MouseEnter/MouseLeave doesn’t work as intended. You would have to do this:


local Dragger = {
	dragging = false;
	dragObj  = nil;
	initPos  = nil;
	startPos = nil;
}

for _, v in next, Frame:GetDescendants() do
	if v.Name == "Icon" then
		v.InputBegan:connect(function (input)
			if Dragger.dragging then
				return
			end
			if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
				Dragger.dragging = true
				Dragger.startPos = v.Position
				Dragger.initPos  = input.Position
				Dragger.dragObj  = v

				local con; con = input.Changed:Connect(function()
					if input.UserInputState == Enum.UserInputState.End then
						Dragger.dragging = false
						
						local objs = Player.PlayerGui:GetGuiObjectsAtPosition(input.Position.x, input.Position.y)
						for _, obj in next, objs do
							if obj.Name == "WeaponSlot" then
								--> e.g. Equip to weapon
								
							elseif obj.Name == "SelectButton" then
								--> Do some logic here
							end
						end
						
						return con:disconnect()
					end
				end)
			end
		end)
	end
end

UserInputService.InputChanged:Connect(function(input)
	if Dragger.dragging and Dragger.dragObj then
		local delta = input.Position - Dragger.initPos

		Dragger.dragObj.Position = UDim2.new(Dragger.startPos.X.Scale, Dragger.startPos.X.Offset + delta.x, Dragger.startPos.Y.Scale, Dragger.startPos.Y.Offset + delta.y)
	end
end)

Can i ask what youmeant with this line?
obj.Name == “SelectButton”

The method ‘GetGuiObjectsAtPosition’ returns an array of all GUI objects whose bounding box has intersected with the Input position. So ‘obj’ in this context is a GUIObject that has intersected with my mouse. So for each object, I check whether it matches some criteria (it doesn’t have to be name, I just thought it was the easiest illustration), if it does then you can perform some action i.e. as you said, checking whether the item has been dragged to a new box.

Ahh ok :+1: I’m getting so close I can taste it :sweat_smile:

robloxapp-20191227-2043519

local Connection; Connection = input.Changed:Connect(function()
if input.UserInputState == Enum.UserInputState.End then
	Dragger.Dragging = false
						
		local Elements = Player.PlayerGui:GetGuiObjectsAtPosition(input.Position.x, input.Position.y)
		for _, frame in next, Elements do
			if frame ~= v then -- ignore the current item you are dragging
				if frame.Name == 'Holder' then -- make sure you are dropping in another slot
						local OriginalFrame = v.Parent
					v.Parent = frame.Parent -- swap positions with the new slot
					frame.Parent = OriginalFrame
				end
			end
		end
			
		return Connection:Disconnect()
	end
end)

For some reason, it drops the first object into a different frame, and then every object after that doesn’t get put in correct folders. guessing it has something to do with me looping through multiple things at once? I might be confusing myself and using wrong values etc.

2 Likes

From your gif it looks like you’re parenting it to the wrong object. Do you have multiple objects with the name ‘Holder’? Is it necessary that you swap the entire frame, can you not just swap the icon images? It’s hard to give you an answer to this one without knowing your architecture and the code that’s reliant on it.

Also, don’t forget you need to reset the position of that object, which may be another cause e.g.:

		local Elements = Player.PlayerGui:GetGuiObjectsAtPosition(input.Position.x, input.Position.y)
		for _, frame in next, Elements do
			if frame ~= v then -- ignore the current item you are dragging
				if frame.Name == 'Holder' then -- make sure you are dropping in another slot
					local OriginalFrame = v.Parent
					v.Parent = frame.Parent -- swap positions with the new slot
					frame.Parent = OriginalFrame
					
					Dragger.dragObj.Position = Dragger.startPos
					break
				end
			end
		end
		Dragger.Dragging = false
		Dragger.dragObj = nil
1 Like

I’ll try to show as much details as possible. Basically, I have the 5 slots, each slot has a ‘Holder’ frame


The Holder contains the key kinda points for each item

And my idea was when you click to move/drag an item, it grabs the ‘Holder’ and moves the holder around. Then when you stop dragging, I was gonna check to.

  1. See if the mouse is over another Holder (new position), if not, then return the Holder back to its original spot, as the player has not moved the item correctly.

  2. Put the dragged Holder into the new ‘Slot’ and move the slots original holder to Slot you dragged the previous holder from.

Probs makes no sense, so maybe a bit more detail.

Basically, if I drag the item in Slot 1, from Slot 1 - Slot 2, then it should go

Slot1.Holder.Parent = Slot2
Slot2.Holder.Parent = Slot1

or move Slot 2 to says Slot 5

Slot2.Holder.Parent = Slot5
Slot5.Holder.Parent = Slot2

So they basically swap spots.

If the player doesn’t move the item to a new slot, then the item just gets reset back to its original pos

Thanks for that, but I realised after posting that it’s probably a position issue. Check the code I added to my post, you have to reset the dragObj position - it should be fine after that :slight_smile:

1 Like

Omg it’s so good!! :smiley: Works like 90% of the time, I think the problem lies with if you don’t have a new slot position

Can see beginning few moves are good, but if I try moving it off the Slot bar then it just stays fixed and things go down from there
robloxapp-20191227-2106308

Again, a position issue I’m afraid. Edit the code to the following:

		local Elements = Player.PlayerGui:GetGuiObjectsAtPosition(input.Position.x, input.Position.y)
		for _, frame in next, Elements do
			if frame ~= v then -- ignore the current item you are dragging
				if frame.Name == 'Holder' then -- make sure you are dropping in another slot
					local OriginalFrame = v.Parent
					v.Parent = frame.Parent -- swap positions with the new slot
					frame.Parent = OriginalFrame
					
					break
				end
			end
		end
		Dragger.dragObj.Position = Dragger.startPos
		Dragger.Dragging = false
		Dragger.dragObj = nil
3 Likes

Works!!! :smiley: :smiley: Thank you so much!!! :smiley: :smiley: :smiley: :smiley:

1 Like