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.
Make sure the parent of your inventory isn’t ClipDescendants
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)
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
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)
That seemed to work 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)
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.
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.
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
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.
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.
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
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
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