面板系统

扩展默认情况下是没有界面显示的,如果一个扩展需要实现界面交互,就需要使用到面板系统相关功能。

面板的定义

package.json 里可以在 panels 字段定义一个或者多个面板,如下所示:

  1. {
  2. "name": "hello-world",
  3. "panels": {
  4. "default": {
  5. "title": "world panel",
  6. "type": "dockable",
  7. "main": "./dist/panels/default",
  8. "icon": "./static/default.png"
  9. },
  10. "list": {
  11. "title": "world list",
  12. "type": "simple",
  13. "main": "./dist/panels/list",
  14. "icon": "./static/list.png",
  15. "flags": {},
  16. "size": {}
  17. }
  18. }
  19. }

我们定义了两个面板:defualtlistdefault 为默认面板,当不指名特定面板的时候,它就做为默认操作对象。

面板各字段含义如下:

  • title:string - 面板标题,支持 i18n:key,必填
  • main:string - 面板源码相对目录,必填
  • icon:string - 面板图标相对目录,必填
  • type:string - 面板类型(dockable | simple),可选
  • flags:{} - 标记,可选
    • resizable - 是否可以改变大小,默认 true,可选
    • save - 是否需要保存,默认 false,可选
    • alwaysOnTop - 是否保持顶层显示,默认 flase,可选
  • size:{} - 大小信息,可选
    • min-width:Number - 最小宽度,可选
    • min-height:Number - 最小高度,可选
    • width:Number - 面板默认宽度,可选
    • height:Number - 面板默认高度,可选

编写面板

在扩展根目录下分别建立两个文件 src/panels/default/index.tssrc/panels/list/index.ts,并将下面的最简面板模板代码分别贴到两个 index.ts 文件中:

  1. module.exports = Editor.Panel.define({
  2. listeners: {
  3. show() { console.log('show'); },
  4. hide() { console.log('hide'); },
  5. },
  6. template: '<div>Hello</div>',
  7. style: 'div { color: yellow; }',
  8. $: {
  9. elem: 'div',
  10. },
  11. methods: {
  12. },
  13. ready() {
  14. },
  15. beforeClose() { },
  16. close() { },
  17. });

listeners - 面板的一些事件监听 template - 面板的 HTML 布局文件 style - 面板的 css 文件 $ - 全局选择器,用于快速访问一些元素 methods - 此面板对外的方法接口 ready - 当面板被打开时会调用 beforeClose - 面板在关闭前会调用 close - 面板在关闭后会调用

显示面板

可以使用 Editor.Panel.open 方法打开任何面板(本扩展自己的面板以及其他扩展的面板)。

假设扩展为 hello-world,那以下两种方式都可以打开默认面板:

  1. // Editor.Panel.open('hello-world.defualt');
  2. Editor.Panel.open('hello-world');

打开其它面板的方式为:

  1. // Editor.Panel.open('{extension-name}.panelName');
  2. Editor.Panel.open('hello-world.list');

通信交互

Cocos Creator 扩展系统基于 Electron 的多进程方式构建,每一个扩展是一个独立的进程,扩展中的每一个面板,也是一个独立的进程。 因此,扩展与面板之间、面板与面板的交互只能通过进程间通信(IPC)实现。 详细信息请参考文档 消息系统

面板向外发送消息

由于面板关闭后,进程也会退出,因此我们通常将扩展作为内存数据的载体。面板中需要用到的数据和逻辑接口一般会从扩展主进程里获取。

如果想 查询设置 位于扩展主进程中的数据,假设扩展中定义了 queryDatasaveData 两个消息,我们可以这样使用:

  1. const data = await Editor.Message.request(packageName, 'queryData', dataName);
  2. await Editor.Message.request(packageName, 'saveData', dataName,dataValue);

如果想广播通知整个扩展系统,则可以利用 广播消息 机制实现,详细信息请参考文档 消息系统

面板接收消息

  1. // package.json
  2. {
  3. "contributions": {
  4. "messages": {
  5. "log": {
  6. "methods": ["log"]
  7. }
  8. }
  9. }
  10. }

上面的消息定义了一个 log 消息,并由此扩展的主进程中的 log 方法处理。接下来我们稍作修改,使消息的接收方变成面板:

  1. // package.json
  2. {
  3. "contributions": {
  4. "messages": {
  5. "log": {
  6. "methods": ["default.log"]
  7. }
  8. }
  9. }
  10. }

default.log 使得消息接收方变成了 default 面板,只需要在面板中实现一个 log 方法,就能够顺利处理此消息了。

更好的面板资源组织方式

在上面的最简面板模板中,我们有两行面板显示相关的代码:

  1. module.exports = Editor.Panel.define({
  2. ...
  3. template: '<div>Hello</div>',
  4. style: 'div { color: yellow; }',
  5. ...
  6. });

大部分情况下,面板布局不可能这么简单。如果继续将复杂的 HTML 布局和 css 样式写在这里,代码将变得不可维护。可以参考文档 入门示例-面板 中创建的项目,我们可以将 htmlcss 代码分离为独立的文件,放入 static 文件夹中。

最终形成的面板模板代码如下所示:

  1. import { readFileSync } from 'fs-extra';
  2. import { join } from 'path';
  3. module.exports = Editor.Panel.define({
  4. listeners: {
  5. show() { console.log('show'); },
  6. hide() { console.log('hide'); },
  7. },
  8. template: readFileSync(join(__dirname, '../../../static/template/default/index.html'), 'utf-8'),
  9. style: readFileSync(join(__dirname, '../../../static/style/default/index.css'), 'utf-8'),
  10. $: {
  11. app: '#app',
  12. },
  13. methods: {
  14. },
  15. ready() {
  16. },
  17. beforeClose() { },
  18. close() { },
  19. });

更多面板实用详情请参考 入门示例-面板