Files
core-staff/docs/07_UI.md
T

6.8 KiB

UI

Built on top of HyUI, a third-party library that wraps Hytale's native Custom UI API in an HTML-like markup (HYUIML). HyUI is bundled into the Staff jar via the shade plugin so you don't need it installed separately.

Reading the source

Each page lives in src/main/java/net/kewwbec/staff/ui/. Pages don't extend the SDK's InteractiveCustomUIPage anymore - they're plain classes that build a PageBuilder and expose an open(store) method. Navigation between pages is new OtherPage(...).open(store).

Page flow

/staffui
    |
    v
[StaffHubPage]
    |
    +-> Player Lookup -> [PlayerLookupPage]  (text search, click result for detail)
                              |
                              v
                          [PlayerDetailPage]
                              |
                              +-> Ban / TempBan / Mute / TempMute / Kick / Warn --> [PunishFormPage]
                              +-> Unban / Unmute (applied inline, page re-opens)
                              +-> History --> [HistoryPage]

Every non-hub page has a Back button. ESC always closes (lifetime is CanDismiss).

Page reference

Page Purpose
StaffHubPage Top-level menu. Currently just routes to Player Lookup; add buttons here for new sections.
PlayerLookupPage Text input + Search button. Online players are listed first; if you type a name not online, the players_seen table is consulted. Click a result to open detail.
PlayerDetailPage Player info + active ban/mute lines + action buttons. Unban / Unmute fire immediately (no form); the page reopens with refreshed data.
PunishFormPage Reason field, plus duration field for timed types. Validation errors re-open the same page with the entered values and a red error line.
HistoryPage Last 50 entries for the target. Read-only.

HyUI patterns we use

All pages follow the same skeleton:

public final class SomePage {
    private final PlayerRef playerRef;
    private final StaffUIContext sCtx;
    private final PageBuilder page;

    public SomePage(PlayerRef playerRef, StaffUIContext sCtx, ...args) {
        this.playerRef = playerRef;
        this.sCtx = sCtx;

        // Build the HYUIML based on constructor state
        String html = """
            <div class="page-overlay">
                <div class="container" data-hyui-title="Title">
                    <div class="container-contents">
                        <button id="someBtn">Do thing</button>
                    </div>
                </div>
            </div>
            """;

        this.page = PageBuilder.pageForPlayer(playerRef)
            .withLifetime(CustomPageLifetime.CanDismiss)
            .fromHtml(html);

        // Wire listeners by element id
        page.addEventListener("someBtn", CustomUIEventBindingType.Activating, (ignored, pCtx) -> {
            // pCtx.getValue("inputId", String.class).orElse("") to read input values
            // Navigate by opening a new page instance
        });
    }

    public void open(Store<EntityStore> store) { page.open(store); }
}

Reading input values

Inside an event listener, the second argument is the HyUI page context, which exposes getValue(id, type):

page.addEventListener("submitBtn", CustomUIEventBindingType.Activating, (ignored, pCtx) -> {
    String reason = pCtx.getValue("reasonInput", String.class).orElse("").trim();
    ...
});

Re-rendering after state changes

We do not mutate the page in place. Instead, we open a fresh page instance with new state:

new PunishFormPage(playerRef, sCtx, uuid, name, type, durationFromForm, reasonFromForm, "Error: ...").open(store);

This is simpler than HyUI's updatePage(true) re-render dance and keeps each page render side-effect-free. The cost is rebuilding the HTML each navigation - cheap compared to anything else we do.

HYUIML class reference (the bits we use)

  • <div class="page-overlay"> - root, dims background, captures input
  • <div class="container" data-hyui-title="..."> - main framed window with header
  • <div class="container-contents"> - inner content area
  • <button id="...">Text</button> - clickable button. Don't use class="back-button" with inline HTML - that class triggers HyUI's BackButton.ui template which expects a nested #HyUIButton child that inline buttons don't have, and your Activating listener fails to bind with "Target element... has no compatible Activating event". Plain <button> works for back buttons too; style separately if you need a different look.
  • <input type="text" id="..." value="..." placeholder="..."> - text input
  • <p> / <label> - text

The full HYUIML reference is in extras to look at/HyUI-Docs/hyuiml-htmlish-in-hytale.md.

Adding a new page

  1. Create src/main/java/net/kewwbec/staff/ui/MyPage.java following the skeleton above.
  2. Use HTMLish in the fromHtml block. Stick to alphanumeric IDs (HyUI sanitizes them, but bugs hide in non-trivial chars).
  3. Wire listeners with page.addEventListener(id, eventType, handler).
  4. To navigate to it, call new MyPage(...).open(store) from another page's handler.
  5. To open it from a command, do the same thing in the command's execute method.

Things to be aware of

  • HyUI must be on the classpath at runtime. This is handled by the shade plugin in pom.xml, which packs HyUI into the Staff jar. Don't drop the shade plugin without a replacement.
  • Sanitized IDs. Use plain alphanumeric ids in HYUIML. Use the same string in addEventListener - HyUI translates internally.
  • Escape user input when embedding into HTML strings. We have a tiny escape(String) helper in each page; use it for any value that came from a player.
  • value attribute is what carries text-field state across re-renders. When you rebuild a form page with an error, set value="..." on each input so the user doesn't lose what they typed.
  • No <script>, no full CSS. Logic is in Java; layout is via Hytale's LayoutMode and flex-weight via style="..." on <div>s if needed.

What this UI deliberately doesn't do

  • Network-wide active punishments view. Could be added as a Hub button if needed.
  • Bulk actions. No "ban everyone in this list" - intentional.
  • In-place updates. Every state change rebuilds the page. Simpler reasoning, slightly more network chatter.
  • Custom themes. We rely on HyUI's default Container/Button styling. Theming via <style> blocks is possible later.