# UI System

The Hytale server implements a server-authoritative UI system with two main subsystems: Pages (full-screen UI) and
Windows (inventory-style containers).

## Architecture Overview

```
UI System
├── Pages System - Full-screen UI (dialogs, menus, custom UIs)
│   ├── CustomUIPage - Base page class
│   ├── UICommandBuilder - DOM manipulation
│   └── UIEventBuilder - Event bindings
└── Windows System - Inventory-style containers
    ├── Window - Base window class
    └── WindowManager - Window lifecycle
```

## Page System

### Core Classes

| Class                        | Package                                                       | Description                             |
|------------------------------|---------------------------------------------------------------|-----------------------------------------|
| `CustomUIPage`               | `com.hypixel.hytale.server.core.entity.entities.player.pages` | Abstract base for all custom pages      |
| `BasicCustomUIPage`          | Same                                                          | Simple pages without event data parsing |
| `InteractiveCustomUIPage<T>` | Same                                                          | Pages with typed event handling         |
| `PageManager`                | Same                                                          | Manages page state for a player         |
| `UICommandBuilder`           | `com.hypixel.hytale.server.core.ui.builder`                   | Builds UI DOM commands                  |
| `UIEventBuilder`             | Same                                                          | Builds event bindings                   |

### Page Lifetime

```java
public enum CustomPageLifetime {
    CantClose(0),                           // User cannot close
    CanDismiss(1),                          // ESC key to dismiss
    CanDismissOrCloseThroughInteraction(2)  // Dismiss or click to close
}
```

### CustomUIPage Base Class

```java
public abstract class CustomUIPage {
    protected final PlayerRef playerRef;
    protected CustomPageLifetime lifetime;
    
    // Build the UI - called to construct the page
    public abstract void build(Ref<EntityStore> ref, UICommandBuilder cmd, 
                              UIEventBuilder events, Store<EntityStore> store);
    
    // Handle event data from client
    public void handleDataEvent(Ref<EntityStore> ref, Store<EntityStore> store, String rawData);
    
    // Rebuild entire page
    protected void rebuild();
    
    // Send partial update
    protected void sendUpdate(UICommandBuilder commandBuilder, boolean clear);
    
    // Close the page
    protected void close();
    
    // Called when page is dismissed by user
    public void onDismiss(Ref<EntityStore> ref, Store<EntityStore> store);
}
```

### InteractiveCustomUIPage<T>

For pages with typed event handling:

```java
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 this for type-safe event handling
    public void handleDataEvent(Ref<EntityStore> ref, Store<EntityStore> store, T data);
    
    // Send update with new event bindings
    protected void sendUpdate(UICommandBuilder cmd, UIEventBuilder events, boolean clear);
}
```

## UICommandBuilder

Builds commands to manipulate the UI DOM structure.

### Command Types

```java
public enum CustomUICommandType {
    Append(0),           // Append UI document
    AppendInline(1),     // Append inline XML
    InsertBefore(2),     // Insert before element
    InsertBeforeInline(3),
    Remove(4),           // Remove element
    Set(5),              // Set property value
    Clear(6)             // Clear children
}
```

### Methods

```java
UICommandBuilder cmd = new UICommandBuilder();

// Load UI documents
cmd.append("Pages/MyPage.ui");                    // Load to root
cmd.append("#Container", "Common/Button.ui");    // Append to selector

// Clear and remove
cmd.clear("#ItemList");                          // Clear children
cmd.remove("#OldElement");                       // Remove element

// Set property values
cmd.set("#Title.Text", "Hello World");           // String
cmd.set("#Counter.Value", 42);                   // Integer
cmd.set("#Progress.Value", 0.75f);               // Float
cmd.set("#Panel.Visible", true);                 // Boolean
cmd.set("#Label.Text", Message.translation("key")); // Localized

// Set with Value references
cmd.set("#Button.Style", Value.ref("Common/Button.ui", "ActiveStyle"));

// Set complex objects
cmd.setObject("#ItemSlot", itemGridSlot);        // Single object
cmd.set("#SlotList", itemGridSlotArray);         // Array
cmd.set("#DataList", itemList);                  // List

// Inline XML
cmd.appendInline("#Container", "<Panel Id='NewPanel'/>");
cmd.insertBefore("#Element", "Pages/Header.ui");
```

