ReactiveUI

ReactiveUI

一个简单、响应式的UI框架,提供更好的事件处理、便捷的UI数据绑定以及可复用组件,用于在Hytale中创建复杂且动态的用户界面!

图书馆

Hytale ReactiveUI

一个现代化的、反应式的 Hytale 服务端模组 UI 框架,通过自动数据绑定、声明式事件处理和可复用组件简化 UI 管理。

加入我们的 Discord 获取支持并了解最新更新!

License: MIT

Hytale ReactiveUI

特性

  • 自动数据绑定 - 值变化时 UI 自动更新
  • 声明式事件处理 - 简洁、流畅的 API 用于处理 UI 事件
  • 可复用组件 - 构建具有正确生命周期管理的模块化 UI 元素
  • 类型安全 - 带类型化父页面引用的泛型元素
  • 数组/列表支持 - 轻松创建动态列表和重复元素
  • 事件驱动架构 - 高效的事件路由和参数解码

安装

Gradle (Kotlin DSL)

repositories {
    mavenCentral()
}

dependencies {
    implementation("dev.jonrapp:hytale-reactiveui:1.0")
}

Gradle (Groovy)

repositories {
    mavenCentral()
}

dependencies {
    implementation 'dev.jonrapp:hytale-reactiveui:1.0'
}

Maven

<dependency>
    <groupId>dev.jonrapp</groupId>
    <artifactId>hytale-reactiveui</artifactId>
    <version>1.0</version>
</dependency>

核心概念

1. 页面

页面是 Hytale 中 UI 的主要入口点。ReactiveUI 提供了 ReactiveUiPage,这是一个增强的页面实现,简化了事件处理、数据绑定和元素管理。它内置了对管理“主元素”(可通过单一元素轻松切换)的支持,非常适合标签页界面或向导式 UI。

public class MyPage extends ReactiveUiPage {

    public MyPage(@Nonnull PlayerRef playerRef) {
        super(playerRef, CustomPageLifetime.CanDismiss);
    }

    @Override
    public void build(@Nonnull Ref<EntityStore> ref, 
                      @Nonnull UICommandBuilder commands, 
                      @Nonnull UIEventBuilder events, 
                      @Nonnull Store<EntityStore> store) {
        // 加载你的 UI 文件
        commands.append("MyPage.ui");

        // 绑定事件
        bindEvent(
            CustomUIEventBindingType.Activating,
            "#TabButton",
            events,
            EventBinding.action("tab-clicked")
                .onEvent(context -> showPrimaryElement(new MyTab(this)))
        );

        // 显示初始元素
        showPrimaryElement(new MyTab(this));
    }

    @Override
    public String getRootContentSelector() {
        return "#Content";  // 主元素显示的位置
    }
}

2. 元素

元素是可复用的 UI 组件,管理自己的生命周期、事件和数据绑定。

public class MyElement extends Element<MyPage> {

    public MyElement(MyPage pageRef) {
        super(pageRef);
    }

    @Override
    protected void onCreate(String root, UICommandBuilder commands, UIEventBuilder events) {
        // 加载元素 UI
        commands.append(root, "MyElement.ui");

        // 绑定按钮点击事件
        bindEvent(
            CustomUIEventBindingType.Activating,
            "#SubmitButton",
            events,
            EventBinding.action("submit-clicked")
                .onEvent(context -> handleSubmit())
        );
    }

    private void handleSubmit() {
        // 处理事件
    }
}

3. 事件绑定

ReactiveUI 提供了一个流畅的 API,用于将事件绑定到 UI 元素,并自动清理。

// 简单的事件绑定
bindEvent(
    CustomUIEventBindingType.Activating,
    "#Button",
    events,
    EventBinding.action("button-clicked")
        .onEvent(context -> {
            // 处理点击
        })
);

// 带参数的事件
bindEvent(
    CustomUIEventBindingType.Activating,
    "#ItemButton",
    events,
    EventBinding.action("item-selected")
        .withEventData("itemId", Codec.STRING, "item_123")
        .onEvent(context -> {
            String itemId = context.getParameter("itemId");
            // 使用参数
        })
);

