--- title: Custom Pages type: docs weight: 2 --- Custom pages allow plugins to create interactive UI overlays that can receive user input and display dynamic content. **Package:** `com.hypixel.hytale.server.core.entity.entities.player.pages` ## Page Lifetimes Control how pages can be closed: | Lifetime | Description | |----------|-------------| | `CustomPageLifetime.CantClose` | Page cannot be dismissed by the player | | `CustomPageLifetime.CanDismiss` | Player can close with Escape key | | `CustomPageLifetime.CanDismissOrCloseThroughInteraction` | Can close via Escape or interaction | ## Creating a Custom Page Extend `CustomUIPage`: ```java public class WelcomePage extends CustomUIPage { public WelcomePage(PlayerRef playerRef) { super(playerRef, CustomPageLifetime.CanDismiss); } @Override public void build(Ref ref, UICommandBuilder builder, UIEventBuilder events, Store store) { // Load a UI template from assets (extension .ui is REQUIRED) builder.append("Pages/WelcomeScreen.ui"); // Set dynamic values builder.set("#player_name", playerRef.getUsername()); builder.set("#server_name", "My Server"); } } ``` ## UICommandBuilder Build UI content dynamically: ### Append Content ```java // Append a UI template (extension .ui is REQUIRED) builder.append("Pages/TemplatePath.ui"); // Append to a specific selector builder.append("#container", "Pages/ChildTemplate.ui"); // Append inline YAML/JSON builder.appendInline("#list", "{ type: text, content: 'Hello' }"); ``` ### Set Values ```java // Set text builder.set("#title", "Welcome!"); builder.set("#description", Message.translation("my.key")); // Set numbers builder.set("#score", 1500); builder.set("#progress", 0.75f); // Set boolean builder.set("#visible", true); // Set to null builder.setNull("#optional_field"); ``` ### Set Objects ```java // Set ItemStack ItemStack item = ...; builder.setObject("#item_display", item); // Set UI Area Area area = new Area(0, 0, 100, 50); builder.setObject("#bounds", area); // Set arrays/lists builder.set("#items", itemStackArray); builder.set("#options", stringList); ``` ### Remove Content ```java // Clear children of an element builder.clear("#container"); // Remove an element entirely builder.remove("#element_id"); // Insert before an element builder.insertBefore("#existing", "Pages/NewElement.ui"); ``` ## UIEventBuilder Bind events to UI elements using `addEventBinding`: ```java import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType; import com.hypixel.hytale.server.core.ui.builder.EventData; @Override public void build(Ref ref, UICommandBuilder builder, UIEventBuilder events, Store store) { builder.append("Pages/Menu.ui"); // Bind click events using addEventBinding events.addEventBinding(CustomUIEventBindingType.Activating, "#button_start", EventData.of("Action", "start_game")); events.addEventBinding(CustomUIEventBindingType.Activating, "#button_settings", EventData.of("Action", "open_settings")); } ``` ### Event Binding Types | Type | Description | |------|-------------| | `Activating` | Button click/activation | | `RightClicking` | Right-click action | | `DoubleClicking` | Double-click action | | `MouseEntered` | Mouse hover enter | | `MouseExited` | Mouse hover exit | | `ValueChanged` | Input value changed | | `FocusGained` | Element focused | | `FocusLost` | Element lost focus | ## Handling Events Override `handleDataEvent` to process user interactions. The `rawData` parameter contains JSON with the event data: ```java public class MenuPage extends CustomUIPage { public MenuPage(PlayerRef ref) { super(ref, CustomPageLifetime.CanDismiss); } @Override public void build(Ref ref, UICommandBuilder builder, UIEventBuilder events, Store store) { builder.append("Pages/MainMenu.ui"); events.addEventBinding(CustomUIEventBindingType.Activating, "#play_button", EventData.of("Action", "play")); events.addEventBinding(CustomUIEventBindingType.Activating, "#quit_button", EventData.of("Action", "quit")); } @Override public void handleDataEvent(Ref ref, Store store, String rawData) { // rawData contains JSON like: {"Action":"play"} if (rawData.contains("\"Action\":\"play\"")) { startGame(); } else if (rawData.contains("\"Action\":\"quit\"")) { close(); } } private void startGame() { // Game logic... close(); // Close the page } } ``` {{< callout type="info" >}} **Tip:** For more complex event handling, consider using a JSON parser library or extending `InteractiveCustomUIPage` with a typed data class. {{< /callout >}} ## Opening Pages Use `PageManager` to open pages: ```java Player player = ...; PageManager pageManager = player.getPageManager(); // Open custom page pageManager.openCustomPage( player.getReference(), player.getReference().getStore(), new MenuPage(player.getPlayerRef()) ); ``` ## Closing Pages ```java // From within the page protected void close() { // Inherited method - closes this page } // From outside pageManager.setPage(ref, store, Page.None); ``` ## Updating Pages Update content without rebuilding: ```java // Send update with new commands UICommandBuilder update = new UICommandBuilder(); update.set("#score", newScore); sendUpdate(update); // Full rebuild rebuild(); // Clear and update sendUpdate(update, true); // clear=true ``` ## Page with Windows Open pages alongside inventory windows: ```java // Open page with windows pageManager.openCustomPageWithWindows( ref, store, new CraftingPage(playerRef), craftingWindow, playerInventoryWindow ); ``` ## Interactive Page Example ```java public class ShopPage extends CustomUIPage { private int selectedItem = -1; private final List items; public ShopPage(PlayerRef ref, List items) { super(ref, CustomPageLifetime.CanDismiss); this.items = items; } @Override public void build(Ref ref, UICommandBuilder builder, UIEventBuilder events, Store store) { builder.append("Pages/Shop.ui"); // Populate items for (int i = 0; i < items.size(); i++) { ShopItem item = items.get(i); builder.set("#item_" + i + "_name", item.getName()); builder.set("#item_" + i + "_price", item.getPrice()); events.addEventBinding(CustomUIEventBindingType.Activating, "#item_" + i, EventData.of("Action", "select").append("Index", String.valueOf(i))); } events.addEventBinding(CustomUIEventBindingType.Activating, "#buy_button", EventData.of("Action", "buy")); events.addEventBinding(CustomUIEventBindingType.Activating, "#close_button", EventData.of("Action", "close")); } @Override public void handleDataEvent(Ref ref, Store store, String rawData) { // Parse JSON event data if (rawData.contains("\"Action\":\"select\"")) { // Extract index from JSON int indexStart = rawData.indexOf("\"Index\":\"") + 9; int indexEnd = rawData.indexOf("\"", indexStart); selectedItem = Integer.parseInt(rawData.substring(indexStart, indexEnd)); updateSelection(); } else if (rawData.contains("\"Action\":\"buy\"") && selectedItem >= 0) { purchaseItem(selectedItem); } else if (rawData.contains("\"Action\":\"close\"")) { close(); } } private void updateSelection() { UICommandBuilder update = new UICommandBuilder(); update.set("#selected_index", selectedItem); update.set("#selected_name", items.get(selectedItem).getName()); sendUpdate(update); } private void purchaseItem(int index) { // Purchase logic... rebuild(); // Refresh UI } } ``` ## Built-in Pages Available page types: | Page | Description | |------|-------------| | `Page.None` | No page open (close current page) | | `Page.Bench` | Crafting/workbench interface | | `Page.Inventory` | Player inventory | | `Page.ToolsSettings` | Tools settings | | `Page.Map` | World map | | `Page.MachinimaEditor` | Machinima editor | | `Page.ContentCreation` | Content creation tools | | `Page.Custom` | Custom UI page | ```java // Open built-in page pageManager.setPage(ref, store, Page.Inventory); ``` ## Best Practices {{< callout type="info" >}} **Custom Page Guidelines:** - Use `CanDismiss` unless the page must be modal - Handle the `onDismiss` callback for cleanup - Use `sendUpdate()` for small changes, `rebuild()` for major changes - Store page state in instance fields - Close pages properly using `close()` or `setPage(Page.None)` {{< /callout >}} {{< callout type="warning" >}} **Important:** Always check if `playerRef.getReference()` is null before accessing the store, as the player may have disconnected. {{< /callout >}}