### Selector Syntax

- `#ElementId` - Select by ID
- `#Parent #Child` - Descendant selector
- `#List[0]` - Index accessor (for lists)
- `#List[0] #Button` - Combined
- `#Element.Property` - Property accessor

## UIEventBuilder

Builds event bindings that trigger server callbacks.

### Event Binding Types

```java
public enum CustomUIEventBindingType {
    Activating(0),           // Click/press
    RightClicking(1),        // Right-click
    DoubleClicking(2),       // Double-click
    MouseEntered(3),         // Mouse enter
    MouseExited(4),          // Mouse leave
    ValueChanged(5),         // Input value changed
    ElementReordered(6),     // Drag reorder
    Validating(7),           // Input validation
    Dismissing(8),           // Page dismiss
    FocusGained(9),          // Element focused
    FocusLost(10),           // Element unfocused
    KeyDown(11),             // Key pressed
    MouseButtonReleased(12), // Mouse released
    SlotClicking(13),        // Inventory slot click
    SlotDoubleClicking(14),  // Slot double-click
    SlotMouseEntered(15),    // Slot hover enter
    SlotMouseExited(16),     // Slot hover exit
    DragCancelled(17),       // Drag cancelled
    Dropped(18),             // Item dropped
    SlotMouseDragCompleted(19),
    SlotMouseDragExited(20),
    SlotClickReleaseWhileDragging(21),
    SlotClickPressWhileDragging(22),
    SelectedTabChanged(23)   // Tab selection
}
```

### Methods

```java
UIEventBuilder events = new UIEventBuilder();

// Simple binding
events.addEventBinding(CustomUIEventBindingType.Activating, "#CloseButton");

// With event data
events.addEventBinding(CustomUIEventBindingType.Activating, "#SubmitButton",
    EventData.of("Action", "Submit"));

// Without interface lock (for non-blocking events)
events.addEventBinding(CustomUIEventBindingType.MouseEntered, "#HoverArea",
    EventData.of("Action", "Hover"), false);

// With value extraction from element
events.addEventBinding(CustomUIEventBindingType.ValueChanged, "#SearchInput",
    EventData.of("@Query", "#SearchInput.Value"), false);
```

### EventData

```java
// Simple key-value
EventData.of("Action", "Submit")

// Multiple values
new EventData()
    .append("Type", "Update")
    .append("Index", "5")

// Value extraction (@ prefix)
EventData.of("@Value", "#InputField.Value")     // Extract from element
EventData.of("@Selected", "#Dropdown.Selected") // Extract selection
```

## Value References

The `Value<T>` class references values either directly or from UI documents.

```java
// Direct value
Value<Integer> count = Value.of(42);
Value<String> text = Value.of("Hello");

// Reference from UI document
Value<String> style = Value.ref("Common/Button.ui", "ActiveStyle");
Value<Integer> border = Value.ref("Pages/Dialog.ui", "BorderWidth");
```

### Encoding Format

References are encoded in JSON as:

```json
{
    "$Document": "Common/Button.ui",
    "@Value": "ActiveStyle"
}
```

## UI Data Types

### PatchStyle (9-Patch Styling)

```java
public class PatchStyle {
    Value<String> texturePath;
    Value<Integer> border;
    Value<Integer> horizontalBorder;
    Value<Integer> verticalBorder;
    Value<String> color;
    Value<Area> area;
}
```

### Area

```java
public class Area {
    int x, y, width, height;
}
```

### Anchor

```java
public class Anchor {
    Value<Integer> left, right, top, bottom;
    Value<Integer> width, height;
    Value<Integer> minWidth, maxWidth;
    Value<Integer> full, horizontal, vertical;
}
```

### ItemGridSlot

```java
public class ItemGridSlot {
    ItemStack itemStack;
    Value<PatchStyle> background, overlay, icon;
    boolean isItemIncompatible;
    boolean isActivatable;
    boolean isItemUncraftable;
    boolean skipItemQualityBackground;
    String name, description;
}
```

