HyCodeYourTale

UI Components

UI Components

Detailní dokumentace k vytváření UI komponent a stránek v Hytale.

---

Přehled Architektury

PageManager (per-Player manager)

├── CustomUIPage (abstraktní třída)
│ ├── BasicCustomUIPage
│ └── InteractiveCustomUIPage

├── UICommandBuilder (sestavení UI příkazů)

└── UIEventBuilder (event bindings)

---

PageManager

Manažer stránek pro každého hráče:

public class PageManager {
@Nullable
private WindowManager windowManager;
private PlayerRef playerRef;
@Nullable
private CustomUIPage customPage;
@Nonnull
private final AtomicInteger customPageRequiredAcknowledgments = new AtomicInteger();

// Inicializace
public void init(@Nonnull PlayerRef playerRef, @Nonnull WindowManager windowManager) {
this.windowManager = windowManager;
this.playerRef = playerRef;
}

// Získání aktuální custom page
@Nullable
public CustomUIPage getCustomPage() {
return this.customPage;
}

// Nastavení vestavěné stránky
public void setPage(@Nonnull Ref ref, @Nonnull Store store, @Nonnull Page page) {
if (this.customPage != null) {
this.customPage.onDismiss(ref, store);
this.customPage = null;
}
this.playerRef.getPacketHandler().writeNoCache(new SetPage(page, false));
}

// Otevření custom page
public void openCustomPage(@Nonnull Ref ref, @Nonnull Store store, @Nonnull CustomUIPage page) {
UICommandBuilder commandBuilder = new UICommandBuilder();
UIEventBuilder eventBuilder = new UIEventBuilder();

if (this.customPage != null) {
this.customPage.onDismiss(ref, ref.getStore());
}

page.build(ref, commandBuilder, eventBuilder, store);

this.updateCustomPage(new CustomPage(
page.getClass().getName(),
true,
true,
page.getLifetime(),
commandBuilder.getCommands(),
eventBuilder.getEvents()
));

this.customPage = page;
}

// Aktualizace custom page
public void updateCustomPage(@Nonnull CustomPage page) {
this.customPageRequiredAcknowledgments.incrementAndGet();
this.playerRef.getPacketHandler().write(page);
}
}

Použití

// Získání PageManageru
Player player = store.getComponent(ref, Player.getComponentType());
PageManager pageManager = player.getPageManager();

// Otevření custom page
MyCustomPage page = new MyCustomPage(playerRef);
pageManager.openCustomPage(ref, store, page);

// Zavření page
pageManager.setPage(ref, store, Page.None);

---

CustomUIPage

Abstraktní třída pro vlastní UI stránky:

public abstract class CustomUIPage {
@Nonnull
protected final PlayerRef playerRef;
@Nonnull
protected CustomPageLifetime lifetime;

public CustomUIPage(@Nonnull PlayerRef playerRef, @Nonnull CustomPageLifetime lifetime) {
this.playerRef = playerRef;
this.lifetime = lifetime;
}

// Životní cyklus
public void setLifetime(@Nonnull CustomPageLifetime lifetime) {
this.lifetime = lifetime;
}

@Nonnull
public CustomPageLifetime getLifetime() {
return this.lifetime;
}

// Hlavní build metoda - MUSÍ být implementována
public abstract void build(
@Nonnull Ref ref,
@Nonnull UICommandBuilder commandBuilder,
@Nonnull UIEventBuilder eventBuilder,
@Nonnull Store store
);

// Event handling (pro non-interactive pages vyhodí UnsupportedOperationException)
public void handleDataEvent(
@Nonnull Ref ref,
@Nonnull Store store,
String rawData
) {
throw new UnsupportedOperationException("...");
}

// Callback při zavření page
public void onDismiss(@Nonnull Ref ref, @Nonnull Store store) {
}

// Rebuild celé stránky
protected void rebuild() {
Ref ref = this.playerRef.getReference();
if (ref != null) {
Store store = ref.getStore();
Player playerComponent = store.getComponent(ref, Player.getComponentType());

UICommandBuilder commandBuilder = new UICommandBuilder();
UIEventBuilder eventBuilder = new UIEventBuilder();
this.build(ref, commandBuilder, eventBuilder, ref.getStore());

playerComponent.getPageManager().updateCustomPage(
new CustomPage(
this.getClass().getName(),
false,
true,
this.lifetime,
commandBuilder.getCommands(),
eventBuilder.getEvents()
)
);
}
}

// Částečná aktualizace
protected void sendUpdate(@Nullable UICommandBuilder commandBuilder, boolean clear) {
Ref ref = this.playerRef.getReference();
if (ref != null) {
Store store = ref.getStore();
Player playerComponent = store.getComponent(ref, Player.getComponentType());

playerComponent.getPageManager().updateCustomPage(
new CustomPage(
this.getClass().getName(),
false,
clear,
this.lifetime,
commandBuilder != null ? commandBuilder.getCommands() : UICommandBuilder.EMPTY_COMMAND_ARRAY,
UIEventBuilder.EMPTY_EVENT_BINDING_ARRAY
)
);
}
}

// Zavření stránky
protected void close() {
Ref ref = this.playerRef.getReference();
if (ref != null) {
Store store = ref.getStore();
Player playerComponent = store.getComponent(ref, Player.getComponentType());
playerComponent.getPageManager().setPage(ref, store, Page.None);
}
}
}

