GUIs and layout¶
Nova has the classic GUI stack: ScreenGui, SurfaceGui, BillboardGui,
Frame, TextLabel, TextButton, ImageLabel, ImageButton,
ScrollingFrame, TextBox, ViewportFrame (no), and the usual input events.
The important gap is the layout and modifier family. These classes are not implemented:
UIListLayout, UIGridLayout, UITableLayout, UIPadding, UIScale,
UICorner, UIGradient, UIStroke, CanvasGroup, UIAspectRatioConstraint,
UISizeConstraint, UITextSizeConstraint.
If your interface uses automatic layout, it will not lay itself out in Nova. The instances are skipped on load, so children keep whatever positions were saved in the file (often all zero).
What still works¶
AnchorPoint,Position,Size,SizeConstraint,AutomaticSizeClipsDescendants,ZIndex,Visible,BackgroundColor3,BackgroundTransparencyTextLabel/TextButton/TextBoxproperties, fonts, text bounds, and inputImageLabel/ImageButton, including 9-slice scalingScrollingFramewithCanvasSizeGuiObject:TweenPosition,TweenSize,TweenSizeAndPositionGuiObject.InputBegan/InputChanged/InputEndedScreenGui.IgnoreGuiInsetand the usual display properties
Replace UIListLayout with a layout pass¶
The smallest useful replacement is a function that positions siblings and updates the canvas size. Run it when the container changes or when a child is added/removed/resized.
-- ReplicatedStorage/Modules/Layout.lua
local Layout = {}
function Layout.column(container: ScrollingFrame, padding: number)
local y = 0
for _, child in ipairs(container:GetChildren()) do
if child:IsA("GuiObject") and child.Visible then
child.Position = UDim2.new(0, 0, 0, y)
y += child.AbsoluteSize.Y + padding
end
end
container.CanvasSize = UDim2.new(0, 0, 0, y)
end
function Layout.watch(container: ScrollingFrame, padding: number)
local function relayout()
Layout.column(container, padding)
end
container.ChildAdded:Connect(relayout)
container.ChildRemoved:Connect(relayout)
container:GetPropertyChangedSignal("AbsoluteSize"):Connect(relayout)
for _, child in ipairs(container:GetChildren()) do
if child:IsA("GuiObject") then
child:GetPropertyChangedSignal("AbsoluteSize"):Connect(relayout)
child:GetPropertyChangedSignal("Visible"):Connect(relayout)
end
end
relayout()
end
return Layout
local Layout = require(game:GetService("ReplicatedStorage"):WaitForChild("Modules"):WaitForChild("Layout"))
local list = script.Parent:WaitForChild("List")
Layout.watch(list, 8)
For horizontal rows, swap the axes:
function Layout.row(container: ScrollingFrame, padding: number)
local x = 0
for _, child in ipairs(container:GetChildren()) do
if child:IsA("GuiObject") and child.Visible then
child.Position = UDim2.new(0, x, 0, 0)
x += child.AbsoluteSize.X + padding
end
end
container.CanvasSize = UDim2.new(0, x, 0, 0)
end
If your children use scale-based sizes, AbsoluteSize is only valid after the
first render. Call the layout once on RunService.Heartbeat or after a
task.defer, then connect the signals.
Replace UIAspectRatioConstraint¶
local function keepAspect(gui: GuiObject, aspect: number)
local function update()
local width = gui.AbsoluteSize.X
if width > 0 then
gui.Size = UDim2.new(0, width, 0, math.floor(width / aspect))
end
end
gui:GetPropertyChangedSignal("AbsoluteSize"):Connect(update)
update()
end
Call keepAspect(frame, 16 / 9) for a 16:9 panel. If the container also changes
size, connect the container's AbsoluteSize as well.
Rounded corners, gradients, and strokes¶
Without UICorner, UIGradient, and UIStroke:
- Rounded corners: use an
ImageLabelwith a 9-slice rounded-rect image, or keep square corners. - Gradients: bake the gradient into an image and use
ImageLabel; setImageColor3for tint variations. - Strokes: put a slightly larger frame behind the content, or use a 9-slice border image.
- Group tint/clip: set
BackgroundTransparency/ImageTransparencyon children individually; useClipsDescendantson the container.
ViewportFrame¶
ViewportFrame is not implemented yet. For a 3D preview:
- Put the model somewhere in the world and point a
Cameraat it. - Use a
SurfaceGuion a part if the preview must be in the world. - For inventory-style previews, a pre-rendered thumbnail is usually simpler.
Debugging a layout that looks wrong¶
- Check the Output window for
skipping unknown class 'UIListLayout'(or another modifier). - Confirm the instances are actually missing in the Explorer.
- Print
AbsoluteSizeandPositionafter one frame; a zero size usually means the parent has not been sized yet. - Re-run the layout after
task.deferor onHeartbeat.
Next¶
- Build a UI without UIListLayout
- API reference: GuiObject
- API reference: ScreenGui
- API reference: UIListLayout (status and alternatives)