This commit is contained in:
watrabi
2025-09-18 17:55:52 -04:00
commit 977f1ff4b8
15030 changed files with 17324420 additions and 0 deletions
@@ -0,0 +1,480 @@
-- Written by Kip Turner, Copyright ROBLOX 2015
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local Utility = require(Modules:FindFirstChild('Utility'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local GuiService = game:GetService('GuiService')
local DEFAULT_WINDOW_SIZE = UDim2.new(1,0,1,0)
local function ScrollingGrid()
local this = {}
this.Enum =
{
ScrollDirection = {["Vertical"] = 1; ["Horizontal"] = 2;};
StartCorner = {["UpperLeft"] = 1; ["UpperRight"] = 2; ["BottomLeft"] = 3; ["BottomRight"] = 4;};
--ChildAlignment = {["UpperLeft"] = 1; ["UpperRight"] = 2; ["BottomLeft"] = 3; ["BottomRight"] = 4;};
}
this.GridItems = {}
this.ItemSet = {}
this.ScrollDirection = this.Enum.ScrollDirection.Vertical
this.StartCorner = this.Enum.StartCorner.UpperLeft
this.FixedRowColumnCount = nil
--this.ChildAlignment = nil
this.CellSize = Vector2.new(100,100)
this.Padding = Vector2.new(0,0)
this.Spacing = Vector2.new(0,0)
function this:GetPadding()
return self.Padding
end
function this:SetPadding(newPadding)
if newPadding ~= self.Padding then
self.Padding = newPadding
self:RecalcLayout()
end
end
function this:GetSpacing()
return self.Spacing
end
function this:SetSpacing(newSpacing)
if newSpacing ~= self.Spacing then
self.Spacing = newSpacing
self:RecalcLayout()
end
end
function this:GetCellSize()
return self.CellSize
end
function this:SetCellSize(cellSize)
if cellSize ~= self.CellSize then
self.CellSize = cellSize
self:RecalcLayout()
end
end
function this:GetScrollDirection()
return self.ScrollDirection
end
function this:SetScrollDirection(newDirection)
if newDirection ~= self.ScrollDirection then
self.ScrollDirection = newDirection
self:RecalcLayout()
end
end
function this:GetStartCorner()
return self.StartCorner
end
function this:SetStartCorner(newStartCorner)
if newStartCorner ~= self.StartCorner then
self.StartCorner = newStartCorner
self:RecalcLayout()
end
end
function this:GetRowColumnConstraint()
return self.FixedRowColumnCount
end
function this:SetRowColumnConstraint(fixedRowColumnCount)
if fixedRowColumnCount < 1 then
fixedRowColumnCount = nil
end
if fixedRowColumnCount ~= self.FixedRowColumnCount then
self.FixedRowColumnCount = fixedRowColumnCount
self:RecalcLayout()
end
end
function this:GetClipping()
return self.Container.ClipsDescendants
end
function this:SetClipping(clippingEnabled)
self.Container.ClipsDescendants = clippingEnabled;
end
function this:GetVisible()
return self.Container.Visible
end
function this:SetVisible(isVisible)
self.Container.Visible = isVisible;
end
function this:GetSize()
return self.Container.Size
end
function this:SetSize(size)
self.Container.Size = size
end
function this:GetPosition()
return self.Container.Position
end
function this:SetPosition(position)
self.Container.Position = position
end
function this:GetParent()
return self.Container.Parent
end
function this:SetParent(parent)
self.Container.Parent = parent
end
function this:GetGuiObject()
return self.Container
end
-- Default selection handles the case of removing the last item in the grid while it is selected
-- Set to nil if do not want a default selection
function this:SetDefaultSelection(selectionObject)
self.DefaultSelection = selectionObject
end
function this:ResetDefaultSelection()
self.DefaultSelection = self.Container
end
----
function this:ContainsItem(gridItem)
return self.ItemSet[gridItem] ~= nil
end
function this:SortItems(sortFunc)
table.sort(self.GridItems, sortFunc)
self:RecalcLayout()
local selectedObject = self:FindAncestorGridItem(GuiService.SelectedCoreObject)
if selectedObject and self:ContainsItem(selectedObject) then
local thisPos = self:GetCanvasPositionForOffscreenItem(selectedObject)
if thisPos then
Utility.PropertyTweener(self.Container, 'CanvasPosition', thisPos, thisPos, 0, Utility.EaseOutQuad, true)
end
end
end
function this:AddItem(gridItem)
if not self:ContainsItem(gridItem) then
table.insert(self.GridItems, gridItem)
self.ItemSet[gridItem] = true
gridItem.Parent = self.Container
if GuiService.SelectedCoreObject == self.DefaultSelection then
GuiService.SelectedCoreObject = gridItem
end
self:RecalcLayout()
end
end
function this:RemoveItem(gridItem)
if self:ContainsItem(gridItem) then
for i, otherItem in pairs(self.GridItems) do
if otherItem == gridItem then
table.remove(self.GridItems, i)
-- Assign a new selection
if GuiService.SelectedCoreObject == gridItem then
GuiService.SelectedCoreObject = self.GridItems[i] or self.GridItems[i-1] or self.GridItems[1] or self.DefaultSelection
end
-- Clean-up
self.ItemSet[gridItem] = nil
gridItem.Parent = nil
self:RecalcLayout()
return
end
end
end
end
function this:RemoveAllItems()
local wasSelected = false
do
local currentSelection = GuiService.SelectedCoreObject
while currentSelection ~= nil and wasSelected == false do
wasSelected = wasSelected or self:ContainsItem(currentSelection)
currentSelection = currentSelection.Parent
end
end
for i = #self.GridItems, 1, -1 do
local removed = table.remove(self.GridItems, i)
self.ItemSet[removed] = nil
removed.Parent = nil
end
if wasSelected then
GuiService.SelectedCoreObject = self.Container
end
self:RecalcLayout()
self.Container.CanvasPosition = Vector2.new(0, 0)
end
function this:Get2DGridIndex(index)
-- 0 base index
local zerobasedIndex = index - 1
local rows, columns = self:GetNumRowsColumns()
local row, column;
-- TODO: implement StartCorner here
if self.ScrollDirection == self.Enum.ScrollDirection.Vertical then
row = math.floor(zerobasedIndex / columns)
column = zerobasedIndex % columns
else
column = math.floor(zerobasedIndex / rows)
row = zerobasedIndex % rows
end
return row, column
end
function this:GetNumRowsColumns()
local rows, columns = 0, 0
local windowSize = self.Container.AbsoluteWindowSize
local padding = self:GetPadding()
local cellSize = self:GetCellSize()
local cellSpacing = self:GetSpacing()
local adjustedWindowSize = Utility.ClampVector2(Vector2.new(0, 0), windowSize - padding, windowSize - padding)
local absoluteCellSize = Utility.ClampVector2(Vector2.new(1,1), cellSize + cellSpacing, cellSize + cellSpacing)
local windowSizeCalc = (adjustedWindowSize + cellSpacing) / absoluteCellSize
if self.ScrollDirection == self.Enum.ScrollDirection.Vertical then
columns = math.max(1, self:GetRowColumnConstraint() or math.floor(windowSizeCalc.x))
rows = math.ceil(math.max(1, #self.GridItems) / columns)
else
rows = math.max(1, self:GetRowColumnConstraint() or math.floor(windowSizeCalc.y))
columns = math.ceil(math.max(1, #self.GridItems) / rows)
end
return rows, columns
end
function this:GetGridPosition(row, column, gridItemSize)
local cellSize = self:GetCellSize()
local spacing = self:GetSpacing()
local padding = self:GetPadding()
return UDim2.new(0, padding.X + column * cellSize.X + column * spacing.X,
0, padding.Y + row * cellSize.Y + row * spacing.Y)
end
function this:GetGridItemSize()
return self.CellSize
--[[
if self.CellSize then
return self.CellSize
end
return UDim2.new(0, (self.Container.AbsoluteSize.X - ((self.Columns + 1) * self.CellPadding.X)) / self.Columns,
0, (self.Container.AbsoluteSize.Y - ((self.Rows + 1) * self.CellPadding.Y)) / self.Rows)
--]]
-- if self.ScrollDirection == EnumScrollDirection.Vertical then
-- return UDim2.new(0,(self.Container.AbsoluteSize.X - ((self.Columns + 1) * self.CellPadding.X)) / self.Columns, 0, self.Container.AbsoluteSize.Y / self.Rows)
-- else
-- return UDim2.new(0,self.Container.AbsoluteSize.Y / self.Rows, 0, (self.Container.AbsoluteSize.Y - ((self.Rows + 1) * self.CellPadding.Y)) / self.Rows)
-- end
end
function this:GetCanvasPositionForOffscreenItem(selectedObject)
-- NOTE: using <= and >= instead of < and > because scrollingframe
-- code may automatically bump it while we are observing the change
if selectedObject and self.Container and self:ContainsItem(selectedObject) then
if self.ScrollDirection == self.Enum.ScrollDirection.Vertical then
if selectedObject.AbsolutePosition.Y <= self.Container.AbsolutePosition.Y then
return Utility.ClampCanvasPosition(self.Container, Vector2.new(0, selectedObject.Position.Y.Offset)) -- - selectedObject.AbsoluteSize.Y/2))
elseif selectedObject.AbsolutePosition.Y + selectedObject.AbsoluteSize.Y >= self.Container.AbsolutePosition.Y + self.Container.AbsoluteWindowSize.Y then
return Utility.ClampCanvasPosition(self.Container, Vector2.new(0, -(self.Container.AbsoluteWindowSize.Y - selectedObject.Position.Y.Offset - selectedObject.AbsoluteSize.Y) )) --+ selectedObject.AbsoluteSize.Y/2))
end
else -- Horizontal
if selectedObject.AbsolutePosition.X <= self.Container.AbsolutePosition.X then
return Utility.ClampCanvasPosition(self.Container, Vector2.new(selectedObject.Position.X.Offset, 0))
elseif selectedObject.AbsolutePosition.X + selectedObject.AbsoluteSize.X >= self.Container.AbsolutePosition.X + self.Container.AbsoluteWindowSize.X then
return Utility.ClampCanvasPosition(self.Container, Vector2.new(-(self.Container.AbsoluteWindowSize.X - selectedObject.Position.X.Offset - selectedObject.AbsoluteSize.X), 0))
end
end
end
end
function this:RecalcLayout()
local padding = self:GetPadding()
local cellSpacing = self:GetSpacing()
local gridItemSize = self:GetGridItemSize()
local rows, columns = self:GetNumRowsColumns()
if self.ScrollDirection == self.Enum.ScrollDirection.Vertical then
self.Container.CanvasSize = UDim2.new(self.Container.Size.X.Scale, self.Container.Size.X.Offset, 0, padding.Y * 2 + rows * gridItemSize.Y + (math.max(0, rows - 1)) * cellSpacing.Y)
else
self.Container.CanvasSize = UDim2.new(0, padding.X * 2 + columns * gridItemSize.X + (math.max(0, columns - 1)) * cellSpacing.X, self.Container.Size.Y.Scale, self.Container.Size.Y.Offset)
end
local grid2DtoIndex = {}
for i = 1, #self.GridItems do
local row, column = self:Get2DGridIndex(i)
local gridItem = self.GridItems[i]
gridItem.Size = UDim2.new(0, gridItemSize.X, 0, gridItemSize.Y)
gridItem.Position = self:GetGridPosition(row, column, gridItemSize)
grid2DtoIndex[row] = grid2DtoIndex[row] or {}
grid2DtoIndex[row][column] = gridItem
end
for rowNum, row in pairs(grid2DtoIndex) do
for columnNum, column in pairs(row) do
local gridItem = grid2DtoIndex[rowNum][columnNum]
if gridItem then
if self.ScrollDirection == self.Enum.ScrollDirection.Vertical then
gridItem.NextSelectionUp = grid2DtoIndex[rowNum - 1] and grid2DtoIndex[rowNum - 1][columnNum] or nil
gridItem.NextSelectionDown = grid2DtoIndex[rowNum + 1] and grid2DtoIndex[rowNum + 1][columnNum] or nil
if gridItem.NextSelectionDown == nil and grid2DtoIndex[rowNum + 1] ~= nil then
gridItem.NextSelectionDown = self.GridItems[#self.GridItems]
end
gridItem.NextSelectionLeft = nil
gridItem.NextSelectionRight = nil
else
gridItem.NextSelectionLeft = grid2DtoIndex[rowNum] and grid2DtoIndex[rowNum][columnNum - 1] or nil
gridItem.NextSelectionRight = grid2DtoIndex[rowNum] and grid2DtoIndex[rowNum][columnNum + 1] or nil
if gridItem.NextSelectionRight == nil and grid2DtoIndex[0] and grid2DtoIndex[0][columnNum + 1] then
gridItem.NextSelectionRight = self.GridItems[#self.GridItems]
end
gridItem.NextSelectionUp = nil
gridItem.NextSelectionDown = nil
end
end
end
end
end
function this:Destroy()
if self.Container then
self.Container:Destroy()
end
end
do
local container = Utility.Create'ScrollingFrame'
{
Size = DEFAULT_WINDOW_SIZE;
Name = "Container";
BackgroundTransparency = 1;
ClipsDescendants = true;
ScrollingEnabled = false;
ScrollBarThickness = 0;
Selectable = false;
}
this.Container = container
this.DefaultSelection = this.Container
this.Container.Changed:connect(function(prop)
if prop == 'AbsoluteSize' then
this:RecalcLayout()
end
end)
this:RecalcLayout()
function this:FindAncestorGridItem(object)
if object ~= nil then
if self:ContainsItem(object) then
return object
end
return self:FindAncestorGridItem(object.Parent)
end
end
local lastSelectedObject = nil
GuiService.Changed:connect(function(prop)
if prop == 'SelectedCoreObject' then
local selectedObject = this:FindAncestorGridItem(GuiService.SelectedCoreObject)
if selectedObject and this:ContainsItem(selectedObject) then
-- print(selectedObject.NextSelectionUp, selectedObject.NextSelectionDown, selectedObject.NextSelectionLeft, selectedObject.NextSelectionRight)
local upDirection = (this.ScrollDirection == this.Enum.ScrollDirection.Vertical) and 'NextSelectionUp' or 'NextSelectionLeft'
local downDirection = (this.ScrollDirection == this.Enum.ScrollDirection.Vertical) and 'NextSelectionDown' or 'NextSelectionRight'
local upObject = selectedObject[upDirection]
local downObject = selectedObject[downDirection]
local nextPos, upPos, downPos;
local gridItemSize = this:GetGridItemSize()
local thisPos = this:GetCanvasPositionForOffscreenItem(selectedObject)
if lastSelectedObject then
local lastUpObject = lastSelectedObject[upDirection]
local lastDownObject = lastSelectedObject[downDirection]
if upObject and lastUpObject == selectedObject then
upPos = this:GetCanvasPositionForOffscreenItem(upObject)
upPos = upPos and upPos + gridItemSize / 2
elseif downObject and lastDownObject == selectedObject then
downPos = this:GetCanvasPositionForOffscreenItem(downObject)
downPos = downPos and downPos - gridItemSize / 2
end
end
if upPos and (upPos.Y < this.Container.CanvasPosition.Y or upPos.X < this.Container.CanvasPosition.X) then
nextPos = upPos
-- print('up' , nextPos , selectedObject.Position, lastSelectedObject and lastSelectedObject.Position)
elseif downPos and (downPos.Y > this.Container.CanvasPosition.Y or downPos.X > this.Container.CanvasPosition.X) then
nextPos = downPos
-- print('down' , nextPos , selectedObject.Position, lastSelectedObject and lastSelectedObject.Position)
else
nextPos = thisPos
-- print('this' , selectedObject.Name , nextPos , selectedObject.Position, lastSelectedObject and lastSelectedObject.Position, selectedObject.AbsolutePosition, this.Container.AbsolutePosition)
end
if nextPos then
-- print("nextPos" , selectedObject.Name , nextPos)
nextPos = Utility.ClampCanvasPosition(this.Container, nextPos)
if thisPos then --and thisPos ~= nextPos then
-- Sort of a hack to not snap on the last one
if (upObject and downObject) then
Utility.PropertyTweener(this.Container, 'CanvasPosition', thisPos, thisPos, 0, Utility.EaseOutQuad, true)
end
end
Utility.PropertyTweener(this.Container, 'CanvasPosition', this.Container.CanvasPosition, nextPos, 0.2, Utility.EaseOutQuad, true)
end
lastSelectedObject = selectedObject
else
lastSelectedObject = nil
end
end
end)
end
return this
end
return ScrollingGrid