DreamGUI
.dui 语言

if 与 for

if / else if / else 按条件切换显示哪一支,for 和 each 按类的数据重复一个模板,以及两者都要守的规则。

.dui 里有两种控制语句,都写在节点块里:if 按条件决定显示哪一支的 widget,for / each 按类的数据把一个模板重复多次。 两者都是运行时的:文件里写的每个 widget 都会进入类,切换和重复发生在类运行的时候。

if 和 else

class /Game/UI/WBP_Title
use "UI/Components/Row.dui" as Row

VerticalBox Root {
    Spacing = 12
    if HasSave() {
        Row Continue { Label = "Continue" }
    } else if IsLoading() {
        Native.Throbber Spinner { }
    } else {
        Text NoSave { Text = "No save data" }
    }
    Row NewGame { Label = "New game" }
}

条件是一个绑定表达式(写法见绑定与路由)。每一支装的是 widget。每一支里的每个 widget 都按顺序成为外层节点的 子节点,并被绑上一个 Shown,条件是“走的正是这一支”:

widget绑上的 Shown
ContinueHasSave()
Spinner!HasSave() && IsLoading()
NoSave!HasSave() && !IsLoading()

所以切换就是改可见性:没走的那支是折叠(Collapsed),不是销毁,它的状态都还在。这也意味着 if 不是编译期条件:每一支都会建出来, 文件里没有任何写法能把一个 widget 排除在类之外。一个根本不该存在的变体,应该是另一个类。

  • 一支里只能有 widget:属性、槽位行、+ 块写在里面都是 DUI2018,槽位声明和循环也是。
  • 分支里的 widget 自己也写了 Shown 时,要它所在的那支被选中、并且它自己的条件成立,它才显示。
  • if 只能写在节点里面。写在文件顶层,或者前面没有 if 的 else,都是 DUI2018;缺条件或缺块也是。
  • else 可以写在 } 所在的那一行,也可以写在下一行。
  • if 和 else 只在开启一个分支的位置才是关键字:一个叫 if 的属性仍然是属性。

下面这样写会被拒绝,因为分支里是一个属性而不是 widget:

Widget Root {
    if HasSave() {
        Text = "Continue"
    }
}

在设计器里改一个由 if 决定可见性的 widget 的可见性,写回会拒绝(DUI7004): 没有哪一行写着它。改条件,或者把 widget 移出分支。见设计器写回。

for 和 each

两者都是按一个源的每一项重复一个模板 widget。源是一个函数 GetOptions(),或者一个变量 Options——FieldNotify 的数组变了, 副本会跟着刷新。循环体里,Prop <- Item.Member 把每个副本的一个属性绑到它那一项的一个成员上。

for:面板里的副本

class /Game/UI/WBP_Options
use "UI/Components/Row.dui" as Row

VerticalBox Options {
    Text Header { Text = "Options" }
    for Option in GetOptions() {
        Row { Label <- Option.Label  Kind <- Option.Kind }
    }
    Text Footer { Text = "Press A to change" }
}

运行时模板留在树里,处于折叠状态;每一项一个副本,按项的顺序占在宿主的子节点里——也就是 for 两边的兄弟节点之间。不虚拟化, 也不额外造任何东西:宿主自己的容器像排普通子节点一样排这些副本。适合短列表:设置页的选项、一排标签页。

each:列表视图的单元格

class /Game/UI/WBP_Inventory
use "UI/Components/Row.dui" as Row

Widget Inventory {
    + UIListView { }
    each Item in GetItems() {
        Row Cell { Label <- Item.Name }
    }
}

each 填的是它所在那个 widget 的列表视图:单元格随着列表滚动被创建和回收。适合长列表。

两者都要守的规则

  • 循环体恰好是一个 widget,即模板(DUI5021、DUI5012)。 要重复几个 widget,就把它们包进一个容器。
  • 循环不能是根,也不能嵌套在另一个循环里(DUI5021、DUI5012)。要嵌套,就写一个组件,内层循环写在它自己的文件里,列表通过一个 props 条目交给它。
  • for 要求宿主能接任意多个子节点(固定格位的宿主不行,DUI5021);each 要求宿主上有 + UIListView(或者一个可回收的滚动视图), 否则是 DUI5012。
  • 循环体里唯一能写的绑定是单跳的 Item.Member;写表达式或 <-> 是 DUI5014。循环体里也不能 emit(DUI6012)。
  • 源必须在类上存在(DUI6006),而且是一个对象数组(DUI6007)。

其余相关的代码:

代码情况
DUI2010循环头不是 <关键字> Var in Source 或 Source() 的形状
DUI3008循环变量遮住了外层循环的变量(警告)
DUI5007建树的调用方没有地方记录循环,循环被跳过(警告)

for 运行时造出来的副本不是文件里的任何一行,设计器写回会跳过它们;循环的模板也不会被设计器删掉或移出循环——没有东西可重复的循环建不起来。 VS Code 在 each 循环体里输入 Item. 时,会按源的元素类型补全成员。

本页目录