Skip to content

feat: rmlui UI system - #610

Merged
sven-n merged 744 commits into
mainfrom
dev/rmlui-ui-system
Oct 13, 2026
Merged

sven-n merged 744 commits into
mainfrom
dev/rmlui-ui-system

Conversation

@nitoygo

@nitoygo nitoygo commented Sep 22, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Replaces the client’s legacy UI with a unified RmlUi system on the SDL_GPU renderer, covering login and character selection, HUD, inventory, dialogs, social windows, NPCs, events, and siege interfaces.

  • Supports Legacy theme, a Modern theme, with shared behavior and independently customizable presentation.
  • Moves layout, sizing, anchoring, and styling into RML/RCSS, with centralized UI scaling and theme-defined window placement.
  • Unifies reusable components, tooltips, dragging, and document lifecycle; retains native 3D content through render targets.
  • Removes obsolete UI toolkits and adds architecture guards, theme contract checks, and headless layout tests.
    Architecture, migration coverage, and known limitations are documented in docs/rmlui-ui-system/

Related issues

One of the problems this will solve is the:
#161

And opens up door for theme modders.

Checklist

  • I have read and followed docs/CODING_RULES.md.
  • I have built the project locally (see docs/build/README.md).
  • Changes are focused on one concern; the diff is as small as it reasonably can be. (large by nature — a UI system migration; landed in reviewable, working slices per window)

@r0naldoom

Copy link
Copy Markdown
Contributor

Heads-up for the layout side of this PR, since it touches UITransform and the HUD.

UI::Scaling::WorldViewport() on main returns the full window, and that is wrong. The original client rendered the world into REFERENCE_HEIGHT - 48, leaving the bottom band to the HUD frame, and docs/superpowers/specs/2026-08-28-responsive-bottom-hud-design.md says the world viewport, terrain culling and the camera frusta must all share the rounded HUD top. Today they share the full window instead, which makes the horizontal field of view about 11% wider than the original client's.

#614 makes WorldViewport() end at the rounded HUD top and keeps the full window only for top view. I diffed dev/rmlui-ui-system against main before writing this: WorldViewport() and SetupMainSceneViewport() are identical on both, so there is no behavioural conflict. The two PRs do touch UITransform.cpp and test_ui_scaling.cpp, so whoever rebases second will see a small textual conflict there.

Mentioning it mainly so the new UI system does not inherit the full-window assumption: the world band is the window minus the scaled HUD frame, not the whole window.

@nitoygo
nitoygo requested a review from sven-n September 23, 2026 08:38
@imyourm8

Copy link
Copy Markdown

This UI system is a game changer! Can't wait for this PR to be merged.

nitoygo added 26 commits October 5, 2026 12:53
A data-fit=fill slot now gets the window's content size as its minimum,
so a percentage that shrinks at a high UI scale or in a small window still
shows the whole panel. This replaces the zero-size fallback pass.

Character info's right-hand pieces and action row are pinned to #panel's
edges in both themes (same places at 190x429), so a wider content-sized
panel stretches like a filled one.
Its .stat-btn display rule had the same specificity as base.rcss's .hidden
and came later, so the + buttons showed with no points to spend.
A theme switch rebuilds each window's document hidden. Character info,
pet, party, quest log, both quest progress windows and NPC dialogue only
show their document from Show(bool), so an open window vanished while the
game still counted it open. Show the new document when the window is open,
as the options window already does.
The durability icons and party list use the dock transform but took the
uncovered edge in stretched 640 units, so at any UI scale but 100 % or a
non-4:3 window they sat off the docks (under an open panel at 80 %).
They now read the edge in their own units each frame, which also follows
a resize or UI-scale change, not only a window opening or closing.

