add gs
This commit is contained in:
+252
@@ -0,0 +1,252 @@
|
||||
--[[
|
||||
Creates a Roact component for inifinite scrolling
|
||||
Props:
|
||||
scrollingFrameProps : dictionary - props for the scrolling frame
|
||||
.Selectable : bool - Whether or not this object should be selectable using joysticks (controller).
|
||||
.ClipsDescendants : bool - Determines whether Roblox will render any portions of its GUI descendants that are outside of its own borders.
|
||||
items : Array - An array of the input item data which is used to construct item component.
|
||||
itemSize : Vector2 - The size for each item in the scrolling frame.
|
||||
itemsPaddingOffset : int - The padding between each item.
|
||||
scrollingDirection : Enum.ScrollingDirection - The scrolling direction of the scrolling frame, can't be Enum.ScrollingDirection.XY.
|
||||
itemOffsetStart: int - The minimum distance of the selected guiobject to the window start border.
|
||||
itemOffsetEnd: int - The minimum distance of the selected guiobject to the window end border.
|
||||
customScrollDist: dictionary - Custom distances to trigger the scroll.
|
||||
generateKey : function() - Used to generate a name for the item.
|
||||
renderItem : function() -
|
||||
Input: item data, item index and an onSelectionGained callback
|
||||
Output: a Roact Component
|
||||
State:
|
||||
viewStart: int - The item start index.
|
||||
viewSize: int - The number of items can be put in the window.
|
||||
paddingStart: int - The padding to apply on the top / left side of the scrolling frame.
|
||||
]]
|
||||
|
||||
local GuiService = game:GetService("GuiService")
|
||||
local Modules = game:GetService("CoreGui").RobloxGui.Modules
|
||||
|
||||
local Utility = require(Modules.Shell.Utility)
|
||||
local Roact = require(Modules.Common.Roact)
|
||||
local WindowedScrollingFrame = Roact.PureComponent:extend("WindowedScrollingFrame")
|
||||
|
||||
|
||||
local function clipCanvasPosition(scrollingFrame, canvasPos)
|
||||
local canvasPosX = canvasPos.X
|
||||
local canvasPosY = canvasPos.Y
|
||||
canvasPosX = math.min(canvasPosX, scrollingFrame.CanvasSize.X.Offset - scrollingFrame.AbsoluteWindowSize.X)
|
||||
canvasPosX = math.max(0, canvasPosX)
|
||||
canvasPosY = math.min(canvasPosY, scrollingFrame.CanvasSize.Y.Offset - scrollingFrame.AbsoluteWindowSize.Y)
|
||||
canvasPosY = math.max(0, canvasPosY)
|
||||
return Vector2.new(canvasPosX, canvasPosY)
|
||||
end
|
||||
|
||||
function WindowedScrollingFrame:init()
|
||||
self.state = {
|
||||
viewStart = 0,
|
||||
viewSize = 0,
|
||||
paddingStart = 0,
|
||||
}
|
||||
|
||||
self.scrollingFrameRef = function(rbx)
|
||||
self.scrollingFrame = rbx
|
||||
end
|
||||
end
|
||||
|
||||
function WindowedScrollingFrame:onSelectionChanged(selectedItem)
|
||||
if not self.scrollingFrame then
|
||||
return
|
||||
end
|
||||
if selectedItem == nil or selectedItem == self.savedSelectedObject or not selectedItem:IsDescendantOf(self.scrollingFrame) then
|
||||
return
|
||||
end
|
||||
self.savedSelectedObject = selectedItem
|
||||
|
||||
local scrollingFrame = self.scrollingFrame
|
||||
local scrollingDirection = self.props.scrollingDirection or Enum.ScrollingDirection.Y
|
||||
local absoluteWindowSize = scrollingFrame.AbsoluteWindowSize
|
||||
local canvasPosition = scrollingFrame.CanvasPosition
|
||||
|
||||
local axisKey = "X"
|
||||
if scrollingDirection == Enum.ScrollingDirection.Y then
|
||||
axisKey = "Y"
|
||||
end
|
||||
|
||||
-- If our scrolling frame has zero height / width, let's not bother trying to
|
||||
-- recompute our sizing
|
||||
if absoluteWindowSize[axisKey] == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local itemOffsetStart = self.props.itemOffsetStart or 0
|
||||
local itemOffsetEnd = self.props.itemOffsetEnd or 0
|
||||
local customScrollDist = self.props.customScrollDist or {}
|
||||
--If the selected guiobject is off-window, we move it back into the window instantly
|
||||
--Then make the motion
|
||||
local instantPos;
|
||||
local tweenTargetPos;
|
||||
if scrollingDirection == Enum.ScrollingDirection.Y then
|
||||
local topDistance = selectedItem.AbsolutePosition.Y - scrollingFrame.AbsolutePosition.Y
|
||||
local bottomDistance = (scrollingFrame.AbsolutePosition + scrollingFrame.AbsoluteWindowSize - selectedItem.AbsolutePosition - selectedItem.AbsoluteSize).Y
|
||||
|
||||
local minDistTop = itemOffsetStart
|
||||
local minDistBottom = itemOffsetEnd
|
||||
|
||||
if topDistance < (customScrollDist.Top or minDistTop) then
|
||||
if topDistance < 0 then
|
||||
instantPos = Vector2.new(canvasPosition.X, canvasPosition.Y + topDistance)
|
||||
end
|
||||
tweenTargetPos = Vector2.new(canvasPosition.X, canvasPosition.Y - (minDistTop - topDistance))
|
||||
elseif bottomDistance < (customScrollDist.Bottom or minDistBottom) then
|
||||
if bottomDistance < 0 then
|
||||
instantPos = Vector2.new(canvasPosition.X, canvasPosition.Y - bottomDistance)
|
||||
end
|
||||
tweenTargetPos = Vector2.new(canvasPosition.X, canvasPosition.Y + minDistBottom - bottomDistance)
|
||||
end
|
||||
elseif scrollingDirection == Enum.ScrollingDirection.X then
|
||||
local leftDistance = selectedItem.AbsolutePosition.X - scrollingFrame.AbsolutePosition.X
|
||||
local rightDistance = (scrollingFrame.AbsolutePosition + scrollingFrame.AbsoluteWindowSize - selectedItem.AbsolutePosition - selectedItem.AbsoluteSize).X
|
||||
|
||||
local minDistLeft = itemOffsetStart
|
||||
local minDistRight = itemOffsetEnd
|
||||
|
||||
if leftDistance < (customScrollDist.Left or minDistLeft) then
|
||||
if leftDistance < 0 then
|
||||
instantPos = Vector2.new(canvasPosition.X + leftDistance, canvasPosition.Y)
|
||||
end
|
||||
tweenTargetPos = Vector2.new(canvasPosition.X - (minDistLeft - leftDistance), canvasPosition.Y)
|
||||
elseif rightDistance < (customScrollDist.Right or minDistRight) then
|
||||
if rightDistance < 0 then
|
||||
instantPos = Vector2.new(canvasPosition.X - rightDistance, canvasPosition.Y)
|
||||
end
|
||||
tweenTargetPos = Vector2.new(canvasPosition.X + minDistRight - rightDistance, canvasPosition.Y)
|
||||
end
|
||||
end
|
||||
if instantPos then
|
||||
instantPos = clipCanvasPosition(scrollingFrame, instantPos)
|
||||
Utility.PropertyTweener(scrollingFrame, "CanvasPosition", instantPos, instantPos, 0, Utility.EaseOutQuad, true, function()
|
||||
if tweenTargetPos then
|
||||
tweenTargetPos = clipCanvasPosition(scrollingFrame, tweenTargetPos)
|
||||
Utility.PropertyTweener(scrollingFrame, "CanvasPosition", instantPos, tweenTargetPos, 0.2, Utility.EaseOutQuad, true)
|
||||
end
|
||||
end)
|
||||
end
|
||||
if not instantPos and tweenTargetPos then
|
||||
tweenTargetPos = clipCanvasPosition(scrollingFrame, tweenTargetPos)
|
||||
Utility.PropertyTweener(scrollingFrame, "CanvasPosition", canvasPosition, tweenTargetPos, 0.2, Utility.EaseOutQuad, true)
|
||||
end
|
||||
end
|
||||
|
||||
function WindowedScrollingFrame:updateViewBounds()
|
||||
if not self.scrollingFrame then
|
||||
return
|
||||
end
|
||||
|
||||
local scrollingFrame = self.scrollingFrame
|
||||
local itemSize = self.props.itemSize
|
||||
local itemsPaddingOffset = self.props.itemsPaddingOffset or 0
|
||||
local scrollingDirection = self.props.scrollingDirection or Enum.ScrollingDirection.Y
|
||||
local absoluteWindowSize = scrollingFrame.AbsoluteWindowSize
|
||||
local canvasPosition = scrollingFrame.CanvasPosition
|
||||
|
||||
local axisKey = "X"
|
||||
if scrollingDirection == Enum.ScrollingDirection.Y then
|
||||
axisKey = "Y"
|
||||
end
|
||||
|
||||
-- If our scrolling frame has zero height / width, let's not bother trying to
|
||||
-- recompute our sizing
|
||||
if absoluteWindowSize[axisKey] == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
canvasPosition = clipCanvasPosition(scrollingFrame, canvasPosition)
|
||||
local itemTotalSize = (itemSize[axisKey] + itemsPaddingOffset)
|
||||
local viewSize = math.ceil(absoluteWindowSize[axisKey] / itemTotalSize) + 1
|
||||
local viewStart = math.floor(canvasPosition[axisKey] / itemTotalSize)
|
||||
local paddingStart = math.max(0, (viewStart - 1) * itemTotalSize)
|
||||
local shouldUpdate = viewSize ~= self.state.viewSize or viewStart ~= self.state.viewStart or paddingStart ~= self.state.paddingStart
|
||||
if shouldUpdate then
|
||||
self:setState({
|
||||
viewStart = viewStart,
|
||||
viewSize = viewSize,
|
||||
paddingStart = paddingStart,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function WindowedScrollingFrame:render()
|
||||
local items = self.props.items
|
||||
local generateKey = self.props.generateKey
|
||||
local renderItem = self.props.renderItem
|
||||
local itemSize = self.props.itemSize
|
||||
local itemsPaddingOffset = self.props.itemsPaddingOffset or 0
|
||||
local scrollingDirection = self.props.scrollingDirection or Enum.ScrollingDirection.Y
|
||||
assert(scrollingDirection ~= Enum.ScrollingDirection.XY, "Can't set ScrollingDirection as XY.")
|
||||
|
||||
local children = {}
|
||||
|
||||
children.UIListLayout = Roact.createElement("UIListLayout", {
|
||||
Padding = UDim.new(0, itemsPaddingOffset),
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
FillDirection = scrollingDirection == Enum.ScrollingDirection.Y and Enum.FillDirection.Vertical or Enum.FillDirection.Horizontal
|
||||
})
|
||||
|
||||
if scrollingDirection == Enum.ScrollingDirection.Y then
|
||||
children.UIPadding = Roact.createElement("UIPadding", {
|
||||
PaddingTop = UDim.new(0, self.state.paddingStart)
|
||||
})
|
||||
elseif scrollingDirection == Enum.ScrollingDirection.X then
|
||||
children.UIPadding = Roact.createElement("UIPadding", {
|
||||
PaddingLeft = UDim.new(0, self.state.paddingStart)
|
||||
})
|
||||
end
|
||||
|
||||
local lowerBound = math.max(1, self.state.viewStart)
|
||||
local upperBound = math.min(#items, self.state.viewStart + self.state.viewSize)
|
||||
|
||||
for i = lowerBound, upperBound do
|
||||
local key = generateKey and generateKey(i) or i
|
||||
children[key] = renderItem(items[i], i)
|
||||
end
|
||||
|
||||
local scrollingFrameProps = self.props.scrollingFrameProps or {}
|
||||
local canvasSize = nil
|
||||
if scrollingDirection == Enum.ScrollingDirection.Y then
|
||||
canvasSize = UDim2.new(1, 0, 0, #items * itemSize.Y + (#items - 1) * itemsPaddingOffset)
|
||||
elseif scrollingDirection == Enum.ScrollingDirection.X then
|
||||
canvasSize = UDim2.new(0, #items * itemSize.X + (#items - 1) * itemsPaddingOffset, 1, 0)
|
||||
end
|
||||
|
||||
return Roact.createElement("ScrollingFrame", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
ScrollingEnabled = false, --Don't let the default select logic affect canvas position
|
||||
CanvasSize = canvasSize,
|
||||
Selectable = scrollingFrameProps.selectable or false,
|
||||
ScrollBarThickness = 0,
|
||||
ClipsDescendants = scrollingFrameProps.clipsDescendants,
|
||||
BackgroundTransparency = 1,
|
||||
ScrollingDirection = scrollingDirection,
|
||||
[Roact.Ref] = self.scrollingFrameRef,
|
||||
[Roact.Change.CanvasPosition] = function() self:updateViewBounds() end,
|
||||
[Roact.Change.AbsoluteSize] = function() self:updateViewBounds() end,
|
||||
}, children)
|
||||
end
|
||||
|
||||
function WindowedScrollingFrame:didMount()
|
||||
self:updateViewBounds()
|
||||
end
|
||||
|
||||
function WindowedScrollingFrame:didUpdate(prevProps, prevState)
|
||||
if not prevProps.inFocus and self.props.inFocus then
|
||||
self.conn = GuiService:GetPropertyChangedSignal("SelectedCoreObject"):connect(function()
|
||||
self:onSelectionChanged(GuiService.SelectedCoreObject)
|
||||
end)
|
||||
elseif prevProps.inFocus and not self.props.inFocus then
|
||||
Utility.DisconnectEvent(self.conn)
|
||||
end
|
||||
|
||||
if self.props ~= prevProps then
|
||||
self:updateViewBounds()
|
||||
end
|
||||
end
|
||||
|
||||
return WindowedScrollingFrame
|
||||
Reference in New Issue
Block a user