CustomPageLifetime

public enum CustomPageLifetime {
CanDismiss, // Hráč může zavřít (ESC)
CannotDismiss, // Hráč nemůže zavřít
Modal // Modální okno
}

---

UICommandBuilder

Builder pro UI příkazy:

public class UICommandBuilder {
public static final CustomUICommand[] EMPTY_COMMAND_ARRAY = new CustomUICommand[0];
@Nonnull
private final List commands = new ObjectArrayList();

// Vyčištění obsahu elementu
@Nonnull
public UICommandBuilder clear(String selector) {
this.commands.add(new CustomUICommand(CustomUICommandType.Clear, selector, null, null));
return this;
}

// Odstranění elementu
@Nonnull
public UICommandBuilder remove(String selector) {
this.commands.add(new CustomUICommand(CustomUICommandType.Remove, selector, null, null));
return this;
}

// Připojení UI souboru
@Nonnull
public UICommandBuilder append(String documentPath) {
this.commands.add(new CustomUICommand(CustomUICommandType.Append, null, null, documentPath));
return this;
}

@Nonnull
public UICommandBuilder append(String selector, String documentPath) {
this.commands.add(new CustomUICommand(CustomUICommandType.Append, selector, null, documentPath));
return this;
}

// Připojení inline UI
@Nonnull
public UICommandBuilder appendInline(String selector, String document) {
this.commands.add(new CustomUICommand(CustomUICommandType.AppendInline, selector, null, document));
return this;
}

// Vložení před element
@Nonnull
public UICommandBuilder insertBefore(String selector, String documentPath) {
this.commands.add(new CustomUICommand(CustomUICommandType.InsertBefore, selector, null, documentPath));
return this;
}

// Nastavení hodnot
@Nonnull
public UICommandBuilder set(String selector, @Nonnull String str);
@Nonnull
public UICommandBuilder set(String selector, @Nonnull Message message);
@Nonnull
public UICommandBuilder set(String selector, boolean b);
@Nonnull
public UICommandBuilder set(String selector, float n);
@Nonnull
public UICommandBuilder set(String selector, int n);
@Nonnull
public UICommandBuilder set(String selector, double n);
@Nonnull
public UICommandBuilder setNull(String selector);

// Nastavení objektů
@Nonnull
public UICommandBuilder setObject(String selector, @Nonnull Object data);
@Nonnull
public UICommandBuilder set(String selector, @Nonnull T[] data);
@Nonnull
public UICommandBuilder set(String selector, @Nonnull List data);

// Získání příkazů
@Nonnull
public CustomUICommand[] getCommands() {
return this.commands.toArray(CustomUICommand[]::new);
}
}

Podporované Typy pro setObject()

// Z static bloku UICommandBuilder:
CODEC_MAP.put(Area.class, Area.CODEC);
CODEC_MAP.put(ItemGridSlot.class, ItemGridSlot.CODEC);
CODEC_MAP.put(ItemStack.class, ItemStack.CODEC);
CODEC_MAP.put(LocalizableString.class, LocalizableString.CODEC);
CODEC_MAP.put(PatchStyle.class, PatchStyle.CODEC);
CODEC_MAP.put(DropdownEntryInfo.class, DropdownEntryInfo.CODEC);
CODEC_MAP.put(Anchor.class, Anchor.CODEC);

---

Příklad: Jednoduchá Stránka

public class SimpleInfoPage extends CustomUIPage {

private final String title;
private final String content;

public SimpleInfoPage(@Nonnull PlayerRef playerRef, String title, String content) {
super(playerRef, CustomPageLifetime.CanDismiss);
this.title = title;
this.content = content;
}

@Override
public void build(
@Nonnull Ref ref,
@Nonnull UICommandBuilder commandBuilder,
@Nonnull UIEventBuilder eventBuilder,
@Nonnull Store store
) {
// Připoj hlavní UI soubor
commandBuilder.append("Pages/InfoPage.ui");

// Nastav hodnoty
commandBuilder.set("#Title.Text", this.title);
commandBuilder.set("#Content.Text", this.content);
}
}

Otevření Stránky

public void showInfoPage(PlayerRef playerRef, String title, String content) {
Ref ref = playerRef.getReference();
if (ref == null) return;

Store store = ref.getStore();
Player player = store.getComponent(ref, Player.getComponentType());

SimpleInfoPage page = new SimpleInfoPage(playerRef, title, content);
player.getPageManager().openCustomPage(ref, store, page);
}

---

Příklad: Stránka se Seznamem

Z TeleportPlugin - WarpListPage:

