Tabs
Arrange the client's two-row side-tab bar, set per-row width and spacing, pick icons, and see which tabs the client owns and what tab_config.json holds.
7 min read
How the tab bar works#
The client's side-tab area is a 300 x 400 region in the bottom-right of the screen. It holds two rows of tab buttons, one pinned to the top and one to the bottom, with a 256 x 308 panel between them that shows the selected tab's interface. Each row holds up to six tabs, for a maximum of twelve, and the client centers whatever tabs a row actually has.
A tab is a small record: a name, a 32 x 32 icon, the interface it opens and the row it sits on. The tab buttons themselves are 46 px tall and skinned with the project's tabs.png, tab_hover.png and tab_clicked.png art. Their width and the gap between them are configurable per row.
Everything about tabs is stored in assets/data/tab_config.json. You edit that file in two places that share one implementation: the Tabs step of the New Project wizard, and the GUI Editor's Game Screen mode. This page describes the Game Screen mode workflow; the wizard step behaves the same way.
Opening the tab editor#
- Click GUI Editor in the top bar.
- Click the Game Screen Editor card under Game Settings.
The tab bar appears on the 900 x 600 replica in the center, the Available Tabs tray and the Selected Tab card sit in a row beneath the replica, and the Tab Configuration panel on the left holds the per-row geometry. A line under the panel's heading explains the gestures: drag tabs on the preview to reorder them, drag one in from Available Tabs to add it, and drag it back down there to remove it.
Adding, moving and removing tabs#
- Add a tab: drag a chip from the Available Tabs tray onto a row, or double-click the chip. The tray lists every tab interface scene in
assets/interface/screens/tabs/that is not already placed. When everything is placed it reads "Every tab this template ships is placed." - Reorder: drag a tab onto another tab. Dropping on the left half inserts before it; the right half inserts after. Dropping into a gap places it where the pointer is. While you drag, empty slots appear to show the remaining room in each row.
- Move between rows: drag a tab from one row to the other. A drop is refused when the target row already has six tabs.
- Remove: drag a tab down onto the tray, or select it and click Remove tab in the Selected Tab card. The tab's interface returns to the tray.
A status line under the row cards in the Tab Configuration panel keeps count, for example Row 1: 5/6 · Row 2: 4/6 · 9 tabs.
Each change is saved to tab_config.json at once and the replica redraws.
Renaming a tab and choosing its icon#
Click a tab on the replica. Its panel opens between the rows, exactly as it will in game, and the Selected Tab card fills in:
| Control | What it does |
|---|---|
| Icon preview and Icon... | Opens a picker grid of every PNG in the project's assets/sprites/ folder plus the bundled numbered icons 1.png to 12.png. The three chrome textures are excluded. |
| Tab name | The label stored for the tab. New tabs start with a name derived from the interface file, for example server_info becomes Server Info. |
| Interface line | Shows <name>.tscn · row N, or <name> · built into the client, row N for the reserved tabs. |
| Remove tab | Removes the tab. It does nothing for the reserved tabs, which cannot be removed. |
An icon you hand-picked is remembered for that interface, so dragging a tab out and back in does not lose it.
Where icons come from#
The client loads a tab's icon from res://assets/sprites/<icon> inside the game build, falling back to its own numbered icon for that slot when the file is missing. The editor seeds assets/sprites/ with 1.png to 12.png when a project is created. To use your own icon, import a PNG into the project's sprites (for example through the Assets tab, or by importing an orb icon in Game Screen mode, which lands in the same folder) and pick it with Icon.... Icons are shown at 32 x 32 centered on the button.
The button chrome (tabs.png, tab_hover.png, tab_clicked.png) ships per project in assets/sprites/ as well. The editor draws it as a nine-patch so a widened tab keeps its border instead of smearing it.
Tab width and spacing#
The Tab Configuration panel has a Tab Bar section with one card per row, each with two sliders and a fit readout:
| Slider | Range | Default |
|---|---|---|
| Tab width | 16 to 150 px | 48 px |
| Spacing | 0 to 32 px | 2 px |
The rows are configured independently because they rarely hold the same number of tabs. The readout under each card shows N tabs · used/300 px, or "at the limit" when the row is as wide as it can go.
The editor caps the width automatically: a row can never be authored wider than the 300 px bar, because the client clips rather than shrinks. The cap is (300 - (tabs - 1) x spacing) / tabs, recomputed whenever you add a tab, move one or change the spacing, so increasing spacing may pull the width down. Reset returns both rows to the client's own 48 px tabs with 2 px gaps.
Tabs the client owns#
Three tab names are reserved: inventory, equipment and skills. The client always loads its own scenes for them, regardless of what the project contains:
- They cannot be removed from the bar or dragged to the tray, and they never appear in the tray.
- The
tabs/inventory.tscn,tabs/equipment.tscnandtabs/skills.tscnfiles in a project are hidden from the GUI Editor's library and skipped when the game is packed. Editing them has no effect in game. - The replica previews reproductions of the client's panels instead: a 5 x 6 grid of 48 px inventory slots, eleven equipment slots at fixed positions, and a two-column skills grid.
The skills panel is data-driven on the client side. It is built from the project's assets/data/skills_config.json, so the preview shows your game's real skills. See Skills.
A server script attached to a tab interface can implement OnLogin(player, interfaceId), which the server calls for interfaces of type tab when the player logs in, so the panel is populated before the player first opens it.
tab_config.json reference#
Saved to assets/data/tab_config.json.
{
"version": 1,
"layout": {
"rows": [
{ "tab_width": 48, "spacing": 2 },
{ "tab_width": 48, "spacing": 2 }
]
},
"tabs": [
{
"index": 0,
"name": "Combat",
"icon": "1.png",
"interface_name": "combat",
"interface_id": 13,
"enabled": true,
"row": 0
}
]
}
layout#
| Field | Type | Notes |
|---|---|---|
rows | array of 2 objects | One entry per row, in order. |
rows[].tab_width | int | Clamped to 16 to 150 by both editor and client. |
rows[].spacing | int | Clamped to 0 to 32 by both editor and client. |
A file with a flat "layout": {"tab_width": 48, "spacing": 2} block is still accepted and applies to both rows. A file with no layout block renders with the client's defaults.
tabs entries#
| Field | Type | Notes |
|---|---|---|
index | int | Position in the flat list; rewritten on every save. |
name | string | The tab's name in the editor. The client stores it but never draws it — a tab shows only its icon. |
icon | string | File name in assets/sprites/. |
interface_name | string | The tab interface's file name without .tscn, or one of the reserved names. |
interface_id | int | Stamped on save from interface_database.json. The client opens tabs by this id, so it is absent for the reserved tabs, which have no database entry. |
enabled | bool | A disabled tab is drawn greyed out. The editor cannot disable a tab; a template may ship one. |
row | int | 0 for the top row, 1 for the bottom row. |
When a gameplay system is turned off, the tabs that pointed at its interfaces are stripped from this file; the reserved tabs always survive. See Templates and gameplay systems. Legacy entries for a built-in shop tab are dropped on load.
With AI (MCP)#
There is no dedicated MCP tool for the tab bar, but define_interface with type: "tab" creates a 256 x 308 tab interface in screens/tabs/, which then appears in the Available Tabs tray. See MCP tools reference.
