GuiService:AddSelectionParent/Tuple not stopping other guis being selected

local function SetSelectionParent(ID,ScreenGui)
	local GuiObjects = {}
	for i,v in pairs(ScreenGui:GetChildren()) do
		if v:IsA("GuiObject") then
			table.insert(GuiObjects,v)
		end
	end
	print(tostring(#GuiObjects).." objects found")
	GuiService:AddSelectionTuple(ID,GuiObjects)
end

The print confirms that the selection is being correctly set, but every gui, including ones that have never been added to a selection, can still be selected, meaning this does absolutely nothing. The same happens with AddSelectionParent. Does anyone know why?

5 Likes

I too need an answer to this :frowning:

This is still happening for AddSelectionTuple. I just tried it out and I can still select everything not just the stuff in the tuple.

Edit: This thread should probably be moved to bug reports.

Both functions still broken as of today.

1 Like

Note that this is in Development Support. Engineers are unlikely to see this topic. Please use Platform Feedback to report bug reports.

I’ve moved the post to engine bugs

1 Like

The “AddSelectionTuple” function has been broken since 2017 and is still broken?

This must be a simple fix, I can’t fathom why it hasn’t been fixed yet.

2 Likes

Not quite sure how I just saw this, but we will look into this. Thanks for the report, and sorry for the delay

6 Likes

Any update on this? It has now been around 28 months.

Give us some good news. 31 months.

1 Like

Just ran into this today. This function still appears to be a no-op. Any updates?

This is still happening as of January 27, 2021.
For some reason, AddSelectionTuple does not behave properly and does some sort of “auto navigation.”

Do note that I have my GuiService.AutoSelectGuiEnabled set to false as I’m doing my initial selections manually while using roblox’s internal navigations.

GuiService:AddSelectionParent("Selection", frame)
-- 'frame' with, say 2 buttons, doesn't behave the same as
GuiService:AddSelectionTuple("Selection", {frame.Button1, frame.Button2})

In my 2 cases, I have a confirmation frame with 2 buttons that pops up over an existing UI.
Using AddSelectionTuple resulted in an unintentional behavior.
Although minor, navigating left, despite having the button’s NextSelectionLeft being nil and only having 2 buttons in the tuple, selects the nearest button that isn’t in the same selection group.

Here’s an example:

I’m using AddSelectionTuple here. Using AddSelectionParent will completely fix this problem. As you can see in the video, navigating left still selects the Skin button despite the NextSelectionLeft being nil and only the Back and Confirm button is in the same selection. (check console in the video). (Note that the reason why there is no hover animation + sound because I disable them when I enter a different UI State)

This was fixed with simply using AddSelectionParent but in my next case arises another problem.

I have a hierarchy that looks like this:
image
And a code that looks like this:

GuiService:AddSelectionTuple("CustomizeScreenOptions", {TitleScreen.Customize.Back, TitleScreen.Customize.Finish, unpack(TitleScreen.Customize.Options:GetChildren())})

It’s working as intended. I have all of these buttons in the same selection tuple, but like the first case, it will do it’s “auto navigation” despite the NextSelection property being nil, but this time it is part of the same selection tuple.

As you can see here, getting close enough to the Finish button will ignore the NextSelectionRight property that is set to nil.

Currently minor things in my case but I can definitely see my other works being bombarded with these bugs. I hope this will get looked at.

Edit: Upon working with console/controller UI, the API for these things are kinda meh and the documentations are not great. There is no way to add/remove objects to selection groups, get what GuiObjects are in what selection groups, or even get a table of selection groups. Hoping for a revamp or I’m stuck with rewriting my own navigation system.

1 Like

Irrelevant with the actual issue, Today I’ve created a ModuleScript that works around these bugs.

This is for anyone that finds these bugs conflicting with their objective.
I call it the SelectionService and simply replaces GuiService.

Right now, it only covers the basic navigation needs by bypassing bugs and adding extra functionalities to the Selection (created internally) used by GuiService.

SelectionService:

--// Settings //--
local Settings = {}
Settings.ContextActionPriority = Enum.ContextActionPriority.Default.Value + 1 -- The default BindAction priority to use
Settings.ContextActionResult = Enum.ContextActionResult.Sink -- Enum.ContextActionResult.Pass if you want other functions binded to the same key to be ran
Settings.StrictSelection = false -- Determines whether the GuiObject's Selection needs to match the next GuiObject's Selection when navigating
Settings.NavigationThumbsticks = {
	["LeftThumbstick"] = true, -- Determines whether Thumbstick1 can be used to navigate
	["RightThumbstick"] = false, -- Determines whether Thumbstick2 can be used to navigate
	["Target"] = 0.5, -- 0-1, Recommended > 0.25 - It will navigate if the Thumbstick distance >= Target
	["Reset"] = 0.1 -- 0-1, Recommended < 0.1 - It will re-allow navigation if the Thumbstick distance <= Reset
	-- Note that the thumbstick direction flag will also reset if a different navigation direction was triggered (i.e. Allowing Up -> Right -> Up without resetting at Reset) 
}
Settings.NavigationBinds = { -- Extra keybinds
	["Left"] = {
		--Enum.KeyCode.A,
		--Enum.KeyCode.Left,
		Enum.KeyCode.DPadLeft
	},
	["Right"] = {
		--Enum.KeyCode.D,
		--Enum.KeyCode.Right,
		Enum.KeyCode.DPadRight
	},
	["Up"] = {
		--Enum.KeyCode.W,
		--Enum.KeyCode.Up,
		Enum.KeyCode.DPadUp
	},
	["Down"] = {
		--Enum.KeyCode.S,
		--Enum.KeyCode.Down,
		Enum.KeyCode.DPadDown
	}
}

--// Dependencies //--
local GuiService = game:GetService("GuiService")
local ContextActionService = game:GetService("ContextActionService")

--// Initialization //--
local SelectionService = {}
SelectionService.__index = SelectionService

--// Selection Functions //--
function SelectionService:SelectGuiObject(guiObject)
	GuiService.SelectedObject = guiObject
end

function SelectionService:SelectPrimaryGuiObjectFromSelection(selection)
	if self.Selections[selection] then
		GuiService.SelectedObject = self.Selections[selection][1]
	end
end

function SelectionService:Unselect()
	GuiService.SelectedObject = nil
end

function SelectionService:AddToSelection(selection, tuple)
	tuple = typeof(tuple) == "Instance" and {tuple} or tuple
	for _, guiObject in ipairs(tuple) do
		if not table.find(self.Selections[selection], guiObject) then
			table.insert(self.Selections[selection], guiObject)
		end
	end
end

function SelectionService:RemoveFromSelection(selection, tuple)
	tuple = typeof(tuple) == "Instance" and {tuple} or tuple
	for _, guiObject in ipairs(tuple) do
		local position = table.find(self.Selections[selection], guiObject)
		if position then
			table.remove(self.Selections[selection], position)
		end
	end
end

function SelectionService:NewSelection(selection, tuple)
	if not self.Selections[selection] then
		self.Selections[selection] = {}
		if tuple then
			self:AddToSelection(selection, tuple)
		end
	end
end

function SelectionService:RemoveSelection(selection)
	if self.Selections[selection] then
		self.Selections[selection] = nil
	end
end

function SelectionService:GetSelectionFromGuiObject(guiObject)
	for selection, objects in pairs(self.Selections) do
		for _, object in ipairs(objects) do
			if object == guiObject then
				return selection
			end
		end
	end
end

function SelectionService:GetSelectedGuiObject()
	return GuiService.SelectedObject
end

--// Navigation Functions //--
function SelectionService:Navigate(direction)
	local currentGuiObject = self:GetSelectedGuiObject()
	if currentGuiObject == nil then
		return Enum.ContextActionResult.Sink
	end
	
	if direction == "Left" then
		if currentGuiObject.NextSelectionLeft ~= nil then
			if Settings.StrictSelection then
				if self:GetSelectionFromGuiObject(currentGuiObject) == self:GetSelectionFromGuiObject(currentGuiObject.NextSelectionLeft) then
					self:SelectGuiObject(GuiService.SelectedObject.NextSelectionLeft)
				end
			else
				self:SelectGuiObject(GuiService.SelectedObject.NextSelectionLeft)
			end
		end
	elseif direction == "Right" then
		if currentGuiObject.NextSelectionRight ~= nil then
			if Settings.StrictSelection then
				if self:GetSelectionFromGuiObject(currentGuiObject) == self:GetSelectionFromGuiObject(currentGuiObject.NextSelectionRight) then
					self:SelectGuiObject(GuiService.SelectedObject.NextSelectionRight)
				end
			else
				self:SelectGuiObject(GuiService.SelectedObject.NextSelectionRight)
			end
		end
	elseif direction == "Up" then
		if currentGuiObject.NextSelectionUp ~= nil then
			if Settings.StrictSelection then
				if self:GetSelectionFromGuiObject(currentGuiObject) == self:GetSelectionFromGuiObject(currentGuiObject.NextSelectionUp) then
					self:SelectGuiObject(GuiService.SelectedObject.NextSelectionUp)
				end
			else
				self:SelectGuiObject(GuiService.SelectedObject.NextSelectionUp)
			end
		end
	elseif direction == "Down" then
		if currentGuiObject.NextSelectionDown ~= nil then
			if Settings.StrictSelection then
				if self:GetSelectionFromGuiObject(currentGuiObject) == self:GetSelectionFromGuiObject(currentGuiObject.NextSelectionDown) then
					self:SelectGuiObject(GuiService.SelectedObject.NextSelectionDown)
				end
			else
				self:SelectGuiObject(GuiService.SelectedObject.NextSelectionDown)
			end
		end
	end
	return Settings.ContextActionResult
end

function SelectionService:GetNavigateDirectionFromThumbstickPosition(position)
	if math.abs(position.X) > math.abs(position.Y) then
		if position.X < 0 then
			return "Left"
		elseif position.X > 0 then
			return "Right"
		end
	elseif math.abs(position.Y) > math.abs(position.X) then
		if position.Y > 0 then
			return "Up"
		elseif position.Y < 0 then
			return "Down"
		end
	end
	
	return "Neutral"
end

function SelectionService:EnableNavigationLeft(priority)
	if #self.Binds.Left > 0 then
		ContextActionService:BindActionAtPriority("SelectionServiceNavigateLeft", (function(action, state, input)
			if state == Enum.UserInputState.Begin then
				self:Navigate("Left")
			end
		end), false, priority or Settings.ContextActionPriority, unpack(self.Binds.Left))
	end
end

function SelectionService:EnableNavigationRight(priority)
	if #self.Binds.Right > 0 then
		ContextActionService:BindActionAtPriority("SelectionServiceNavigateRight", (function(action, state, input)
			if state == Enum.UserInputState.Begin then
				self:Navigate("Right")
			end
		end), false, priority or Settings.ContextActionPriority, unpack(self.Binds.Right))
	end
end

function SelectionService:EnableNavigationUp(priority)
	if #self.Binds.Up > 0 then
		ContextActionService:BindActionAtPriority("SelectionServiceNavigateUp", (function(action, state, input)
			if state == Enum.UserInputState.Begin then
				self:Navigate("Up")
			end
		end), false, priority or Settings.ContextActionPriority, unpack(self.Binds.Up))
	end
end

function SelectionService:EnableNavigationDown(priority)
	if #self.Binds.Down > 0 then
		ContextActionService:BindActionAtPriority("SelectionServiceNavigateDown", (function(action, state, input)
			if state == Enum.UserInputState.Begin then
				self:Navigate("Down")
			end
		end), false, priority or Settings.ContextActionPriority, unpack(self.Binds.Down))
	end
end

function SelectionService:EnableNavigationByThumbstick(priority)
	local thumbsticks = {}
	if Settings.NavigationThumbsticks["LeftThumbstick"] then
		table.insert(thumbsticks, Enum.KeyCode.Thumbstick1)
	end
	if Settings.NavigationThumbsticks["RightThumbstick"] then
		table.insert(thumbsticks, Enum.KeyCode.Thumbstick2)
	end
	if #thumbsticks > 0 then
		ContextActionService:BindActionAtPriority("SelectionServiceNavigateByThumbstick", (function(action, state, input)
			local thumbstickFlags = (input.KeyCode == Enum.KeyCode.Thumbstick1 and self.Flags.Thumbstick1) or (input.KeyCode == Enum.KeyCode.Thumbstick2 and self.Flags.Thumbstick2)
			if thumbstickFlags then -- weird bug where `thumbstickFlags` would return false if thumbstick moves too fast (during initialization)
				if math.abs(input.Position.X) <= Settings.NavigationThumbsticks.Reset then
					thumbstickFlags.XReady = true
				end
				if math.abs(input.Position.Y) <= Settings.NavigationThumbsticks.Reset then
					thumbstickFlags.YReady = true
				end
				if math.abs(input.Position.X) >= Settings.NavigationThumbsticks.Target or math.abs(input.Position.Y) >= Settings.NavigationThumbsticks.Target then
					local direction = self:GetNavigateDirectionFromThumbstickPosition(input.Position)
					if direction ~= "Neutral" then
						local directionReady = ((direction == "Left" or direction == "Right") and "XReady") or ((direction == "Up" or direction == "Down") and "YReady")
						if thumbstickFlags[directionReady] then
							thumbstickFlags[directionReady] = false
							thumbstickFlags[(directionReady == "XReady" and "YReady") or (directionReady == "YReady" and "XReady")] = true
							self:Navigate(direction)
						end
					end
				end
			end
		end), false, priority or Settings.ContextActionPriority, unpack(thumbsticks))
	end
end

function SelectionService:EnableNavigation(priority)
	self:EnableNavigationLeft(priority)
	self:EnableNavigationRight(priority)
	self:EnableNavigationUp(priority)
	self:EnableNavigationDown(priority)
	self:EnableNavigationByThumbstick(priority)
end

function SelectionService:DisableNavigationLeft()
	ContextActionService:UnbindAction("SelectionServiceNavigateLeft")
end

function SelectionService:DisableNavigationRight()
	ContextActionService:UnbindAction("SelectionServiceNavigateRight")
end

function SelectionService:DisableNavigationUp()
	ContextActionService:UnbindAction("SelectionServiceNavigateUp")
end

function SelectionService:DisableNavigationDown()
	ContextActionService:UnbindAction("SelectionServiceNavigateDown")
end

function SelectionService:DisableNavigationByThumbstick()
	ContextActionService:UnbindAction("SelectionServiceNavigateByThumbstick")
end

function SelectionService:DisableNavigation()
	self:DisableNavigationLeft()
	self:DisableNavigationRight()
	self:DisableNavigationUp()
	self:DisableNavigationDown()
	self:DisableNavigationByThumbstick()
end

--// Navigation Binds //--
function SelectionService:BindToNavigate(direction, tuple)
	tuple = typeof(tuple) == "EnumItem" and {tuple} or typeof(tuple) == "table" and tuple or {}
	for _, key in ipairs(tuple) do
		if typeof(key) == "EnumItem" and not table.find(self.Binds[direction], key) then
			table.insert(self.Binds[direction], key)
		end
	end
	local navigateInfo = ContextActionService:GetBoundActionInfo("SelectionServiceNavigate" ..direction)
	if next(navigateInfo) ~= nil then
		if direction == "Left" then
			self:DisableNavigationLeft()
			self:EnableNavigationLeft(navigateInfo.priorityLevel)
		elseif direction == "Right" then
			self:DisableNavigationRight()
			self:EnableNavigationRight(navigateInfo.priorityLevel)
		elseif direction == "Up" then
			self:DisableNavigationUp()
			self:EnableNavigationUp(navigateInfo.priorityLevel)
		elseif direction == "Down" then
			self:DisableNavigationDown()
			self:EnableNavigationDown(navigateInfo.priorityLevel)
		end
	end
end

function SelectionService:UnbindFromNavigate(direction, tuple)
	tuple = typeof(tuple) == "EnumItem" and {tuple} or typeof(tuple) == "table" and tuple or {}
	for _, key in ipairs(tuple) do
		if typeof(key) == "EnumItem" then
			local position = table.find(self.Binds[direction], key)
			if position then
				table.remove(self.Binds[direction], position)
			end
		end
	end
	local navigateInfo = ContextActionService:GetBoundActionInfo("SelectionServiceNavigate" ..direction)
	if next(navigateInfo) ~= nil then
		if direction == "Left" then
			self:DisableNavigationLeft()
			self:EnableNavigationLeft(navigateInfo.priorityLevel)
		elseif direction == "Right" then
			self:DisableNavigationRight()
			self:EnableNavigationRight(navigateInfo.priorityLevel)
		elseif direction == "Up" then
			self:DisableNavigationUp()
			self:EnableNavigationUp(navigateInfo.priorityLevel)
		elseif direction == "Down" then
			self:DisableNavigationDown()
			self:EnableNavigationDown(navigateInfo.priorityLevel)
		end
	end
end

function SelectionService:AutoBindDefaults(selection)
	self:BindToNavigate("Left", Settings.NavigationBinds.Left)
	self:BindToNavigate("Right", Settings.NavigationBinds.Right)
	self:BindToNavigate("Up", Settings.NavigationBinds.Up)
	self:BindToNavigate("Down", Settings.NavigationBinds.Down)
end

--// Constructor //--
function SelectionService.new()
	GuiService.AutoSelectGuiEnabled = false
	GuiService.GuiNavigationEnabled = false
	
	local self = setmetatable({}, SelectionService)
	
	self.Binds = {}
	self.Binds.Left = {}
	self.Binds.Right = {}
	self.Binds.Up = {}
	self.Binds.Down = {}
	
	self.Flags = {}
	self.Flags.Thumbstick1 = {}
	self.Flags.Thumbstick1.XReady = true
	self.Flags.Thumbstick1.YReady = true
	self.Flags.Thumbstick2 = {}
	self.Flags.Thumbstick2.XReady = true
	self.Flags.Thumbstick2.YReady = true
	
	self.Selections = {}
	
	self:AutoBindDefaults()
	
	return self
end

return SelectionService.new()

Code + Main Functions:

local SelectionService = require(path)
SelectionService:EnableNavigation()
-- Yep, that's it. Upon requiring the module, it will automatically setup
-- the keybinds for you.
-- You will still need to select an object before enabling navigation.
SelectionService:SelectGuiObject(newGameButton)
-- If you need to disable navigation for things such as a busy state,
-- (i.e. waiting to load a player's inventory frame), you can use the following
SelectionService:DisableNavigation()
-- It is also recommended (by me, at least) to unselect the current selected
-- GuiObject just as a safety measure
SelectionService:Unselect()
-- If you want to use Settings.StrictSelection, you can use either/both of
-- the following functions:
SelectionService:NewSelection("MenuTabs", {
    MenuTabs.Stats,
    MenuTabs.Inventory,
    MenuTabs.Settings
})
if playerStatus == "Developer" then
    MenuTabs.AdminCommands.Visible = true
    SelectionService:AddToSelection("MenuTabs", {
        MenuTabs.AdminCommands
    })
end
-- If you offer players the ability to change keybinds, you can use:
SelectionService:UnbindFromNavigate("Up", Enum.KeyCode.DPadUp)
SelectionService:BindToNavigate("Up", Enum.KeyCode.W)

Just know that this only covers the basic navigation functionalities, nothing more. Feel free to take and expand upon this pseudo-service of mine :smiley:

1 Like

This is still occurring!

GuiService:AddSelectionTuple is still allowing every other selectable object to be Selected automatically even though the GuiService.SelectedObject is set to an object within this SelectionTuple.

This means that selecting isn’t accurate at all in certain instances

3 Likes

Hi there! It looks like there’s some confusion about GuiService:AddSelectionTuple and I definitely agree that the documentation could be more clear which we’re working on improving. AddSelectionTuple accepts a tuple and not a table. Example:

guiservice:AddSelectionTuple("Bla!", B1, B2, B3) -- correct 
guiservice:AddSelectionTuple("Bla!", {B1, B2, B3}) -- not correct

If you already have your guiobjects in a table, one way to pass them to AddSelectionTuple is to unpack them.

local t = {B1, B2, B3}
guiservice:AddSelectionTuple("Bla!", upack(t))

The incorrect case should probably be warned about in intellisense in the script editor, or throw a warning, etc. Seems somewhat broken API design that it intakes a table while it only works for tuple GuiObjects.

Marked your post as solution here though for visibility.

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