# UI 系统 相比于[上一页](./ui.md),本页主要注重于 UI 控制系统的说明,通过了解控制系统,你可以让你的 UI 以你想要的方式展现出来。 ## mainUi 与 fixedUi 样板一个提供了两种类型的 UI 控制器,分别是 `mainUi` `fixedUi`,它们两个都能控制 UI 的显示,但是显示方式有所不同。 对于 `mainUi`,它主要注重于主要 UI 的显示,使用它所显示的 UI 会有明显的嵌套关系。例如,从怪物手册中打开怪物详细信息界面,从设置中打开快捷键设置界面等,这些都是由父 UI 又打开了一个子 UI,有嵌套关系。而同时,如果关闭了一个 UI,那么它的所有子级 UI 也会一并被关闭。 而对于 `fixedUi`,它所显示的 UI 不再拥有嵌套关系,所有 UI 的关系都是平等的,都会一并显示,关闭 UI 时也只会关闭当前 UI,不会关闭其他 UI。例如像状态栏、自定义工具栏、标记怪物等 UI 就是基于 `fixedUi` 的。 这两个 UI 都是 UI 控制器类 `UiController` 的实例。 下面我们来说明一下显示与控制 UI 的流程。 ## 注册 UI 想要让 UI 控制器知道你想打开哪个 UI,首先就要注册 UI,使用 `register` 函数: ```ts function register(...list: GameUi[]): void ``` 注册的 UI 要求是一个 `GameUi` 实例,可以通过 `new` 来创建: ```ts interface GameUi { new(id: string, ui: Component): GameUi } ``` 这里的 `id` 指这个 UI 的名称,`ui` 表示这个 UI 的内容,需要是一个导出组件,或者是函数式组件(参考 Vue 官方文档的渲染函数页面)。注册 UI 是一个模式化的方式,一般直接照葫芦画瓢即可: ```js const { mainUi, fixedUi } = Mota.requireAll('var'); const { m } = Mota.requireAll('fn'); const { GameUi } = Mota.requireAll('class'); const myUI = m().export(); const myUI2 = m().export(); // 注册 UI mainUi.register(new GameUi('myUI', myUI)); // 或者一次性注册多个 mainUi.register( new GameUi('myUI', myUI), new GameUi('myUI2', myUI2) ); ``` ## 打开与关闭 UI 可以使用 `open` 函数来打开一个 UI: ```ts function open(ui: string, vBind?: any, vOn?: any): number ``` 其中 `ui` 参数表示要打开的 UI,后面两个参数请参考[传递参数与监听](#传递参数与监听)。该函数会返回一个数字,表示被打开的 UI 的唯一标识符,注意每次打开 UI 都会生成一个新的标识符,即使打开的 UI 是同一个 UI。 ```js // 打开刚刚注册的 UI const num = mainUi.open('myUI'); ``` 如果想要关闭 UI,可以使用下面这两个函数: ```ts function close(num: number): void function closeByName(name: string): void ``` 前者是根据 UI 的标识符关闭 UI,后者是根据 UI 名称关闭 UI,对于后者,会将所有名称匹配的 UI 都关闭,如果是 `mainUi` 上,第一个匹配的 UI 之后的所有 UI 也会全部关闭。 ```js mainUi.close(num); mainUi.closeByName('myUI'); ``` ## 传递参数与监听 与在 UI 编写页面中传递参数与监听事件类似,打开 UI 的时候也可以传递参数或监听事件,通过 `open` 函数的后两个参数实现。 - `vBind`参数:向 UI 传递参数 - `vOn`参数:监听 UI 的 `emits` 这两个参数都要求传入一个对象,对于 `vBind`,键表示参数(`props`)名称,值表示其值,与 UI 编写不同的是不再需要传入函数。而对于 `vOn`,键表示名称,不再包含 `on` 作为前缀,直接填写 `emits` 的名称即可,值是一个函数,表示事件触发时执行的内容。 对于 `vBind`,一定会包括下面两个参数: - `num`: 本次打开的 UI 的标识符 - `ui`: UI 实例,类型是 `GameUi` ```js const myUI = m() .defineProps({ id: String, // 直接由 UI 控制系统打开的 UI 必须拥有这两个参数 num: Number, ui: GameUi }) .defineEmits(['myEmits']) .export(); // ...此处省略注册 mainUi.open( 'myUi', { id: 'redSlime' // 传入 id 参数,值为 'redSlime',直接是值即可,不需要是函数 }, { // 监听 myEmits 事件,触发时在控制台打印 'emitted!' myEmits: () => { console.log('emitted!'); } } ) ``` ## 显示方式 对于 `mainUi`,有两种显示方式,分别是全部显示与仅显示最后一个 UI,可以通过下面两个函数设置: ```ts function showAll(): void function showEnd(): void ``` 其中前者是设置为全部显示,后者是只显示最后一个。在大部分时刻,`mainUi` 都是处于全部显示的状态。 ```js mainUi.showAll(); mainUi.open('myUI'); mainUi.open('myUI2'); // 此时 myUI 与 myUI2 都会显示 mainUi.showEnd(); // 此时只会显示 myUI2,因为它是最后一个打开的 ``` ## 防闪烁处理 如果你把所有 UI 全部关闭,然后立刻打开一个新的 UI,会出现闪烁现象,观感很差,于是样板提供了下面这个函数用于防闪烁处理: ```ts function holdOn(): { end(): void } ``` 它的作用是暂时维持下一次 UI 全部关闭时不会引起闪烁现象,之后便失效(注意只会触发一次)。该功能的原理是在调用后,如果 UI 全部关闭,那么维持 UI 根组件不会关闭,从而防止了闪烁现象。如果调用之后一直没有新的 UI 打开,那么会引起一直处于 UI 根组件打开状态,导致 UI 假死,请注意避免这种情况。 对于返回值,它是一个对象,包含一个 `end` 方法,调用后可以立刻关闭 UI,即结束这一次的维持。 ```js const num = mainUi.open('myUI'); // 防闪烁 const { end } = mainUi.holdOn(); mainUi.close(num); // 这时 UI 整体不会被关闭,除非调用 end,或者打开新的 UI mainUi.open('myUI2'); // 此时防闪烁功能便会失效 ``` ## 高级用法 ### 获取 GameUi 实例 可以使用 `get` 函数获取已经注册的 `GameUi` 实例: ```ts function get(id: string): GameUi ``` 例如,我们注册了 `myUI` 这个 UI,可以这样获取: ```js const myUI = mainUi.get('myUI'); ``` ### 事件监听 `GameUi` 和 `UiController` 都继承自 `EventEmitter`(详见[事件触发系统](./event-emitter.md))。 对于 `GameUi`,它有下列事件可以被监听: - `open()`: 这个 UI 被打开时触发,无参数 - `close()`: 这个 UI 被关闭时触发,无参数 对于 `UiController`,它有下列事件可以被监听: - `focus(before, after)`: 当 UI 被聚焦时触发,`before` 参数表示之前聚焦的内容,也可能不存在被聚焦的内容,会是 `null`,`after` 参数表示聚焦至哪一个 UI。该事件会在 `mainUi.focusByNum` 或者 `mainUi.focus` 函数执行后触发,这两个函数在样板中没有调用案例,如果需要可以自行调用。该事件继承自类 [`Focus`](../api/class/focus) 的事件。 - `unfocus(before)`: 取消任何聚焦时触发,与 `focus` 事件类似。该事件继承自类 [`Focus`](../api/class/focus) 的事件。 - `add(item)`: 打开新 UI 时触发,参数是打开的 UI,是 `GameUi` 实例。该事件继承自类 [`Focus`](../api/class/focus) 的事件。 - `pop(item)`: 弹出最后一个 UI 时触发,参数是被弹出的 `GameUi` 实例。注意关闭 UI 不会触发此事件,因为关闭 UI 会使用 `splice` 而不是 `pop`。该事件继承自类 [`Focus`](../api/class/focus) 的事件。 - `splice(spliced)`: 当 UI 被截断(关闭)时触发,参数是被关闭的 UI 数组。该事件继承自类 [`Focus`](../api/class/focus) 的事件。 - `start()`: 当 UI 根组件被打开时触发。当 UI 控制器从没有任何 UI 变成有至少一个 UI 被显示时,也即当没有 UI 打开的情况下任何 UI 被打开时,会触发此事件。无参数。 - `end()`: 当 UI 根组件被关闭时触发,即当所有 UI 都被关闭时触发。无参数。