@Bones @awry_y The challenge is that Iām trying to drag elements inside of a scrollingframe.
This is because users of my plugin have the option to create unlimited elements and the only way to store unlimited elements is in a scrolling frame.
And
I have already done what you said and more. You also have to set up Hover events and determine the mouse direction. heres my currency script:
local SortableListModule = {}
-- YZ: SortableList functionality on the Client
-- BY: AverageRobloxDev2 (CC BY-SA)
-------------------------
--// Variables
-------------------------
local holding = false
local currentFrame = nil -- the hidden selected frame
local currentFrameParent = nil -- the scrollingFrame
local events = {}
local positionPrompt -- a box displaying where it will appear
local fakeFrame = nil -- a clone of the selected frame to be displayed when moving
-------------------------
--// Main Methods
-------------------------
function SortableListModule.drag(frame)
if not holding and frame then
initialize(frame)
startDragging()
stop()
end
end
-------------------------
--// Functions
-------------------------
function startDragging()
local currentSelected = nil
local previousMousePosY = nil -- Changed to focus only on Y-axis movement
local function manageMouse()
local mouse = game.Players.LocalPlayer:GetMouse()
local mousePosY = mouse.Y
if mousePosY then
-- Fake frame is a clone of the real object that is moved freely in a seperate screengui
fakeFrame.Position = UDim2.new(0, fakeFrame.Position.X.Offset, 0, mousePosY)
if previousMousePosY then
local mouseDirection = mousePosY - previousMousePosY -- Calculate Y-axis direction
if mouseDirection ~= 0 and currentSelected then
positionPrompt.Visible = true
if mouseDirection > 0 then
changePosition(positionPrompt, currentSelected.LayoutOrder + 1)
elseif mouseDirection < 0 then
changePosition(positionPrompt, currentSelected.LayoutOrder - 1)
end
end
end
previousMousePosY = mousePosY -- Update the previous position
end
end
for _, v in ipairs(currentFrameParent:GetChildren()) do
local button = v:FindFirstChild("TextButton") or v:FindFirstChild("ImageButton")
if button then
v.MouseEnter:Connect(function()
currentSelected = v
end)
end
end
while holding do
task.wait()
manageMouse()
end
end
function initialize(frame)
holding = true
currentFrame = frame:Clone()
currentFrameParent = frame.Parent
frame:Destroy()
local function convertScaleToOffset(guiObject)
local parent = guiObject.Parent
if not parent then
print("no parent")
return
end
local parentSize = parent.AbsoluteSize
local size = guiObject.Size
local newWidthOffset = size.X.Scale * parentSize.X + size.X.Offset
local newHeightOffset = size.Y.Scale * parentSize.Y + size.Y.Offset
guiObject.Size = UDim2.new(0, newWidthOffset, 0, newHeightOffset)
local position = guiObject.Position
local newPosXOffset = position.X.Scale * parentSize.X + position.X.Offset
local newPosYOffset = position.Y.Scale * parentSize.Y + position.Y.Offset
guiObject.Position = UDim2.new(0, newPosXOffset, 0, newPosYOffset)
end
-- Create the fake frame to display during drag
local screenGui = Instance.new("ScreenGui")
screenGui.Parent = game.Players.LocalPlayer.PlayerGui
fakeFrame = currentFrame:Clone()
fakeFrame.Name = "FakeFrame"
fakeFrame.BackgroundTransparency = 0
fakeFrame.BackgroundColor3 = Color3.new(1,1,1)
fakeFrame.Parent = currentFrameParent
convertScaleToOffset(fakeFrame)
fakeFrame.Parent = screenGui
-- Create position prompt
positionPrompt = Instance.new("Frame")
positionPrompt.Size = currentFrame.Size
positionPrompt.BackgroundTransparency = 1
positionPrompt.Parent = currentFrameParent
positionPrompt.Visible = false
local decorFrame = Instance.new("Frame")
decorFrame.Size = UDim2.new(0.977, 0, 1, 0)
decorFrame.BackgroundTransparency = 1
decorFrame.Position = UDim2.new(0.012, 0, 0, 0)
local Uistroke = Instance.new("UIStroke")
Uistroke.Parent = decorFrame
Uistroke.Transparency = 0.9
-- Set real frame visibility to false
currentFrame.Visible = false
enforceRequirements(frame)
setUpEvent()
sortList()
end
function stop()
holding = false
-- Set real frame back to its new position
currentFrame.Visible = true
currentFrame.LayoutOrder = positionPrompt.LayoutOrder
currentFrame.Parent = currentFrameParent
-- Cleanup GUI
fakeFrame.Parent:Destroy()
positionPrompt:Destroy()
-- Cleanup events
for _, v in ipairs(events) do
v:Disconnect()
end
events = {}
currentFrame = nil
currentFrameParent = nil
end
function changePosition(object, newPosition)
local allFrames = {}
for _, frame in ipairs(currentFrameParent:GetChildren()) do
local button = frame:FindFirstChild("TextButton") or frame:FindFirstChild("ImageButton")
if button then
table.insert(allFrames, frame)
end
end
table.sort(allFrames, function(a, b)
return a.LayoutOrder < b.LayoutOrder
end)
local currentPosition = object.LayoutOrder
object.LayoutOrder = newPosition
for _, frame in ipairs(allFrames) do
if frame ~= object then
local layoutOrder = frame.LayoutOrder
if newPosition > currentPosition then
if layoutOrder > currentPosition and layoutOrder <= newPosition then
frame.LayoutOrder = frame.LayoutOrder - 1
end
elseif newPosition < currentPosition then
if layoutOrder < currentPosition and layoutOrder >= newPosition then
frame.LayoutOrder = frame.LayoutOrder + 1
end
end
end
end
end
function sortList()
local currentFrames = {}
local existingOrders = {}
for _, frame in ipairs(currentFrameParent:GetChildren()) do
local button = frame:FindFirstChild("TextButton") or frame:FindFirstChild("ImageButton")
if button then
local layoutOrder = tonumber(frame.LayoutOrder) or 0 -- Ensure LayoutOrder is a number
currentFrames[#currentFrames + 1] = frame
-- Store frames in a table keyed by LayoutOrder
existingOrders[layoutOrder] = existingOrders[layoutOrder] or {}
table.insert(existingOrders[layoutOrder], frame)
end
end
local sortedFrames = {}
-- Prepare a list of frames sorted by their LayoutOrder
for order in pairs(existingOrders) do
for _, frame in ipairs(existingOrders[order]) do
table.insert(sortedFrames, frame) -- Maintain original order for duplicates
end
end
-- Sort frames by their LayoutOrder
table.sort(sortedFrames, function(a, b)
return a.LayoutOrder < b.LayoutOrder
end)
-- Update LayoutOrder to be sequential
for newOrder, frame in ipairs(sortedFrames) do
frame.LayoutOrder = newOrder
end
end
function setUpEvent()
local event = game.UserInputService.InputEnded:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 then
holding = false
end
end)
table.insert(events, event)
end
function enforceRequirements()
local layout = currentFrameParent:FindFirstChildOfClass("UIListLayout")
if not layout or layout.SortOrder ~= Enum.SortOrder.LayoutOrder then
error("Parent must have a UIListLayout with SortOrder set to LayoutOrder.")
end
end
return SortableListModule
As I said though its impossible to make. It just doesnt work.