KityMinder 渲染和布局重构方案
=====

## 背景

当前的节点渲染和布局把很多部分的功能都揉在一起来实现，维护和添加新功能都比较困难，所以决定把现有的 layout.js、layout.default.js 和 layout.bottom.js 重构。

## 约定

为了不产生误会，下面约定一些词汇的意思。

样式：对节点或脑图其他内容的外观的描述。

渲染：对节点的绘制的过程。渲染参照的坐标系是节点画布的坐标系。

布局：对节点定位的过程，发生在渲染之后。定位使用的坐标系是脑图画布的坐标系。

节点树多边形：表示一个节点以及其子树所占的多边形区域。

## 样式、渲染和布局的梳理

### Theme

负责注册和管理样式。

注册样式：

```js
KityMinder.registerTheme( 'default', {
    'root-padding': [30, 20],
    'root-color': 'black',
    'root-background': '#A4c5c0',
    'root-stroke': 'none',
    'root-radius': 4,
    'root-space': [10, 5],
    'root-font-family': 'Arial',
    'root-font-size': 14,
    'main-connect-width': 2,
    'main-connect-color': 'white',
    'main-connect-type': 'arc',
    'sub-connect-type': 'fold'
    ...
});
```

切换样式：

```js
minder.useTheme('default');
```

读取样式：

```js
minder.getStyle('root-radius'); // returns 4
```

```js
node.getStyle('radius'); // node.getType() 返回 'root', 会自动拼接样式项，返回 4
node.getStyle('padding-left'); // returns 20 发现结尾是 '-left'、'-right' 等的会自动找到样式表描述的数组按照 CSS 的方式查找值
```

### Render

Render 负责渲染工作。

对于一个节点，有两个渲染容器（以前是多个），一个是通过 `getContentGroup()` 来获取的内容容器，一个是通过 `getDecorationGroup()` 来获得的装饰容器。布局的时候，内容容器最终决定该节点的渲染大小，装饰容器里的内容会被忽略。

节点的渲染分步进行，按顺序描述是：`center`, `left`, `right`, `top`, `bottom`。之前渲染这五个步骤是通过事件的形式来通知，每次渲染都清除了原本的内容重新创建节点，造成性能浪费。修改渲染的机制，改成从模块直接注册渲染的节：

```js
KityMinder.registerModule( 'MyModule', {
    commands: { ... },
    events: { ... },
    renderers: {
        left: kity.createClass( 'MyRenderer', {
            base: Renderer,

            create: function(node) {
                this.content = new kity.Group();
                // 添加元素
                node.getContentGroup().addShape(this.content);
            },

            update: function(node) {
                var content = this.content;
                // update content
                // 需要返回更新后内容所占的矩形区域，简单的返回形式是 return this.content.getRenderBox();
                return {
                    x: 30,
                    y: -30,
                    width: 24,
                    height: 24
                };
            }
        }
    }
});
```

用 `renderers` 节来描述渲染器，节的 `key` 指定渲染器使用的阶段，节的 `value` 给出渲染器的类。渲染器需要继承 `Renderer` 类，并且实现两个抽象方法：`create()` 和 `update()`。

`create` 方法只会在节点创建的时候调用一次，此时应该创建渲染所需要的元素，并且附加到节点上。

`update` 方法会在节点需要重新渲染的时候被调用，此时可以选择隐藏、显示或更新元素。

在渲染过程中，节点提供下述信息：

- `node.getStyle(name)` 获得节点当前的某个样式描述
- `node.getContentBox()` 获得节点当前已渲染的内容在节点的 ConentGroup 的坐标系中所占的矩形区域。
- `node.getType()` 获得节点的类型（'root' | 'main' | 'sub'）

要触发一个节点的渲染，直接调用 'node.render()' 即可。

### Layout

Layout 负责布局工作，布局一般发生在渲染之后。对一个节点的布局，指的是排列其子树。

允许多种布局，布局注册的方式如下：

```js
KityMinder.registerLayout('default', kity.createClass('DefaultLayout', {
    base: Layout,
    doLayout: function(node) {
        // do layout
    }
}));
```

让节点布局的方式是调用节点的 `node.layout([name])` 方法。该方法的执行过程如下：

- 调用 `node.getLayout()` 获得节点的布局名称
  - 如果节点中的 data 包含布局名称，返回该布局名称，否则下一步
  - 如果节点是根节点，返回 KityMinder.getDefaultLayout()
  - 返回父节点的 `getLayout()`
- 根据布局名称获得相应的布局方法，把节点作为参数传进去调用

