UIListLayouts SUCK! the most DIFFICULT gui on ROBLOX:

DRAGGABLE LIST ITEMS

A Very Very Difficult GUI item to make


Simple Functionality?

As a pro scripter, lets see how I tried to replicate it

Why is it so difficult?

UIListLayouts are to blame: They offer basic functionality, but they cannot support more complex projects.

  1. I cannot easily track the positions of GUI objects and must rely on MouseEnter which is not reliable in this situation. Ideally I should track based on the original position of each element rather than MouseEnter.

  2. Its very snapped because UILists are snappy. I cant smoothly tween it.

As a pro, how will I solve this?

Iā€™m going to have to create my own UIListLayout and customly manage the elements. This is a pain but is nessessary for complex functionality

Existing resources

Since Iā€™ve already spent 2 days making this, are there any existing resources? I really need to get past this stage.

2 Likes

if you can solve it then solve it. roblox will give you the basic resources and let you do the rest.

2 Likes

I can solve it but it will take forever. Is there any alternatives? Maybe a free resource somewhere?

1 Like

no idea, but i watched the video about your replica, and it looks completely fine. looks as if it works correctly

if youā€™re the pro you say you are, it should be no problem :upside_down_face:

9 Likes

this isnā€™t really a UIListLayout issue since dragging doesnā€™t have much to do with them (other than having locked position; UIListLayout is only for listing elements not dragging elements in a list)

iā€™m not sure if the new UIDragDetector helps with the dragging but recreating this shouldnā€™t be too hard for a ā€˜pro scripterā€™; it took me around 25 minutes (ignore the janky hover script)

1 Like

You are NOT a professional scripter as you say you areā€¦
This feature is effortless to make. Here let me tell you how:

In your list layout frame add another frame parented to the parent frame of the list layout so basically have 2 sister frames. One of the frames has the UI list layout and its elements and the other one is empty. Next, you need to utilize a property called ā€œLayout Orderā€ of the UI list layout elements. See when the player is starting to drag an element you just parent a clone of that element to the second frame we made and move it accordingly. Make sure to have the original element in the UI list layout be transparent. Then just use basic maths to calculate at what possible position we need to put the UI element and when the player releases their mouse you just delete the temporary frame we made and make the original frame visible again.

You probably already made that but I wrote a guide just to tell you how easy it is to make it and how the UI list layout is PERFECT. I love them, especially with the new flex features roblox added. Sooā€¦ stop ranting/complaining. : /

2 Likes

@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.

How did you make this script? It looks like exactly what Iā€™m looking for. And I lied: im not a pro scripter. But thanks for helping

Can you send a copy of it or something similar?