The buff row and centred HUD text centre between both edges, so a left
dock moves them too (GetScreenLeft()).
Pet info is the second data-fit=fill window: its title and messages centre
on the panel, its group boxes are pinned to the right edge (same places at
190x429), and its corner close is a RmlUi target. The fill size helper
(UI::RmlBridge::FillPlacementSize) and the corner-close rule move to shared
code so character info and later windows reuse them.
Each placed window has its own slot space, but a grid's drop guide
compared itself with the picked item's position in its owner window's
space, so dragging from the inventory to the vault drew no guide. Take
the item's box from the pointer in the target grid's own space.
It was drawn in the stretched 640x480 space at the first dock column,
so on wide screens it was wider than the docks beside it and ignored the
UI scale. It now docks like its neighbours from a slot after character
info, its hit box reads #panel, and it no longer needs its own column
case in GetScreenWidth(). Its rewards box clips its rows to its area.
A focused field consumes Return, so the scripted press never reached the
key state the game submits from, unlike a physical press.
The warp list docks from a left region in the workspace. Its rows already
filled the dock's height; they now fill whatever height a theme's fill
slot gives, and its width comes from #panel, so a theme can dock it
top-left, centre-left or as a wider full-height column. A window can name
a smaller fill minimum than its content size; the move map's is its
chrome and three rows.
The friend list moves and sizes itself, so its slot only says where it
first opens (UI::Placement::InitialPosition()); the player's moves win
after that. Both themes keep today's bottom-right corner above the HUD.
Kanturu entry and the Cursed Temple entry and result panels get slots on a
panel-stage region: the original's 640x480 stage centred on the screen,
where 1dp is one of its units, each panel at its original height. Their
hit boxes read #panel, and the result banner centres over its panel
instead of the reference width.
A window drawn wholly by RmlUi whose hit box reads its #panel returns its
document from CObject::GetFillDocument(); CObject then sizes the panel
for a fill slot and the placement service re-applies the size after a
theme switch. 24 windows opt in: the docked RmlUi windows and the
centred NPC panels. Native grids and live 3D stay content-sized. The
native corner close reads the panel's width, so it stays on the corner
of a filled panel.
The HUD joins the workspace's layout model: HUD components receive a
position and available size from slots in shell regions that either
reserve space or overlay, keeping their own documents and controllers.
Phases H1-H4, open points, updated task status and a handover prompt.
The workspace gains a flex shell: header, a middle row (left, content, right) and footer, each
region reserving its space or overlaying the content area as the theme chooses. The main HUD is
one unit (#hud_layout) in a footer slot, placed through a small participant adapter
(PlacementParticipant, RegisterWorkspaceDocument) without being a CObject. The content area and
the minimap's clip now come from that slot; GetStripRect() and HudReserve() are gone.

Placement still runs immediately after a window opens or closes, so it never draws a frame out
of place; passive changes mark it stale for one pass in Update(). A hidden HUD collapses its
slot. The NPC panel stage stays outside the shell, centred on the whole screen as before.
…ow them

The workspace header now places both top corners: the MU Helper bar on the left and the modern
menu buttons on the right. A theme can cap a dock region at the content area (max-height); the
placement service then draws that region's windows at the scale that fits, so modern's docked
panels sit below the menu buttons instead of covering them. Legacy does not cap, so its docks
still reach the top as the original's did.
The chat stands in a chat-stack region on the content area's bottom-left: the log on the input
box, the input on the HUD, overlapping by one unit as the original drew them. Both keep their
native hit tests in HUD space; the slot's box is converted into their native positions, and a
log resize re-places at once so it grows upward. The document adapter gains options for an
owner's own measure, a slot kept while hidden, and a callback after placement.
…nt deferral

Eight event HUDs (castle timers, battle soccer, duel, Kanturu info, Empire Guardian timer,
Doppelganger frame, duel spectators) take their place from an event-hud region: the original's
640x480 frame standing on the HUD, so a theme writes their original coordinates and can anchor
them elsewhere. RegisterHudWindow() converts a slot's box to such a window's HUD-space position.
$win gains names for each, so they can be shown without their event.

Theme-owned window placement is done; tracked-deferrals.md records what is left of it.
…on checks

No window registers or opens the refinery or server-division interfaces, so GetScreenWidth()'s
column for them never applied. Dragging is decided for dialogs and the friend system only;
docked windows stay in their slots. Both themes were checked at 1920x1080, 1280x720 and a live
resolution switch.
drag-ui presses at one window pixel, moves to another over eight frames with the button held,
and releases there, so window dragging can be checked without a person at the mouse. $dialog and
$dialog title show a test confirmation.
@nitoygo

nitoygo commented Oct 11, 2026

Copy link
Copy Markdown
Collaborator Author

Hello @sven-n @Mosch0512, just committed a few bugfixes and docs cleanup.
(Also added theme inheritance feature)

Shouldn't break anything from your last pull/test of this branch :)

The README keeps a short feature list, release summary and build quick
start, and leaves platform setup to the build guide, which now covers
dependencies and first configuration. The running section is corrected
(the server comes from config.ini, port 44405, unless `connect` overrides
it) and documents every config.ini key the client reads, the command-line
flags, the chat commands and the diagnostic environment variables.
@nitoygo
nitoygo force-pushed the dev/rmlui-ui-system branch from f3cb583 to 4a8ea34 Compare October 11, 2026 17:25
@vguille vguille mentioned this pull request Oct 11, 2026
3 tasks done
@vguille

vguille commented Oct 11, 2026

Copy link
Copy Markdown

Hi @nitoygo ! I've opened the classic (0.97) theme PR against dev/rmlui-ui-system: #711

It's split into commits:

  • the generic C++ (theme capabilities and a few HUD model fields);
  • the theme itself (rml/rcss + the original art in Data/Interface/Classic and Data/Logo/Classic);
  • the classic login / character select scenes, kept separate so they're easy to drop.

The description lists the C++ the classic look needed, a couple of naming questions, and a few known issues. One of them is the thin dark line around sprites after the padding change, which I traced to the REPEAT sampler.

It's not finished yet, but I'd be really happy if you could take a look and give it a try when you have time. Any feedback is welcome. Thanks for the theme engine, it made this possible!

# Conflicts:
#	src/source/UI/Inventory/MyInventory.cpp
#	src/source/UI/NewUI/Inventory/NewUIItemEnduranceInfo.cpp
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants