Panels and modals¶
Tick UI groups draw work into named, persistent containers
called panels. A window, a modal, a popup, and the built-in
debugger overlay are all panels. They differ in their PanelConfig
and in how you show them. This page covers the panel lifecycle,
the visibility states, and how Tick UI resolves which panel
owns input when several overlap.
What a panel is¶
A panel is identified by a string name (hashed via
string.GetHashCode()), so the same BeginPanel("settings", …)
call across frames refers to the same panel. Panels persist between
frames. Tick UI remembers their visibility, draw command list,
and whether they were used last frame.
if (ui.BeginPanel("settings", panelRect, PanelConfig.Default))
{
// panel body. Only runs while the panel is visible.
ui.EndPanel();
}
BeginPanel returns false when the panel is hidden, so always
guard the body with the return value. EndPanel only runs when
BeginPanel returned true.
The visibility state machine¶
Each panel has one of three states:
Visible: the panel renders this frame and processes input.Hiding: the closing frame.BeginPanelreturnstruefor one more frame so the body runs a final time, but the panel no longer accepts input.EndPanelthen moves it toHidden.Hidden: the panel is not active.BeginPanelreturnsfalse.
Toggle states with ShowPanel("id") and HidePanel("id"). Place
these calls outside the matching BeginPanel block. They are
declarative state changes that take effect from the next frame.
To start a panel hidden, set Hidden = true on the PanelConfig
passed to BeginPanel. The first time ShowPanel (or ShowModal)
is called, the panel transitions to Visible and renders.
First-frame initialisation¶
A panel can seed state the first time it appears, such as focusing
a textbox or resetting a scroll position. Detect this with
IsPanelFirstShown():
if (ui.BeginPanel("login", rect, PanelConfig.Default))
{
if (ui.IsPanelFirstShown())
ui.SetFocus("username");
ui.EndPanel();
}
IsPanelFirstShown() returns true for the frame the panel was
not used last frame. This covers the first appearance and any time
the panel was hidden then re-shown.
Configuration with PanelConfig¶
BeginPanel takes a PanelConfig:
| Field | Effect |
|---|---|
Hidden |
Start hidden; require an explicit ShowPanel / ShowModal. |
AcceptsInput |
When false, the panel never owns input even when it overlaps the mouse. Use for read-only HUDs. |
AutoCloseOnOutsideClick |
The panel hides itself when the user clicks outside its rect. The behaviour that powers popups. |
BackdropColor |
When set, Tick UI fills the screen with this colour before the panel body runs. Use for the dim "behind a modal" effect. |
Three named presets cover the common shapes:
PanelConfig.Default: visible, accepts input, no backdrop. The baseline window.PanelConfig.ModalDefault:Defaultplus a semi-transparent blackBackdropColor. Pair withShowModalfor the canonical modal.PanelConfig.OverlayDefault: visible, does not accept input. The HUD overlay shape that draws but never intercepts clicks.
The show call you use, not the config, controls whether a panel blocks input to anything underneath:
ShowPanel(id): visible, does not block.ShowModal(id): visible, setsBlocksInput. The input-priority walk inBeginFrameprefers any blocking panel over non-blocking ones, so nothing below the modal receives input.
Modals¶
A modal is a panel with a BackdropColor shown via ShowModal. The
backdrop draws the dim layer behind it automatically. ShowModal
makes it block input below.
The standard pattern:
if (ui.BeginPanel("confirm_quit", rect, new PanelConfig
{
Hidden = true,
BackdropColor = new Color(0, 0, 0, 0.5f),
}))
{
using (ui.Auto().Center(360, 160))
{
ui.FillRounded(panelColor, 8);
ui.Text("Quit without saving?", font, 18, Color.white,
TextAlign.MiddleCenter);
// ... buttons ...
}
ui.EndPanel();
}
if (userPressedQuit)
ui.ShowModal("confirm_quit");
Note the following:
BackdropColordraws the dim layer. The body needs no separateui.Filland noConsumeInput, because the modal already blocks input below viaShowModal.Hidden = trueplusShowModal(id)starts the panel hidden and opens it as a modal when triggered. To keep the panel always visible and modal whenever it is open, omitHiddenand callShowModalat first appearance.- The body rect (
screenRectabove) usually covers the whole screen so the centred dialog can position itself anywhere inside it. The panel's ownRectdefines its hit-test area. The backdrop draws independently across the whole root rect.
Popups¶
Popups are panels that close themselves when the user clicks
outside their rect. Use them for context menus, dropdowns, and
ephemeral choosers. There is a dedicated BeginPopup / EndPopup
API that wraps a PanelConfig with AutoCloseOnOutsideClick = true:
if (ui.BeginPopup("dropdown", rect, hidden: true))
{
// popup body
ui.EndPopup();
}
if (userClickedTrigger)
ui.ShowPopup("dropdown", mousePosition);
Popups dismiss on outside interaction and leave input to other
panels intact. To build a popup that also blocks input underneath,
use BeginPanel directly with
new PanelConfig { AutoCloseOnOutsideClick = true,
BackdropColor = ... }, then show via ShowModal.
How Tick UI resolves the hovered panel¶
When several panels overlap on screen, one of them owns the input
for that frame. Tick UI resolves this in BeginFrame:
- Tick UI sorts blocking panels (those shown via
ShowModal) to the end of the panel list. - The frame walks the panel list once and remembers the last panel that matched, so the topmost match wins.
- A panel matches if
BlocksInputis true, or ifAcceptsInputis true and its rect contains the mouse.
The selected panel becomes the hovered panel. Only widgets inside
it receive hover, click, and keyboard events for that frame. A
panel that is not the hovered panel still renders, but its
Button and Textbox widgets do not respond.
The built-in debugger overlay (TICK_DEBUGGER) stays on top of
your UI through a separate mechanism: its panels live in the
Debugger scene rather than the User scene, and EndFrame draws
the debugger scene in its own layer after every user panel.
Exclude a panel from input¶
A panel with AcceptsInput = false (the shape that
PanelConfig.OverlayDefault provides) draws normally but never
matches in the hovered-panel walk, regardless of mouse position.
Use it for read-only HUDs that draw on top of the game and do not
intercept clicks.
Consume input inside a panel body¶
ui.ConsumeInput() claims input from inside a panel body. With
ShowModal already blocking everything underneath, the dim-background
scenario rarely needs it. Use it for a non-blocking panel that
swallows clicks in a sub-rect, such as a transparent overlay where
clicks within a button must not fall through to the scene beneath.
Additional resources¶
- Stacks. The panel stack in context.
- Frame lifecycle. Where input-focus resolution sits in the per-frame flow.
- Show a modal. The procedure for the modal pattern described above.
- Animation guide. Combining panel show and hide with animations.