public class WarpListPage extends InteractiveCustomUIPage {
@Nonnull
private static final String PAGE_UI_FILE = "Pages/WarpEntryButton.ui";
private final Consumer callback;
private final Map warps;
@Nonnull
private String searchQuery = "";

public WarpListPage(@Nonnull PlayerRef playerRef, Map warps, Consumer callback) {
super(playerRef, CustomPageLifetime.CanDismiss, WarpListPageEventData.CODEC);
this.warps = warps;
this.callback = callback;
}

private void buildWarpList(@Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder) {
// Vyčisti seznam
commandBuilder.clear("#WarpList");

ObjectArrayList warps = new ObjectArrayList(this.warps.keySet());

if (warps.isEmpty()) {
// Prázdný seznam - zobraz zprávu
commandBuilder.appendInline("#WarpList",
"Label { Text: %server.customUI.warpListPage.noWarps; Style: (Alignment: Center); }");
} else {
// Filtruj podle vyhledávání
if (!this.searchQuery.isEmpty()) {
warps.removeIf(w -> !w.toLowerCase().contains(this.searchQuery));
}

Collections.sort(warps);

// Přidej položky
for (int i = 0; i < warps.size(); i++) {
String selector = "#WarpList[" + i + "]";
String warp = warps.get(i);

// Připoj UI pro položku
commandBuilder.append("#WarpList", "Pages/WarpEntryButton.ui");

// Nastav hodnoty
commandBuilder.set(selector + " #Name.Text", warp);
commandBuilder.set(selector + " #World.Text", this.warps.get(warp).getWorld());

// Event binding pro kliknutí
eventBuilder.addEventBinding(
CustomUIEventBindingType.Activating,
selector,
EventData.of("Warp", warp),
false
);
}
}
}

@Override
public void build(
@Nonnull Ref ref,
@Nonnull UICommandBuilder commandBuilder,
@Nonnull UIEventBuilder eventBuilder,
@Nonnull Store store
) {
// Hlavní UI
commandBuilder.append("Pages/WarpListPage.ui");

// Event pro vyhledávací pole
eventBuilder.addEventBinding(
CustomUIEventBindingType.ValueChanged,
"#SearchInput",
EventData.of("@SearchQuery", "#SearchInput.Value")
);

// Sestav seznam
this.buildWarpList(commandBuilder, eventBuilder);
}

// Event handling - viz UI_EVENTS.md
}

---

UI Soubory (.ui)

UI se definuje v .ui souborech v Assets:

Assets/Common/UI/
├── Pages/
│ ├── WarpListPage.ui
│ ├── WarpEntryButton.ui
│ └── InfoPage.ui
└── Components/
├── Button.ui
└── Input.ui

Příklad: WarpListPage.ui

Panel {
Id: "WarpListContainer"
Style: (Width: 400px; Height: 300px; Alignment: Center)

Label {
Id: "Title"
Text: %server.customUI.warpListPage.title
Style: (FontSize: 24; Alignment: TopCenter)
}

TextInput {
Id: "SearchInput"
Placeholder: %server.customUI.warpListPage.search
Style: (Width: 100%; Height: 30px)
}

ScrollPanel {
Id: "WarpList"
Style: (Width: 100%; FlexGrow: 1)
}
}

Příklad: WarpEntryButton.ui

Button {
Style: (Width: 100%; Height: 40px; Margin: 5px 0)

Label {
Id: "Name"
Style: (FontWeight: Bold)
}

Label {
Id: "World"
Style: (Color: #888888; FontSize: 12)
}
}

---

CustomUICommandType

public enum CustomUICommandType {
Clear, // Vyčistí obsah elementu
Remove, // Odstraní element
Append, // Připojí UI soubor
AppendInline, // Připojí inline UI
InsertBefore, // Vloží před element
InsertBeforeInline,
Set // Nastaví hodnotu
}

---

Selektory

Selektory pro UI elementy:

| Selektor | Popis |
|----------|-------|
| #ElementId | Element podle ID |
| #Parent #Child | Vnořený element |
| #List[0] | První položka seznamu |
| #List[0] #Name | Vnořený element v položce |
| #Element.Property | Vlastnost elementu |

Příklady

// Nastavení textu
commandBuilder.set("#Title.Text", "Hello World");

// Nastavení viditelnosti
commandBuilder.set("#Panel.Visible", true);

// Nastavení vnořeného elementu
commandBuilder.set("#Container #Button.Enabled", false);

// Položka seznamu
commandBuilder.set("#ItemList[0] #Name.Text", "First Item");

---

Shrnutí

| Třída | Účel |
|-------|------|
| PageManager | Per-player správce stránek |
| CustomUIPage | Abstraktní třída pro custom UI |
| UICommandBuilder | Builder pro UI příkazy |
| CustomPageLifetime | Životní cyklus stránky |

| Metoda UICommandBuilder | Účel |
|-------------------------|------|
| append(path) | Připojí UI soubor |
| appendInline(selector, ui) | Připojí inline UI |
| clear(selector) | Vyčistí obsah |
| remove(selector) | Odstraní element |
| set(selector, value) | Nastaví hodnotu |

| Operace | Metoda |
|---------|--------|
| Otevření page | pageManager.openCustomPage(ref, store, page) |
| Zavření page | pageManager.setPage(ref, store, Page.None) |
| Aktualizace | page.sendUpdate(commandBuilder, clear) |
| Rebuild | page.rebuild() |

Last updated: 20. ledna 2026