### LocalizableString

```java
LocalizableString.fromString("Hello")              // Plain string
LocalizableString.fromMessageId("key")             // Translation key
LocalizableString.fromMessageId("key", Map.of("name", "Player"))  // With params
```

## Complete Page Example

```java
public class ShopPage extends InteractiveCustomUIPage<ShopPage.ShopEventData> {
    
    private final List<ShopItem> items;
    
    public ShopPage(PlayerRef playerRef, List<ShopItem> items) {
        super(playerRef, CustomPageLifetime.CanDismiss, ShopEventData.CODEC);
        this.items = items;
    }
    
    @Override
    public void build(Ref<EntityStore> ref, UICommandBuilder cmd, 
                     UIEventBuilder events, Store<EntityStore> store) {
        // Load main layout
        cmd.append("Pages/ShopPage.ui");
        
        // Set title
        cmd.set("#Title.Text", Message.translation("shop.title"));
        
        // Build item list
        cmd.clear("#ItemList");
        for (int i = 0; i < items.size(); i++) {
            ShopItem item = items.get(i);
            String selector = "#ItemList[" + i + "]";
            
            cmd.append("#ItemList", "Pages/ShopElementButton.ui");
            cmd.set(selector + " #Name.Text", item.getName());
            cmd.set(selector + " #Price.Text", String.valueOf(item.getPrice()));
            cmd.setObject(selector + " #Icon", item.toItemGridSlot());
            
            events.addEventBinding(
                CustomUIEventBindingType.Activating,
                selector,
                EventData.of("Action", "Buy").append("Index", String.valueOf(i)),
                false
            );
        }
        
        // Close button
        events.addEventBinding(CustomUIEventBindingType.Activating, "#CloseButton",
            EventData.of("Action", "Close"));
        
        // Search input
        events.addEventBinding(CustomUIEventBindingType.ValueChanged, "#SearchInput",
            EventData.of("Action", "Search").append("@Query", "#SearchInput.Value"),
            false);
    }
    
    @Override
    public void handleDataEvent(Ref<EntityStore> ref, Store<EntityStore> store, 
                               ShopEventData data) {
        switch (data.action) {
            case "Close" -> close();
            case "Buy" -> {
                if (data.index >= 0 && data.index < items.size()) {
                    buyItem(ref, store, items.get(data.index));
                    // Update UI
                    UICommandBuilder cmd = new UICommandBuilder();
                    cmd.set("#Balance.Text", getPlayerBalance(ref, store));
                    sendUpdate(cmd, false);
                }
            }
            case "Search" -> {
                filterItems(data.query);
                rebuild();
            }
        }
    }
    
    public static class ShopEventData {
        public static final BuilderCodec<ShopEventData> CODEC = BuilderCodec
            .builder(ShopEventData.class, ShopEventData::new)
            .addField(new KeyedCodec<>("Action", Codec.STRING), 
                (d, v) -> d.action = v, d -> d.action)
            .addField(new KeyedCodec<>("Index", Codec.INTEGER), 
                (d, v) -> d.index = v, d -> d.index)
            .addField(new KeyedCodec<>("Query", Codec.STRING), 
                (d, v) -> d.query = v, d -> d.query)
            .build();
        
        String action;
        int index = -1;
        String query;
    }
}
```

## PageManager

```java
Player player = ...;
PageManager pageManager = player.getPageManager();

// Open custom page
pageManager.openCustomPage(ref, store, new ShopPage(playerRef, items));

// Open built-in page
pageManager.setPage(ref, store, Page.Inventory);
pageManager.setPage(ref, store, Page.None);  // Close

// Open page with windows
pageManager.openCustomPageWithWindows(ref, store, customPage, window1, window2);

// Get current custom page
CustomUIPage current = pageManager.getCustomPage();
```

### Built-in Pages

```java
public enum Page {
    None(0),            // No page
    Bench(1),           // Crafting bench
    Inventory(2),       // Player inventory
    ToolsSettings(3),   // Builder tools
    Map(4),             // World map
    MachinimaEditor(5), // Machinima
    ContentCreation(6), // Content creation
    Custom(7)           // Custom server page
}
```

