Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

部件节点

最常用的形态。parser 产出 DsWidget 节点,携带:

  • 一个名字(syn::Ident),
  • 属性表(Vec<DsAttr>),
  • 附魔(Vec<syn::Expr>),
  • 事件咒符(Vec<DsOn>),
  • 子节点(Vec<DsTreeRef>)。

后端通过 inscribe_widget(name, attrs, enchants, on_handlers, children) 一次取走全部。

全部形态

use xrune::ui;
fn Text(s: &'static str) -> &'static str { s }
#[derive(Debug)]
struct DisabledMarker;
fn app(parent: i32) {
let Disabled = DisabledMarker;
ui! {
    :(
        parent: parent
    :)
root_widget {
    /* 全形:名 + 具名属性 + body。 */
    container (width: 480, height: 320, color: "dark") {
        placeholder {}
    }

    /* 不带括号:零属性。 */
    container {}

    /* 不带 body:零子节点。 */
    header (height: 40, text: "Hello")

    /* 空 body 等同于不带 body。 */
    header (height: 40, text: "Hello") {}

    /* 位置属性(DsAttr.name = None)。 */
    text ("hello world")
    button (Text("Save"), Disabled)
}
}
}
fn main() {}

具名属性 vs 位置属性

DsAttrname: Option<syn::Ident>。parser 先尝试 name: value 形态,匹配不上则退化为裸表达式的位置属性。同一个部件里两种形态可混用,顺序保留:

use xrune::ui;
fn Text(s: &'static str) -> &'static str { s }
#[derive(Debug)]
struct DisabledMarker;
fn app(parent: i32) {
let Disabled = DisabledMarker;
ui! {
    :(
        parent: parent
    :)
button (Text("Save"), priority: 1, Disabled)
}
}
fn main() {}

name_str() 把属性名作为 Option<&str> 交给符文师匹配;位置属性返回 None

属性值是真实的 Rust

属性值是 syn::Expr。凡能解析为 Rust 表达式的都行:

use xrune::ui;
use std::fmt;
struct State;
impl State { fn set(&self, _: u32) {} }
struct DebugClosure<F>(F);
impl<F> fmt::Debug for DebugClosure<F> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("<closure>") }
}
fn app(parent: i32, items: Vec<u32>, state: State) {
let on_change = DebugClosure(move |v| state.set(v));
ui! {
    :(
        parent: parent
    :)
slider (
    min: 0,
    max: items.len() as u32,
    step: 1.0 / 60.0,
    on_change: on_change
)
}
}
fn main() {}

每个值怎么用,由符文师决定。没有任何编译期约束规定属性名必须对应某个类型。

子节点本身就是完整的树

use xrune::ui;
#[derive(Debug)]
struct Item { name: String }
fn app(parent: i32, items: Vec<Item>, item: &Item) {
ui! {
    :(
        parent: parent
    :)
window (title: "Cast") {
    column (gap: 8) {
        header (text: "xrune")
        list (items: items.iter()) {
            row (text: item.name)
        }
    }
}
}
}
fn main() {}

每个子节点本身就是一棵 DsTree,所以部件、ifwalk@nichematch 都能嵌进另一个部件的 body 里。组合没有任何语法限制:哪些组合确有意义,由符文师把关。

源码出处

crates/xrune_nexus/src/ds_node/ds_widget.rs。由 tests.rsparse_widget_* 用例验证。