459 lines
14 KiB
Lua
459 lines
14 KiB
Lua
local CoreGui = game:GetService("CoreGui")
|
|
local ContextActionService = game:GetService("ContextActionService")
|
|
local TweenService = game:GetService("TweenService")
|
|
|
|
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
|
|
local Utility = require(RobloxGui.Modules.Settings.Utility)
|
|
|
|
local INPUT_TYPE_ROW_HEIGHT = 30
|
|
local ACTION_ROW_HEIGHT = 20
|
|
local ROW_PADDING = 5
|
|
local COLUMN_PADDING = 5
|
|
|
|
local CORE_SECURITY_COLUMN_COLOR = Color3.new(0.1, 0, 0)
|
|
local DEV_SECURITY_COLUMN_COLOR = Color3.new(0, 0, 0)
|
|
|
|
local EXPAND_ROTATE_IMAGE_TWEEN_OUT = TweenInfo.new(0.150, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut)
|
|
local EXPAND_ROTATE_IMAGE_TWEEN_IN = TweenInfo.new(0.150, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut)
|
|
|
|
local ROW_PULSE = TweenInfo.new(0.5, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut, 0, true, 0)
|
|
local CONTAINER_SCROLL = TweenInfo.new(0.25, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut)
|
|
|
|
local container = nil
|
|
local boundInputTypeRows = {}
|
|
local boundInputTypesByRows = {}
|
|
local boundInputTypeActionRows = {}
|
|
local boundActionInfoByRows = {}
|
|
local inputTypesByActionRows = {}
|
|
local inputTypesByHeaders = {}
|
|
local headersByInputTypes = {}
|
|
local inputTypesExpanded = {}
|
|
|
|
local layoutOrderDirty = true
|
|
|
|
local rowTypePrecedence = {
|
|
BoundInputType = 3,
|
|
TableHeader = 2,
|
|
BoundAction = 1
|
|
}
|
|
|
|
local function sortInputTypeRows(a, b)
|
|
local inputTypeA = boundInputTypesByRows[a]
|
|
local inputTypeB = boundInputTypesByRows[b]
|
|
return tostring(inputTypeA) < tostring(inputTypeB)
|
|
end
|
|
|
|
local function sortActionRows(a, b)
|
|
local actionA = boundActionInfoByRows[a]
|
|
local actionB = boundActionInfoByRows[b]
|
|
if actionA and actionB then
|
|
local rowInputTypeA = inputTypesByActionRows[a]
|
|
local rowInputTypeB = inputTypesByActionRows[b]
|
|
if rowInputTypeA ~= rowInputTypeB then
|
|
return tostring(rowInputTypeA) < tostring(rowInputTypeB)
|
|
end
|
|
if actionA.isCore and not actionB.isCore then
|
|
return true
|
|
elseif not actionA.isCore and actionB.isCore then
|
|
return false
|
|
end
|
|
local stackOrderA = actionA.stackOrder
|
|
local stackOrderB = actionB.stackOrder
|
|
if stackOrderA and stackOrderB then
|
|
return stackOrderA > stackOrderB --descending sort
|
|
else
|
|
return true
|
|
end
|
|
else
|
|
return true
|
|
end
|
|
end
|
|
|
|
local function createEmptyRow(name, height)
|
|
local row = Utility:Create("Frame") {
|
|
Name = name,
|
|
BackgroundTransparency = 1,
|
|
ZIndex = 6,
|
|
Size = UDim2.new(1, 0, 0, height or 0)
|
|
}
|
|
local columnList = Utility:Create("UIListLayout") {
|
|
FillDirection = Enum.FillDirection.Horizontal,
|
|
SortOrder = Enum.SortOrder.LayoutOrder,
|
|
Padding = UDim.new(0, COLUMN_PADDING),
|
|
Parent = row
|
|
}
|
|
return row
|
|
end
|
|
|
|
local function createButtonRow(name, height)
|
|
local row = Utility:Create("TextButton") {
|
|
Name = name,
|
|
BackgroundTransparency = 1,
|
|
ZIndex = 6,
|
|
Text = "",
|
|
Size = UDim2.new(1, 0, 0, height or 0),
|
|
}
|
|
local columnList = Utility:Create("UIListLayout") {
|
|
FillDirection = Enum.FillDirection.Horizontal,
|
|
SortOrder = Enum.SortOrder.LayoutOrder,
|
|
Padding = UDim.new(0, COLUMN_PADDING),
|
|
Parent = row
|
|
}
|
|
return row
|
|
end
|
|
|
|
local function createEmptyColumn(row, columnName)
|
|
local column = Utility:Create("Frame") {
|
|
Name = columnName,
|
|
BackgroundColor3 = Color3.new(0, 0, 0),
|
|
BackgroundTransparency = 0.75,
|
|
BorderSizePixel = 0,
|
|
Size = UDim2.new(1, 0, 1, 0),
|
|
ZIndex = 6,
|
|
ClipsDescendants = true,
|
|
Parent = row
|
|
}
|
|
|
|
return column
|
|
end
|
|
|
|
local function createImageColumn(row, columnName, image, aspectRatio, imageSize)
|
|
local column = createEmptyColumn(row, columnName)
|
|
|
|
local aspectRatioConstraint = Utility:Create("UIAspectRatioConstraint") {
|
|
AspectRatio = aspectRatio or 1,
|
|
Parent = column
|
|
}
|
|
|
|
local imageLabel = Utility:Create("ImageLabel") {
|
|
Name = "ColumnImage",
|
|
BackgroundTransparency = 1,
|
|
Position = UDim2.new(0.5, 0, 0.5, 0),
|
|
Size = UDim2.new(imageSize or 1, 0, imageSize or 1, 0),
|
|
AnchorPoint = Vector2.new(0.5, 0.5),
|
|
ZIndex = 6,
|
|
Image = image,
|
|
Parent = column
|
|
}
|
|
|
|
return column
|
|
end
|
|
|
|
local function createTextColumn(row, columnName, text)
|
|
local column = createEmptyColumn(row, columnName)
|
|
|
|
local textLabel = Utility:Create("TextLabel") {
|
|
Name = "ColumnText",
|
|
BackgroundTransparency = 1,
|
|
Position = UDim2.new(0.5, 0, 0.5, 0),
|
|
Size = UDim2.new(1, -10, 1, -10),
|
|
AnchorPoint = Vector2.new(0.5, 0.5),
|
|
ZIndex = 6,
|
|
Text = text,
|
|
TextSize = 18,
|
|
TextColor3 = Color3.new(1, 1, 1),
|
|
TextXAlignment = Enum.TextXAlignment.Left,
|
|
Font = Enum.Font.SourceSans,
|
|
Parent = column
|
|
}
|
|
|
|
return column
|
|
end
|
|
|
|
local function createActionColumns(row, backgroundColor)
|
|
local x = 0
|
|
|
|
local insetWidth = ACTION_ROW_HEIGHT + (COLUMN_PADDING * 2)
|
|
local insetCol = createEmptyColumn(row, "Inset")
|
|
insetCol.LayoutOrder = 0
|
|
insetCol.BackgroundTransparency = 1
|
|
insetCol.Size = UDim2.new(0, insetWidth, 1, 0)
|
|
x = x + insetWidth + COLUMN_PADDING
|
|
|
|
local priorityWidth = 80
|
|
local priorityCol = createTextColumn(row, "Priority", "Priority")
|
|
priorityCol.LayoutOrder = 1
|
|
priorityCol.BackgroundColor3 = backgroundColor
|
|
priorityCol.Size = UDim2.new(0, priorityWidth, 1, 0)
|
|
x = x + priorityWidth + COLUMN_PADDING
|
|
|
|
local securityWidth = 80
|
|
local securityCol = createTextColumn(row, "Security", "Security")
|
|
securityCol.LayoutOrder = 2
|
|
securityCol.BackgroundColor3 = backgroundColor
|
|
securityCol.Size = UDim2.new(0, securityWidth, 1, 0)
|
|
x = x + securityWidth + COLUMN_PADDING
|
|
|
|
local nameCol = createTextColumn(row, "ActionName", "Action Name")
|
|
nameCol.LayoutOrder = 3
|
|
nameCol.BackgroundColor3 = backgroundColor
|
|
nameCol.Size = UDim2.new(1/4, 0, 1, 0)
|
|
|
|
local inputTypesCol = createTextColumn(row, "InputTypes", "Input Types")
|
|
inputTypesCol.LayoutOrder = 4
|
|
inputTypesCol.BackgroundColor3 = backgroundColor
|
|
inputTypesCol.Size = UDim2.new(3/4, -x - COLUMN_PADDING, 1, 0)
|
|
|
|
return insetCol, priorityCol, securityCol, nameCol, inputTypesCol
|
|
end
|
|
|
|
local function updateContainerCanvas()
|
|
debug.profilebegin("updateContainerCanvas")
|
|
|
|
if layoutOrderDirty then
|
|
layoutOrderDirty = false
|
|
local idx = 1
|
|
|
|
local inputTypeRowList = {}
|
|
|
|
for inputType, inputTypeRow in pairs(boundInputTypeRows) do
|
|
table.insert(inputTypeRowList, inputTypeRow)
|
|
end
|
|
|
|
table.sort(inputTypeRowList, sortInputTypeRows)
|
|
|
|
for i, inputTypeRow in pairs(inputTypeRowList) do
|
|
local inputType = boundInputTypesByRows[inputTypeRow]
|
|
inputTypeRow.LayoutOrder = idx; idx = idx + 1
|
|
local headerRow = headersByInputTypes[inputType]
|
|
if headerRow then
|
|
headerRow.LayoutOrder = idx; idx = idx + 1
|
|
end
|
|
|
|
local actionRows = boundInputTypeActionRows[inputType]
|
|
if actionRows then
|
|
local actionRowList = {}
|
|
for actionName, actionRow in pairs(actionRows) do
|
|
table.insert(actionRowList, actionRow)
|
|
end
|
|
|
|
table.sort(actionRowList, sortActionRows)
|
|
|
|
for i, actionRow in pairs(actionRowList) do
|
|
actionRow.LayoutOrder = idx; idx = idx + 1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
debug.profileend()
|
|
end
|
|
|
|
local function scrollContainerToRow(row)
|
|
local scrollOffset = row.AbsolutePosition.Y - container.AbsolutePosition.Y
|
|
local newCanvasPosition = container.CanvasPosition + Vector2.new(0, scrollOffset)
|
|
TweenService:Create(container, CONTAINER_SCROLL, { CanvasPosition = newCanvasPosition }):Play()
|
|
end
|
|
|
|
local ActionBindingsTab = {}
|
|
|
|
function ActionBindingsTab.initializeGui(tabFrame)
|
|
local scrollingFrame = Utility:Create("ScrollingFrame") {
|
|
Position = UDim2.new(0.5, 0, 0.5, 0),
|
|
Size = UDim2.new(1, -10, 1, -10),
|
|
AnchorPoint = Vector2.new(0.5, 0.5),
|
|
BorderSizePixel = 0,
|
|
ScrollBarThickness = 4,
|
|
BackgroundTransparency = 1,
|
|
ZIndex = 6,
|
|
Parent = tabFrame
|
|
}
|
|
container = scrollingFrame
|
|
|
|
local listLayout = Utility:Create("UIListLayout") {
|
|
Padding = UDim.new(0, ROW_PADDING),
|
|
Parent = scrollingFrame
|
|
}
|
|
listLayout.SortOrder = Enum.SortOrder.LayoutOrder
|
|
listLayout:GetPropertyChangedSignal("AbsoluteContentSize"):Connect(function() container.CanvasSize = UDim2.new(0, 0, 0, listLayout.AbsoluteContentSize.Y) end)
|
|
|
|
ActionBindingsTab.updateGuis()
|
|
|
|
ContextActionService.BoundActionAdded:connect(function(actionName, createTouchButton, actionInfo, isCore)
|
|
actionInfo.isCore = isCore
|
|
ActionBindingsTab.updateActionRows(actionName, actionInfo)
|
|
|
|
layoutOrderDirty = true
|
|
updateContainerCanvas()
|
|
end)
|
|
ContextActionService.BoundActionRemoved:connect(function(actionName, actionInfo, isCore)
|
|
actionInfo.isCore = isCore
|
|
ActionBindingsTab.removeActionRows(actionName, actionInfo)
|
|
|
|
layoutOrderDirty = true
|
|
updateContainerCanvas()
|
|
end)
|
|
end
|
|
|
|
function ActionBindingsTab.updateBoundInputTypeRow(inputType)
|
|
local existingRow = boundInputTypeRows[inputType]
|
|
if not existingRow then
|
|
local row = createButtonRow("BoundInputType", INPUT_TYPE_ROW_HEIGHT)
|
|
|
|
local expandImageCol = createImageColumn(row, "ExpandImage", "rbxasset://textures/ui/ExpandArrowSheet.png", 1, 0.35)
|
|
expandImageCol.ColumnImage.ImageRectSize = Vector2.new(21, 21)
|
|
expandImageCol.ColumnImage.ImageRectOffset = Vector2.new(0, 0)
|
|
|
|
local inputTypeCol = createTextColumn(row, "InputType", tostring(inputType))
|
|
inputTypeCol.Size = UDim2.new(1, -INPUT_TYPE_ROW_HEIGHT - COLUMN_PADDING, 1, 0)
|
|
inputTypeCol.ColumnText.Font = Enum.Font.SourceSansBold
|
|
|
|
local tableHeaderRow = createEmptyRow("TableHeader", ACTION_ROW_HEIGHT)
|
|
tableHeaderRow.Visible = false
|
|
local _, priorityCol, securityCol, nameCol, inputTypesCol = createActionColumns(tableHeaderRow, DEV_SECURITY_COLUMN_COLOR)
|
|
priorityCol.ColumnText.Font = Enum.Font.SourceSansBold
|
|
securityCol.ColumnText.Font = Enum.Font.SourceSansBold
|
|
nameCol.ColumnText.Font = Enum.Font.SourceSansBold
|
|
inputTypesCol.ColumnText.Font = Enum.Font.SourceSansBold
|
|
|
|
boundInputTypeRows[inputType] = row
|
|
boundInputTypesByRows[row] = inputType
|
|
inputTypesByHeaders[tableHeaderRow] = inputType
|
|
headersByInputTypes[inputType] = tableHeaderRow
|
|
|
|
tableHeaderRow.Parent = container
|
|
row.Parent = container
|
|
|
|
TweenService:Create(inputTypeCol, ROW_PULSE, { BackgroundColor3 = Color3.new(0.5, 0.5, 0.5) }):Play()
|
|
|
|
inputTypesExpanded[inputType] = false
|
|
row.MouseButton1Click:connect(function()
|
|
inputTypesExpanded[inputType] = not inputTypesExpanded[inputType]
|
|
|
|
local inputTypeActionRows = boundInputTypeActionRows[inputType]
|
|
if not inputTypesExpanded[inputType] then
|
|
expandImageCol.ColumnImage.ImageRectOffset = Vector2.new(0, 0)
|
|
tableHeaderRow.Visible = false
|
|
if inputTypeActionRows then
|
|
for _, actionRow in pairs(inputTypeActionRows) do
|
|
actionRow.Visible = false
|
|
end
|
|
end
|
|
else
|
|
expandImageCol.ColumnImage.ImageRectOffset = Vector2.new(21, 0)
|
|
tableHeaderRow.Visible = true
|
|
if inputTypeActionRows then
|
|
for _, actionRow in pairs(inputTypeActionRows) do
|
|
actionRow.Visible = true
|
|
end
|
|
end
|
|
end
|
|
|
|
updateContainerCanvas()
|
|
if inputTypesExpanded[inputType] then
|
|
TweenService:Create(inputTypeCol, ROW_PULSE, { BackgroundColor3 = Color3.new(0.5, 0.5, 0.5) }):Play()
|
|
scrollContainerToRow(row)
|
|
end
|
|
end)
|
|
end
|
|
end
|
|
|
|
function ActionBindingsTab.updateActionRowForInputType(actionName, actionInfo, inputType)
|
|
local inputTypeActionRows = boundInputTypeActionRows[inputType]
|
|
if not inputTypeActionRows then
|
|
inputTypeActionRows = {}
|
|
boundInputTypeActionRows[inputType] = inputTypeActionRows
|
|
end
|
|
|
|
local existingRow = inputTypeActionRows[actionName]
|
|
if not existingRow then
|
|
local row = createEmptyRow("BoundAction", ACTION_ROW_HEIGHT)
|
|
row.Visible = inputTypesExpanded[inputType]
|
|
|
|
local inputTypeNames = {}
|
|
for i, inputType in pairs(actionInfo.inputTypes) do
|
|
inputTypeNames[i] = tostring(inputType)
|
|
end
|
|
|
|
local insetCol, priorityCol, securityCol, nameCol, inputTypesCol = createActionColumns(row, actionInfo.isCore and CORE_SECURITY_COLUMN_COLOR or DEV_SECURITY_COLUMN_COLOR)
|
|
priorityCol.ColumnText.Text = actionInfo.priorityLevel or "Default"
|
|
securityCol.ColumnText.Text = actionInfo.isCore and "Core" or "Developer"
|
|
nameCol.ColumnText.Text = actionName
|
|
inputTypesCol.ColumnText.Text = table.concat(inputTypeNames, ", ")
|
|
|
|
if actionInfo.isCore then
|
|
priorityCol.ColumnText.Font = Enum.Font.SourceSansItalic
|
|
securityCol.ColumnText.Font = Enum.Font.SourceSansItalic
|
|
nameCol.ColumnText.Font = Enum.Font.SourceSansItalic
|
|
inputTypesCol.ColumnText.Font = Enum.Font.SourceSansItalic
|
|
end
|
|
|
|
inputTypeActionRows[actionName] = row
|
|
inputTypesByActionRows[row] = inputType
|
|
boundActionInfoByRows[row] = actionInfo
|
|
row.Parent = container
|
|
|
|
if row.Visible then
|
|
TweenService:Create(nameCol, ROW_PULSE, { BackgroundColor3 = Color3.new(0.5, 0.5, 0.5) }):Play()
|
|
else
|
|
local inputTypeRow = boundInputTypeRows[inputType]
|
|
if inputTypeRow then
|
|
TweenService:Create(inputTypeRow.InputType, ROW_PULSE, { BackgroundColor3 = Color3.new(0.5, 0.5, 0.5) }):Play()
|
|
end
|
|
end
|
|
|
|
existingRow = row
|
|
end
|
|
end
|
|
|
|
function ActionBindingsTab.updateActionRows(actionName, actionInfo)
|
|
for _, inputType in pairs(actionInfo.inputTypes) do
|
|
ActionBindingsTab.updateBoundInputTypeRow(inputType)
|
|
ActionBindingsTab.updateActionRowForInputType(actionName, actionInfo, inputType)
|
|
end
|
|
end
|
|
|
|
function ActionBindingsTab.removeActionRows(actionName, actionInfo)
|
|
for _, inputType in pairs(actionInfo.inputTypes) do
|
|
local inputTypeActionRows = boundInputTypeActionRows[inputType]
|
|
if inputTypeActionRows then
|
|
local row = inputTypeActionRows[actionName]
|
|
row:Destroy()
|
|
inputTypeActionRows[actionName] = nil
|
|
|
|
--The following code looks weird. It's because Lua has no way to determine
|
|
--if a table is explicitly empty in both the array and dictionary parts.
|
|
--This does it though.
|
|
local isEmpty = true
|
|
for _, __ in pairs(inputTypeActionRows) do
|
|
isEmpty = false
|
|
break
|
|
end
|
|
|
|
if isEmpty then
|
|
local inputTypeRow = boundInputTypeRows[inputType]
|
|
if inputTypeRow then
|
|
inputTypeRow:Destroy()
|
|
boundInputTypeRows[inputType] = nil
|
|
end
|
|
local tableHeaderRow = headersByInputTypes[inputType]
|
|
if tableHeaderRow then
|
|
headersByInputTypes[tableHeaderRow] = nil
|
|
tableHeaderRow:Destroy()
|
|
headersByInputTypes[inputType] = nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
updateContainerCanvas()
|
|
container.UIListLayout:ApplyLayout()
|
|
end
|
|
|
|
function ActionBindingsTab.updateGuis()
|
|
local boundCoreActions = ContextActionService:GetAllBoundCoreActionInfo()
|
|
for actionName, actionInfo in pairs(boundCoreActions) do
|
|
actionInfo.isCore = true
|
|
ActionBindingsTab.updateActionRows(actionName, actionInfo)
|
|
end
|
|
local boundActions = ContextActionService:GetAllBoundActionInfo()
|
|
for actionName, actionInfo in pairs(boundActions) do
|
|
actionInfo.isCore = false
|
|
ActionBindingsTab.updateActionRows(actionName, actionInfo)
|
|
end
|
|
|
|
layoutOrderDirty = true
|
|
updateContainerCanvas()
|
|
end
|
|
|
|
return ActionBindingsTab |