overlay layout¶
a HUD is a panel that floats over the whole screen, on top of the game: a compass strip, a kill feed, a crosshair.
an overlay is the root a panel hangs from. two things make a root an overlay: it fills the viewport, and it lets mouse input fall through to the game behind it instead of swallowing every click.
the overlay root¶
Goo.Hud.Overlay() returns the canonical overlay container. you use it as the root your Build() returns:
var root = Hud.Overlay();
Overlay() bakes in four properties so you do not reinvent them per HUD:
Position = PositionMode.Absolute,Top = 0,Left = 0,Width = 100%,Height = 100%. the root covers the viewport regardless of where the host panel sits in layout.FlexDirection = FlexDirection.Column. yoga and CSS default a flex container toRow, which is right for prose but wrong for almost every HUD. an overlay stacks top-to-bottom, soOverlay()flips the default for you.PointerEvents = PointerEvents.None. without this, the invisible full-screen root sits in front of the game and eats every click. this is the single property that separates a HUD from a modal. more on it below.
Overlay() is a factory, so compose extra fields with a C# with expression. it must be called from inside Build(), the same rule as new Container().
anchoring to a corner¶
most HUDs pin their content to a screen region: a feed in the bottom-left, a score in the top-right, a compass at top-center. Goo.Layout.ResolveAnchor turns an anchor corner into the flex triple that places content there.
// from archive/Code/Demo/KeystrokeVisualizerUI.cs
var r = Layout.ResolveAnchor( Anchor ); // Anchor is a Layout.Anchor enum value
var root = Hud.Overlay() with
{
Padding = AnchorMargin,
JustifyContent = r.JustifyContent,
AlignItems = r.AlignItems,
};
var column = new Container { FlexDirection = r.FlexDirection, Gap = ChipGap };
Layout.Anchor has seven values: TopLeft, TopCenter, TopRight, BottomLeft, BottomCenter, BottomRight, and Center. ResolveAnchor returns an AnchorResolution carrying JustifyContent, AlignItems, and FlexDirection. set the first two on the overlay root and the third on the content column.
when you do not need the resolution pieces individually, Hud.Anchored does the wiring for you: it wraps your content in a full-bleed, pointer-through cell that pins it to the anchor corner.
root.Children.Add( Hud.Anchored( Layout.Anchor.TopCenter, content, key: "compass" ) );
the wrapper is always FlexDirection.Column; when you want the ColumnReverse stack-up behavior described above, put it on an inner stacking column, never on the anchored wrapper itself (top anchors would render at the bottom).
the FlexDirection is the subtle one. top-anchored rows use ColumnReverse and bottom-anchored rows use Column, so in both cases a freshly added child appears closest to the anchored edge. AnchorResolution.StacksUp is true when the direction is Column. a slide-in animation reads it to know whether new entries rise from below or drop from above.
a worked example: the compass HUD¶
overlay-layout has no live blessed demo in the current source tree: the compass demo that used to back this article, Code/Demo/Compass/CompassUI.cs, was moved to archive/Code/Demo/Compass/CompassUI.cs when demos were archived out of the active codebase, and no replacement has taken its place there. the snippets below are quoted verbatim from that archived file, a heading strip pinned to the top-center of the screen, and they show the standard overlay pattern: OnEnabled sizes the host panel, and Build() calls Hud.Overlay() and adds an anchored child.
GooPanel<T> exposes the engine panel as Panel. size it once in OnEnabled so the host covers the viewport:
// from archive/Code/Demo/Compass/CompassUI.cs
protected override void OnEnabled()
{
base.OnEnabled();
Panel.Style.Width = Length.Percent( 100 );
Panel.Style.Height = Length.Percent( 100 );
}
Build() returns an Overlay() root and adds the compass band as an anchored child at TopCenter:
// from archive/Code/Demo/Compass/CompassUI.cs
protected override Container Build()
{
var root = Hud.Overlay();
root.Children.Add( HudLayout.Anchored( Layout.Anchor.TopCenter, _view.Build(), key: "compass" ) );
return root;
}
HudLayout.Anchored is the demo's own local helper, not a library call; it predates Goo.Hud.Anchored and does the same wiring. new code should call the library factory directly, the same Hud.Anchored shown earlier in this article:
root.Children.Add( Hud.Anchored( Layout.Anchor.TopCenter, content, key: "compass" ) );
Hud.Anchored wraps the content in a full-screen absolute cell that resolves JustifyContent and AlignItems from Layout.ResolveAnchor, so the content lands at the chosen corner without you wiring up those properties by hand.
the band redraws as the player turns. the demo uses a CompassView helper that tracks heading state. OnUpdate ticks the view and rebuilds only when it reports a change:
// from archive/Code/Demo/Compass/CompassUI.cs
protected override void OnUpdate()
{
if ( _view.Tick( Scene, Time.Delta ) ) Rebuild();
base.OnUpdate();
}
the condition gate is the point worth keeping: an overlay that calls Rebuild() every frame rebuilds its whole subtree every frame. gate the rebuild on a meaningful change and the HUD costs nothing while the player holds still. verify this whole worked example in s&box before treating it as current.
the rest of the Hud family¶
Overlay() and Anchored() have a family of siblings on Goo.Hud, all returning a plain Container:
| factory | what it bakes in |
|---|---|
Fill() |
absolute, 100% x 100%, pointer-through. the standard full-bleed layer for shader overlays. resolves inside the nearest positioned ancestor; add Position = Relative to that ancestor if needed. |
Scrim( color ) |
absolute, all four edges pinned to zero, optional background color. it does not set PointerEvents: assign it yourself, since scrims typically toggle between None and All on open and close. |
DismissScrim( onDismiss, color ) |
a Scrim wired as an invisible click-catcher: PointerEvents = All, SwallowClick = true, and your close action on OnClick. draw the dismissable content after it in the sibling order. |
ScrollArea( height ) |
a live scroll viewport: OverflowY = Scroll, PointerEvents = All, Column layout. the engine needs all three of a bounded size, Scroll overflow, and pointer events, or the viewport looks inert; pass height to bound it here, or omit it and bound the panel from its parent. |
FillRect( frac, color ) |
an absolute left-anchored fill bar: 100% height, width = frac (clamped 0..1) of the parent. the parent supplies the track: Position = Relative, a bounded size, and the track background. |
Wallpaper( texture, path ) |
a Fill() carrying a full-bleed Image child. pass texture: null to load by path; path is ignored when texture is non-null. must be called from within Build(). |
Spacer() |
sets only FlexGrow = 1. pushes siblings apart; everything else is caller-controlled. |
Divider( color ) |
a 1px full-width horizontal rule. defaults to a low-alpha white line; pass a color to override. |
Particles( field, draw ) |
a self-driving particle overlay: absolute, full-screen, pointer-through. the field steps itself in the paint callback; spawn into it from any handler. see particles. |
factory output is fully overridable: goo style lists resolve last-declared-wins, so a with expression placed after the factory call overrides any field it baked in, including Position, size, and PointerEvents. Hud.Fill() with { PointerEvents = PointerEvents.All } is the idiom for an interactive full-bleed layer. the only ordering rule is that the override comes after the factory, never before.
pointer events: the one rule that matters¶
an overlay is a full-screen panel sitting in front of the game. if it accepts pointer events, it intercepts every click, and the game stops responding to the mouse even though the overlay looks empty. PointerEvents = PointerEvents.None is what makes the overlay see-through to input.
Hud.Overlay() sets it on the root. when you build the overlay by hand, set it yourself, on the root and on any purely-decorative child that should not catch the cursor. a HUD element that does need clicks (a button baked into the overlay) sets PointerEvents = PointerEvents.All on just that element to opt back in.
see also¶
- overview - the model, blobs, and the tree
- arranging children - flex direction, justify, and align, the layout surface anchoring builds on
- container-reference - the full property surface, including
Position,PointerEvents, andOverflow - styles - init-only style properties and how to keep them dry
- animations - the rebuild loop and the animators a live HUD drives its motion with