// 条件事件处理(如果已处理则返回 true)
bindEvent(
    CustomUIEventBindingType.Activating,
    "#ConditionalButton",
    events,
    EventBinding.action("conditional-action")
        .onEventConditional(context -> {
            if (someCondition()) {
                // 处理事件
                return true;  // 事件被消费
            }
            return false;  // 继续到下一个处理器
        })
);

4. 自动数据绑定

使用 @UIBinding 注解实现值变化时自动更新 UI。

public class PlayerCard extends Element<MyPage> {

    @UIBinding(selector = "#PlayerName.TextSpans")
    private UIBindable<String> playerName;

    @UIBinding(selector = "#PlayerScore.TextSpans")
    private UIBindable<String> score;

    public PlayerCard(MyPage pageRef) {
        super(pageRef);
    }

    @Override
    protected void onCreate(String root, UICommandBuilder commands, UIEventBuilder events) {
        commands.append(root, "PlayerCard.ui");

        // 设置初始值
        playerName.set("Steve");
        score.set("100");
    }

    public void updateScore(int newScore) {
        // 调用 set() 时 UI 自动更新
        score.set(String.valueOf(newScore));
    }
}

关键点:

  • 字段由框架自动初始化
  • 调用 set() 会立即更新 UI
  • 使用 set(value, commands) 可以批量执行多个更新
  • 支持 StringMessage 或任何类型(通过 toString() 转换)

5. 数组/列表元素

为列表、库存或重复模式创建元素的多个实例。

public class ItemList extends Element<MyPage> {

    public ItemList(MyPage pageRef) {
        super(pageRef);
    }

    @Override
    protected void onCreate(String root, UICommandBuilder commands, UIEventBuilder events) {
        commands.append(root, "ItemList.ui");

        // 创建 10 个物品元素
        for (int i = 0; i < 10; i++) {
            ItemElement item = new ItemElement(pageRef, i);
            item.create("#ItemContainer", i, commands, events);
        }
    }
}

public class ItemElement extends Element<MyPage> {

    private final int index;

    @UIBinding(selector = "#ItemIndex.TextSpans")
    private UIBindable<String> itemIndex;

    public ItemElement(MyPage pageRef, int index) {
        super(pageRef);
        this.index = index;
    }

    @Override
    protected void onCreate(String root, UICommandBuilder commands, UIEventBuilder events) {
        commands.append(root, "ItemElement.ui");

        // 设置索引值(与创建时批量处理)
        itemIndex.set(String.valueOf(index), commands);
    }
}

工作原理:

  • create(root, index, commands, events) 会创建一个带有索引选择器的容器
  • 每个实例都会获得唯一的选择器,例如 #ItemElement0#ItemElement1
  • 数据绑定会自动限定到每个实例

高级用法

类型安全的页面引用

元素是泛型的,并提供对其父页面的类型安全访问:

public class MyElement extends Element<MySpecificPage> {

    public MyElement(MySpecificPage pageRef) {
        super(pageRef);
    }

    @Override
    protected void onCreate(String root, UICommandBuilder commands, UIEventBuilder events) {
        // 以完全类型安全的方式访问页面特有方法
        pageRef.someCustomMethod();
    }
}

手动事件注册

如需更多控制,可以直接注册事件处理器:

registerEventHandler("my-action", EventHandlerBuilder.create()
    .withParameter("playerId", Codec.STRING)
    .build(context -> {
        String playerId = context.getParameter("playerId");
        // 处理事件
    })
);

生命周期管理

元素在卸载时会自动清理其事件处理器:

@Override
public void onUnload() {
    super.onUnload();  // 清理所有已注册的事件
    // 在此添加自定义清理代码
}

示例

请查看 examples 目录获取完整的工作示例,包括:

  • 带主元素切换的标签页界面
  • 带迭代元素的动态列表
  • 带自动 UI 更新的数据绑定
  • 带参数的事件处理

许可证

本项目采用 MIT 许可证 - 详情请见 LICENSE 文件。

贡献

欢迎贡献!请随时提交 Pull Request。

从旧版本(HyUI)迁移

如果你正在从本项目的先前版本(HyUI)迁移,唯一的变化是页面类和包的命名:

HyUiPage -> ReactiveUiPage

import dev.jonrapp.hyui. -> import dev.jonrapp.hytaleReactiveUi.

链接