跳到正文
本頁目錄

表單指南

LeviLamina提供了一套極簡的表單API,允許開發者用幾行代碼實現複雜的表單功能。
表單API的頭文件被存放於ll/api/form中,此外由於Minecraft的表單ID在運行時是唯一的,所以我們提供了FormIdManager 用於獲取Minecraft的表單ID,允許開發者自己實現一套表單API。

FormIdManager

FormIdManager的頭文件位於ll/api/form/FormIdManager.h,導出了ll::form::FormIdManager::genFormId方法,用於獲取一個唯一的表單ID。

示例

C++
#include "ll/api/form/FormIdManager.h"

void test() { uint formId = ll::form::FormIdManager::genFormId(); }

SimpleForm

SimpleForm是一個簡單的表單,提供了標題、內容和按鈕。
SimpleForm的頭文件位於ll/api/form/SimpleForm.h。
此外,由於SimpleForm的方法例如appendButton返回SimpleForm的引用,所以可以鏈式調用。

用法

  1. 引用頭文件ll/api/form/SimpleForm.h
  2. 構造一個SimpleForm對象,可以使用帶參數和不帶參數的構造函數,使用帶參數的構造函數需要傳入標題和內容,使用不帶參數的構造函數需要手動調用 setTitle和setContent方法來設置標題和內容
  3. 通過appendButton方法添加按鈕,通過appendHeader、appendLabel和appendDivider方法添加僅視覺效果元素。
  4. 使用sendTo方法將表單發送給玩家,需要傳入Player對象的引用和一個回調函數,回調函數的參數為Player 對象的引用、玩家選擇的按鈕索引和取消原因的枚舉。具體參數可以參考頭文件ll/api/form/SimpleForm.h中Callback的聲明。

Tip

當按鈕的索引為-1時代表玩家取消的表單。
FormCancelReason的本質是std::optional<ModalFormCancelReason>,所以你需要先判斷其是否有值才能使用,並且你需要使用 ModalFormCancelReason的枚舉值來判斷取消的原因。示例中使用了magic_enum庫來獲取枚舉值的名稱。

示例

C++
#include "ll/api/form/SimpleForm.h"
#include "magic_enum/magic_enum.hpp"

void sendSimpleFormToPlayer(Player& player) {
    ll::form::SimpleForm form("I'm title", "I'm content");
    // Or
    //    ll::form::SimpleForm form;
    //    form.setTitle("I'm title").setContent("I'm content");
    form.appendHeader("header")
        .appendButton("Button1")
        .appendDivider()
        .appendHeader("header")
        .appendLabel("label")
        .appendButton("Button2")
        .sendTo(player, [](Player& player, int selected, ll::form::FormCancelReason reason) {
            switch (selected) {
            case 0: {
                player.sendMessage("You clicked Button1");
                break;
            }
            case 1: {
                player.sendMessage("You clicked Button2");
                break;
            }
            case -1: {
                player.sendMessage("You closed the form");
                player.sendMessage(
                    reason.transform(magic_enum::enum_name<ModalFormCancelReason>).value_or("Unknown Reason")
                );
                break;
            }
            }
        });
}

CustomForm

CustomForm是一個複雜的表單,提供了標題、標籤(Label)、輸入框(Input)、開關(Toggle)、下拉框(Dropdown)、滾動條(Slider) 和分段滾動條(StepSlider)。

用法

  1. 引用頭文件ll/api/form/CustomForm.h
  2. 構造一個CustomForm對象,可以使用帶參數和不帶參數的構造函數,使用帶參數的構造函數需要傳入標題,使用不帶參數的構造函數需要手動調用 setTitle方法來設置標題
  3. 通過appendInput、appendToggle、appendDropdown、appendSlider和appendStepSlider方法添加各種自定義表單元素,通過appendHeader、appendLabel、appendDivider方法添加各種僅視覺效果元素
  4. 使用sendTo方法將表單發送給玩家,需要傳入Player對象的引用和一個回調函數,回調函數的參數為Player 對象的引用、表單的結果和取消原因的枚舉。具體參數可以參考頭文件ll/api/form/CustomForm.h中Callback的聲明。

Tip

表單的結果CustomFormResult實際上是std::optional<std::unordered_map<std::string, CustomFormElementResult>> ,一個存儲了元素名稱和結果的無序關聯容器。你可以通過元素名稱來獲取對應的結果,結果的類型是CustomFormElementResult ,是一個聯合體(std::variant<std::monostate, uint64, double, std::string>),所以你需要先使用std::holds_alternative 判斷其類型通過std::get獲取。

示例

C++
#include "ll/api/form/CustomForm.h"
#include "magic_enum/magic_enum.hpp"

