DreamGUI
.dui 语言

绑定与路由

三种箭头把树接到类的代码上——绑定驱动属性、路由调用处理函数、双向绑定镜像变量——以及每个 widget 都有的 Shown。

三种箭头把树接到类的代码上:

写法方向例子
Prop <- Expr类 → 树:属性跟着表达式走Text <- GetTitle()
Event -> Handler树 → 类:事件调用类的一个函数OnClicked -> HandleConfirm
Prop <-> Var两个方向:属性和变量互相镜像Value <-> Volume

这里的“类”就是这棵树编译进去的那个 user widget——UMG 也把事件处理放在那里。函数和变量可以写在 C++ 里,也可以写在 Blueprint 的图表里。

<- 驱动属性

class /Game/UI/WBP_Inventory

Widget Root {
    + VerticalBox { Spacing = 8 }
    Text Title { Text <- GetTitle() }
    Image Glow { RenderOpacity <- Fade() * 0.8 }
    Widget Badge { Shown <- Count() > 0 && !IsLocked() }
}

右边是一个表达式,随着类的运行重新求值。表达式里可以有:

  • 类的函数调用,Func() 或 Func(a, b);
  • 类的变量,Volume——包括 props 里的条目;
  • 字面值:数字、字符串、true、false,或 @Resource;
  • 运算符,从最松到最紧:||;&&;== !=;< <= > >=;+ -;* %;以及前缀 ! 和 -。括号分组。

没有 /——它属于路径和注释——也没有 ? :、下标 Items[0]、数组或字典字面值。表达式里每加一个运算符,都得能降成 Blueprint 图、 教给 VS Code 扩展的扫描器、写进三处文档,所以除法和二选一写成类上的函数,下标写成一个带参数的函数;对 widget 来说,二选一就是一个 if。

一个裸的 Func() 直接绑定那个函数;更复杂的表达式会被编译成一个生成的函数。对目标属性的要求:

  • 属性要有 setter(DUI5005)——组件的 prop 例外,见编写组件。
  • 只能绑定 widget、它的视觉或某个行为上的整个属性(DUI5008):不能绑槽位属性、 容器属性,也不能绑结构体里的某个字段(AnchorData.SizeDelta <- … 不行)。
代码情况
DUI2011右边解析不了
DUI5004类上没有这个函数,或者裸绑定的函数带参数
DUI5011编译器没法把这个表达式降成一个函数

在 VS Code 里,编辑器运行时 <- 后面的补全会去问编辑器这个类到底声明了什么,函数调用有参数提示,悬停能看到函数和变量的类型。

-> 路由事件

class /Game/UI/WBP_Dialog

Widget Root {
    + HorizontalBox { Spacing = 16 }
    Native.Button Confirm { OnClicked -> HandleConfirm }
    Widget Cancel {
        + UIButton { OnClick -> HandleCancel }
    }
}

左边是 widget、它的视觉或某个行为上的一个事件:BlueprintAssignable 委托(Controls/ 那一族控件声明的),或者 FDreamUIEventDelegate 属性(较老的 Interaction/ 行为声明的)。每个 widget 都有 OnVisibilityChanged、OnFocusReceived、 OnFocusLost。右边是类的一个函数(不带括号),或者 emit 一个本类声明的事件(见编写组件):

class /Game/UI/Components/WBP_Row

events {
    Picked(Number Index)
}

Widget Root {
    + UIButton { OnClick -> emit Picked(0) }
}
代码情况
DUI5010左边不是目标上的事件
DUI6004类上没有这个处理函数
DUI6005处理函数的参数和事件的参数不一致

编译器把每条路由解析进 UDreamWidgetBlueprint::EventBindings,UDreamUserWidget::BindEventBindings 在 Initialize 时把它们接上。 在设计器里,同一件事就是细节面板的 Events 一节:那里的 + 会建一个签名正确的自定义事件,并写出指向它的路由。

FDreamUIEventDelegate 自带的逐实例事件列表是遗留功能,只读。存在里面的绑定照样会触发,也还能从面板上删掉,但不能再新建: 那种绑定是在任意对象上调一个函数、带一个字面参数,UMG 里没有对应物,.dui 里也没有写法。打开带有这种绑定的面板会记一条警告点名它。 不会自动改写——把“在那个行为上用这个值调 Foo”改成“调 user widget 上的一个处理函数”会改变游戏的行为——所以下次碰到时把它们重写成路由。

<-> 双向镜像

class /Game/UI/WBP_Audio

Widget Root {
    + VerticalBox { Spacing = 8 }
    Native.Slider Volume { Value <-> MasterVolume }
    Native.Toggle Mute { bIsOn <-> bMuted }
}

属性跟着变量走,控件自己产生的变化再写回变量。变量必须是这个类的 FieldNotify 变量。

<-> 是两半拼起来的:正向是一个生成的 getter,和任何绑定函数一样;反向是一条生成的路由 OnValueChangedBP -> 生成的 setter。所有值类控件都通过 OnValueChangedBP 这个约定的名字广播变化,没有这个事件的目标会报 DUI5010。<-> 右边的名字不是这个类的变量(以上一次编译为准)是 DUI5011。

VS Code 里 <-> 后面的补全列出类的变量,FieldNotify 的排在前面。

Shown

每个 widget 都有 Shown:true 是 Visible,false 是 Collapsed。它自己不存任何东西——读它就是读 Visibility,写它就是写 Visibility——条件绑定的就是它:

class /Game/UI/WBP_Detail

Widget Root {
    + VerticalBox { }
    Widget Detail {
        Shown <- HasDetail()
    }
    Text Empty {
        Text = "Nothing selected"
        Shown = false
    }
}

一个 Hidden 或命中测试不可见的 widget 也算“显示”,Shown 为 true 时它保持原来的状态,不会被改成 Visible。if 给每个分支里的 widget 绑的也是 Shown,见 if 与 for。Shown 和 Visibility 是同一个值,设计器写回时只比较文件写出来的那一面。

本页目录