## Window System

### Window Base Class

```java
public abstract class Window {
    public abstract JsonObject getData();
    protected abstract boolean onOpen0();
    protected abstract void onClose0();
    
    public void handleAction(Ref<EntityStore> ref, Store<EntityStore> store, WindowAction action);
    public void close();
    public void invalidate();  // Mark for update
    public void registerCloseEvent(Consumer<Window> callback);
}
```

### Window Types

```java
public enum WindowType {
    Container(0),
    PocketCrafting(1),
    BasicCrafting(2),
    DiagramCrafting(3),
    StructuralCrafting(4),
    Processing(5),
    Memories(6)
}
```

### WindowManager

```java
WindowManager wm = player.getWindowManager();

// Open window
OpenWindow packet = wm.openWindow(myWindow);

// Open multiple
List<OpenWindow> packets = wm.openWindows(window1, window2);

// Close
wm.closeWindow(windowId);
wm.closeAllWindows();

// Update
wm.updateWindow(window);
wm.updateWindows();  // Update all dirty

// Get window
Window w = wm.getWindow(windowId);
```

## HUD System

The HUD is a persistent UI layer separate from pages.

### CustomHud Packet

```java
public class CustomHud implements Packet {
    boolean clear;
    CustomUICommand[] commands;
}
```

### HUD Components

```java
public enum HudComponent {
    Hotbar(0), StatusIcons(1), Reticle(2), Chat(3),
    Requests(4), Notifications(5), KillFeed(6), InputBindings(7),
    PlayerList(8), EventTitle(9), Compass(10), ObjectivePanel(11),
    PortalPanel(12), BuilderToolsLegend(13), Speedometer(14),
    UtilitySlotSelector(15), BlockVariantSelector(16),
    BuilderToolsMaterialSlotSelector(17), Stamina(18),
    AmmoIndicator(19), Health(20), Mana(21), Oxygen(22), Sleep(23)
}
```

## Protocol Packets

### Page Packets

| Packet            | ID  | Direction | Description       |
|-------------------|-----|-----------|-------------------|
| `SetPage`         | 216 | S->C      | Set built-in page |
| `CustomHud`       | 217 | S->C      | Update HUD        |
| `CustomPage`      | 218 | S->C      | Send custom page  |
| `CustomPageEvent` | 219 | C->S      | Page event        |

### Window Packets

| Packet             | ID  | Direction | Description    |
|--------------------|-----|-----------|----------------|
| `OpenWindow`       | 200 | S->C      | Open window    |
| `UpdateWindow`     | 201 | S->C      | Update window  |
| `CloseWindow`      | 202 | S->C      | Close window   |
| `ClientOpenWindow` | 203 | C->S      | Request window |
| `SendWindowAction` | 204 | C->S      | Window action  |

## Known UI Documents

| Path                       | Purpose             |
|----------------------------|---------------------|
| `Pages/DialogPage.ui`      | Dialog/conversation |
| `Pages/BarterPage.ui`      | Trading interface   |
| `Pages/ShopPage.ui`        | Shop interface      |
| `Pages/RespawnPage.ui`     | Death/respawn       |
| `Pages/ChangeModelPage.ui` | Model selection     |
| `Pages/WarpListPage.ui`    | Teleport list       |
| `Pages/CommandListPage.ui` | Command help        |
| `Pages/PluginListPage.ui`  | Plugin list         |
| `Common/TextButton.ui`     | Reusable button     |

## Best Practices

1. **Use InteractiveCustomUIPage<T>** - Provides type-safe event handling
2. **Define event data codec** - Create a proper BuilderCodec for your event data
3. **Use sendUpdate for partial updates** - Don't rebuild entire page for small changes
4. **Extract values with @ prefix** - Use `@PropertyName` syntax to extract element values
5. **Set locksInterface appropriately** - Use `false` for non-blocking events like hover
6. **Clear lists before rebuilding** - Always `cmd.clear()` before repopulating lists
7. **Handle dismiss** - Override `onDismiss()` for cleanup
8. **Use Value references** - Reference styles from UI documents for consistency
