Control sizes: anchors, offsets and bounds
A working right/bottom HUD, anchor arithmetic, minimum/maximum bounds and why sizes reset.
Four edges instead of one position
Each Control edge is calculated relative to its parent's anchorable area: edge = anchor × parent_size + offset. Left/right use width; top/bottom use height. Anchor 0 is the axis start and 1 its end. Offset uses logical UI pixels. Size bounds and growth directions apply after this calculation.
| Placement | Anchors | Offsets |
|---|---|---|
| Top-right HUD, width 160 | Left = Right = 1, Top = Bottom = 0 | Left -176, Right -16, Top 16, Bottom 80 |
| Stretched bottom panel | Left 0, Right 1, Top = Bottom = 1 | Left 16, Right -16, Top -80, Bottom -16 |
| Fill the parent | FullRect | Zero offsets |
At parent width 960, the first example's left edge is 784 and right edge 944, leaving 16 pixels at the edge. At width 390 its left edge becomes 214 while width remains 160. This does not scale content.
Run the example
Add UiSampleStyle.cs. Attach AnchorsDemo.cs below to a separate level's Control root, build and run with F6. Resize the window: HP stays at the top right, the bottom panel stretches and the blue rectangle retains its specified size bounds.
AnchorsDemo.cs
using Reckfell;
public partial class AnchorsDemo : Control
{
public PanelContainer Hud { get; private set; } = null!;
public PanelContainer Bottom { get; private set; } = null!;
public Control Bounded { get; private set; } = null!;
public override void _Ready()
{
UiSampleStyle.Prepare(this);
Hud = new PanelContainer { Name = "Hud" }; AddChild(Hud);
Hud.AddChild(new Label { Text = "HP 100", HorizontalAlignment = HorizontalAlignment.Center });
Hud.SetAnchorsPreset(LayoutPreset.TopRight);
Hud.SetOffset(Side.Left, -176); Hud.SetOffset(Side.Right, -16);
Hud.SetOffset(Side.Top, 16); Hud.SetOffset(Side.Bottom, 80);
Bottom = new PanelContainer { Name = "Bottom" }; AddChild(Bottom);
Bottom.AddChild(new Label { Text = UiSampleStyle.Text("Панель у нижнего края", "Bottom-edge panel") });
Bottom.SetAnchorsPreset(LayoutPreset.BottomWide);
Bottom.SetOffset(Side.Left, 16); Bottom.SetOffset(Side.Right, -16);
Bottom.SetOffset(Side.Top, -80); Bottom.SetOffset(Side.Bottom, -16);
Bounded = new ColorRect
{
Name = "Bounded", Color = new Color(.15f, .35f, 1), Position = new Vector2(16, 110),
CustomMinimumSize = new Vector2(64, 32), CustomMaximumSize = new Vector2(160, 90),
MouseFilter = MouseFilterEnum.Ignore
};
AddChild(Bounded); Bounded.Size = new Vector2(999, 999);
}
}The code assigns an anchor preset before offsets. This avoids intermediate conflicts between opposite anchors. SetAnchor/SetAnchorAndOffset have options for retaining offsets and pushing the opposite anchor; individual assignment order can affect results. Prefer a preset for a standard arrangement.
SetAnchorsPreset(FullRect) does not mean zero offsets: keepOffsets = false changes anchors while recalculating offsets to preserve the current rectangle; true preserves numeric offsets. To fill a parent with matching offsets, use SetAnchorsAndOffsetsPreset(FullRect) and the intended margin, as the example root does. Check the final Size after minimum/maximum constraints.
Minimum and maximum
A built-in minimum depends on text, font, theme and container content. CustomMinimumSize adds a minimum requirement; it does not set the final size. Inspect GetCombinedMinimumSize() for the resulting requirement.
CustomMaximumSize bounds each axis. The example's request for 999 × 999 becomes 160 × 90, while requesting zero becomes 64 × 32. A negative maximum component means unbounded on that axis; zero does not mean unbounded. Do not set minimum above maximum. Nested controls and containers can also affect constraints; inspect the final layout, not only an entered field.
GrowHorizontal and GrowVertical choose how the rectangle changes under constraints: Begin, End or Both. They do not scale drawing. Scale is not a substitute for correct size and can conflict with container expectations.
When anchors are insufficient
A container owns its direct child Controls' layout. Configure minimum/maximum and SizeFlags rather than setting Position each frame. FullRect on a parent does not automatically fill the rest of the hierarchy. Content taller than the viewport needs scrolling or a different arrangement, not a negative size.
This image shows ResponsiveMenu: the card narrows and text wraps while font size stays unchanged. Changing the logical UI area, stretching rendered output and system DPI are different settings.