void sendCustomFormToPlayer(Player& player) {
    ll::form::CustomForm     form;
    std::vector<std::string> names;
    form.setTitle("CustomForm")
        .appendHeader("header")
        .appendLabel("label")
        .appendInput("input1", "input")
        .appendToggle("toggle", "toggle")
        .appendSlider("slider", "slider", 0, 100, 1)
        .appendDivider()
        .appendStepSlider("stepSlider", "stepSlider", {"a", "b", "c"})
        .appendDropdown("dropdown", "dropdown", {"a", "b", "c"})
        .appendLabel("label")
        .appendDropdown("emptydropdown", "empty dropdown", names)
        .setSubmitButton("Apply")
        .sendTo(
            player,
            [](Player& player, ll::form::CustomFormResult const& data, ll::form::FormCancelReason reason) {
                if (!data) {
                    player.sendMessage("CustomForm callback canceled");
                    player.sendMessage(reason.transform(magic_enum::enum_name<ModalFormCancelReason>)
                                            .value_or("Unknown Reason"));
                    return;
                }
                for (auto [name, result] : *data) {
                    if (std::holds_alternative<uint64_t>(result)) {
                        player.sendMessage(
                            fmt::format("CustomForm callback {} = {}", name, std::get<uint64_t>(result))
                        );
                    } else if (std::holds_alternative<double>(result)) {
                        player.sendMessage(
                            fmt::format("CustomForm callback {} = {}", name, std::get<double>(result))
                        );
                    } else if (std::holds_alternative<std::string>(result)) {
                        player.sendMessage(
                            fmt::format("CustomForm callback {} = {}", name, std::get<std::string>(result))
                        );
                    }
                }
            }
        );
}

ModalForm

ModalForm是一個模態表單,提供了標題、內容和兩個按鈕,通常用於實現雙項選擇。

用法

  1. 引用頭文件ll/api/form/ModalForm.h
  2. 構造一個ModalForm對象,可以使用帶參數和不帶參數的構造函數,使用帶參數的構造函數需要傳入標題、內容、上按鈕和下按鈕,使用不帶參數的構造函數需要手動調用 setTitle和setContent方法來設置標題和內容,setUpperButton和setLowerButton方法來設置上按鈕和下按鈕。
  3. 使用sendTo方法將表單發送給玩家,需要傳入Player對象的引用和一個回調函數,回調函數的參數為Player 對象的引用、玩家選擇的按鈕結果和取消原因的枚舉。具體參數可以參考頭文件ll/api/form/ModalForm.h中Callback的聲明。

Tip

ModalFormResult實際上是std::optional<ModalFormSelectedButton>的別名,而ModalFormSelectedButton是一個Bool枚舉,有Upper = true和 Lower = false兩個值,分別代表上按鈕和下按鈕。

示例

C++
#include "ll/api/form/ModalForm.h"
#include "magic_enum/magic_enum.hpp"

void sendModalFormToPlayer(Player& player) {
    ll::form::ModalForm form;
    form.setTitle("ModalForm")
        .setUpperButton("Upper")
        .setLowerButton("Lower")
        .sendTo(
            player,
            [](Player& player, ll::form::ModalFormResult selected, ll::form::FormCancelReason reason) {
                if (!selected) {
                    player.sendMessage("ModalForm callback canceled");
                    player.sendMessage(reason.transform(magic_enum::enum_name<ModalFormCancelReason>)
                                            .value_or("Unknown Reason"));
                    return;
                }
                player.sendMessage(fmt::format("ModalForm callback {}", (bool)selected));
            }
        );
}

sendUpdate

玩家處於表單打開狀態時使用SimpleForm::sendUpdate、CustomForm::sendUpdate和ModalForm::sendUpdate可以更新表單,且不限制更新的表單內容和類型,參數同sendTo。這些方法需要保證玩家處於表單打開中的狀態,否則無效果。

示例

C++
#include "ll/api/chrono/GameChrono.h"
#include "ll/api/coro/CoroTask.h"
#include "ll/api/form/CustomForm.h"
#include "ll/api/service/Bedrock.h"
#include "ll/api/thread/ServerThreadExecutor.h"
#include "mc/server/ServerLevel.h"
#include <fmt/chrono.h>

std::unique_ptr<ll::form::CustomForm> buildStatusWindow() {
    auto form = std::make_unique<ll::form::CustomForm>("Status");
    auto now  = std::chrono::system_clock::now();
    auto ticks =
        ll::service::getLevel().transform([](auto& level) { return level.getCurrentServerTick().tickID; }).value_or(-1);
    form->appendHeader("System Info")
        .appendLabel(fmt::format("Time: {}", now))
        .appendLabel(fmt::format("Ticks: {}", ticks))
        .appendDivider()
        .setSubmitButton("Close");
    return std::move(form);
}

void openStatusWindow(Player& player) {
    ll::coro::keepThis([uid = player.getOrCreateUniqueID()]() -> ll::coro::CoroTask<void> {
        auto closed   = std::make_shared<bool>(false);
        auto callback = [closed](auto const&...) { *closed = true; };

        auto player = ll::service::getLevel().transform([uid](auto& level) { return level.getPlayer(uid); });
        if (!player || *closed) co_return;
        buildStatusWindow()->sendTo(*player, callback);

        while (true) {
            co_await ll::chrono::ticks{20};

            player = ll::service::getLevel().transform([uid](Level& level) { return level.getPlayer(uid); });
            if (!player || *closed) co_return;
            buildStatusWindow()->sendUpdate(*player, callback);
        }
    }).launch(ll::thread::ServerThreadExecutor::getDefault());
}