Issue with explorer system

Hello,

I’m trying to remake the Explorer in-game. So far, it’s going well, but there’s an issue (obviously).
When I parent an instance for the 4th time and onwards, it messes with the other children’s order. My system uses frames and the LayoutOrder property to order properly.

initial (this is fine)

image

parent one (this is fine)

parent three - parent part back to Workspace

this is fine and looks the same as initial

parent four - issue

image

and the weird thing is the LayoutOrders are returns to how it should after parent three. It’s something to do with how the LayoutOrders are changed.

My system uses a dictionary of instance as the key, and it’s corresponding frame as the value. It uses Instance:GetPropertyChangedSignal("Parent") to detect when the frame moves parent and therefore what should update.

connection
--create frame connections
local function connect(instance: Instance)
	--get the frame
	local frame = iconsHandler.Icons[instance]

	--button image
	--if the instance has at least 1 child, show the button. Otherwise, hide it.
	if (#instance:GetChildren() >= 1) then
		frame.Dropdown.Visible = true
	else
		frame.Dropdown.Visible = false
	end

	frame.Dropdown.Image = collapsed

	--ignore if connections exist already
	if (iconsHandler.Connections[instance]) then return nil end

	--add connections for the instance
	iconsHandler.Connections[instance] = {
		Connections = {
			--when the button is clicked
			frame.Dropdown.MouseButton1Click:Connect(function() onClick(instance, frame) end),

			--when the instance name changes
			instance:GetPropertyChangedSignal("Name"):Connect(function() frame.NameText.Text = instance.Name end),
		
			--when the instance changes parent
			instance:GetPropertyChangedSignal("Parent"):Connect(function()
				--get this data sector
				local sector = iconsHandler.Connections[instance]
				
				--get the old parent
				local oldParent = sector.Data.PreviousParent
				if (instance:IsDescendantOf(temp)) then return nil end --ignore if it was only moved to the holder
				
				--assign the new old parent
				sector.Data.PreviousParent = instance.Parent
				
				--call function for a child added for the new parent
				childAddedWhilstShowing(instance.Parent, instance, iconsHandler.Icons[instance.Parent])
				
				if (oldParent and oldParent ~= temp) then
					--call function for child removed for old parent
					childRemovedWhilstShowing(oldParent, instance, iconsHandler.Icons[oldParent])
				end
			end)
		}, Data = {
			PreviousParent = nil --placeholder to show it will be used
		}
	} :: {Connections: {RBXScriptConnection}, Data: {[string]: any}}
end
function to update layout orders

this function works fine for other uses; like the first 3 parent changes, and whenever the parent frame is collapsed and re-expanded. But, I think the issue might be here.

--update layout orders with nested instances
function iconsHandler:UpdateInstanceLayoutOrders(instance: Instance)
	removeLayoutOrderGaps()
	
	--get the instance frame
	local frame = self.Icons[instance]
	
	local children = instance:GetChildren()
	
	--get child counter + increment child layout orders
	local counter = 0
	for _, child in next, children, nil do
		counter += 1
		self.Icons[child].LayoutOrder = frame.LayoutOrder + counter
	end
	
	--update for all other frames
	for k, otherFrame in next, self.Icons, nil do
		--ignore if lower display order, or is a child of the instance
		if (frame.LayoutOrder >= otherFrame.LayoutOrder or table.find(children, k)) then continue end

		--increment order
		otherFrame.LayoutOrder += counter
	end
end
child added whilst frame is showing
--when a child is added to the instance while it's shown
local function childAddedWhilstShowing(parent: Instance, child: Instance, frame: Frame)
	--if the parent has 1 or more child, hence should have the button, make button visible
	if (#parent:GetChildren() >= 1) then
		frame.Dropdown.Visible = true
		frame.Dropdown.Image = expanded

	else --hide it and return to collapsed
		frame.Dropdown.Visible = false
		frame.Dropdown.Image = collapsed
	end
	
	--update the layout orders and show the new frame
	iconsHandler:UpdateInstanceLayoutOrders(parent)
	
	--show the new child icon
	iconsHandler:ShowIcons({child})
	
	for _, v in next, parent:GetDescendants(), nil do --update indentation for each descendant
		iconsHandler.Icons[v].UIPadding.PaddingLeft = getSize(v)
	end
end

I think the issue could be to do with how the frame layout orders are incremented, but I’m not entirely sure on that.

Any help is appreciated!

5 Likes

Here’s a system I made.

wait(1)

local plr = game.Players.LocalPlayer
local plrGUI = plr.PlayerGui

local gui = Instance.new("ScreenGui", plrGUI)
gui.Name = "EriExplorer"
gui.ResetOnSpawn = false

function createUIList(parent)
	local uiautosortlist = Instance.new("UIListLayout", parent)
	uiautosortlist.HorizontalFlex = Enum.UIFlexAlignment.Fill
	uiautosortlist.VerticalFlex = Enum.UIFlexAlignment.None
	uiautosortlist.SortOrder = Enum.SortOrder.LayoutOrder
	return uiautosortlist
end

function createExplorer()
	local frame = Instance.new("ScrollingFrame", gui)
	frame.Position = UDim2.new(1, 0, 0, 0)
	frame.AnchorPoint = Vector2.new(1, 0)
	frame.Size = UDim2.new(0.2, 0, 1, 0)
	frame.BackgroundColor3 = Color3.new(0, 0, 0)
	frame.BackgroundTransparency = 0.5
	frame.CanvasSize = UDim2.new(0, 0, 0, 20)

	local uiautosortlist = Instance.new("UIListLayout", frame)
	uiautosortlist.HorizontalFlex = Enum.UIFlexAlignment.Fill
	uiautosortlist.VerticalFlex = Enum.UIFlexAlignment.None
	uiautosortlist.SortOrder = Enum.SortOrder.LayoutOrder

	local nochildrendiv = Instance.new("TextButton", frame)
	nochildrendiv.LayoutOrder = 5
	nochildrendiv.Name = "NOCHILDDIV"
	nochildrendiv.Text = "No children V"
	nochildrendiv.BackgroundColor3 = Color3.new(1, 1, 1)
	nochildrendiv.BackgroundTransparency = 0
	nochildrendiv.TextColor3 = Color3.new(0, 0, 0)
	nochildrendiv.Size = UDim2.new(1, 0, 0, 20)
	nochildrendiv.TextScaled = true

	function createButton(parent, thing)
		local parentFrame = Instance.new("Frame", parent)
		parentFrame.Name = thing.Name
		createUIList(parentFrame)

		parentFrame.BackgroundColor3 = Color3.new(1, 1, 1)
		parentFrame.BackgroundTransparency = 1
		parentFrame.Size = UDim2.new(1, 0, 0, 20)
		parentFrame.AutomaticSize = Enum.AutomaticSize.Y

		local button = Instance.new("TextButton", parentFrame)
		button.Name = thing.Name
		button.Text = thing.Name

		if thing:IsA("Folder") then
			button.BackgroundColor3 = Color3.new(1, 1, 0.498039)
		elseif thing:IsA("RemoteEvent") then
			button.BackgroundColor3 = Color3.new(0.666667, 0.333333, 1)
		elseif thing:IsA("RemoteFunction") then
			button.BackgroundColor3 = Color3.new(0.666667, 0.666667, 1)
		elseif thing:IsA("BindableEvent") then
			button.BackgroundColor3 = Color3.new(1, 0.666667, 0.498039)
		elseif thing:IsA("LocalScript") then
			button.BackgroundColor3 = Color3.new(0, 0.666667, 1)
		elseif thing:IsA("Script") then
			button.BackgroundColor3 = Color3.new(0, 0.333333, 1)
		elseif thing:IsA("ModuleScript") then
			button.BackgroundColor3 = Color3.new(0.666667, 0.333333, 0.498039)
		elseif thing:IsA("ValueBase") then
			button.BackgroundColor3 = Color3.new(0.333333, 0.333333, 0.498039)
		elseif thing:IsA("Model") then
			button.BackgroundColor3 = Color3.new(1, 0.333333, 0)
		else
			button.BackgroundColor3 = Color3.new(1, 1, 1)
		end
		button.BackgroundTransparency = 0.5
		button.TextColor3 = Color3.new(0, 0, 0)
		button.Size = UDim2.new(1, 0, 0, 20)
		button.TextScaled = true
		
		button.Font = Enum.Font.Roboto
		if hasChildren(thing) then
			button.Font = Enum.Font.RobotoCondensed
		end
		
		button.MouseButton2Up:Connect(function()
			contextMenu(thing)
		end)

		return button
	end

	function hasChildren(parent)
		for _, v in parent:GetChildren() do
			if v then
				return true
			end
		end
		return false
	end

	function changeSize(framee, dir)
		-- Start with the parent of the current frame
		local currentParent = framee.Parent

		-- Loop through the parent hierarchy
		while currentParent do
			-- Check if the parent is a Frame with the name "CHILDHOLD"
			if currentParent:IsA("Frame") then
				if dir == true then
					currentParent.Size = currentParent.Size + UDim2.new(0, 0, 0, framee.Size.Y.Offset)

					frame.CanvasSize = frame.CanvasSize + UDim2.new(0, 0, 0, framee.Size.Y.Offset)
				else
					currentParent.Size = currentParent.Size - UDim2.new(0, 0, 0, framee.Size.Y.Offset)

					frame.CanvasSize = frame.CanvasSize - UDim2.new(0, 0, 0, framee.Size.Y.Offset)
				end
			end

			-- Stop if the parent is a ScrollingFrame
			if currentParent:IsA("ScrollingFrame") then
				break
			end

			-- Move to the next parent
			currentParent = currentParent.Parent
		end
	end

	function ParentHandler(p, button)
		if not button.Parent:FindFirstChild("CHILDHOLD") then
			if hasChildren(p) then
				openParent(p, button)
			end
		else
			closeParent(button)
		end
	end

	function openParent(p, button)
		local childrenHolder = Instance.new("Frame", button.Parent)
		childrenHolder.Name = "CHILDHOLD"
		childrenHolder.Size = UDim2.new(1, 0, 0, 0)
		childrenHolder.AutomaticSize = Enum.AutomaticSize.Y
		createUIList(childrenHolder)
		for i, c in p:GetChildren() do
			local childbutton = createButton(childrenHolder, c)

			childbutton.MouseButton1Click:Connect(function()
				ParentHandler(c, childbutton)
			end)

			--childrenHolder.Size += UDim2.new(0, 0, 0, 20)
		end
		changeSize(childrenHolder, true)
	end

	function closeParent(button)
		local parent = button.Parent
		local CHILDHOLD = parent:FindFirstChild("CHILDHOLD")
		changeSize(CHILDHOLD, false)
		for _, v in CHILDHOLD:GetChildren() do
			v:Destroy()
		end
		CHILDHOLD:Destroy()
	end

	for i, c in game:GetChildren() do
		local service = createButton(frame, c)

		service.Parent.LayoutOrder = 0
		if not hasChildren(c) then
			service.Parent.LayoutOrder = 10
		else
			service.MouseButton1Click:Connect(function()
				ParentHandler(c, service)
			end)
		end

		frame.CanvasSize = frame.CanvasSize + UDim2.new(0, 0, 0, 20)
	end
end

function enableDragging(frame, dragHandle)
	local dragging = false
	local dragStart, startPos

	dragHandle.InputBegan:Connect(function(input)
		if input.UserInputType == Enum.UserInputType.MouseButton1 then
			dragging = true
			dragStart = input.Position
			startPos = frame.Position
		end
	end)

	dragHandle.InputChanged:Connect(function(input)
		if dragging and input.UserInputType == Enum.UserInputType.MouseMovement then
			local delta = input.Position - dragStart
			frame.Position = UDim2.new(
				startPos.X.Scale,
				startPos.X.Offset + delta.X,
				startPos.Y.Scale,
				startPos.Y.Offset + delta.Y
			)
		end
	end)

	dragHandle.InputEnded:Connect(function(input)
		if input.UserInputType == Enum.UserInputType.MouseButton1 then
			dragging = false
		end
	end)
end

function monitorEvent(thing)
	local monitor = Instance.new("Frame", gui)
	monitor.Name = "MONITOR"..thing.Name
	monitor.Size = UDim2.new(0, 400, 0, 200)
	monitor.BackgroundColor3 = Color3.fromRGB(59, 59, 59)
	monitor.Position = UDim2.new(0.5, 0, 0.5, 0)
	monitor.AnchorPoint = Vector2.new(0.5, 0.5)
	
	createUIList(monitor)
	
	local namelableldrag = Instance.new("TextButton", monitor)
	namelableldrag.Size = UDim2.new(1, 0, 0, 50)
	namelableldrag.BackgroundColor3 = Color3.fromRGB(49, 49, 49)
	namelableldrag.Text = thing.Name
	namelableldrag.TextColor3 = Color3.fromRGB(255, 255, 255)
	namelableldrag.TextScaled = true

	enableDragging(monitor, namelableldrag)
	namelableldrag.MouseButton2Up:Connect(function()
		monitor:Destroy()
	end)

	local disp = Instance.new("ScrollingFrame", monitor)
	disp.Name = "MONITOR"..thing.Name
	disp.Size = UDim2.new(1, 0, 1, 0)
	disp.BackgroundColor3 = Color3.fromRGB(59, 59, 59)
	disp.Position = UDim2.new(0.5, 0, 0.5, 0)
	disp.AnchorPoint = Vector2.new(0.5, 0.5)
	
	createUIList(disp)
	
	thing.OnClientEvent:Connect(function(...)
		local args = {...}
		local text = ""
		for i, v in args do
			text = text..tostring(v).." "
		end
		local label = Instance.new("TextLabel", disp)
		label.Size = UDim2.new(1, 0, 0, 20)
		label.BackgroundColor3 = Color3.fromRGB(49, 49, 49)
		label.Text = text
		label.TextColor3 = Color3.fromRGB(255, 255, 255)
		label.TextScaled = true
		label.TextWrapped = true
		disp.CanvasSize += UDim2.new(0, 0, 0, 20)
	end)
end

function contextMenu(thing)
	local context = Instance.new("Frame", gui)
	context.Size = UDim2.new(0, 200, 0, 300)
	context.BackgroundColor3 = Color3.fromRGB(59, 59, 59)
	context.Position = UDim2.new(0.5, 0, 0.5, 0)
	context.AnchorPoint = Vector2.new(0.5, 0.5)

	local uilist = createUIList(context)
	uilist.VerticalFlex = Enum.UIFlexAlignment.Fill

	local namelableldrag = Instance.new("TextButton", context)
	namelableldrag.Size = UDim2.new(1, 0, 0, 50)
	namelableldrag.BackgroundColor3 = Color3.fromRGB(49, 49, 49)
	namelableldrag.Text = thing.Name
	namelableldrag.TextColor3 = Color3.fromRGB(255, 255, 255)
	namelableldrag.TextScaled = true

	enableDragging(context, namelableldrag)
	namelableldrag.MouseButton2Up:Connect(function()
		context:Destroy()
	end)
	
	if thing:IsA("BasePart") then
		local poslabel = Instance.new("TextLabel", context)
		poslabel.Size = UDim2.new(1, 0, 0, 20)
		poslabel.BackgroundColor3 = Color3.fromRGB(49, 49, 49)
		poslabel.Text = "Position: "..tostring(thing.Position)
		poslabel.TextColor3 = Color3.fromRGB(255, 255, 255)
		poslabel.TextScaled = true
		
		local rotlabel = Instance.new("TextLabel", context)
		rotlabel.Size = UDim2.new(1, 0, 0, 20)
		rotlabel.BackgroundColor3 = Color3.fromRGB(49, 49, 49)
		rotlabel.Text = "Rotation: "..tostring(thing.Rotation)
		rotlabel.TextColor3 = Color3.fromRGB(255, 255, 255)
		rotlabel.TextScaled = true
		
		local sizelabel = Instance.new("TextLabel", context)
		sizelabel.Size = UDim2.new(1, 0, 0, 20)
		sizelabel.BackgroundColor3 = Color3.fromRGB(49, 49, 49)
		sizelabel.Text = "Size: "..tostring(thing.Size)
		sizelabel.TextColor3 = Color3.fromRGB(255, 255, 255)
		sizelabel.TextScaled = true
		
		local colorlabel = Instance.new("TextLabel", context)
		colorlabel.Size = UDim2.new(1, 0, 0, 20)
		colorlabel.BackgroundColor3 = thing.Color
		colorlabel.Text = tostring(thing.BrickColor)
		colorlabel.TextColor3 = Color3.fromRGB(0, 0, 0)
		colorlabel.TextStrokeTransparency = 0
		colorlabel.TextScaled = true
		
		local tpbutton = Instance.new("TextButton", context)
		tpbutton.Size = UDim2.new(1, 0, 0, 20)
		tpbutton.BackgroundColor3 = Color3.fromRGB(49, 49, 49)
		tpbutton.Text = "Teleport"
		tpbutton.TextColor3 = Color3.fromRGB(255, 255, 255)
		tpbutton.TextScaled = true
		tpbutton.Activated:Connect(function()
			local char = game.Players.LocalPlayer.Character
			char:MoveTo(thing.Position)
		end)
		
		local walktobutton = Instance.new("TextButton", context)
		walktobutton.Size = UDim2.new(1, 0, 0, 20)
		walktobutton.BackgroundColor3 = Color3.fromRGB(49, 49, 49)
		walktobutton.Text = "Walk to"
		walktobutton.TextColor3 = Color3.fromRGB(255, 255, 255)
		walktobutton.TextScaled = true
		walktobutton.Activated:Connect(function()
			local char = game.Players.LocalPlayer.Character
			local hum = char:FindFirstChildOfClass("Humanoid")
			hum:MoveTo(thing.Position)
		end)
		
		local hlbutton = Instance.new("TextButton", context)
		hlbutton.Size = UDim2.new(1, 0, 0, 20)
		hlbutton.BackgroundColor3 = Color3.fromRGB(49, 49, 49)
		hlbutton.Text = "Highlight"
		hlbutton.TextColor3 = Color3.fromRGB(255, 255, 255)
		hlbutton.TextScaled = true
		hlbutton.Activated:Connect(function()
			Instance.new("Highlight", thing)
		end)
	elseif thing:IsA("RemoteEvent") then
		local argsbox = Instance.new("TextBox", context)
		argsbox.Size = UDim2.new(1, 0, 0, 20)
		argsbox.BackgroundColor3 = Color3.fromRGB(49, 49, 49)
		argsbox.Text = ""
		argsbox.PlaceholderText = "Put arguments in here arg1, arg2, ..."
		argsbox.TextColor3 = Color3.fromRGB(255, 255, 255)
		argsbox.TextScaled = true

		local firebutton = Instance.new("TextButton", context)
		firebutton.Size = UDim2.new(1, 0, 0, 20)
		firebutton.BackgroundColor3 = Color3.fromRGB(49, 49, 49)
		firebutton.Text = "Fire event"
		firebutton.TextColor3 = Color3.fromRGB(255, 255, 255)
		firebutton.TextScaled = true
		firebutton.Activated:Connect(function()
			local argsText = argsbox.Text
			local args = {}
			for arg in string.gmatch(argsText, "[^,]+") do
				table.insert(args, arg:match("^%s*(.-)%s*$"))
			end

			thing:FireServer(unpack(args))
		end)

		local monitorbutton = Instance.new("TextButton", context)
		monitorbutton.Size = UDim2.new(1, 0, 0, 20)
		monitorbutton.BackgroundColor3 = Color3.fromRGB(49, 49, 49)
		monitorbutton.Text = "Monitor event"
		monitorbutton.TextColor3 = Color3.fromRGB(255, 255, 255)
		monitorbutton.TextScaled = true
		monitorbutton.Activated:Connect(function()
			monitorEvent(thing)
		end)
	elseif thing:IsA("ValueBase") then
		local valuelabel = Instance.new("TextLabel", context)
		valuelabel.Size = UDim2.new(1, 0, 0, 20)
		valuelabel.BackgroundColor3 = Color3.fromRGB(49, 49, 49)
		valuelabel.Text = tostring(thing.Value)
		valuelabel.TextColor3 = Color3.fromRGB(255, 255, 255)
		valuelabel.TextStrokeTransparency = 0
		valuelabel.TextScaled = true
	elseif thing:IsA("Model") then
		local primarylabel = Instance.new("TextLabel", context)
		primarylabel.Size = UDim2.new(1, 0, 0, 20)
		primarylabel.BackgroundColor3 = Color3.fromRGB(49, 49, 49)
		primarylabel.Text = tostring(thing.PrimaryPart)
		primarylabel.TextColor3 = Color3.fromRGB(255, 255, 255)
		primarylabel.TextStrokeTransparency = 0
		primarylabel.TextScaled = true
	end
end

createExplorer()
3 Likes

Can you send me a copy of the buggy children and their parent in the UI? That will be very helpful to know where are things going wrong.

1 Like

It’s not only set children; it’s with any instance. The issue is with how the corresponding frames’ LayoutOrder is updated.

As you can see here, the first time they are parented it all works fine, but the next time it goes wrong.

Child removed whilst showing
--when a child is removed from the instance while it's showing
local function childRemovedWhilstShowing(parent: Instance, child: Instance, frame: Frame)
	--if the parent has 1 or more child, hence should have the button, make button visible
	if (#parent:GetChildren() >= 1) then
		frame.Dropdown.Visible = true
		frame.Dropdown.Image = expanded

	else --hide it and return to collapsed
		frame.Dropdown.Visible = false
		frame.Dropdown.Image = collapsed
	end
	
	--get removed child's icon
	local removedIcon = iconsHandler.Icons[child]
	
	--remove gaps in the layout orders
	removeLayoutOrderGaps()
	
	--update layout orders for frames
	iconsHandler:UpdateInstanceLayoutOrders(parent)
end
Non-module code
--this is just a draft of the non-module code
task.wait(5)
--module
local iconManager = require("./Show/IconManager")

--wait until game is loaded
repeat task.wait() until game:IsLoaded()

--gui elements
local mainFrame = script.Parent:WaitForChild("MainFrame")
local holder = mainFrame:WaitForChild("Holder")

--services to be shown initially
local services = {
	game:GetService("Workspace"),
	game:GetService("Players"),
	game:GetService("Lighting"),
	game:GetService("MaterialService"),
	game:GetService("ReplicatedFirst"),
	game:GetService("ReplicatedStorage"),
	game:GetService("ServerScriptService"),
	game:GetService("ServerStorage"),
	game:GetService("StarterGui"),
	game:GetService("StarterPack"),
	game:GetService("StarterPlayer"),
	game:GetService("Teams"),
	game:GetService("SoundService"),
	game:GetService("TextChatService")
}

--initially create frames
iconManager:Init()

--show service frames
iconManager:ShowIcons(services)

game.DescendantAdded:Connect(function(i)
	if i:IsDescendantOf(script.Parent) or iconManager.Icons[i] then return nil end
	iconManager:CreateIcon(i)
end)

If I didn’t understand what you were asking, please correct me :slight_smile:


thanks, but I’d prefer to make my own.

1 Like

I think I phrased it badly: I need the UI elements of the children you moved when it bugs. So it isn’t specific. I want to know how you do organize your UI elements to see if the problem is here.

Also, what’s frame in the Child remove whilst showing? Is it the one where you render every objects?

1 Like

I organize my UI elements with a UIListLayout and a ScrollingFrame.

Initially, the elements are organized based off of their index in game:GetDescendants(). When a child is added to the parent whilst the parent is being shown, all of the parent’s children’s elements LayoutOrder is updated, and all the other frames have their LayoutOrder shifted by the amount of children the parent has.

Init function
--initially create game icons
function iconsHandler:Init()
	local descendants = game:GetDescendants()
	for i, instance in next, descendants, nil do
		self:CreateIcon(instance)
		--task.wait()
	end

	self:UpdateLayoutOrders() --update display orders
end

--update display orders
function iconsHandler:UpdateLayoutOrders()
	--update each instance's display order to it's position in game:GetDescendants()
	for i, instance in next, game:GetDescendants(), nil do
		--add an icon if one for the instance doesn't exist already
		if (not self.Icons[instance]) then
			self:CreateIcon(instance)
		end

		self.Icons[instance].LayoutOrder = i
	end
end

When a child frame isn’t being shown, it’s stored in the TEMP folder and has no connections. When it’s moved to the ScrollingFrame, it is connected up and the LayoutOrder of all the frames are updated so it’s slotted in at the right place.

The frame in the child added and child removed functions refer to the frame element of the parent, I named that parameter a little vaguely, sorry. frame in the added function refers to the new parent (where the instance was moved to), but frame in the removed function refers to the old parent (where the instance was moved from).

Template:
image
Holder:
image

1 Like
  1. Is the Holder where you put all the objects in the world?
  2. Where do you put the children’s icons? In the parent’s UI element?

That’s all I need if I want to do anything else for you, please.

1 Like

Holder is where I put all objects currently being displayed in the world - the other ones are stored in TEMP.

All the frames are parented to the holder, but their LayoutOrder is modified so they display in the correct structure. I then modify their UIPadding to make them look indented.

In summary;
This structure:


looks like this in the Holder:

and all other icons are in TEMP.

and the LayoutOrders are such:
Workspace: 1
Camera: 2
AudioListener: 3
and so on.


and then when I put the part in:
image
All icons currently shown above the part don’t change LayoutOrder, but all the ones below it are shifted so the Part fits in.
E.g.
Terrain: 3 → 4
12345koip (my character): 4 → 5

This is the same when bulk parenting instances too.

1 Like

You can look how I did it tough, I’m putting the children inside the parent frame and it has an automatic size so it will always be under the parent.

1 Like

Oooooh yea! That’s way clearer now! I do understand where there’s a problem:

I think the problem is maybe the layout messing up when you remove the children. Is there a function when you remove the children? If it does, please send it. I think the problem is here.

If you want, you can also try renaming the terrain to “AA” and see what happens.

2 Likes

There is a function where I remove the children, it’s the childRemovedWhilstShowing function. Here it is again:

--when a child is removed from the instance while it's showing
local function childRemovedWhilstShowing(parent: Instance, child: Instance, frame: Frame)
	--if the parent has 1 or more child, hence should have the button, make button visible
	if (#parent:GetChildren() >= 1) then
		frame.Dropdown.Visible = true
		frame.Dropdown.Image = expanded

	else --hide it and return to collapsed
		frame.Dropdown.Visible = false
		frame.Dropdown.Image = collapsed
	end
	
	--get removed child's icon
	local removedIcon = iconsHandler.Icons[child]
	
	--remove gaps in the layout orders
	removeLayoutOrderGaps()
	
	--update layout orders for frames
	iconsHandler:UpdateInstanceLayoutOrders(parent)
end

I don’t have a function other than that one because when the child frames get added to the holder they all get sorted anyway.

All removeLayoutOrderGaps does is remove gaps between layout orders while keeping the original order, if you need to know what it does…

--remove gaps between LayoutOrders
local function removeLayoutOrderGaps()
	--gather all frames into one table
	local allFrames = {}

	for _, frame in next, iconsHandler.Icons, nil do
		table.insert(allFrames, frame)
	end

	--sort them based on LayoutOrder
	table.sort(allFrames, function(a: Frame, b: Frame): boolean
		return a.LayoutOrder < b.LayoutOrder
	end)

	--assign a sequential LayoutOrder to each
	for i, frame in next, allFrames, nil do
		frame.LayoutOrder = i
	end
end

but it’s really weird because I checked the layout orders of the frames for the times I parent it where it works, and they all look fine. I also added warn statements for when the layout order gets changed - what it gets changed to, and for the AudioListener (example) it outputs 3, which it should be, but it gets set to 8.

2 Likes

Yea, that’s pretty strange. What about the other objects’ layout order? Maybe are they also set wrong?

Could be. I removed all calls for removeLayoutOrderGaps and this is the result when I parent it to the camera for the second time:
image
and when I collapse and re-expand the camera, they all get sorted correctly again from my onClick function.

I looked at the LayoutOrders for the items shown in the screenshot, and they are all consecutive from 1-7 up until AudioListener, where it jumps up to 8, and Part is set to 9. This bit could be to do with the fact I don’t remove 1 layout order from all the other frames when the instance is removed, but not sure. (edit: removing 1 layout order from higher instances messes up the order a little).

What’s also interesting is they don’t move to the bottom of the display, only to the bottom of that hierarchical container (the Workspace here) which narrows it definitely down to the layout being incorrectly set after the children are removed.

2 Likes

Found out something after thinking a bit.

When you do the function to fill back gaps, you already do reorder the layout. So try maybe silencing (–) the iconsHandler:UpdateInstanceLayoutOrders(parent) part in the childRemovedWhilstShowing function and see what happens maybe.

Oh, and maybe for the iconsHandler:UpdateInstanceLayoutOrders function, add another counter when updating the other frames and change otherFrame.LayoutOrder += counter to otherFrame.LayoutOrder = frame.LayoutOrder + counter + OtherFrameCounter. This will take care of the coherence with the frame with children.

Using a different counter for the other frames does… this.


It’s all over the place!

With a little experimenting, I decided to leave out the update for the parent it was previously removed from, and it now causes this when the part is parented to the camera:
image

and the correct display in a slightly different order when it isn’t:
image
but I’ll mess around with the order after this issue is fixed.

Definitely to do with the instance re-ordering, though.

In the end, I just decided to rewrite the whole system, and it works fine now. Thanks both of you for trying to help! I’ll un-solve this topic if I find any other issues related.

Nice to know your project goes back nice, and sorry if I didn’t manage to find a solution in the meantime.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.