Custom Pages System
The Custom Pages System allows you to create fully customizable GUI interfaces for players. This includes interactive dialogs, settings pages, choice menus, shop interfaces, and more.
Architecture
Section titled “Architecture”PageManager (Per-Player)├── Standard Pages (Page enum)│ └── None, Bench, Inventory, ToolsSettings, Map, etc.└── Custom Pages (CustomUIPage hierarchy) ├── BasicCustomUIPage - Simple display-only pages ├── InteractiveCustomUIPage<T> - Pages with event handling └── ChoiceBasePage - Choice/dialog pages
UICommandBuilder - Build UI element commandsUIEventBuilder - Bind events to UI elementsEventData - Pass data with eventsValue<T> - Reference UI document valuesPageManager
Section titled “PageManager”The PageManager handles opening, closing, and updating pages for each player. Access it through the Player component.
Key Methods
Section titled “Key Methods”| Method | Description |
|---|---|
setPage(ref, store, page) | Set a standard page (from Page enum) |
setPage(ref, store, page, canClose) | Set page with close-through-interaction option |
openCustomPage(ref, store, customPage) | Open a custom UI page |
openCustomPageWithWindows(ref, store, page, windows...) | Open custom page with inventory windows |
updateCustomPage(customPage) | Send updates to the current custom page |
handleEvent(ref, store, event) | Process incoming page events |
getCustomPage() | Get the currently open custom page |
Standard Page Enum
Section titled “Standard Page Enum”The Page enum defines built-in page types:
| Value | Description |
|---|---|
None | No page open (closes current page) |
Bench | Crafting bench interface |
Inventory | Player inventory |
ToolsSettings | Tool settings interface |
Map | World map |
MachinimaEditor | Machinima editing tools |
ContentCreation | Content creation tools |
Custom | Custom page (used internally) |
Page Acknowledgment System
Section titled “Page Acknowledgment System”The PageManager uses an acknowledgment system to ensure UI updates are processed in order. When a custom page is opened or updated, the client must acknowledge receipt before data events are processed. This prevents race conditions between UI updates and user interactions.
// Internal tracking - handled automaticallyprivate final AtomicInteger customPageRequiredAcknowledgments = new AtomicInteger();Opening a Standard Page
Section titled “Opening a Standard Page”Player playerComponent = store.getComponent(ref, Player.getComponentType());PageManager pageManager = playerComponent.getPageManager();
// Open inventorypageManager.setPage(ref, store, Page.Inventory);
// Open page that can be closed by clicking elsewherepageManager.setPage(ref, store, Page.Bench, true);
// Close any open pagepageManager.setPage(ref, store, Page.None);CustomUIPage Hierarchy
Section titled “CustomUIPage Hierarchy”CustomUIPage (Base Class)
Section titled “CustomUIPage (Base Class)”The abstract base class for all custom pages.
public abstract class CustomUIPage { protected final PlayerRef playerRef; protected CustomPageLifetime lifetime;
// Must implement - builds the initial page UI public abstract void build( Ref<EntityStore> ref, UICommandBuilder commandBuilder, UIEventBuilder eventBuilder, Store<EntityStore> store );
// Override to handle raw data events (use InteractiveCustomUIPage instead) public void handleDataEvent(Ref<EntityStore> ref, Store<EntityStore> store, String rawData);
// Override for cleanup when page is dismissed public void onDismiss(Ref<EntityStore> ref, Store<EntityStore> store);
// Rebuild the entire page UI protected void rebuild();
// Send partial updates to the page protected void sendUpdate(); protected void sendUpdate(UICommandBuilder commandBuilder); protected void sendUpdate(UICommandBuilder commandBuilder, boolean clear);
// Close this page (sets page to None) protected void close();}CustomPageLifetime Enum
Section titled “CustomPageLifetime Enum”Controls how the page can be closed:
| Value | Description |
|---|---|
CantClose | Player cannot close the page (e.g., death screen) |
CanDismiss | Player can dismiss with escape key |
CanDismissOrCloseThroughInteraction | Can dismiss or close by clicking outside |
BasicCustomUIPage
Section titled “BasicCustomUIPage”For simple pages that don’t need event handling:
public abstract class BasicCustomUIPage extends CustomUIPage { public BasicCustomUIPage(PlayerRef playerRef, CustomPageLifetime lifetime) { super(playerRef, lifetime); }
// Simplified build method - no event builder needed public abstract void build(UICommandBuilder commandBuilder);}InteractiveCustomUIPage
Section titled “InteractiveCustomUIPage”For pages that handle user interactions. The generic type T represents your event data class:
public abstract class InteractiveCustomUIPage<T> extends CustomUIPage { protected final BuilderCodec<T> eventDataCodec;
public InteractiveCustomUIPage( PlayerRef playerRef, CustomPageLifetime lifetime, BuilderCodec<T> eventDataCodec ) { super(playerRef, lifetime); this.eventDataCodec = eventDataCodec; }
// Override to handle typed event data public void handleDataEvent(Ref<EntityStore> ref, Store<EntityStore> store, T data);
// Extended sendUpdate with event builder support protected void sendUpdate( UICommandBuilder commandBuilder, UIEventBuilder eventBuilder, boolean clear );}UICommandBuilder
Section titled “UICommandBuilder”Build UI manipulation commands to send to the client.
Command Types
Section titled “Command Types”| Type | Description |
|---|---|
Append | Add elements from a document path |
AppendInline | Add elements from inline UI definition |
InsertBefore | Insert elements before a selector |
InsertBeforeInline | Insert inline elements before a selector |
Remove | Remove elements matching selector |
Set | Set property value on elements |
Clear | Clear children of elements |
Methods
Section titled “Methods”UICommandBuilder builder = new UICommandBuilder();
// Append UI document to page or containerbuilder.append("Pages/MyPage.ui");builder.append("#Container", "Components/Button.ui");
// Append inline UI definitionbuilder.appendInline("#List", "Label { Text: Hello; }");
// Insert before elementbuilder.insertBefore("#ExistingElement", "Components/Header.ui");builder.insertBeforeInline("#Footer", "Divider { }");
// Remove elementsbuilder.remove("#ElementToRemove");
// Clear container childrenbuilder.clear("#ListContainer");
// Set property valuesbuilder.set("#Label.Text", "Hello World");builder.set("#Checkbox.Value", true);builder.set("#Slider.Value", 50);builder.set("#Input.Value", 3.14);builder.set("#Element.Visible", false);
// Set with Message (for localization)builder.set("#Title.TextSpans", Message.translation("my.translation.key"));builder.set("#Desc.TextSpans", Message.raw("Plain text"));
// Set null valuebuilder.setNull("#OptionalField.Value");
// Set arraysbuilder.set("#Dropdown.Entries", dropdownEntries);
// Set with Value reference (for document references)builder.set("#Button.Style", Value.ref("Common/Button.ui", "DefaultStyle"));UIEventBuilder
Section titled “UIEventBuilder”Bind events to UI elements so the server receives callbacks when users interact.
Event Binding Types
Section titled “Event Binding Types”| Type | Description |
|---|---|
Activating | Element clicked/activated |
RightClicking | Right mouse button click |
DoubleClicking | Double click |
MouseEntered | Mouse enters element |
MouseExited | Mouse leaves element |
ValueChanged | Input value changed |
ElementReordered | Drag-reorder completed |
Validating | Input validation |
Dismissing | Page being dismissed |
FocusGained | Element gained focus |
FocusLost | Element lost focus |
KeyDown | Key pressed |
MouseButtonReleased | Mouse button released |
SlotClicking | Inventory slot clicked |
SlotDoubleClicking | Inventory slot double-clicked |
SlotMouseEntered | Mouse enters slot |
SlotMouseExited | Mouse exits slot |
DragCancelled | Drag operation cancelled |
Dropped | Drop completed |
SelectedTabChanged | Tab selection changed |
Methods
Section titled “Methods”UIEventBuilder eventBuilder = new UIEventBuilder();
// Simple event binding (locks interface while processing)eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#Button");
// With custom dataeventBuilder.addEventBinding( CustomUIEventBindingType.Activating, "#SaveButton", EventData.of("Action", "Save"));
// Without interface lock (for real-time updates)eventBuilder.addEventBinding( CustomUIEventBindingType.ValueChanged, "#SearchInput", EventData.of("@Query", "#SearchInput.Value"), false // Don't lock interface);
// Complex event data with multiple fieldseventBuilder.addEventBinding( CustomUIEventBindingType.Activating, "#SubmitButton", new EventData() .append("Action", "Submit") .append("@Name", "#NameInput.Value") .append("@Amount", "#AmountSlider.Value") .append("@Enabled", "#EnableCheckbox.Value"), true // Lock interface);Event Data Keys
Section titled “Event Data Keys”- Static keys (e.g.,
"Action","Index") - Sent as literal string values - Reference keys (prefixed with
@, e.g.,"@Name") - Reference UI element values at event time
EventData
Section titled “EventData”Create key-value pairs to send with events.
// Single key-valueEventData data = EventData.of("Action", "Save");
// Multiple values with chainingEventData data = new EventData() .append("Type", "Update") .append("Index", "5") .append("@Value", "#Input.Value");
// Enum valuesEventData data = new EventData() .append("Mode", MyEnum.OPTION_A); // Sends enum name as stringReference values from UI documents or provide direct values.
// Reference a value defined in a UI documentValue<String> styleRef = Value.ref("Common/Button.ui", "DefaultStyle");
// Direct valueValue<String> directValue = Value.of("my-value");
// Use with UICommandBuilder.set() for referencescommandBuilder.set("#Button.Style", styleRef);ChoiceBasePage
Section titled “ChoiceBasePage”A specialized interactive page for presenting choices to players, commonly used for shops, dialogs, and selection menus.
Structure
Section titled “Structure”public abstract class ChoiceBasePage extends InteractiveCustomUIPage<ChoicePageEventData> { private final ChoiceElement[] elements; private final String pageLayout;
public ChoiceBasePage(PlayerRef playerRef, ChoiceElement[] elements, String pageLayout) { super(playerRef, CustomPageLifetime.CanDismiss, ChoicePageEventData.CODEC); // ... }}ChoiceElement
Section titled “ChoiceElement”Base class for choice options:
public abstract class ChoiceElement { protected String displayNameKey; // Localization key for display name protected String descriptionKey; // Localization key for description protected ChoiceInteraction[] interactions; // Actions when selected protected ChoiceRequirement[] requirements; // Requirements to select
// Implement to render the choice button public abstract void addButton( UICommandBuilder commandBuilder, UIEventBuilder eventBuilder, String selector, PlayerRef playerRef );
// Check if player meets requirements public boolean canFulfillRequirements(Store<EntityStore> store, Ref<EntityStore> ref, PlayerRef playerRef);}ChoiceInteraction
Section titled “ChoiceInteraction”Actions executed when a choice is selected:
public abstract class ChoiceInteraction { public abstract void run(Store<EntityStore> store, Ref<EntityStore> ref, PlayerRef playerRef);}ChoiceRequirement
Section titled “ChoiceRequirement”Conditions that must be met to select a choice:
public abstract class ChoiceRequirement { public abstract boolean canFulfillRequirement( Store<EntityStore> store, Ref<EntityStore> ref, PlayerRef playerRef );}ChoicePageEventData
Section titled “ChoicePageEventData”Event data sent when a choice is selected:
public static class ChoicePageEventData { private int index; // Index of selected choice
public int getIndex() { return this.index; }}Complete Examples
Section titled “Complete Examples”Basic Information Page
Section titled “Basic Information Page”A simple page that displays information without interaction:
import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime;import com.hypixel.hytale.server.core.Message;import com.hypixel.hytale.server.core.entity.entities.player.pages.BasicCustomUIPage;import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder;import com.hypixel.hytale.server.core.universe.PlayerRef;
public class WelcomePage extends BasicCustomUIPage {
private static final String LAYOUT = "Pages/WelcomePage.ui"; private final String playerName; private final int onlineCount;
public WelcomePage(PlayerRef playerRef, String playerName, int onlineCount) { super(playerRef, CustomPageLifetime.CanDismiss); this.playerName = playerName; this.onlineCount = onlineCount; }
@Override public void build(UICommandBuilder commandBuilder) { commandBuilder.append(LAYOUT); commandBuilder.set("#Title.TextSpans", Message.translation("welcome.title").param("player", playerName)); commandBuilder.set("#OnlineCount.Text", String.valueOf(onlineCount)); commandBuilder.set("#ServerTime.Text", java.time.LocalTime.now().toString()); }}Opening the page:
Player playerComponent = store.getComponent(ref, Player.getComponentType());PlayerRef playerRef = store.getComponent(ref, PlayerRef.getComponentType());
WelcomePage page = new WelcomePage(playerRef, player.getDisplayName(), onlinePlayerCount);playerComponent.getPageManager().openCustomPage(ref, store, page);Interactive Settings Page
Section titled “Interactive Settings Page”A page with form inputs and event handling:
import com.hypixel.hytale.codec.Codec;import com.hypixel.hytale.codec.KeyedCodec;import com.hypixel.hytale.codec.builder.BuilderCodec;import com.hypixel.hytale.component.Ref;import com.hypixel.hytale.component.Store;import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime;import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType;import com.hypixel.hytale.protocol.packets.interface_.Page;import com.hypixel.hytale.server.core.Message;import com.hypixel.hytale.server.core.entity.entities.Player;import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage;import com.hypixel.hytale.server.core.ui.builder.EventData;import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder;import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder;import com.hypixel.hytale.server.core.universe.PlayerRef;import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
public class PlayerSettingsPage extends InteractiveCustomUIPage<PlayerSettingsPage.SettingsEventData> {
private static final String LAYOUT = "Pages/PlayerSettings.ui"; private boolean notificationsEnabled; private int renderDistance;
public PlayerSettingsPage(PlayerRef playerRef, boolean notifications, int renderDistance) { super(playerRef, CustomPageLifetime.CanDismissOrCloseThroughInteraction, SettingsEventData.CODEC); this.notificationsEnabled = notifications; this.renderDistance = renderDistance; }
@Override public void build(Ref<EntityStore> ref, UICommandBuilder commandBuilder, UIEventBuilder eventBuilder, Store<EntityStore> store) { commandBuilder.append(LAYOUT);
// Set initial values commandBuilder.set("#NotificationsToggle.Value", notificationsEnabled); commandBuilder.set("#RenderDistanceSlider.Value", renderDistance); commandBuilder.set("#RenderDistanceLabel.Text", String.valueOf(renderDistance));
// Bind events eventBuilder.addEventBinding( CustomUIEventBindingType.ValueChanged, "#RenderDistanceSlider", EventData.of("@RenderDistance", "#RenderDistanceSlider.Value"), false // Don't lock - allow real-time updates );
eventBuilder.addEventBinding( CustomUIEventBindingType.Activating, "#SaveButton", new EventData() .append("Action", "Save") .append("@Notifications", "#NotificationsToggle.Value") .append("@RenderDistance", "#RenderDistanceSlider.Value") );
eventBuilder.addEventBinding( CustomUIEventBindingType.Activating, "#CancelButton", EventData.of("Action", "Cancel") ); }
@Override public void handleDataEvent(Ref<EntityStore> ref, Store<EntityStore> store, SettingsEventData data) { // Handle real-time slider updates if (data.renderDistance != null && data.action == null) { UICommandBuilder builder = new UICommandBuilder(); builder.set("#RenderDistanceLabel.Text", String.valueOf(data.renderDistance)); sendUpdate(builder); return; }
// Handle button actions if (data.action == null) return;
Player playerComponent = store.getComponent(ref, Player.getComponentType());
switch (data.action) { case "Save": // Save settings this.notificationsEnabled = data.notifications != null && data.notifications; this.renderDistance = data.renderDistance != null ? data.renderDistance : 8;
// Apply settings to player... playerComponent.sendMessage(Message.translation("settings.saved")); playerComponent.getPageManager().setPage(ref, store, Page.None); break;
case "Cancel": playerComponent.getPageManager().setPage(ref, store, Page.None); break; } }
// Event data class with codec public static class SettingsEventData { public static final BuilderCodec<SettingsEventData> CODEC = ((BuilderCodec.Builder) ((BuilderCodec.Builder)((BuilderCodec.Builder)BuilderCodec.builder( SettingsEventData.class, SettingsEventData::new) .append(new KeyedCodec<>("Action", Codec.STRING), (d, v) -> d.action = v, d -> d.action).add()) .append(new KeyedCodec<>("@Notifications", Codec.BOOLEAN), (d, v) -> d.notifications = v, d -> d.notifications).add()) .append(new KeyedCodec<>("@RenderDistance", Codec.INTEGER), (d, v) -> d.renderDistance = v, d -> d.renderDistance).add()) .build();
private String action; private Boolean notifications; private Integer renderDistance; }}Choice Dialog Page
Section titled “Choice Dialog Page”A dialog presenting multiple choices to the player:
import com.hypixel.hytale.component.Ref;import com.hypixel.hytale.component.Store;import com.hypixel.hytale.server.core.Message;import com.hypixel.hytale.server.core.entity.entities.player.pages.choices.ChoiceBasePage;import com.hypixel.hytale.server.core.entity.entities.player.pages.choices.ChoiceElement;import com.hypixel.hytale.server.core.entity.entities.player.pages.choices.ChoiceInteraction;import com.hypixel.hytale.server.core.entity.entities.player.pages.choices.ChoiceRequirement;import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder;import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder;import com.hypixel.hytale.server.core.universe.PlayerRef;import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
public class QuestDialogPage extends ChoiceBasePage {
public QuestDialogPage(PlayerRef playerRef, String questId) { super(playerRef, createChoices(questId), "Pages/QuestDialog.ui"); }
private static ChoiceElement[] createChoices(String questId) { return new ChoiceElement[] { new QuestChoiceElement( "quest.accept.title", "quest.accept.description", new ChoiceInteraction[] { new AcceptQuestInteraction(questId) }, null // No requirements ), new QuestChoiceElement( "quest.decline.title", "quest.decline.description", new ChoiceInteraction[] { new DeclineQuestInteraction(questId) }, null ), new QuestChoiceElement( "quest.later.title", "quest.later.description", new ChoiceInteraction[] { new ClosePageInteraction() }, null ) }; }}
// Custom choice elementclass QuestChoiceElement extends ChoiceElement {
public QuestChoiceElement(String displayKey, String descKey, ChoiceInteraction[] interactions, ChoiceRequirement[] requirements) { super(displayKey, descKey, interactions, requirements); }
@Override public void addButton(UICommandBuilder commandBuilder, UIEventBuilder eventBuilder, String selector, PlayerRef playerRef) { commandBuilder.append(selector, "Components/QuestChoiceButton.ui"); commandBuilder.set(selector + " #Title.TextSpans", Message.translation(displayNameKey)); commandBuilder.set(selector + " #Description.TextSpans", Message.translation(descriptionKey)); }}
// Interaction implementationsclass AcceptQuestInteraction extends ChoiceInteraction { private final String questId;
public AcceptQuestInteraction(String questId) { this.questId = questId; }
@Override public void run(Store<EntityStore> store, Ref<EntityStore> ref, PlayerRef playerRef) { // Start the quest for the player // QuestManager.startQuest(playerRef, questId); playerRef.sendMessage(Message.translation("quest.accepted")); }}
class DeclineQuestInteraction extends ChoiceInteraction { private final String questId;
public DeclineQuestInteraction(String questId) { this.questId = questId; }
@Override public void run(Store<EntityStore> store, Ref<EntityStore> ref, PlayerRef playerRef) { playerRef.sendMessage(Message.translation("quest.declined")); }}
class ClosePageInteraction extends ChoiceInteraction { @Override public void run(Store<EntityStore> store, Ref<EntityStore> ref, PlayerRef playerRef) { // Page will close automatically after interaction }}Page Supplier Pattern
Section titled “Page Supplier Pattern”For pages triggered by block/entity interactions, use a supplier:
import com.hypixel.hytale.codec.Codec;import com.hypixel.hytale.codec.KeyedCodec;import com.hypixel.hytale.codec.builder.BuilderCodec;import com.hypixel.hytale.component.ComponentAccessor;import com.hypixel.hytale.component.Ref;import com.hypixel.hytale.server.core.entity.InteractionContext;import com.hypixel.hytale.server.core.entity.entities.player.pages.CustomUIPage;import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.OpenCustomUIInteraction;import com.hypixel.hytale.server.core.universe.PlayerRef;import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
public class MyPageSupplier implements OpenCustomUIInteraction.CustomPageSupplier {
public static final BuilderCodec<MyPageSupplier> CODEC = ((BuilderCodec.Builder) BuilderCodec.builder(MyPageSupplier.class, MyPageSupplier::new) .appendInherited(new KeyedCodec<>("ConfigId", Codec.STRING), (data, o) -> data.configId = o, data -> data.configId, (data, parent) -> data.configId = parent.configId) .add()) .build();
protected String configId;
@Override public CustomUIPage tryCreate(Ref<EntityStore> ref, ComponentAccessor<EntityStore> componentAccessor, PlayerRef playerRef, InteractionContext context) { // Create and return the page instance return new MyConfigPage(playerRef, configId); }}Updating Pages Dynamically
Section titled “Updating Pages Dynamically”Partial Updates
Section titled “Partial Updates”Send incremental changes without rebuilding the entire page:
// In your InteractiveCustomUIPage subclassprivate void updateScore(int newScore) { UICommandBuilder builder = new UICommandBuilder(); builder.set("#ScoreLabel.Text", String.valueOf(newScore)); sendUpdate(builder);}
// With event bindings updateprivate void addNewListItem(String itemName) { UICommandBuilder commandBuilder = new UICommandBuilder(); UIEventBuilder eventBuilder = new UIEventBuilder();
int index = items.size(); String selector = "#ItemList[" + index + "]";
commandBuilder.append("#ItemList", "Components/ListItem.ui"); commandBuilder.set(selector + " #Name.Text", itemName);
eventBuilder.addEventBinding( CustomUIEventBindingType.Activating, selector, EventData.of("Index", String.valueOf(index)), false );
sendUpdate(commandBuilder, eventBuilder, false);}Full Rebuild
Section titled “Full Rebuild”When many things change, rebuild the entire page:
private void refreshPage() { rebuild(); // Calls build() again and sends full update}Clearing and Updating
Section titled “Clearing and Updating”// Clear list then repopulateprivate void refreshList(List<String> items) { UICommandBuilder builder = new UICommandBuilder(); UIEventBuilder eventBuilder = new UIEventBuilder();
builder.clear("#ItemList");
for (int i = 0; i < items.size(); i++) { String selector = "#ItemList[" + i + "]"; builder.append("#ItemList", "Components/ListItem.ui"); builder.set(selector + " #Label.Text", items.get(i)); eventBuilder.addEventBinding( CustomUIEventBindingType.Activating, selector, EventData.of("Index", String.valueOf(i)) ); }
sendUpdate(builder, eventBuilder, false);}UI File System (.ui Files)
Section titled “UI File System (.ui Files)”Hytale uses .ui files as text-based assets that define UI layouts, styles, and components. These files are loaded by the client and referenced by server-side code via UICommandBuilder.
File Format
Section titled “File Format”UI files are registered as text assets and can be edited in the asset editor:
assetTypeRegistry.registerAssetType( new CommonAssetTypeHandler("UI", null, ".ui", AssetEditorEditorType.Text));Directory Structure
Section titled “Directory Structure”UI files follow an organized directory structure:
UI Assets├── Common/│ └── TextButton.ui # Reusable button component├── Common.ui # Global styles and constants└── Pages/ ├── BasicTextButton.ui # Simple button templates ├── [Feature]Page.ui # Main page layouts ├── [Feature]*.ui # Related sub-components └── [SubFeature]/ # Nested feature folders └── *.uiKnown UI Files
Section titled “Known UI Files”Based on decompiled code analysis, the following .ui files are referenced:
Common/Shared UI Files
Section titled “Common/Shared UI Files”| File Path | Description |
|---|---|
Common.ui | Global styles (DefaultTextButtonStyle, SecondaryTextButtonStyle) |
Common/TextButton.ui | Reusable button with LabelStyle, SelectedLabelStyle |
Page UI Files
Section titled “Page UI Files”| File Path | Purpose |
|---|---|
Pages/BasicTextButton.ui | Simple text button template |
Pages/DialogPage.ui | Dialog/conversation interface |
Pages/ShopPage.ui | Shop interface layout |
Pages/ShopItemButton.ui | Individual shop item button |
Pages/BarterPage.ui | Barter trading interface |
Pages/BarterTradeRow.ui | Individual trade row element |
Pages/GridLayoutSpacer.ui | Grid layout spacing element |
Pages/RespawnPage.ui | Death/respawn screen |
Pages/DroppedItemElement.ui | Dropped item display |
Pages/RespawnPointRenamePopup.ui | Respawn point naming dialog |
Pages/RespawnPointSelectPage.ui | Respawn point selection |
Pages/RespawnPointNearbyPage.ui | Nearby respawn points list |
Pages/RespawnPointButton.ui | Respawn button with styles |
Pages/WarpListPage.ui | Warp/teleport list |
Pages/WarpListEntry.ui | Warp list entry element |
Pages/TeleporterSettingsPage.ui | Teleporter settings |
Pages/InstanceListPage.ui | Instance management |
Pages/ConfigureInstanceBlockPage.ui | Instance block configuration |
Pages/ChangeModelPage.ui | Model selection interface |
Pages/CommandListPage.ui | Command browser |
Pages/SubCommand.ui | Subcommand display |
Pages/CommandVariant.ui | Command variant element |
Pages/Parameter.ui | Command parameter element |
Pages/ArgumentType.ui | Argument type display |
Pages/PluginListPage.ui | Plugin browser |
Pages/PluginListButton.ui | Plugin list button |
Pages/EntitySpawnPage.ui | Entity spawner interface |
Pages/ParticleSpawnPage.ui | Particle spawner interface |
Pages/PlaySoundPage.ui | Sound player interface |
Pages/ChunkTintPage.ui | Chunk tinting settings |
Pages/LaunchPadPage.ui | Launch pad configuration |
Pages/PrefabSpawnerPage.ui | Prefab spawner settings |
Pages/ItemRepairPage.ui | Item repair interface |
Pages/ItemRepairElement.ui | Repair item element |
Pages/ObjectiveAdminPanelPage.ui | Objective admin panel |
Pages/ObjectiveDataSlot.ui | Objective data slot |
Pages/PortalDeviceSummonPage.ui | Portal summoning interface |
Pages/PortalDeviceActivePage.ui | Active portal display |
Pages/PortalDeviceErrorPage.ui | Portal error display |
Pages/PortalPillElement.ui | Portal pill element |
Pages/PortalBulletPoint.ui | Portal bullet point |
Pages/PrefabPage.ui | Prefab browser |
Pages/PrefabSavePage.ui | Prefab save dialog |
Pages/PrefabTeleportPage.ui | Prefab teleporter |
Pages/PrefabEditorLoadSettingsPage.ui | Prefab editor settings |
Pages/PrefabEditorSaveSettingsPage.ui | Prefab save settings |
Pages/PrefabEditorExitConfirmPage.ui | Exit confirmation |
Pages/ObjImportPage.ui | OBJ file import |
Pages/ImageImportPage.ui | Image import |
Pages/ScriptedBrushPage.ui | Scripted brush list |
Pages/MemoriesCategoryPanel.ui | Memories category panel |
Pages/MemoryCategory.ui | Memory category element |
Pages/MemoriesPanel.ui | Memories panel |
Pages/Memory.ui | Individual memory element |
Pages/ChestMarkerElement.ui | Chest marker element |
Naming Conventions
Section titled “Naming Conventions”UI files follow consistent naming patterns:
| Pattern | Example | Usage |
|---|---|---|
[Feature]Page.ui | ShopPage.ui, DialogPage.ui | Main page layouts |
[Feature]Button.ui | ShopItemButton.ui, PluginListButton.ui | Button templates |
[Feature]Element.ui | DroppedItemElement.ui, PortalPillElement.ui | Reusable elements |
[Feature]Entry.ui | WarpListEntry.ui | List entry items |
[Feature]Slot.ui | ObjectiveDataSlot.ui | Slot elements |
[Feature]Row.ui | BarterTradeRow.ui | Row layouts |
Element Selector Syntax
Section titled “Element Selector Syntax”UI elements are referenced using CSS-like selectors:
| Syntax | Description | Example |
|---|---|---|
#ElementId | Select by ID | #SaveButton |
#ElementId.Property | Access property | #Label.Text |
#Parent[index] | Array indexing | #ItemList[0] |
#Parent #Child | Nested selection | #Container #Title |
#Parent #Child.Property | Nested property | #List[0] #Name.Text |
Referencing Styles from UI Files
Section titled “Referencing Styles from UI Files”Use Value.ref() to reference styles defined in UI files:
// Reference a style from another UI fileValue<String> buttonStyle = Value.ref("Common.ui", "DefaultTextButtonStyle");commandBuilder.set("#MyButton.Style", buttonStyle);
// Reference from component fileValue<String> labelStyle = Value.ref("Pages/BasicTextButton.ui", "SelectedLabelStyle");commandBuilder.set("#Label.Style", labelStyle);
// Reference respawn button stylesValue<String> defaultStyle = Value.ref("Pages/RespawnPointButton.ui", "DefaultRespawnButtonStyle");Value<String> selectedStyle = Value.ref("Pages/RespawnPointButton.ui", "SelectedRespawnButtonStyle");Loading UI Files
Section titled “Loading UI Files”UI files are loaded using UICommandBuilder:
UICommandBuilder builder = new UICommandBuilder();
// Load main page layoutbuilder.append("Pages/ShopPage.ui");
// Append child elements into containersbuilder.append("#ItemContainer", "Pages/ShopItemButton.ui");builder.append("#ItemContainer", "Pages/ShopItemButton.ui");
// Set properties on loaded elementsbuilder.set("#ItemContainer[0] #Name.Text", "Sword");builder.set("#ItemContainer[0] #Price.Text", "100 gold");builder.set("#ItemContainer[1] #Name.Text", "Shield");builder.set("#ItemContainer[1] #Price.Text", "75 gold");Creating Custom UI Files
Section titled “Creating Custom UI Files”When creating custom pages, follow these conventions:
- Page files - Name as
[Feature]Page.uiand place inPages/ - Component files - Name descriptively and place in
Common/or alongside pages - Define styles - Export reusable styles for server-side reference
- Use consistent IDs - Follow existing naming patterns for element IDs
Best Practices
Section titled “Best Practices”- Use appropriate lifetime - Choose
CantCloseonly when necessary (e.g., death screen) - Don’t lock for real-time updates - Set
locksInterface=falsefor sliders and search inputs - Validate on server - Never trust client data; always validate in
handleDataEvent - Use translation keys - Use
Message.translation()for localizable text - Handle dismissal - Override
onDismiss()for cleanup when pages are closed - Use reference keys - Prefix with
@to read UI values at event time - Batch updates - Combine multiple
set()calls in onesendUpdate() - Check validity - Verify entity references are valid before processing events
- Use suppliers - Implement
CustomPageSupplierfor interaction-triggered pages - Define codecs properly - Create
BuilderCodecfor your event data classes - Organize UI files - Keep page layouts in
Pages/, shared components inCommon/ - Reference styles - Use
Value.ref()to reuse styles across UI files