Skip to content

Gui — 表单

三种基岩版原生表单,都是 builder 风格,结果通过回调返回。

回调是 FnOnce恰好触发一次,在服务器线程上——或者一次也不触发,如果模组在玩家回应之前被卸载了。

SimpleForm — 按钮列表

rust
use levilamina::prelude::*;

SimpleFormBuilder::new("§l主菜单")
    .content("选一个:")
    .button("传送")
    .button_with_image("商店", "textures/items/emerald", "path")
    .button("关闭")
    .send(&player, |resp| {
        match resp {
            FormResponse::Button(0) => { /* 传送 */ }
            FormResponse::Button(1) => { /* 商店 */ }
            _ => {}
        }
    })?;
方法说明
.content(text)顶部说明文字
.button(text)一个按钮
.button_with_image(text, image, image_type)带图标,image_type"path""url"
.header(text) / .label(text) / .divider()排版元素

按钮索引会被排版元素影响吗

不会。FormResponse::Button(i) 里的 i 只数按钮header / label / divider 不占号。

CustomForm — 表单控件

rust
CustomFormBuilder::new("§l设置")
    .input("nick", "昵称", "输入昵称", "")
    .toggle("pvp", "开启 PVP", false)
    .dropdown("mode", "模式", &["和平", "普通", "困难"], 1)
    .slider("radius", "半径", 1.0, 64.0, 1.0, 16.0)
    .step_slider("quality", "画质", &["低", "中", "高"], 2)
    .submit("保存")
    .send(&player, |resp| {
        if let FormResponse::Custom(map) = resp {
            let pvp = map.get("pvp").and_then(|v| v.as_bool()).unwrap_or(false);
            let mode = map.get("mode").and_then(|v| v.as_index()).unwrap_or(0);
        }
    })?;

每个控件的第一个参数是名字,回调里按名字取值——不是按下标。加删控件不会打乱已有代码。

方法值类型
.input(name, text, placeholder, default)Text(String)
.toggle(name, text, default)Int(0/1)
.dropdown(name, text, &options, default)Choice { index, text }
.slider(name, text, min, max, step, default)Float
.step_slider(name, text, &steps, default)Choice { index, text }

FormValue 的取值方法:as_i64() as_f64() as_bool() as_str() as_index()

as_index() 只对下拉和步进滑块有意义,其他一律返回 None

ModalForm — 两个按钮

rust
ModalFormBuilder::new("§c确认", "真的要删除这块地皮吗?")
    .upper("确认删除")
    .lower("取消")
    .send(&player, |resp| {
        if let FormResponse::Modal { upper: true } = resp {
            // 确认
        }
    })?;

只有两个按钮,不能多也不能少。破坏性操作的二次确认用它。

FormResponse

rust
pub enum FormResponse {
    Cancelled { reason: i32 },     // 玩家按了 ESC,或客户端拒绝
    Button(usize),                 // SimpleForm
    Custom(HashMap<String, FormValue>),  // CustomForm
    Modal { upper: bool },         // ModalForm
}

一定要处理 Cancelled

玩家随时可以按 ESC。如果你的流程依赖表单结果(比如"选完世界才能继续"),Cancelled 分支要么什么都不做,要么退回上一级菜单——不要假设一定有结果。

表单链

一个表单的回调里再开下一个表单是完全正常的用法:

rust
SimpleFormBuilder::new("选择世界")
    .button("世界 A")
    .send(&player, move |resp| {
        if let FormResponse::Button(i) = resp {
            SimpleFormBuilder::new("选择操作")
                .button("传送")
                .send(&player, |_| {})
                .ok();
        }
    })?;

注意闭包里要用的 Player 需要 move 进去——Player 可以克隆,成本很低。