RapidGameFramework
Reusable Godot managers for data-driven small games
Loading...
Searching...
No Matches
UI Layout Standards

s

These standards exist to prevent repeated browser/mobile layout regressions in menus, popups, lists, achievement pages, shops, inventories, and future game UI.

Core Rules

  • Viewport size and user text-size preference may change layout geometry. Content changes, selection changes, hover states, audio toggles, and status text updates must not resize neighboring controls.
  • If a fix applies to more than one game, it belongs in a reusable system first. Scene scripts should call the shared system and only provide game data, callbacks, labels, and sizing intent.
  • Each screen must have one clear layout owner. Scene scripts may provide page intent, but sizing should flow through reusable framework helpers such as layoutManager.apply_responsive_shell(...), layoutManager.apply_vertical_budget(...), inventoryList, achievementScreen, statsPanel, resultsScreenController, and standard_settings_popup.
  • A page should divide its vertical space into fixed regions and flexible regions. Fixed regions include headers, nav bars, action rows, filters, section labels, and short status panels. Flexible regions include inventory lists, card grids, battle logs, achievement collections, and scroll bodies.
  • Long or variable text belongs in an internally scrollable region. It must not change the height of the page after user interaction.
  • Buttons, cards, list rows, and icon tiles must have stable minimum sizes. Changing selected state, disabled state, tooltip text, or icon texture must not alter their layout footprint.

Page Layout

Use layoutManager.apply_vertical_budget(...) for pages with mixed fixed and flexible controls.

The budget is authoritative. A page must be calculated from the viewport minus reserved header/footer/nav space, then every visible child region must be assigned a height inside that remaining budget. If fixed regions and flexible minimums cannot all fit, the framework scales them down and pushes excess content into the intended inner scroll regions. The page itself must not grow under the nav bar, even after selection changes, opening packs, changing result text, increasing text size, or switching tabs.

Recommended structure:

  • Fixed: title, helper text, filters, section headers, action rows, short status/result panels.
  • Flex: the main list, grid, scroll frame, battle log, or collection body.
  • Overflow: handled inside the flexible region with a scroll bar.

Do not size primary lists from item count, selected item, status message line count, or current content text. For example, a shop list should stay the same height when a booster pack is opened, a single is selected, or a result message changes.

Every dynamic page should have a smoke test that simulates its largest common content state, then calls layoutManager.vertical_budget_fits(...) against the reserved page height. This is the framework-level pass/fail check; screenshots are useful for review, but they are not the guardrail.

Composite widgets such as inventory tables, card grids, editors, and battle panels must be budgeted by the outer reusable widget, not only by an internal scroll body. The framework must be able to measure the widget's nested visible footprint and prove that it fits the reserved page height. Use layoutManager.measure_vertical_footprint(...) or layoutManager.vertical_budget_fits(...) in smoke tests for any page with nested reusable widgets. A test that only checks the parent panel height is insufficient.

Dynamic Status And Result Text

Variable-length messages must use the framework scroll-frame pattern:

  • Use layoutManager.wrap_label_in_scroll_frame(...) to wrap existing status, battle log, purchase result, run result, or helper text labels.
  • Use layoutManager.resize_text_scroll_frame(...) with content-derived visible line intent and min/max bounds. The frame may grow within its assigned fixed budget, but it must not push the main list/grid/nav off screen.
  • Use resultsScreenController for match/run/battle-complete modals so action buttons stay in a fixed footer and result text scrolls in the middle.
  • Do not solve clipping by hardcoding one game's status box height. If the message shape is reusable, add the behavior to layoutManager or the relevant shared controller and add a headless smoke assertion there.

Popups And Popovers

Use the right primitive for the interaction:

  • Use PopupPanel through shared controllers for true modal panels such as settings, stats, achievements, result screens, confirmations, and menu subpages.
  • Use an anchored in-scene PanelContainer for local popovers attached to a specific card, tile, artifact, inventory row, or HUD icon.
  • Do not use PopupPanel for small local detail popovers. Godot popup window sizing can interact with theme minimums and viewport clamping, causing the popover to stretch like a full-height panel.
  • Local popovers must set explicit size, custom_minimum_size, top-left anchors, z_index, opaque background style, and viewport-clamped position.

Achievements

  • Achievement definitions live in data/games/<game_id>/achievements.json.
  • Achievement unlock state is global across save slots. Reusable achievement pages and popups must refresh statsManager global achievement state before rendering rows so a new save cannot make previously unlocked achievements appear locked.
  • Main menu achievement pages must be able to load definitions directly from game data. They must not depend solely on data/build_profiles, because individual exports may exclude build profile catalogs.
  • Full-screen/main-menu achievement pages should use achievementScreen.render_page(...) with a visible scrollable collection area.
  • Pause-menu achievement popups should use achievementScreen.show_popup(...) or update_popup(...) with the shared viewport clamp.
  • Tests must verify rendered achievement rows, not guessed legacy node ids.

Audio And Settings

  • Audio-only settings changes must not trigger full menu/page relayout.
  • Mute buttons should update the stored setting, sync audioManager immediately, and refresh only the visible speaker icon.
  • Theme, text-size, density, and low-power changes may trigger full restyling. Volume and mute changes should sync buses without rebuilding the current menu.

Cross-Framework Ownership

Recurring UI bugs should map to these shared owners:

  • Achievements: statsManager owns global unlock state, and achievementScreen owns row rendering, filtering, popup/page sizing, and global-state refresh before render.
  • Layout budgets: layoutManager owns page region allocation, popup viewport clamps, framed status/result text, bottom navs, and density/theme application.
  • Main-menu mute: settingsManager owns stored audio flags, audioManager owns bus synchronization, and start/menu screens only own the icon control.
  • Card/list rows: inventoryList and adapters own reusable list sizing, tooltip metadata, sorting, and selection behavior.
  • Result modals: resultsScreenController, statsPanel, and achievementScreen own modal shells with scrollable content and fixed actions.

When a scene has custom behavior in any of these areas, it must either call the shared owner or document why the shared owner is insufficient and extend the shared owner first.

Required Tests

Whenever a shared UI helper, menu page, popup, list, or achievement screen is changed, add or update headless smoke coverage for the rendered behavior:

  • The expected page has nonzero visible content rows.
  • Main flexible list/grid regions have stable heights across selection/status changes.
  • Primary lists use available page space at a representative embedded web size such as 640 x 800.
  • Back/action buttons remain visible in mobile-safe popups.
  • Local popovers remain compact and do not use PopupPanel.
  • Audio mute toggles synchronize the audio bus without changing menu geometry.
  • Global achievement rows render from a blank/new save after global unlock state already exists.
  • Shared status/result frames cap height and keep overflow scrollable.

The test should fail when the player-visible contract fails. Avoid checking only internal state or one guessed node path when the problem is a rendered page.

Review Checklist

Before calling a UI issue complete:

  • Resize the viewport or simulate a narrow/tall embedded size.
  • Change selection and status/result text on the page.
  • Confirm no unrelated controls resize.
  • Confirm scrollable regions clearly contain overflow.
  • Confirm local popovers are compact, opaque, anchored near the source control, and clamped inside the viewport.
  • Confirm achievements render actual rows for each game menu and single-game build path.
  • Run the headless smoke suite and make sure new assertions cover the reported failure mode.