Only one at a time UI button selection system

There has to be a better way of doing this:

local buttonsArray = {}

for i, v in ScreenGui.Chassis.TopMiddleBar:GetChildren() do
	if v:IsA("TextButton") then
		buttonsArray[v.Name] = v
	end
end
for i, v in ScreenGui.Chassis.BottomMiddleBar:GetChildren() do
	if v:IsA("TextButton") then
		buttonsArray[v.Name] = v
	end
end

something.SomeEvent:Connect(function()
	if buttonsArray["Move"]:GetAttribute("selected") == true then
		-- do stuff
	elseif buttonsArray["None"]:GetAttribute("selected") == true then
		-- do other stuff
	end
end)

The script in question is in starterCharacterScripts and sadly cannot be moved to StarterGUI.

This is what my hierarchy looks like:
image

Here’s what the localScript looks like that’s under “chassis”

local buttonsArray = {}

for i, v in script.Parent.TopMiddleBar:GetChildren() do
	if v:IsA("TextButton") then
		table.insert(buttonsArray, v)
	end
end
for i, v in script.Parent.BottomMiddleBar:GetChildren() do
	if v:IsA("TextButton") then
		table.insert(buttonsArray, v)
	end
end

for i, v in buttonsArray do
	v.MouseButton1Click:Connect(function()
		for all, buttons in buttonsArray do
			buttons.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
			buttons:SetAttribute("selected", false)
		end
		v.BackgroundColor3 = Color3.fromRGB(200, 255, 200)
		v:SetAttribute("selected", true)
	end)
end

All I’m trying to achieve is only a single button can be selected at a time. This is what I’ve tried so far. It barely works… and I feel like it can be so so much better, that it basically doesn’t work. Thanks!!!

1 Like

What’s wrong with it as written? Do you just think the code looks ugly, or is it actually dysfunctional? You have quite an elegant solution as is imo. I have a script written to do the same thing, and your code is much more compact than mine…

All I’d add is that the modifying the selected attribute might be unnecessary, which you do with buttons:SetAttribute("selected", false) and v:SetAttribute("selected", true). I believe it’s used to determine the button’s color (between the values you specify for the button’s selected and unselected states), but since you’re changing the color manually, the value of selected might be irrelevant.

Thanks for the compliment! Yes, I think it looks ugly and very drawn out, it could be much simpler, I just don’t know how.
You’re right, I can use the color as the value to see if the button is selected. I might implement that and mark your post as the solution if I don’t get anything else in the next 24 hours. Thanks!

First, why can’t it be moved to starterGui? Second, I would recommend doing if buttonsArray["Move"]:GetAttribute("selected") then ... instead of

If it exists, will automatically check if true; if it doesn’t exist, will automatically return false; if does exist, but false will return false.

Also doing

if not v:IsA("TextButton") then continue end

buttonsArray[v.Name] = v

Is better for optimization. Causes less nesting.

This is because: if it does exist and is a boolean attribute, Roblox lua will automatically check it’s boolean state.

It can’t be moved to starter GUI because the GUI for some reason doesn’t get loaded. I think I clicked a setting somewhere and it’s broke
image
I have this ghetto solution for that issue though :stuck_out_tongue:
I think it might have to do something with the character never spawning, I turned that setting off.

Anyways… I’ll add that :smiley:
so, just minor changes, thanks!
Also, what exactly does “continue” do? It’s not giving me any errors, but I’ve never seen it before. Thanks!

Continue simply skips the current for loop iteration. This means if

v:IsA("TextButton")

Return false, it will simply do nothing. Kinda like return for functions, except it actually skips to the next iteration.

Now could you elaborate on the gui not being loaded problem? I feel it’s a very easy fix.

Honestly this seems like a code review. But your code seems like it would work just fine, though yeah may not be very pretty (personal opinion). Personally I would turn the selection into a module where you can easily access what’s already registered and know when something is selected.

Then turn the connection, presumably an activator event into something that calls functions, instead of having a single thousand lined function, reduces if statements. So that your methods of buttonsArray["None"] will be easily accessible and organized. Though I don’t know your coding habits or how you organize your code so this is just one of the million examples someone could make of your idea.

For example:

This is the module script, that’s located somewhere.

-- // Variables
local parent = script.Parent

local module = {}
module.__selection = {}
module._previous = nil
module.Current = nil

-- // Not actually important
module._selectionUpdated = Instance.new("BindableEvent")
module.SelectionUpdated = module._selectionUpdated.Event

-- // Constants
local DEFAULT_UNSELECTED_COLOR = Color3.fromRGB(255, 255, 255)
local DEFAULT_SELECTED_COLOR = Color3.fromRGB(200, 255, 200)

-- // Functions & Methods
function module.deselect(child)
	module._previous = child
	if not child then return end

	child.BackgroundColor3 = DEFAULT_UNSELECTED_COLOR
end

function module.select(child)
	module.deselect(module.Current)
	if child == module._previous then return end

	child.BackgroundColor3 = DEFAULT_SELECTED_COLOR
	module.Current = child
	module._selectionUpdated:Fire(child)
end

-- // This is so you can define which buttons you want only one selected of
function module.registerChildren(children)
	for _, child in ipairs(children) do
		if not (child:IsA("GuiButton")) then continue end
		if (module.__selection[child.Name]) then
			warn(`Child with name already registered as {child:GetFullName()}. \n{debug.traceback(nil, 2)}`)
			continue
		end

		module.__selection[child.Name] = child
		child.Activated:Connect(function()
			module.select(child)
		end)
	end
end

local function init()
	module.registerChildren(parent.TopMiddleBar:GetChildren())
	module.registerChildren(parent.BottomMiddleBar:GetChildren())

	return module
end

return init()

And this would be that one script that’s doing all the handy work based on selection, also located somewhere:

local above = require(directory.to.whats.above)
local method_holder = {}

method_holder.Move = function()
	-- Do stuff ( :
end

method_holder.None = function()
	-- // Presumably nothing but y'know ( ::::
end

-- // I think
activation:Connect(function()
	-- // I assume this is your do something script
	-- // Because nothing is selected, do nothing
	if not (above.Current) then return end
	local method = method_holder[above.Current.Name]
	assert(method, `Non-registered method for selected button {above.Current:GetFullName()}.`)

	method()
end)
2 Likes

I’ll mark this post as the solution because it’s definitely an improvement over my code. Thanks!

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