Containers: space allocation and scrolling
A complete C# layout: Expand/Fill, width bounds, ratios and revealing a list item.
Choose the layout owner
| Container | Purpose |
|---|---|
| HBoxContainer / VBoxContainer | Row / column |
| GridContainer | A grid with a chosen column count |
| FlowContainer and variants | Wrap items onto the next row/column |
| MarginContainer | Padding around content |
| PanelContainer | Themed background and content space |
| CenterContainer / AspectRatioContainer | Centering / aspect ratio |
| ScrollContainer | A scrollable visible region |
| TabContainer / SplitContainer | Tabs / split panes |
A container sorts eligible direct child Controls. An intermediate plain Entity breaks that relationship. Nest containers to build rows and columns. Do not stack several children directly in PanelContainer when they need sequential layout: put a VBox/HBox inside it.
Ordinary layout includes direct child Controls that are visible and not TopLevel. If an item falls out of a row, check these conditions before changing anchors. TopLevel excludes a Control from ordinary container sorting; it does not pin Position within the row.
Run the example
Add UiSampleStyle.cs. Attach ContainersDemo.cs to a Control root, build and run with F6. The top row shows A/B/C allocation; twenty buttons scroll below it. Navigate with Tab: FollowFocus reveals the selected item. Call RevealLast from your own jump-to-end button if needed.
ContainersDemo.cs
using Reckfell;
public partial class ContainersDemo : Control
{
public HBoxContainer Row { get; private set; } = null!;
public PanelContainer A { get; private set; } = null!;
public PanelContainer B { get; private set; } = null!;
public PanelContainer C { get; private set; } = null!;
public ScrollContainer Scroll { get; private set; } = null!;
public Button Last { get; private set; } = null!;
public override void _Ready()
{
UiSampleStyle.Prepare(this);
var margin = new MarginContainer(); AddChild(margin);
margin.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect, margin: 16);
var column = new VBoxContainer(); margin.AddChild(column);
column.AddChild(new Label { Text = UiSampleStyle.Text("Контейнеры и прокрутка", "Containers and scrolling") });
Row = new HBoxContainer(); column.AddChild(Row);
A = Item("A", 64); B = Item("B", 64); C = Item("C", 64);
A.CustomMaximumSize = new Vector2(140, -1);
A.SizeFlagsHorizontal = SizeFlags.ExpandFill; B.SizeFlagsHorizontal = SizeFlags.ExpandFill;
B.SizeFlagsStretchRatio = 2; C.SizeFlagsHorizontal = SizeFlags.Fill;
Row.AddChild(A); Row.AddChild(B); Row.AddChild(C);
Scroll = new ScrollContainer
{
SizeFlagsVertical = SizeFlags.ExpandFill, FollowFocus = true,
HorizontalScrollMode = ScrollContainer.ScrollMode.Disabled
};
column.AddChild(Scroll);
var list = new VBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill }; Scroll.AddChild(list);
for (int i = 1; i <= 20; i++)
{
Last = new Button { Text = UiSampleStyle.Text("Пункт ", "Item ") + i, CustomMinimumSize = new Vector2(0, 40) };
list.AddChild(Last);
}
}
private static PanelContainer Item(string text, float width)
{
var panel = new PanelContainer { CustomMinimumSize = new Vector2(width, 60) };
panel.AddChild(new Label { Text = text, HorizontalAlignment = HorizontalAlignment.Center });
return panel;
}
public void RevealLast()
{
Last.GrabFocus();
Callable.From(() => Scroll.EnsureControlVisible(Last)).CallDeferred();
}
}Fill and Expand have different jobs
| Flag | Result |
|---|---|
| Fill | Fill the rectangle allocated by the container |
| Expand | Participate in allocating extra space |
| ExpandFill | Receive extra space and fill it |
| ShrinkBegin / Center / End | Use a smaller size with the chosen alignment |
| Maximize | Extra priority/use of a maximum in a supporting container; not a synonym for FullRect |
A and B use ExpandFill, but A is capped at 140. B receives released space; C does not expand. SizeFlagsStretchRatio is a relative weight, not an exact percentage of final width: minima, maxima and gaps participate. In BoxContainer, Maximize boosts stretch priority for Expand; other items' minima and maximum bounds still apply. Check flag semantics for the specific container.
Scroll without expanding the window
The ScrollContainer receives available height through ExpandFill. It contains one column of twenty items whose vertical minimum exceeds the visible area. Horizontal scrolling is disabled and the column fills the available width. A nonshrinking label wider than the window still needs wrapping or a different structure; hiding a scrollbar does not fix its content.
EnsureControlVisible() needs a descendant of that ScrollContainer and current rectangles. Wait for container sorting after dynamically changing the tree. RevealLast defers the request; Size immediately after AddChild is not necessarily final layout. FollowFocus helps keyboard use but does not define the entire focus route.
Diagnostics
| Problem | Cause / action |
|---|---|
| Position resets itself | The parent container owns it |
| A direct child is not laid out | It must be a visible Control without TopLevel; wait for sorting after changes |
| Fill does not expand a row | Fill uses the allocated share; Expand participates in extra-space distribution |
| Two items do not divide space 1:2 | Minima, maxima and other participants also count |
| A hidden item leaves space | Check which Control is hidden and wait for sorting |
| Window grows instead of scrolling | Excessive ScrollContainer minimum or incorrect parent SizeFlags |
| Revealing an item fails immediately | Layout has not updated, or the object is not a descendant |