mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-05 13:17:49 +00:00
293 lines
6.5 KiB
Lua
293 lines
6.5 KiB
Lua
--[[
|
|
// CarouselView.lua
|
|
|
|
// View for a carousel. Used for GameGenre screen
|
|
// TODO: Support Vertical?
|
|
//
|
|
// Current this supports a focus that is aligned to the left (0, 0), in the future we
|
|
// could do other alignments if we need them
|
|
]]
|
|
local CoreGui = Game:GetService("CoreGui")
|
|
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
|
|
local Modules = GuiRoot:FindFirstChild("Modules")
|
|
|
|
local Utility = require(Modules:FindFirstChild('Utility'))
|
|
|
|
local function createCarouselView()
|
|
local this = {}
|
|
|
|
local items = {}
|
|
local padding = 0
|
|
local itemSizePercentOfContainer = 1
|
|
local focusItem = nil
|
|
|
|
local BASE_TWEEN_TIME = 0.2
|
|
|
|
local container = Utility.Create'ScrollingFrame'
|
|
{
|
|
Name = "CarouselContainer";
|
|
BackgroundTransparency = 1;
|
|
ClipsDescendants = false;
|
|
ScrollingEnabled = false;
|
|
Selectable = false;
|
|
ScrollBarThickness = 0;
|
|
}
|
|
|
|
local function isVisible(item)
|
|
return item.AbsolutePosition.x + item.AbsoluteSize.x >= 0 and
|
|
item.AbsolutePosition.x < GuiRoot.AbsoluteSize.x
|
|
end
|
|
local function getFocusSize()
|
|
local size = container.Size.Y.Offset
|
|
return UDim2.new(0, size, 0, size)
|
|
end
|
|
local function getNonFocusSize()
|
|
local size = container.Size.Y.Offset * itemSizePercentOfContainer
|
|
return UDim2.new(0, size, 0, size)
|
|
end
|
|
local function getItemSize(item)
|
|
if item == focusItem then
|
|
return getFocusSize()
|
|
else
|
|
return getNonFocusSize()
|
|
end
|
|
end
|
|
|
|
local function getItemLayoutPosition(index)
|
|
local focusIndex = this:GetItemIndex(focusItem)
|
|
local offsetFromFocus = index - focusIndex
|
|
local x, y = 0, 0
|
|
|
|
if index > focusIndex then
|
|
-- items to the right of focus need additional buffer due to focus size being larger
|
|
x = getFocusSize().X.Offset + offsetFromFocus * padding + (offsetFromFocus - 1) * getNonFocusSize().X.Offset
|
|
else
|
|
x = offsetFromFocus * padding + offsetFromFocus * getNonFocusSize().X.Offset
|
|
end
|
|
y = (index == focusIndex and 0) or (container.Size.Y.Offset - getNonFocusSize().Y.Offset) / 2
|
|
|
|
return UDim2.new(0, x, 0, y)
|
|
end
|
|
|
|
local function recalcLayout()
|
|
for i = 1, #items do
|
|
local item = items[i]
|
|
local size = getItemSize(item)
|
|
local position = getItemLayoutPosition(i)
|
|
if item:IsDescendantOf(game.Workspace) then
|
|
item:TweenSizeAndPosition(size, position, Enum.EasingDirection.Out, Enum.EasingStyle.Quad, 0, true)
|
|
else
|
|
item.Size = size
|
|
item.Position = position
|
|
end
|
|
end
|
|
end
|
|
|
|
--[[ Public API ]]--
|
|
function this:ChangeFocus(newFocus)
|
|
-- We don't use SetFocus() as that function will do a recalc. We want to get the next position
|
|
-- from current position, not recalcd postion for each item
|
|
if self:ContainsItem(newFocus) then
|
|
focusItem = newFocus
|
|
for i = 1, #items do
|
|
local item = items[i]
|
|
item:TweenSizeAndPosition(getItemSize(item), getItemLayoutPosition(i), Enum.EasingDirection.Out, Enum.EasingStyle.Quad, BASE_TWEEN_TIME, true)
|
|
end
|
|
end
|
|
end
|
|
|
|
function this:SetSize(newSize)
|
|
if newSize ~= container.Size then
|
|
container.Size = newSize
|
|
container.CanvasSize = UDim2.new(0, container.Size.X.Offset * 2, 1, 0)
|
|
recalcLayout()
|
|
end
|
|
end
|
|
|
|
function this:SetPosition(newPosition)
|
|
container.Position = newPosition
|
|
end
|
|
|
|
function this:SetPadding(newPadding)
|
|
if newPadding ~= padding then
|
|
padding = newPadding
|
|
recalcLayout()
|
|
end
|
|
end
|
|
|
|
function this:SetItemSizePercentOfContainer(value)
|
|
if value ~= itemSizePercentOfContainer then
|
|
itemSizePercentOfContainer = value
|
|
recalcLayout()
|
|
end
|
|
end
|
|
|
|
function this:SetParent(newParent)
|
|
container.Parent = newParent
|
|
end
|
|
|
|
function this:SetFocus(newFocusItem)
|
|
if self:ContainsItem(newFocusItem) and newFocusItem ~= focusItem then
|
|
focusItem = newFocusItem
|
|
recalcLayout()
|
|
end
|
|
end
|
|
|
|
function this:GetFocusItem()
|
|
return focusItem
|
|
end
|
|
|
|
function this:GetItemAt(index)
|
|
return items[index]
|
|
end
|
|
|
|
function this:GetFront()
|
|
return items[1]
|
|
end
|
|
|
|
function this:GetBack()
|
|
return items[#items]
|
|
end
|
|
|
|
function this:GetItemIndex(item)
|
|
for i = 1, #items do
|
|
if items[i] == item then
|
|
return i
|
|
end
|
|
end
|
|
|
|
return 0
|
|
end
|
|
|
|
function this:GetCount()
|
|
return #items
|
|
end
|
|
|
|
function this:GetVisibleCount()
|
|
local visibleItemCount = 0
|
|
for i = 1, #items do
|
|
if isVisible(items[i]) then
|
|
visibleItemCount = visibleItemCount + 1
|
|
end
|
|
end
|
|
|
|
return visibleItemCount
|
|
end
|
|
|
|
function this:GetFirstVisibleItemIndex()
|
|
for i = 1, #items do
|
|
if isVisible(items[i]) then
|
|
return i
|
|
end
|
|
end
|
|
end
|
|
|
|
function this:GetLastVisibleItemIndex()
|
|
for i = #items, 1, -1 do
|
|
if isVisible(items[i]) then
|
|
return i
|
|
end
|
|
end
|
|
end
|
|
|
|
function this:GetFullVisibleItemCount()
|
|
local containerSizeX = container.AbsoluteSize.x
|
|
-- remove focus from the size, and figure out how many other items can fit
|
|
local fittingSize = containerSizeX - getFocusSize().X.Offset
|
|
if fittingSize <= 0 then
|
|
return 0
|
|
end
|
|
|
|
local itemSize = getNonFocusSize().X.Offset + padding
|
|
local count = math.floor(fittingSize/itemSize) + 1
|
|
return count
|
|
end
|
|
|
|
function this:InsertCollectionFront(collection)
|
|
for i = #collection, 1, -1 do
|
|
local item = collection[i]
|
|
table.insert(items, 1, item)
|
|
item.Parent = container
|
|
end
|
|
recalcLayout()
|
|
end
|
|
|
|
function this:InsertCollectionBack(collection)
|
|
for i = 1, #collection do
|
|
local item = collection[i]
|
|
table.insert(items, item)
|
|
item.Parent = container
|
|
end
|
|
recalcLayout()
|
|
end
|
|
|
|
function this:RemoveAmountFromFront(amount)
|
|
for i = 1, amount do
|
|
local item = table.remove(items, 1)
|
|
item.Parent = nil
|
|
end
|
|
recalcLayout()
|
|
end
|
|
|
|
function this:RemoveAmountFromBack(amount)
|
|
for i = 1, amount do
|
|
local item = table.remove(items)
|
|
item.Parent = nil
|
|
end
|
|
recalcLayout()
|
|
end
|
|
|
|
function this:InsertFront(newItem)
|
|
table.insert(items, 1, newItem)
|
|
newItem.Parent = container
|
|
recalcLayout()
|
|
end
|
|
|
|
function this:InsertBack(newItem)
|
|
table.insert(items, newItem)
|
|
newItem.Parent = container
|
|
recalcLayout()
|
|
end
|
|
|
|
function this:RemoveFront()
|
|
local item = table.remove(items, 1)
|
|
item.Parent = nil
|
|
recalcLayout()
|
|
end
|
|
|
|
function this:RemoveBack()
|
|
local item = table.remove(items, #items)
|
|
item.Parent = nil
|
|
recalcLayout()
|
|
end
|
|
|
|
function this:RemoveItem(item)
|
|
for i = 1, #items do
|
|
if items[i] == item then
|
|
local removedItem = table.remove(items, i)
|
|
removedItem.Parent = nil
|
|
break
|
|
end
|
|
end
|
|
recalcLayout()
|
|
end
|
|
|
|
function this:RemoveAllItems()
|
|
for i = #items, 1, -1 do
|
|
local item = table.remove(items, #items)
|
|
item.Parent = nil
|
|
end
|
|
end
|
|
|
|
function this:ContainsItem(item)
|
|
if not item then
|
|
return false
|
|
end
|
|
return item.Parent == container
|
|
end
|
|
|
|
return this
|
|
end
|
|
|
|
return createCarouselView
|