Files
rojo/plugin/src/App/Components/VirtualScroller.lua
2024-04-02 00:04:58 -07:00

169 lines
4.3 KiB
Lua

local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin
local Packages = Rojo.Packages
local Roact = require(Packages.Roact)
local Assets = require(Plugin.Assets)
local Theme = require(Plugin.App.Theme)
local bindingUtil = require(Plugin.App.bindingUtil)
local e = Roact.createElement
local VirtualScroller = Roact.Component:extend("VirtualScroller")
function VirtualScroller:init()
self.scrollFrameRef = Roact.createRef()
self:setState({
WindowSize = Vector2.new(),
CanvasPosition = Vector2.new(),
})
self.totalCanvas, self.setTotalCanvas = Roact.createBinding(0)
self.padding, self.setPadding = Roact.createBinding(0)
self:refresh()
if self.props.updateEvent then
self.connection = self.props.updateEvent:Connect(function()
self:refresh()
end)
end
end
function VirtualScroller:didMount()
local rbx = self.scrollFrameRef:getValue()
local windowSizeSignal = rbx:GetPropertyChangedSignal("AbsoluteWindowSize")
self.windowSizeChanged = windowSizeSignal:Connect(function()
self:setState({ WindowSize = rbx.AbsoluteWindowSize })
self:refresh()
end)
local canvasPositionSignal = rbx:GetPropertyChangedSignal("CanvasPosition")
self.canvasPositionChanged = canvasPositionSignal:Connect(function()
if math.abs(rbx.CanvasPosition.Y - self.state.CanvasPosition.Y) > 5 then
self:setState({ CanvasPosition = rbx.CanvasPosition })
self:refresh()
end
end)
self:refresh()
end
function VirtualScroller:willUnmount()
self.windowSizeChanged:Disconnect()
self.canvasPositionChanged:Disconnect()
if self.connection then
self.connection:Disconnect()
self.connection = nil
end
end
function VirtualScroller:refresh()
local props = self.props
local state = self.state
local count = props.count
local windowSize, canvasPosition = state.WindowSize.Y, state.CanvasPosition.Y
local bottom = canvasPosition + windowSize
local minIndex, maxIndex = 1, count
local padding, canvasSize = 0, 0
local pos = 0
for i = 1, count do
local height = props.getHeightBinding(i):getValue()
canvasSize += height
if pos > bottom then
-- Below window
if maxIndex > i then
maxIndex = i
end
end
pos += height
if pos < canvasPosition then
-- Above window
minIndex = i
padding = pos - height
end
end
self.setPadding(padding)
self.setTotalCanvas(canvasSize)
self:setState({
Start = minIndex,
End = maxIndex,
})
end
function VirtualScroller:didUpdate(previousProps)
if self.props.count ~= previousProps.count then
-- Items have changed, so we need to refresh
self:refresh()
end
end
function VirtualScroller:render()
local props, state = self.props, self.state
local items = {}
for i = state.Start, state.End do
local content = props.render(i)
if content == nil then
continue
end
items["Item" .. i] = e("Frame", {
LayoutOrder = i,
Size = props.getHeightBinding(i):map(function(height)
return UDim2.new(1, 0, 0, height)
end),
BackgroundTransparency = 1,
}, content)
end
return Theme.with(function(theme)
return e("ScrollingFrame", {
Size = props.size,
Position = props.position,
AnchorPoint = props.anchorPoint,
BackgroundTransparency = props.backgroundTransparency or 1,
BackgroundColor3 = props.backgroundColor3 or theme.BorderedContainer.BackgroundColor,
BorderColor3 = props.borderColor3 or theme.BorderedContainer.BorderColor,
CanvasSize = self.totalCanvas:map(function(s)
return UDim2.fromOffset(0, s)
end),
ScrollBarThickness = 9,
ScrollBarImageColor3 = theme.ScrollBarColor,
ScrollBarImageTransparency = props.transparency:map(function(value)
return bindingUtil.blendAlpha({ 0.65, value })
end),
TopImage = Assets.Images.ScrollBar.Top,
MidImage = Assets.Images.ScrollBar.Middle,
BottomImage = Assets.Images.ScrollBar.Bottom,
ElasticBehavior = Enum.ElasticBehavior.Always,
ScrollingDirection = Enum.ScrollingDirection.Y,
VerticalScrollBarInset = Enum.ScrollBarInset.ScrollBar,
[Roact.Ref] = self.scrollFrameRef,
}, {
Layout = e("UIListLayout", {
Padding = UDim.new(0, 0),
SortOrder = Enum.SortOrder.LayoutOrder,
FillDirection = Enum.FillDirection.Vertical,
}),
Padding = e("UIPadding", {
PaddingTop = self.padding:map(function(p)
return UDim.new(0, p)
end),
}),
Content = Roact.createFragment(items),
})
end)
end
return VirtualScroller